From 6caf3c64ffc7e1184516164f5150bc7184223da5 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:15:52 +0000 Subject: [PATCH 01/17] Add ruff to pyproject --- pyproject.toml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b661a0b2..c46b28fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ jupyter = "^1.1.1" arviz = "^0.20.0" oggm = "^1.6.2" ruamel-yaml = "^0.18.10" +ruff = ">0.9.6" [tool.poetry.scripts] initialize = "pygem.bin.op.initialize:main" @@ -53,4 +54,16 @@ duplicate_gdirs = "pygem.bin.op.duplicate_gdirs:main" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "C", # mccabe complexity + "E", "W", # Pycodestyle + "F", # Pyflakes + "I", # isort +] From 0c97a078809fb40e8691ace38519c018b8b03c37 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:16:19 +0000 Subject: [PATCH 02/17] Ignore some linting errors for now --- pyproject.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c46b28fb..d1fe5a37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,3 +67,20 @@ select = [ "F", # Pyflakes "I", # isort ] +ignore = [ + "B006", # Mutable data structures in argument defaults + "B007", # Loop control variable not used within loop body + "B008", # Function call `range` in argument defaults + "B023", # Function definition does not bind loop variable + "C405", # Unnecessary list literal + "C408", # Unnecessary `dict()` call + "C414", # Unnecessary `list()` call + "C416", # Unnecessary list comprehension + "C901", # Function too complex + "E402", # Module level import not at top of file + "E501", # Line too long + "E712", # Avoid equality comparisons to `False` + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` + "E722", # Using bare `except` + "F841", # Local variable assigned to but never used +] From c9c85109321618a8b661822cdc2e2fe658632678 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:20:21 +0000 Subject: [PATCH 03/17] Add 'sample_data/' to .gitignore --- .gitignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 91f5ccd2..8ac222a3 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -# pycache +# Subdirectories __pycache__/ - -# vscode +sample_data/ .vscode/ # python bytecode From ca85ce033544fb178cfbcad120724ce7513087cc Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:33:46 +0000 Subject: [PATCH 04/17] Run linter and formatter via pre-commit --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..143c24fe --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.6 + hooks: + - id: ruff # run the linter + args: [ --fix ] + - id: ruff-format # run the formatter \ No newline at end of file From 5b8fde25ffcb19f284d3287a6c9a88aef3d46dd2 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:15:51 +0000 Subject: [PATCH 05/17] Fix ruff distribution in deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d1fe5a37..093b1bd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ jupyter = "^1.1.1" arviz = "^0.20.0" oggm = "^1.6.2" ruamel-yaml = "^0.18.10" -ruff = ">0.9.6" +ruff = ">=0.9.6" [tool.poetry.scripts] initialize = "pygem.bin.op.initialize:main" From 64ccada9e59246736ba20458447b7b4b0537b220 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:35:37 +0000 Subject: [PATCH 06/17] Use line length of 88 (default) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 093b1bd2..e7afc075 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.ruff] -line-length = 79 +line-length = 88 # Default [tool.ruff.lint] select = [ From c6655385cd2df34fcfdd6c75e8bc7296c9a4e725 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:47:16 +0000 Subject: [PATCH 07/17] Add ruff linting and formatting checks to test suite workflow --- .github/workflows/test_suite.yml | 7 +++++++ 1 file changed, 7 insertions(+) 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 From 722e9de01fdc8ee35af9f6fa9d99ea1b638db6ee Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:53:47 +0000 Subject: [PATCH 08/17] Apply automatic fixes for linting errors --- docs/conf.py | 4 +- pygem/__init__.py | 3 +- pygem/bin/op/duplicate_gdirs.py | 6 +- pygem/bin/op/initialize.py | 29 +- pygem/bin/op/list_failed_simulations.py | 24 +- .../postproc/postproc_binned_monthly_mass.py | 46 +- .../postproc/postproc_compile_simulations.py | 81 +- pygem/bin/postproc/postproc_distribute_ice.py | 35 +- pygem/bin/postproc/postproc_monthly_mass.py | 30 +- pygem/bin/preproc/preproc_fetch_mbdata.py | 13 +- pygem/bin/preproc/preproc_wgms_estimate_kp.py | 116 +-- pygem/bin/run/__init__.py | 2 +- pygem/bin/run/run_calibration.py | 410 +++++----- .../run/run_calibration_frontalablation.py | 734 +++++++++--------- pygem/bin/run/run_calibration_reg_glena.py | 134 ++-- pygem/bin/run/run_mcmc_priors.py | 107 +-- pygem/bin/run/run_simulation.py | 288 ++++--- pygem/class_climate.py | 112 +-- pygem/gcmbiasadj.py | 190 ++--- pygem/glacierdynamics.py | 317 ++++---- pygem/massbalance.py | 91 +-- pygem/mcmc.py | 31 +- pygem/oggm_compat.py | 65 +- pygem/output.py | 125 +-- pygem/pygem_modelsetup.py | 38 +- pygem/scraps/dummy_task_module.py | 3 +- pygem/scraps/run.py | 4 +- pygem/setup/__init__.py | 2 +- pygem/setup/config.py | 19 +- pygem/shop/debris.py | 75 +- pygem/shop/icethickness.py | 54 +- pygem/shop/mbdata.py | 100 +-- pygem/shop/oib.py | 21 +- pygem/tests/__init__.py | 2 +- pygem/tests/test_config.py | 12 +- pygem/tests/test_notebooks.py | 2 +- pygem/utils/_funcs.py | 24 +- pygem/utils/_funcs_selectglaciers.py | 24 +- setup.py | 2 +- 39 files changed, 1709 insertions(+), 1666 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7b3a8e23..81631e2a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,9 @@ import os import sys + import tomllib + sys.path.insert(0, os.path.abspath('../pygem/')) # source pyproject.toml to get release @@ -61,4 +63,4 @@ "use_repository_button": True, "show_nav_level":2, "navigation_depth":3, - } \ No newline at end of file + } diff --git a/pygem/__init__.py b/pygem/__init__.py index 4d90acd4..d1aa0ade 100755 --- a/pygem/__init__.py +++ b/pygem/__init__.py @@ -6,7 +6,8 @@ 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..3a9ccbac 100644 --- a/pygem/bin/op/duplicate_gdirs.py +++ b/pygem/bin/op/duplicate_gdirs.py @@ -10,8 +10,10 @@ import argparse import os import shutil + # pygem imports from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -32,7 +34,7 @@ def main(): 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}/' @@ -46,4 +48,4 @@ def main(): 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..24b95a02 100644 --- a/pygem/bin/op/initialize.py +++ b/pygem/bin/op/initialize.py @@ -7,11 +7,14 @@ 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 @@ -21,14 +24,14 @@ def print_file_tree(start_path, indent=""): # Loop through all files and directories in the current directory for item in os.listdir(start_path): path = os.path.join(start_path, item) - + # Print the current item with indentation print(indent + "|-- " + item) - + # Recursively call this function if the item is a directory if os.path.isdir(path): print_file_tree(path, indent + " ") - + def get_confirm_token(response): """Extract confirmation token for Google Drive large file download.""" for key, value in response.cookies.items(): @@ -56,11 +59,11 @@ def get_unique_folder_name(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. """ @@ -100,7 +103,7 @@ 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') @@ -110,7 +113,7 @@ def main(): 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 +121,13 @@ 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'}) except: pass - + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/op/list_failed_simulations.py b/pygem/bin/op/list_failed_simulations.py index 707b1f12..4625c2b3 100644 --- a/pygem/bin/op/list_failed_simulations.py +++ b/pygem/bin/op/list_failed_simulations.py @@ -8,20 +8,24 @@ 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): # define base directory @@ -29,14 +33,14 @@ def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_end # 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', + 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/' else: @@ -64,7 +68,7 @@ def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_end # 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) @@ -80,7 +84,7 @@ def main(): 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, + 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)') @@ -128,5 +132,5 @@ def main(): 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..b9111bbc 100644 --- a/pygem/bin/postproc/postproc_binned_monthly_mass.py +++ b/pygem/bin/postproc/postproc_binned_monthly_mass.py @@ -10,18 +10,18 @@ # 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 @@ -50,19 +50,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 @@ -108,11 +108,11 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): # 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,9 +124,9 @@ 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), + 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 @@ -146,7 +146,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 @@ -210,13 +210,13 @@ def update_xrdataset(input_ds, h_monthly, m_spec_monthly, 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 + m_spec_monthly ) output_ds_all['bin_mass_monthly'].values = ( m_monthly @@ -244,7 +244,7 @@ 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_massbalclim_monthly.values, binned_ds.bin_mass_annual.values, binned_ds.bin_thick_annual.values ) @@ -271,7 +271,7 @@ 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') @@ -288,6 +288,6 @@ def main(): p.map(run,simpath) print('Total processing time:', time.time()-time_start, 's') - + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/postproc/postproc_compile_simulations.py b/pygem/bin/postproc/postproc_compile_simulations.py index 8b998626..89189ccf 100644 --- a/pygem/bin/postproc/postproc_compile_simulations.py +++ b/pygem/bin/postproc/postproc_compile_simulations.py @@ -8,20 +8,21 @@ 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 @@ -64,8 +65,8 @@ def run(args): 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', + main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], + rgi_regionsO2='all', rgi_glac_number='all', glac_no=None, debug=True) @@ -85,7 +86,7 @@ def run(args): # 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): + 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) @@ -150,12 +151,12 @@ 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 + # get list of glacier simulation files sim_dir = base_dir + gcm + '/' + scenario + '/stats/' ### LEVEL III ### @@ -186,7 +187,7 @@ 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 ### @@ -240,7 +241,7 @@ def run(args): 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 @@ -255,7 +256,7 @@ def run(args): 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 @@ -270,7 +271,7 @@ def run(args): 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_mass_annual = np.concatenate((reg_glac_gcm_mass_annual, glac_mass_annual), axis=0) # aggregate gcms if reg_glac_allgcms_runoff_monthly is None: @@ -302,7 +303,7 @@ def run(args): 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 @@ -331,7 +332,7 @@ def run(args): lon=(["glacier"], cenlon_list), lat=(["glacier"], cenlat_list), time=tvals, - ) + ) coord_order = ["realization","glacier","time"] else: coords_dict=dict( @@ -340,7 +341,7 @@ def run(args): lon=(["glacier"], cenlon_list), lat=(["glacier"], cenlat_list), time=tvals, - ) + ) coord_order = ["model","glacier","time"] #glac_runoff_monthly @@ -477,7 +478,7 @@ def run(args): ds.glac_prec_monthly.attrs['temporal_resolution'] = 'monthly' ds.glac_prec_monthly.attrs['comment'] = 'only the liquid precipitation, solid precipitation excluded' ds.glac_prec_monthly.attrs['grid_mapping'] = 'crs' - + #glac_mass_monthly elif var=='glac_mass_monthly': ds = xr.Dataset( @@ -525,7 +526,7 @@ def run(args): ds.glac_mass_annual.attrs['temporal_resolution'] = 'annual' ds.glac_mass_annual.attrs['comment'] = 'mass of ice based on area and ice thickness at start of the year' ds.glac_mass_annual.attrs['grid_mapping'] = 'crs' - + # crs attributes - same for all vars ds.crs.attrs['grid_mapping_name'] = 'latitude_longitude' ds.crs.attrs['longitude_of_prime_meridian'] = 0.0 @@ -533,7 +534,7 @@ def run(args): ds.crs.attrs['inverse_flattening'] = 298.257223563 ds.crs.attrs['proj4text'] = '+proj=longlat +datum=WGS84 +no_defs' ds.crs.attrs['crs_wkt'] = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' - + # time attributes - different for monthly v annual ds.time.attrs['long_name'] = 'time' if 'annual' in var: @@ -542,7 +543,7 @@ def run(args): elif 'monthly' in var: ds.time.attrs['range'] = str(time_values[0]) + ' - ' + str(time_values[-1]) ds.time.attrs['comment'] = 'start of the month' - + ds.RGIId.attrs['long_name'] = 'Randolph Glacier Inventory Id' ds.RGIId.attrs['comment'] = 'RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)' ds.RGIId.attrs['cf_role'] = 'timeseries_id' @@ -551,20 +552,20 @@ def run(args): 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('__','_') else: @@ -574,8 +575,8 @@ def run(args): loop_end = time.time() 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'] @@ -590,14 +591,14 @@ def run(args): else: fn_merge_list = glob.glob(f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) fn_merge_list_start = [int(f.split('-')[-2]) for f in fn_merge_list] - + if len(fn_merge_list) > 0: 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: @@ -605,9 +606,9 @@ def run(args): # save 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) @@ -622,7 +623,7 @@ def main(): 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='+', + 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")') @@ -632,13 +633,13 @@ def main(): 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/', + 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")', + 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, @@ -673,7 +674,7 @@ def main(): 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: + 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}') @@ -711,10 +712,10 @@ def main(): # 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') if __name__=='__main__': - main() \ No newline at end of file + main() diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index cf350f8f..952c7572 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -7,32 +7,33 @@ """ # Built-in libraries import argparse -import collections -import copy -import inspect import multiprocessing import os -import glob -import sys import time from functools import partial + import matplotlib.pyplot as plt + # External libraries import numpy as np import xarray as xr + # oggm -from oggm import workflow, tasks, cfg +from oggm import tasks, workflow from oggm.sandbox import distribute_2d + # pygem imports 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(): @@ -54,8 +55,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: @@ -76,7 +77,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' @@ -113,9 +114,9 @@ def run(simpath, debug=False): glac_no = pygem_fn_split[0] glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[0, :] glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) - # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== + # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_calving']: + if glacier_rgi_table['TermType'] not in [1,5] or not pygem_prms['setup']['include_calving']: gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False else: @@ -141,7 +142,7 @@ def run(simpath, debug=False): # distribute simulation to 2d ds = workflow.execute_entity_task( distribute_2d.distribute_thickness_from_simulation, - gdir, + 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 @@ -171,6 +172,6 @@ def main(): p.map(run_with_debug, args.simpath) print('Total processing time:', time.time()-time_start, 's') - + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/postproc/postproc_monthly_mass.py b/pygem/bin/postproc/postproc_monthly_mass.py index 972bb570..8a7895b3 100644 --- a/pygem/bin/postproc/postproc_monthly_mass.py +++ b/pygem/bin/postproc/postproc_monthly_mass.py @@ -10,27 +10,23 @@ # 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 ----- @@ -95,7 +91,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 @@ -145,7 +141,7 @@ def update_xrdataset(input_ds, glac_mass_monthly): encoding[vn] = {'_FillValue': None, 'zlib':True, 'complevel':9 - } + } output_ds_all['glac_mass_monthly'].values = ( glac_mass_monthly @@ -169,8 +165,8 @@ 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() @@ -185,7 +181,7 @@ def run(simpath): # close datasets output_ds_stats.close() - + except: pass else: @@ -219,6 +215,6 @@ def main(): p.map(run,simpath) print('Total processing time:', time.time()-time_start, 's') - + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/preproc/preproc_fetch_mbdata.py b/pygem/bin/preproc/preproc_fetch_mbdata.py index c2282c22..1a07e4d0 100644 --- a/pygem/bin/preproc/preproc_fetch_mbdata.py +++ b/pygem/bin/preproc/preproc_fetch_mbdata.py @@ -10,21 +10,18 @@ # 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): @@ -63,7 +60,7 @@ def run(fp='', debug=False, overwrite=False): 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}') - + mbdf_subset.to_csv(fp, index=False) if debug: print(f'Filled global geodetic mass balance data saved to: {fp}') @@ -88,4 +85,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/preproc/preproc_wgms_estimate_kp.py b/pygem/bin/preproc/preproc_wgms_estimate_kp.py index 0cabd84c..891f3f30 100644 --- a/pygem/bin/preproc/preproc_wgms_estimate_kp.py +++ b/pygem/bin/preproc/preproc_wgms_estimate_kp.py @@ -14,15 +14,17 @@ 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 @@ -34,23 +36,23 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w """ 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/' - + # Process each region regions_str = ['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18'] rgi60_df = None @@ -65,7 +67,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 +77,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,46 +85,46 @@ 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: print(nrow, 'of', wgms_ee_df_winter.shape[0]) name = wgms_ee_df_winter.loc[nrow,'NAME'] wgmsid = wgms_ee_df_winter.loc[nrow,'WGMS_ID'] year = wgms_ee_df_winter.loc[nrow,'YEAR'] - + try: - e_idx = np.where((wgms_e_df['NAME'] == name) & - (wgms_e_df['WGMS_ID'] == wgmsid) & + 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.to_csv(wgms_ee_winter_fp, index=False) - + # Export subset of data wgms_ee_df_winter_subset = wgms_ee_df_winter.loc[wgms_ee_df_winter['BEGIN_PERIOD'] > subset_time_value] wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna(subset=['END_WINTER']) @@ -136,7 +138,7 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta # Load data assert os.path.exists(wgms_ee_winter_fp_subset), 'wgms_ee_winter_fn_subset does not exist!' wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding='unicode_escape') - + # 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']] @@ -148,24 +150,24 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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) + 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) @@ -176,7 +178,7 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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) - + # ===== PROCESS THE OBSERVATIONS ====== prec_cn = pygem_prms['climate']['ref_gcm_name'] + '_prec' wgms_df[prec_cn] = np.nan @@ -188,14 +190,14 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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 @@ -211,31 +213,31 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta # 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']) - + # Fraction of the months to remove - remove_prec_begin = (gcm_prec[glac,idx_begin] * + 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] * + 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 - + # 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 - + # 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] - + # 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 - + # Drop nan values wgms_df_wkp = wgms_df.dropna(subset=['kp']).copy() wgms_df_wkp.reset_index(inplace=True, drop=True) @@ -246,14 +248,14 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta # Calculate stats for all and each region wgms_df_wkp['reg'] = [x.split('-')[1].split('.')[0] for x in wgms_df_wkp['rgiid'].values] reg_unique = list(wgms_df_wkp['reg'].unique()) - + # 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) - + # 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] @@ -264,11 +266,11 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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()) @@ -278,8 +280,8 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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())) @@ -289,7 +291,7 @@ def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_sta 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) @@ -317,7 +319,7 @@ def main(): 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] - + subset_time_value = 20000000 # if not all outputs already exist, subset the input data and create the necessary outputs @@ -330,19 +332,19 @@ 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_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, + est_kp(wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, wgms_ee_winter_fp_kp=wgms_ee_winter_fp_kp, wgms_reg_kp_stats_fp=wgms_reg_kp_stats_fp) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/__init__.py b/pygem/bin/run/__init__.py index 58bc9316..e7b24f6e 100755 --- a/pygem/bin/run/__init__.py +++ b/pygem/bin/run/__init__.py @@ -4,4 +4,4 @@ copyright © 2018 David Rounce 0): - + modelprms = {'kp': pygem_prms['sim']['params']['kp'], 'tbias': pygem_prms['sim']['params']['tbias'], 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], 'ddfice': pygem_prms['sim']['params']['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'], 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], 'precgrad': pygem_prms['sim']['params']['precgrad']} - + #%% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== # - precipitation factor, temperature bias, degree-day factor of snow if args.option_calibration == 'emulator': @@ -571,7 +565,7 @@ def run(list_packed_vars): 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 @@ -579,7 +573,7 @@ def run(list_packed_vars): modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] nsims = pygem_prms['calib']['emulator_params']['emulator_sims'] - + # Load sims df sims_fp = pygem_prms['root'] + '/Output/emulator/sims/' + glacier_str.split('.')[0].zfill(2) + '/' sims_fn = glacier_str + '-' + str(nsims) + '_emulator_sims.csv' @@ -599,7 +593,7 @@ def run(list_packed_vars): 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) + 'nbinyears_negmbclim:', nbinyears_negmbclim) tbias_stepsmall = 0.05 while nbinyears_negmbclim > 10: modelprms['tbias'] = modelprms['tbias'] - tbias_stepsmall @@ -609,22 +603,22 @@ def run(list_packed_vars): 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 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'], + 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'], + 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)) @@ -638,35 +632,35 @@ def run(list_packed_vars): 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'], + 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, + 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'], + 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, + 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, + 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'] @@ -675,21 +669,21 @@ def run(list_packed_vars): scale=pygem_prms['calib']['emulator_params']['tbias_sigma'], size=nsims) if debug: 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'], + 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: + 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] @@ -698,22 +692,22 @@ def run(list_packed_vars): 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'], + + + 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)) - + # ----- Export results ----- - sims_df = pd.DataFrame(output_all, columns=['tbias', 'kp', 'ddfsnow', 'mb_mwea', + 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) @@ -761,33 +755,33 @@ def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_ 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_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']]) - + 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), + 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, + + 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 - + """ Single parameter optimizer based on a mid-point approach + Computationally more robust and sometimes faster than scipy minimize """ assert prm2opt is not None, 'For single_param_optimizer you must specify parameter to optimize' - + if prm2opt == 'kp': prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] @@ -803,7 +797,7 @@ 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'] @@ -817,12 +811,12 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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']]) - + 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)) - + # Optimize the model parameter if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: modelprms[prm2opt] = prm_bndlow @@ -832,15 +826,15 @@ 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 and + 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, + 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 @@ -852,20 +846,20 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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'] - + sims_df['mb_em_dif'] = sims_df['mb_em'] - sims_df['mb_mwea'] + # ----- TEMPERATURE BIAS BOUNDS ----- # Selects from emulator sims dataframe sims_df_subset = sims_df.loc[sims_df['kp']==1, :] tbias_bndhigh = float(sims_df_subset['tbias'].max()) tbias_bndlow = float(sims_df_subset['tbias'].min()) - + # 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 @@ -885,8 +879,8 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, continue_param_search = False tbias_opt = tbias_bndlow kp_opt= kp_bndhigh - troubleshoot_fp = (pygem_prms['root'] + '/Output/errors/' + - args.option_calibration + '/' + + 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) @@ -894,13 +888,13 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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 + '/' + + 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) @@ -910,13 +904,13 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, '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']]) @@ -972,25 +966,25 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, '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))) - + # 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']]) # Upper bound modelprms['kp'] = kp_bndhigh mb_mwea_kp_high = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - + # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: kp_opt = kp_bndlow @@ -1006,43 +1000,43 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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), + 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, + 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) + 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) - + # 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), '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') - + 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']: @@ -1050,9 +1044,9 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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) @@ -1065,7 +1059,7 @@ 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 ====== # 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 @@ -1082,9 +1076,9 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, else: 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 """ @@ -1098,7 +1092,7 @@ def calc_mb_total_minelev(modelprms): # 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 * + 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 @@ -1118,7 +1112,7 @@ def calc_mb_total_minelev(modelprms): 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): @@ -1132,8 +1126,8 @@ def get_priors(priors): elif priors[param]['type'] == 'gamma': dist = stats.gamma(a=priors[param]['alpha'], scale=1/priors[param]['beta']) elif priors[param]['type'] == 'truncnormal': - dist = stats.truncnorm(a=(priors[param]['low']-priors[param]['mu'])/priors[param]['sigma'], - b=(priors[param]['high']-priors[param]['mu'])/priors[param]['sigma'], + 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 @@ -1151,7 +1145,7 @@ def get_initials(dists, threshold=.01): if all(p > threshold for p in ps): initials = xs return initials - + def mb_max(*args, **kwargs): """ Model parameters cannot completely melt the glacier (psuedo-likelihood fxn) """ if kwargs['massbal'] < mb_max_loss: @@ -1160,7 +1154,7 @@ def mb_max(*args, **kwargs): 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) @@ -1171,7 +1165,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): return 0 else: return -np.inf - # --------------------------------- + # --------------------------------- # --------------------------------- # ----- MASS BALANCE MAX LOSS ----- @@ -1186,7 +1180,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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 / + mb_max_loss = (-1 * consensus_mass / pygem_prms['constants']['density_water'] / gdir.rgi_area_m2 / (gdir.dates_table.shape[0] / 12)) # --------------------------------- @@ -1197,7 +1191,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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_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']) @@ -1264,7 +1258,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # -------------------- # ----- 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 @@ -1285,12 +1279,12 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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'], + 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 @@ -1313,7 +1307,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): '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) + fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) + '/fig/') os.makedirs(fp, exist_ok=True) if args.ncores > 1: @@ -1345,11 +1339,11 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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): @@ -1363,7 +1357,7 @@ 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) + '/' if not os.path.exists(mcmc_good_fp): @@ -1371,7 +1365,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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) + '/' @@ -1400,7 +1394,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): modelprms['ddfsnow'] = ddfsnow_init modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] continue_param_search = True - + # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid, debug=False): @@ -1426,29 +1420,29 @@ def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_ 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_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) 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), + 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, + + 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] @@ -1478,12 +1472,12 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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) - + 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)) - + # Optimize the model parameter if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: modelprms[prm2opt] = prm_bndlow @@ -1497,29 +1491,29 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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, + 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))) - + # Lower bound modelprms['kp'] = kp_bndlow mb_mwea_kp_low = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) # Upper bound modelprms['kp'] = kp_bndhigh mb_mwea_kp_high = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - + # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: kp_opt = kp_bndlow @@ -1535,7 +1529,7 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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: @@ -1575,7 +1569,7 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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: @@ -1602,13 +1596,13 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, tbias_bndhigh = modelprms['tbias'] # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, - 'ddfsnow': ddfsnow_opt, + 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) - + # Update parameter values tbias_opt = modelprms_opt['tbias'] modelprms['tbias'] = tbias_opt @@ -1617,8 +1611,8 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, else: tbias_opt = modelprms['tbias'] - - + + # Export model parameters modelprms = modelprms_opt for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: @@ -1628,7 +1622,7 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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) @@ -1641,8 +1635,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 ===== # used in Rounce et al. (2020; MCMC paper) # - precipitation factor, then temperature bias (no ddfsnow) @@ -1654,13 +1648,13 @@ 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'] - + # ----- FUNCTIONS ----- def objective(modelprms_subset): """ Objective function for mass balance data (mimize difference between model and observation). @@ -1673,7 +1667,7 @@ 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) @@ -1681,7 +1675,7 @@ def objective(modelprms_subset): return mb_dif_mwea_abs - def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, + 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. @@ -1706,7 +1700,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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 @@ -1723,7 +1717,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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) @@ -1780,7 +1774,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 @@ -1803,14 +1797,14 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, # ----- 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,9 +1813,9 @@ 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, + modelparams_opt, mb_mwea = run_objective(modelprms_subset, mb_obs_mwea, modelprms_bnds=modelprms_bnds, ftol_opt=1e-3) kp_opt = modelparams_opt['kp'] @@ -1839,7 +1833,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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) @@ -1860,7 +1854,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, os.makedirs(fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-cal_fail.txt" with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' had no flowlines or mb_data.') + text_file.write(glacier_str + ' had no flowlines or mb_data.') # Global variables for Spyder development if args.ncores == 1: @@ -1902,7 +1896,7 @@ def main(): # 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): @@ -1921,4 +1915,4 @@ def main(): print('Total processing time:', time.time()-time_start, 's') if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_calibration_frontalablation.py b/pygem/bin/run/run_calibration_frontalablation.py index 6b3a5fb3..5a3abbe3 100644 --- a/pygem/bin/run/run_calibration_frontalablation.py +++ b/pygem/bin/run/run_calibration_frontalablation.py @@ -9,38 +9,36 @@ """ # 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 ### @@ -92,7 +90,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, calc_mb_geo_correction=False, reset_gdir=True): """ Compute the calving flux for a group of glaciers - + Parameters ---------- main_glac_rgi : pd.DataFrame @@ -108,7 +106,7 @@ 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'], @@ -143,14 +141,14 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, output_df['oggm_dynamics'] = 0 output_df['mb_mwea_fa_asl_lost'] = 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']) - + # 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, + gdir = single_flowline_glacier_directory_with_calving(glacier_str, logging_level='CRITICAL', reset=reset_gdir, facorrected=False @@ -165,7 +163,7 @@ 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,:], @@ -173,15 +171,15 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, '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 @@ -197,24 +195,24 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, if pygem_prms['calib']['priors_reg_fn'] is not None: # Load priors priors_df = pd.read_csv(pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn']) - priors_idx = np.where((priors_df.O1Region == glacier_rgi_table['O1Region']) & + priors_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']} - + '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] @@ -223,13 +221,13 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, else: fs = pygem_prms['sim']['oggm_dynamics']['fs'] glen_a_multiplier = pygem_prms['sim']['oggm_dynamics']['glen_a_multiplier'] - + # CFL number (may use different values for calving to prevent errors) - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: + 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'] - + # ----- Mass balance model for ice thickness inversion using OGGM ----- mbmod_inv = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, fls=fls, option_areaconstant=False, @@ -242,7 +240,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, else: 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 @@ -253,7 +251,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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) - + # ------ 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 @@ -267,8 +265,8 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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, + + 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 @@ -277,7 +275,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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.) @@ -285,7 +283,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, # 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]) * + 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] @@ -293,37 +291,37 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, # 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]:', +# print('avg frontal ablation [Gta]:', # np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) -# print('avg frontal ablation [Gta]:', +# 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 # calving flux (Gt/yr) 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)) + + 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: @@ -338,7 +336,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, _, 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) @@ -346,15 +344,15 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, ev_model.mb_model.glac_wide_massbaltotal = ( ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) - calving_flux_km3a = (ev_model.mb_model.glac_wide_frontalablation.sum() * pygem_prms['constants']['density_water'] / + 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]:', +# print('avg frontal ablation [Gta]:', # np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) -# print('avg frontal ablation [Gta]:', +# 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) @@ -365,19 +363,19 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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)) + + 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: @@ -386,17 +384,17 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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() / + 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 - + # Area for retreat if args.verbose or debug: # print('\n----- area calcs -----') @@ -408,14 +406,14 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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) - + # Remove glaciers that failed to run if fa_glac_data_reg is None: reg_calving_gta_obs_good = None @@ -431,7 +429,7 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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 @@ -444,27 +442,27 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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)) - + # ----- 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 @@ -472,35 +470,35 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b # 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)) 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_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) @@ -509,7 +507,7 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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)) @@ -517,13 +515,13 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b # 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, @@ -538,22 +536,22 @@ 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) # ----- Optimize further using mid-point "bisection" method ----- # Consider replacing with scipy.optimize.brent 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 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, @@ -563,11 +561,11 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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): - + calving_k = calving_k_bndlow # Re-run the regional frontal ablation estimates output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( @@ -578,23 +576,23 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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 + + 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) @@ -607,7 +605,7 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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 +615,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 calving_k < calving_k_bndlow: calving_k = calving_k_bndlow - + return output_df, calving_k @@ -640,11 +638,11 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): if verbose: 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', + '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) @@ -673,7 +671,7 @@ 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['RGIId'] = fa_glac_data3['RGIId'] @@ -683,7 +681,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) @@ -702,7 +700,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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] - + 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,29 +714,29 @@ 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])) + '.' + + 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)] main_glac_rgi.reset_index(inplace=True, drop=True) - + # ----- QUALITY CONTROL USING MB_CLIM COMPARED TO REGIONAL MASS BALANCE ----- mb_data['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in mb_data.rgiid.values] mb_data_reg = mb_data.loc[mb_data['O1Region'] == reg, :] @@ -755,28 +753,28 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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: - 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', + 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 + + # Load observations fa_obs_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_cn])) fa_obs_unc_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_unc_cn])) # fa_glacname_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg.glacier_name)) rgi_area_dict = dict(zip(main_glac_rgi.RGIId, main_glac_rgi.Area)) - + output_df_all['fa_gta_obs'] = output_df_all['RGIId'].map(fa_obs_dict) output_df_all['fa_gta_obs_unc'] = output_df_all['RGIId'].map(fa_obs_unc_dict) # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) output_df_all['area_km2'] = output_df_all['RGIId'].map(rgi_area_dict) - + # ----- 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) @@ -799,7 +797,7 @@ 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] # Correct by using mean + 3std as maximum climatic mass balance if output_df_badmbclim.shape[0] > 0: @@ -812,13 +810,13 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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'] - + 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 - + # ---- FIRST ROUND CALIBRATION ----- # ----- OPTIMIZE CALVING_K BASED ON INDIVIDUAL GLACIER FRONTAL ABLATION DATA ----- failed_glacs = [] @@ -830,7 +828,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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.reset_index(inplace=True, drop=True) @@ -838,7 +836,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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: @@ -860,17 +858,17 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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 - + if verbose: print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) print(' fa_model_bndlow [Gt/yr] :', reg_calving_gta_mod_bndlow) print(' fa_model_bndhigh [Gt/yr] :', reg_calving_gta_mod_bndhigh) - - + + run_opt = False if bndhigh_good and bndlow_good: if reg_calving_gta_obs < reg_calving_gta_mod_bndlow: @@ -889,9 +887,9 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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, + 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'] @@ -899,7 +897,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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:') @@ -908,14 +906,14 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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, + 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)) - + # Lower uncertainty if verbose: print('\n\n----- lower uncertainty:') @@ -930,23 +928,23 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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, + 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)]) - + # Final if verbose: print('----- final -----') - print(rgiid, 'calving_k (med/high/low/nmad):', np.round(calving_k_med,2), + 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) @@ -956,7 +954,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # 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) @@ -965,28 +963,28 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati with open(output_fp + output_fn[:-4] + "-failed.txt", "w") as f: for item in failed_glacs: f.write(f"{item}\n") - + else: output_df_all = pd.read_csv(output_fp + output_fn) - + # ----- VIEW DIAGNOSTICS OF 'GOOD' GLACIERS ----- # special for 17 because so few 'good' glaciers if reg in [17]: output_df_all_good = output_df_all.loc[(output_df_all['calving_k'] < calving_k_bndhigh_set), :] else: - output_df_all_good = output_df_all.loc[(output_df_all['fa_gta_obs'] == output_df_all['fa_gta_max']) & + 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), + print(' calving_k mean/med:', np.round(calving_k_reg_mean,2), np.round(np.median(output_df_all_good.calving_k),2)) - + output_df_all['calving_flux_Gta_rnd1'] = output_df_all['calving_flux_Gta'].copy() output_df_all['calving_k_rnd1'] = output_df_all['calving_k'].copy() - + # ----- PLOT RESULTS FOR EACH GLACIER ----- if len(rgiids_good)>0: with np.errstate(all='ignore'): @@ -999,9 +997,9 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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}) - + # ----- Scatter plot ----- # Marker size glac_area_all = output_df_all_good['area_km2'].values @@ -1010,12 +1008,12 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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', + + 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_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) @@ -1023,13 +1021,13 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # Log scale 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, + 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', + 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), @@ -1040,7 +1038,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # 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]), + 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) @@ -1053,40 +1051,40 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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) - + # ----- 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', + 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', + 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'], + 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), + 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') - + # Save figure fig.set_size_inches(6,6) fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind-good.png' fig.savefig(fig_fullfn, bbox_inches='tight', dpi=300) - + # ----- 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) for nglac, rgiid in enumerate(output_df_all.RGIId): @@ -1096,29 +1094,29 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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.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) - + # ----- 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, + + 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) @@ -1127,14 +1125,14 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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) - + 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] - + # Update mass balance data output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) output_df_missing['RGIId'] = rgiids_missing @@ -1147,76 +1145,76 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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']) failed_glacs = [] for nglac, rgiid in enumerate(rgiids_missing): glacier_str = rgiid.split('-')[1] - try: + 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)) - + # 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'] + + 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)) - + # 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 - + 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,'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,'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,'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'] - + # 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))), + # 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 - + # 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, + 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, @@ -1224,130 +1222,130 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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']) - + # 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 - + if verbose: 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']: # 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'] + + 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,'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,'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,'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']: # 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'] + + 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,'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,'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,'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'] + + # mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + # output_df.loc[0,'mb_mwea_fa_asl_lost']) if verbose: print('\n\n\n-------') print('mb_clim_obs:', np.round(output_df_missing.loc[nglac,'mb_clim_mwea_obs'],2)) print('mb_clim_fa_corrected:', np.round(mb_clim_fa_corrected,2)) - + calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 calving_k_next = calving_k - calving_k_step_missing while output_df.loc[0,'mb_mwea_fa_asl_lost'] > fa_mwea_max and calving_k_next > 0: 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 - + # 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'] + + 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,'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,'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) - + output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], + output_df_missing.loc[nglac,'area_km2']*1e6) + if verbose: print('mb_clim_fa_corrected (updated):', np.round(mb_clim_fa_corrected,2)) - + # If mass balance is higher than 95% threshold, then just make sure correction is reasonable (no more than 10%) else: calving_k = calving_k_med calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 calving_k_next = calving_k - calving_k_step_missing - while (output_df.loc[0,'mb_mwea_fa_asl_lost'] > 0.1*output_df_missing.loc[nglac,'mb_clim_mwea_obs'] and + while (output_df.loc[0,'mb_mwea_fa_asl_lost'] > 0.1*output_df_missing.loc[nglac,'mb_clim_mwea_obs'] and calving_k_next > 0): calving_k -= calving_k_step_missing - + # Estimate frontal ablation for missing glaciers output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( reg_calving_flux(main_glac_rgi_ind, calving_k, args, debug=True, calc_mb_geo_correction=True)) - + calving_k_next = calving_k - calving_k_step_missing - + # 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'] + + 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,'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,'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) - + 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 + 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 @@ -1360,43 +1358,43 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # ----- CORRECTION FOR TOTAL MASS BALANCE IN ANTARCTICA/SUBANTARCTIC ----- if reg in [19]: - -# #%% STATISTICS FOR CALIBRATED GLACIERS + +# #%% 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,'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,'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, + + 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) - + 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] - + # Update mass balance data output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) output_df_missing['RGIId'] = rgiids_missing @@ -1409,15 +1407,15 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati output_df_missing.loc[x,'area_km2']*1e6) for x in output_df_missing.index] output_df_missing['mb_total_mwea_obs'] = output_df_missing['mb_clim_mwea_obs'] output_df_missing['mb_total_gta_obs'] = output_df_missing['mb_clim_gta_obs'] - + # Uncertainty with calving_k based on regional calibration # calving_k_nmad_missing = 1.4826 * median_abs_deviation(output_df_all.calving_k) calving_k_nmad_missing = np.median(output_df_all_good.calving_k_nmad) output_df_missing['calving_k_nmad'] = calving_k_nmad_missing - + # Check that climatic mass balance is reasonable 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']) for nglac, rgiid in enumerate(rgiids_missing): @@ -1427,62 +1425,62 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # 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)) - + # 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_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 (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_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 - + # 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))), + # 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 - + # 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, + 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, @@ -1490,16 +1488,16 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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 - + 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)) - + if bndhigh_good and bndlow_good: if verbose: print('\n-------') @@ -1510,7 +1508,7 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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 else: @@ -1518,17 +1516,17 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # Estimate frontal ablation for missing glaciers output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( reg_calving_flux(main_glac_rgi_ind, calving_k, args, debug=True, calc_mb_geo_correction=True, reset_gdir=reset_gdir)) - + mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) - + calving_k_next = calving_k - calving_k_step_missing - + if verbose: print(calving_k, 'mb_fa_mwea:', np.round(mb_fa_mwea,2), 'mb_fa_mwea_max:', np.round(fa_mwea_max,2)) - + # 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] - + mb_clim_mwea = mb_total_mwea + mb_fa_mwea if verbose: print('mb_total_mwea:', np.round(mb_total_mwea,2)) @@ -1540,20 +1538,20 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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_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 - + 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)) @@ -1571,12 +1569,12 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # # 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_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) @@ -1592,9 +1590,9 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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}) - + # ----- Scatter plot ----- # Marker size glac_area_all = output_df_all['area_km2'].values @@ -1603,12 +1601,12 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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', + + 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_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) @@ -1616,28 +1614,28 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # Log scale 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, + 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', + 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', +# 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]), + 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) @@ -1650,26 +1648,26 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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) - + # ----- 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', + 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', + ax[1,1].scatter(output_df_all['area_km2'], output_df_all['calving_k'], + color='k', marker='o', linewidth=1, facecolor='none', s=s_byarea, clip_on=True) ax[1,1].set_xlabel('Area (km2)', size=12) ax[1,1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) - - + + # Save figure fig.set_size_inches(6,6) fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind.png' @@ -1684,37 +1682,37 @@ def merge_ind_calving_k(regions=list(range(1,20)), output_fp='', merged_calving_ 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') 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): + 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}') - + return @@ -1739,18 +1737,18 @@ 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'] - + 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), + 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 @@ -1759,10 +1757,10 @@ 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) @@ -1776,7 +1774,7 @@ 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]) @@ -1788,18 +1786,18 @@ def plot_calving_k_allregions(output_fp=''): 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): 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)) @@ -1809,9 +1807,9 @@ def plot_calving_k_allregions(output_fp=''): # 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 @@ -1820,32 +1818,32 @@ def plot_calving_k_allregions(output_fp=''): 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', + + 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', + + 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) # 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]), + # 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) @@ -1858,17 +1856,17 @@ def plot_calving_k_allregions(output_fp=''): # 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='-', + # 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=':', + # 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) @@ -1877,30 +1875,30 @@ def plot_calving_k_allregions(output_fp=''): # 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, + 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', + 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) - + # Save figure fig_fn = ('allregions_calving_ObsMod.png') fig.set_size_inches(6.5,5.5) @@ -1929,7 +1927,7 @@ def main(): 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)') + 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', @@ -1969,4 +1967,4 @@ def main(): plot_calving_k_allregions(output_fp=output_fp) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_calibration_reg_glena.py b/pygem/bin/run/run_calibration_reg_glena.py index 18e5ca16..cf96cf0c 100644 --- a/pygem/bin/run/run_calibration_reg_glena.py +++ b/pygem/bin/run/run_calibration_reg_glena.py @@ -5,36 +5,38 @@ 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 @@ -155,7 +157,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') @@ -176,39 +178,39 @@ def surf_to_nan(surf_h, thick): def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False): """ Calculate the modeled volume [km3] and consensus volume [km3] for the given set of glaciers """ - + reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 for nglac, gdir in enumerate(gdirs): if nglac%2000 == 0: 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 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 - + 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') - + return reg_vol_km3_modeled, reg_vol_km3_consensus - + #%% def main(): parser = getparser() @@ -222,32 +224,32 @@ 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', + 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_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.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.') - + # ===== 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']) - + # ===== LOAD CLIMATE DATA ===== # Climate class gcm_name = args.ref_gcm_name @@ -266,14 +268,14 @@ def main(): 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) - + # ===== 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) else: assert True==False, 'Adjust nyears for non-monthly timestep' - + reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 mbmods = [] @@ -282,17 +284,17 @@ def main(): # Select subsets of data glacier_rgi_table = main_glac_rgi_subset.loc[main_glac_rgi_subset.index.values[glac], :] glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) - + if glac%1000 == 0: 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,:], @@ -300,20 +302,20 @@ def main(): '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_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') modelprms_all = modelprms_dict['emulator'] - + # Loop through model parameters modelprms = {'kp': modelprms_all['kp'][0], 'tbias': modelprms_all['tbias'][0], @@ -321,53 +323,53 @@ def main(): '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']) + # 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') - + # ===== 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) # 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, + 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, + 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: @@ -378,20 +380,20 @@ def main(): 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, + 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) # 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, + 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) @@ -399,25 +401,25 @@ def to_minimize(a_multiplier): try: 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 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) - + glena_df.to_csv(f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}", index=False) + print('\n\n------\nTotal processing time:', time.time()-time_start, 's') if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_mcmc_priors.py b/pygem/bin/run/run_mcmc_priors.py index 04fc088f..88e5a3ce 100644 --- a/pygem/bin/run/run_mcmc_priors.py +++ b/pygem/bin/run/run_mcmc_priors.py @@ -1,12 +1,12 @@ """ 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 @@ -42,7 +43,7 @@ 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', + '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(): @@ -77,10 +78,10 @@ def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): 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) @@ -92,25 +93,25 @@ def plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2=''): fig, ax = plt.subplots(1,2, figsize=(6,4), gridspec_kw = {'wspace':0.3, 'hspace':0.3}) labelsize = 1 fig.text(0.5,0.9, 'Region ' + str(reg) + ' (subregion: ' + str(regO2) + ')'.replace(' (subregion: )', '(all subregions)'), ha='center', size=14) - + nbins = 50 ax[0].hist(main_glac_rgi_subset['kp'], bins=nbins, color='grey') ax[0].set_xlabel('kp (-)') ax[0].set_ylabel('Count (glaciers)') ax[1].hist(main_glac_rgi_subset['tbias'], bins=50, color='grey') ax[1].set_xlabel('tbias (degC)') - + fig_fn = str(reg) + '-' + str(regO2) + '_hist_mcmc_priors.png'.replace('-_','_') fig.savefig(fig_fp + fig_fn, pad_inches=0, dpi=150) def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): # ===== REGIONAL PRIOR: PRECIPITATION FACTOR ====== - nbins = 50 + nbins = 50 ncols = 3 nrows = int(np.ceil(len(rgi_regionsO2)/ncols)) priors_df_regO1 = priors_df.loc[priors_df['O1Region'] == reg] - + fig, ax = plt.subplots(nrows, ncols, squeeze=False, gridspec_kw={'wspace':0.5, 'hspace':0.5}) nrow = 0 ncol = 0 @@ -120,9 +121,9 @@ 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', + 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] @@ -131,12 +132,12 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): # 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', + 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', + 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 @@ -152,27 +153,27 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 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.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) # ===== 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 nglaciers = tbias_values.shape[0] - + # Plot histogram - counts, bins, patches = ax[nrow,ncol].hist(tbias_values, facecolor='grey', edgecolor='grey', + 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] @@ -181,14 +182,14 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): # 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', + 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', + 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: @@ -202,7 +203,7 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): for nextra in np.arange(0,n_extras): ax[nrow,ncol].axis('off') ncol += 1 - + # Labels fig.text(0.04, 0.5, 'Probability Density', va='center', ha='center', rotation='vertical', size=12) fig.text(0.5, 0.04, r'$T_{bias}$ ($^\circ$C)', va='center', ha='center', size=12) @@ -217,9 +218,9 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, # Load glaciers 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,22 +231,22 @@ 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] - + 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'] @@ -253,7 +254,7 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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) @@ -261,32 +262,32 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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), @@ -299,10 +300,10 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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, + 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,25 +316,25 @@ 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), @@ -341,16 +342,16 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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: + + 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, + 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) @@ -374,4 +375,4 @@ def main(): print('\n\n------\nTotal processing time:', time.time()-time_start, 's') if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_simulation.py b/pygem/bin/run/run_simulation.py index 3436797c..2819151f 100755 --- a/pygem/bin/run/run_simulation.py +++ b/pygem/bin/run/run_simulation.py @@ -16,47 +16,45 @@ # 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 @@ -68,7 +66,7 @@ def none_or_value(value): if value.lower() in {"none", "null"}: return None return value - + def getparser(): """ Use argparse to add arguments from the command line @@ -165,9 +163,9 @@ def getparser(): 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']) + 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']) + 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', @@ -209,7 +207,7 @@ 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 @@ -218,23 +216,23 @@ def run(list_packed_vars): 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']) - + # GCM Simulation Period dates_table = modelsetup.datesmodelrun( - startyear=args.gcm_startyear, endyear=args.gcm_endyear, + 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']: @@ -249,7 +247,7 @@ def run(list_packed_vars): 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, @@ -267,7 +265,7 @@ def run(list_packed_vars): except: gcm_elev = None 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: @@ -324,10 +322,10 @@ def run(list_packed_vars): 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: @@ -357,15 +355,15 @@ def run(list_packed_vars): 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) - - + + # ===== 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) @@ -386,7 +384,7 @@ def run(list_packed_vars): # 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']: + 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 @@ -398,7 +396,7 @@ def run(list_packed_vars): # 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], @@ -414,29 +412,29 @@ def run(list_packed_vars): gcm_tempstd = gcm_tempstd[::-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.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_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') modelprms_all = modelprms_dict[args.option_calibration] @@ -467,17 +465,17 @@ def run(list_packed_vars): 'precgrad': modelprms_all['precgrad'] * nsims} else: nsims = 1 - + # Calving parameter - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: + if glacier_rgi_table['TermType'] not in [1,5] or not pygem_prms['setup']['include_frontalablation']: calving_k = None else: - # Load quality controlled frontal ablation data + # Load quality controlled frontal ablation data fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['frontalablation']['frontalablation_relpath']}/analysis/{pygem_prms['calib']['data']['frontalablation']['frontalablation_cal_fn']}" assert os.path.exists(fp), 'Calibrated calving dataset does not exist' 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) @@ -489,29 +487,29 @@ def run(list_packed_vars): 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_values < 0.001] = 0.001 calving_k_values[calving_k_values > 5] = 5 - + # calving_k_values[:] = calving_k - + while not abs(np.median(calving_k_values) - calving_k) < 0.001: calving_k_values = calving_k + np.random.normal(loc=0, scale=calving_k_nmad, size=nsims) calving_k_values[calving_k_values < 0.001] = 0.001 calving_k_values[calving_k_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: + 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], @@ -522,26 +520,26 @@ def run(list_packed_vars): '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']: + 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'] - + 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] @@ -551,7 +549,7 @@ def run(list_packed_vars): args.option_dynamics = None fs = pygem_prms['sim']['oggm_dynamics']['fs'] glen_a_multiplier = pygem_prms['sim']['oggm_dynamics']['glen_a_multiplier'] - + # Time attributes and values if pygem_prms['climate']['gcm_wateryear'] == 'hydro': annual_columns = np.unique(dates_table['wateryear'].values)[0:int(dates_table.shape[0]/12)] @@ -580,30 +578,30 @@ def run(list_packed_vars): 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)) + @@ -617,15 +615,15 @@ 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']) +# 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)') @@ -644,12 +642,12 @@ def run(list_packed_vars): cfg.PARAMS['use_kcalving_for_run'] = True tasks.find_inversion_calving_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref), glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) - + # ----- INDENTED TO BE JUST WITH DYNAMICS ----- tasks.init_present_time_glacier(gdir) # adds bins below if pygem_prms['mb']['include_debris']: debris.debris_binned(gdir, fl_str='model_flowlines') # add debris enhancement factors to flowlines - + try: nfls = gdir.read_pickle('model_flowlines') except FileNotFoundError as e: @@ -666,15 +664,15 @@ 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, @@ -684,24 +682,24 @@ def run(list_packed_vars): 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, + 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] - + # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Glacier-wide frontal ablation (m3 w.e.) @@ -709,7 +707,7 @@ def run(list_packed_vars): 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]) * + 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] @@ -717,21 +715,21 @@ def run(list_packed_vars): # 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]:', + print('avg frontal ablation [Gta]:', np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', + 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 + '/' + 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 + '/' @@ -739,7 +737,7 @@ def run(list_packed_vars): 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 ' + + text_file.write(glacier_str + ' failed to complete ' + str(count_exceed_boundary_errors) + ' simulations') elif gdir.is_tidewater: if debug: @@ -755,7 +753,7 @@ def run(list_packed_vars): _, 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) @@ -764,9 +762,9 @@ def run(list_packed_vars): ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) if debug: - print('avg frontal ablation [Gta]:', + print('avg frontal ablation [Gta]:', np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', + print('avg frontal ablation [Gta]:', np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) except: @@ -783,7 +781,7 @@ def run(list_packed_vars): _, 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) @@ -792,15 +790,15 @@ def run(list_packed_vars): ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) if debug: - print('avg frontal ablation [Gta]:', + print('avg frontal ablation [Gta]:', np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', + 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!') @@ -831,18 +829,18 @@ def run(list_packed_vars): ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) if debug: - print('avg frontal ablation [Gta]:', + print('avg frontal ablation [Gta]:', np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', + 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 + '/' + 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 + '/' @@ -850,14 +848,14 @@ def run(list_packed_vars): 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 ' + + 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 @@ -880,19 +878,19 @@ def run(list_packed_vars): 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) - - + + # Record output for successful runs if successful_run: - + if args.option_dynamics is not None: if debug: graphics.plot_modeloutput_section(ev_model) @@ -900,14 +898,14 @@ def run(list_packed_vars): 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]) + 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)) @@ -915,14 +913,14 @@ def run(list_packed_vars): 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) - + # RECORD PARAMETERS TO DATASET output_glac_temp_monthly[:, n_iter] = mbmod.glac_wide_temp output_glac_prec_monthly[:, n_iter] = mbmod.glac_wide_prec @@ -947,9 +945,9 @@ def run(list_packed_vars): 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_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: @@ -960,7 +958,7 @@ def run(list_packed_vars): 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: + 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] @@ -994,7 +992,7 @@ def run(list_packed_vars): # 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 * + 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: @@ -1005,7 +1003,7 @@ def run(list_packed_vars): 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: + 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] @@ -1017,35 +1015,35 @@ def run(list_packed_vars): 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 = 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 = 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 = 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 = 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 = 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 = np.append(output_glac_bin_refreeze_monthly, output_glac_bin_refreeze_monthly_sim[:,:,np.newaxis], axis=2) @@ -1055,7 +1053,7 @@ 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, + output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, dates_table=dates_table, nsims=1, gcm_name = gcm_name, @@ -1104,7 +1102,7 @@ def run(list_packed_vars): output_stats.save_xr_ds() # instantiate dataset for merged simulations - output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, + output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, dates_table=dates_table, nsims=nsims, gcm_name = gcm_name, @@ -1167,7 +1165,7 @@ def run(list_packed_vars): 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] @@ -1200,13 +1198,13 @@ def run(list_packed_vars): # ----- DECADAL ICE THICKNESS STATS FOR OVERDEEPENINGS ----- 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, + output_binned = output.binned_stats(glacier_rgi_table=glacier_rgi_table, dates_table=dates_table, nsims=1, nbins = surface_h_initial.shape[0], @@ -1245,7 +1243,7 @@ def run(list_packed_vars): output_binned.save_xr_ds() # instantiate dataset for merged simulations - output_binned = output.binned_stats(glacier_rgi_table=glacier_rgi_table, + output_binned = output.binned_stats(glacier_rgi_table=glacier_rgi_table, dates_table=dates_table, nsims=nsims, nbins = surface_h_initial.shape[0], @@ -1291,7 +1289,7 @@ def run(list_packed_vars): 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.save_xr_ds() @@ -1363,7 +1361,7 @@ def main(): 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))) - + # Read realizations from argument parser if args.realization is not None: realizations = [args.realization] @@ -1373,7 +1371,7 @@ def main(): 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 +1380,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,7 +1391,7 @@ 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: @@ -1408,4 +1406,4 @@ def main(): print('Total processing time:', time.time()-time_start, 's') if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/class_climate.py b/pygem/class_climate.py index bd701d39..1b8f1cb7 100755 --- a/pygem/class_climate.py +++ b/pygem/class_climate.py @@ -8,11 +8,15 @@ 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 @@ -21,7 +25,7 @@ class of climate data and functions associated with manipulating the dataset to class GCM(): """ Global climate model data properties and functions used to automatically retrieve data. - + Attributes ---------- name : str @@ -31,27 +35,27 @@ class GCM(): realization : str realization from large ensemble (example: '1011.001' or '1301.020') """ - def __init__(self, + 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!' - + # 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) @@ -74,7 +78,7 @@ def __init__(self, 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) @@ -97,10 +101,10 @@ def __init__(self, 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 @@ -125,7 +129,7 @@ def __init__(self, self.timestep = pygem_prms['time']['timestep'] self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] - + elif self.name == 'ERA-Interim': # Variable names self.temp_vn = 't2m' @@ -147,7 +151,7 @@ def __init__(self, self.timestep = pygem_prms['time']['timestep'] self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] - + # Standardized CMIP5 format (GCM/RCP) elif 'rcp' in scenario: # Variable names @@ -173,7 +177,7 @@ def __init__(self, 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 @@ -195,14 +199,14 @@ def __init__(self, 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 +215,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,7 +231,7 @@ 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 + + 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] @@ -236,27 +240,27 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): 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] - + 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] - + 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 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 +269,19 @@ 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']): """ 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 +292,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 @@ -303,34 +307,34 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ 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])) - + # 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()) - + # 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')) == + 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')) == + 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] + .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]) @@ -340,11 +344,11 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ .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 + + 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] @@ -352,26 +356,26 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ 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] - + 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] - + 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 else: 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 @@ -391,7 +395,7 @@ 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 # (1 kg m-2 s-1) * (1 m3/1000 kg) * (3600 s / hr) * (24 hr / day) = (m day-1) diff --git a/pygem/gcmbiasadj.py b/pygem/gcmbiasadj.py index e3cc8e81..09f40e89 100755 --- a/pygem/gcmbiasadj.py +++ b/pygem/gcmbiasadj.py @@ -8,15 +8,15 @@ 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 @@ -50,16 +50,16 @@ def monthly_std_2darray(x): """ 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): """ 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 +70,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 @@ -86,12 +86,12 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab # 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:] - + # Roll months so they are aligned with simulation months 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) @@ -104,7 +104,7 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab # 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) @@ -123,7 +123,7 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab # 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)) - + # 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 @@ -135,10 +135,10 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab 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)) - + # 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:]) @@ -146,13 +146,13 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab 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) - + 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 @@ -160,7 +160,7 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab ref_spinupyears=0, gcm_spinupyears=0): """ Huss and Hock (2015) precipitation bias correction based on mean (multiplicative) - + Parameters ---------- ref_prec : np.array @@ -173,7 +173,7 @@ 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 @@ -183,21 +183,21 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab 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:] - + # Roll months so they are aligned with simulation months 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) 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: @@ -209,13 +209,13 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) 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)) - + # 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:]) @@ -223,8 +223,8 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab 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' - + assert gcm_prec_biasadj.min() >= 0, 'gcm_prec_adj is producing a negative precipitation value' + return gcm_prec_biasadj, gcm_elev_biasadj @@ -232,7 +232,7 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table ref_spinupyears=0, gcm_spinupyears=0): """ Precipitation bias correction based on mean with limited maximum - + Parameters ---------- ref_prec : np.array @@ -243,7 +243,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 @@ -255,21 +255,21 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table 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:] - + # Roll months so they are aligned with simulation months 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) 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: @@ -281,28 +281,28 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) 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)) - + # 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]]) # 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))) / + (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()), + .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)) + 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] @@ -310,16 +310,16 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table 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]) - + # Replace outliers with monthly mean adjusted for the normalized annual variation - outlier_replacement = (gcm_prec_annual_norm_repeated * + 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:]) @@ -327,21 +327,21 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table 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' - + 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): """ 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 +349,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 +359,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 ------- @@ -376,7 +376,7 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, # 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 - + # 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) @@ -389,64 +389,64 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) 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 - + # 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 - + 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 - + # 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)): + 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) + 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): """ 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 +454,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 +464,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,7 +473,7 @@ 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] @@ -482,7 +482,7 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, # 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:] - + # 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) @@ -495,49 +495,49 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) 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 - + # bc_prec = bc_prec[0] 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 - + # 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_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) + 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 + 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 - + + 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 - + """ Monthly average array from reference data rolled to ensure proper months + Parameters ---------- ref_array : np.array @@ -546,7 +546,7 @@ 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 @@ -554,12 +554,12 @@ def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_starty """ # 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] - + # Roll months so they are aligned with simulation months roll_amt = -1*(12 - gcm_subset_idx_start%12) ref_array_monthly_avg = np.roll(monthly_avg_2darray(ref_array), roll_amt, axis=1) gcm_array = np.tile(ref_array_monthly_avg, int(dates_table.shape[0]/12)) - + # 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) @@ -570,5 +570,5 @@ def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_starty 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 + + return gcm_array diff --git a/pygem/glacierdynamics.py b/pygem/glacierdynamics.py index 03c22a26..a604b7c7 100755 --- a/pygem/glacierdynamics.py +++ b/pygem/glacierdynamics.py @@ -9,15 +9,16 @@ from time import gmtime, strftime import numpy as np + #import pandas as pd #import netCDF4 import xarray as xr - -from oggm import cfg, utils +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 @@ -34,15 +35,15 @@ class MassRedistributionCurveModel(FlowlineModel): """ def __init__(self, flowlines, mb_model=None, y0=0., - glen_a=None, fs=0., is_tidewater=False, water_level=0, + glen_a=None, fs=0., is_tidewater=False, water_level=0, # calving_k=0, inplace=False, debug=True, - option_areaconstant=False, spinupyears=0, + option_areaconstant=False, spinupyears=0, constantarea_years=0, **kwargs): """ Instanciate the model. - + Parameters ---------- flowlines : list @@ -82,15 +83,15 @@ def __init__(self, flowlines, mb_model=None, y0=0., # 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' - - + + 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 +103,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,7 +112,7 @@ 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: @@ -121,8 +122,8 @@ def run_until(self, y1, run_single_year=False): 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): @@ -255,17 +256,17 @@ 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: @@ -326,14 +327,14 @@ 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): @@ -342,19 +343,19 @@ def updategeometry(self, year, debug=False): 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): # 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, + 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) @@ -363,7 +364,7 @@ def updategeometry(self, year, debug=False): 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] while fa_m3 > 0 and len(glac_idx_bsl) > 0: if debug: @@ -372,14 +373,14 @@ def updategeometry(self, year, debug=False): # OGGM code # 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), + 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 @@ -387,53 +388,53 @@ def updategeometry(self, year, debug=False): 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 + 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']) # 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), + 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() + 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 * + +# 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: @@ -443,10 +444,10 @@ def updategeometry(self, year, debug=False): # 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) - + + 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 @@ -458,13 +459,13 @@ def updategeometry(self, year, debug=False): 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 @@ -504,52 +505,52 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, 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 last_above_wl is not None: if (fl.bed_h[last_above_wl] < self.water_level): # Volume [m3] and bed elevation [masl] of each bin if debug: print('\nyear:', year, '\n sea level:', self.water_level, 'bed elev:', np.round(fl.bed_h[last_above_wl], 2)) print(' estimate frontal ablation') print(' min elevation:', fl.surface_h[last_above_wl]) - + # --- The rest is for calving only --- self.calving_rate_myr = 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] 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, + + #%%%% ====== 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,118 +568,118 @@ 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() - + # 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)) if debug: - print('\nmax icethickness change:', np.round(icethickness_change.max(),3), - '\nmin icethickness change:', np.round(icethickness_change.min(),3), + 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] + 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 / + massbalclim_retreat[glac_idx_t0_retreated] = (glacier_volumechange_remaining / glacier_area_t0_retreated.sum() / sec_in_year) - # Mass redistribution + # 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, + 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), + 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') - + # Index bins that are advancing 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] - + 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() + 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: @@ -686,105 +687,105 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb # 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], + 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 - + 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]) - + 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], + + 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 - + if debug: print('thick_reduction:', thick_reduction) print('----\nprior to correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term]) - + self.fls[0].thick[glac_idx_t0_term] = (self.fls[0].thick[glac_idx_t0_term] - thick_reduction) glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter - + # Advance volume [m3] - advance_volume = ((glacier_area_t0_raw[glac_idx_t0_term] * thick_t0_raw[glac_idx_t0_term]).sum() + advance_volume = ((glacier_area_t0_raw[glac_idx_t0_term] * thick_t0_raw[glac_idx_t0_term]).sum() - (glacier_area_t1[glac_idx_t0_term] * self.fls[0].thick[glac_idx_t0_term]).sum()) - + if debug: print('post correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term]) print('surface_h:', self.fls[0].surface_h[glac_idx_t0]) print('advance_volume:', advance_volume) print('icethickness_change:', icethickness_change) - + # 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 == + 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 + 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) - + # Average area of glacier terminus [m2] # exclude the bin at the terminus, since this bin may need to be filled first try: @@ -792,19 +793,19 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb 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: + 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 + glac_idx_initial[(heights[glac_idx_initial] - heights[glac_idx_initial].min()) / + (heights[glac_idx_initial].max() - heights[glac_idx_initial].min()) * 100 < pygem_prms['sim']['terminus_percentage']]) if glac_idx_terminus_initial.shape[0] <= 1: glac_idx_terminus_initial = glac_idx_initial.copy() - + minelev_idx = np.where(heights == heights[glac_idx_terminus_initial].min())[0][0] glac_idx_terminus_removemin = list(glac_idx_terminus_initial) glac_idx_terminus_removemin.remove(minelev_idx) 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 @@ -819,35 +820,35 @@ 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 / + 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(), + 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, + + + 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 +869,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 +893,36 @@ 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]) / + 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 + + 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() 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 +930,20 @@ 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 - + 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 # 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..8401f53b 100644 --- a/pygem/massbalance.py +++ b/pygem/massbalance.py @@ -7,10 +7,13 @@ """ # 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 @@ -97,7 +100,7 @@ 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) @@ -167,9 +170,9 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, # 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 @@ -188,7 +191,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 @@ -214,13 +217,13 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=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 - + # Glacier indices glac_idx_t0 = glacier_area_t0.nonzero()[0] - + nbins = heights.shape[0] nmonths = self.glacier_gcm_temp.shape[0] @@ -236,7 +239,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, if self.glacier_area_initial.sum() > 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) @@ -255,7 +258,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, # 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 + # 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) + @@ -302,7 +305,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, 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 @@ -605,7 +608,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, # Record binned glacier area 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, + self._convert_glacwide_results(year, glacier_area_t0, heights, fls=fls, fl_id=fl_id, option_areaconstant=option_areaconstant) ## if debug: @@ -622,7 +625,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, 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) @@ -641,24 +644,24 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, 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 #%% - def _convert_glacwide_results(self, year, glacier_area, heights, + 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 @@ -679,7 +682,7 @@ 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) - + # 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) @@ -690,7 +693,7 @@ def _convert_glacwide_results(self, year, glacier_area, heights, 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()) + glacier_area.sum()) else: mb_max_loss = 0 mb_mwea = 0 @@ -733,15 +736,15 @@ def _convert_glacwide_results(self, year, glacier_area, heights, # 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] + 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_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 - - + + # 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)] - @@ -774,14 +777,14 @@ def _convert_glacwide_results(self, year, glacier_area, heights, ela_mask = np.zeros(heights.shape) 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 - # ===== Off-glacier ==== + # ===== 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) @@ -805,54 +808,54 @@ def _convert_glacwide_results(self, year, glacier_area, heights, 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) * + # Compute difference between volume change + vol_change_annual_mbmod = (self.glac_wide_massbaltotal.reshape(-1,12).sum(1) * pygem_prms['constants']['density_water'] / pygem_prms['constants']['density_ice']) vol_change_annual_diag = np.zeros(vol_change_annual_mbmod.shape) vol_change_annual_diag[0:diag.volume_m3.values[1:].shape[0]] = diag.volume_m3.values[1:] - diag.volume_m3.values[:-1] vol_change_annual_dif = vol_change_annual_diag - vol_change_annual_mbmod # Reduce glacier melt by the difference - vol_change_annual_mbmod_melt = (self.glac_wide_melt.reshape(-1,12).sum(1) * + 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]) - + 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 - + # 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) - + # Glacier-wide runoff (m3) 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): @@ -1011,4 +1014,4 @@ def _surfacetypeDDFdict(self, modelprms, include_firn=pygem_prms['mb']['include_ 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 + return surfacetype_ddf_dict diff --git a/pygem/mcmc.py b/pygem/mcmc.py index 1f41c27c..abd2f98a 100644 --- a/pygem/mcmc.py +++ b/pygem/mcmc.py @@ -7,14 +7,15 @@ 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 @@ -54,7 +55,7 @@ def log_normal_density(x, **kwargs): k = mu.shape[-1] return torch.tensor([ - -k/2.*torch.log(torch.tensor(2*np.pi)) - + -k/2.*torch.log(torch.tensor(2*np.pi)) - torch.log(sigma).nansum() - 0.5*(((x-mu)/sigma)**2).nansum() ]) @@ -93,16 +94,16 @@ 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` @@ -127,7 +128,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()): @@ -153,7 +154,7 @@ def check_priors(self): def update_modelprms(self, m): 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): @@ -183,7 +184,7 @@ def log_likelihood(self): for i, pred in enumerate(self.preds): 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 @@ -233,7 +234,7 @@ 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 @@ -325,7 +326,7 @@ def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_facto self.preds_primes, \ torch.vstack(self.steps), \ self.acceptance - + ### some other useful functions ### def effective_n(x): @@ -471,4 +472,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 7a98e844..2337fef0 100755 --- a/pygem/oggm_compat.py +++ b/pygem/oggm_compat.py @@ -8,19 +8,24 @@ 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 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,9 +35,9 @@ 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'], + +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) @@ -57,19 +62,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 - + # 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 +93,15 @@ 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'], + 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')): @@ -110,12 +115,12 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit 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'], + +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'], + 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']): @@ -149,13 +154,13 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg # 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 - + # 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 +170,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 +179,15 @@ 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'], + 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!') @@ -203,15 +208,15 @@ 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, - ); + ) # 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,9 +224,9 @@ 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): diff --git a/pygem/output.py b/pygem/output.py index 1d428720..7d212e3e 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -11,15 +11,22 @@ 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 @@ -229,7 +236,7 @@ def _init_dicts(self): '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 @@ -253,19 +260,19 @@ def create_xr_ds(self): except: pass # Encoding (specify _FillValue, offsets, etc.) - + if vn not in noencoding_vn: self.encoding[vn] = {'_FillValue': None, 'zlib':True, 'complevel':9 - } + } self.output_xr_ds['RGIId'].values = np.array([self.glacier_rgi_table.loc['RGIId']]) self.output_xr_ds['CenLon'].values = np.array([self.glacier_rgi_table.CenLon]) self.output_xr_ds['CenLat'].values = np.array([self.glacier_rgi_table.CenLat]) self.output_xr_ds['O1Region'].values = np.array([self.glacier_rgi_table.O1Region]) self.output_xr_ds['O2Region'].values = np.array([self.glacier_rgi_table.O2Region]) self.output_xr_ds['Area'].values = np.array([self.glacier_rgi_table.Area * 1e6]) - + self.output_xr_ds.attrs = {'source': f'PyGEMv{self.pygem_version}', 'institution': pygem_prms['user']['institution'], 'history': f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + datetime.today().strftime('%Y-%m-%d'), @@ -275,11 +282,11 @@ def create_xr_ds(self): 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 +304,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,8 +324,8 @@ 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', @@ -331,14 +338,14 @@ def _update_dicts(self): '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), + 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), + 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', @@ -352,69 +359,69 @@ def _update_dicts(self): '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), + self.output_coords_dict['offglac_runoff_monthly'] = collections.OrderedDict([('glac', self.glac_values), ('time', self.time_values)]) - self.output_attrs_dict['offglac_runoff_monthly'] = { + 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), + 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), + 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), + 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), + 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), + 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), + 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'} - + # 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), + 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), + 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', @@ -423,26 +430,26 @@ def _update_dicts(self): '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), + 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), + 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), + 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), + 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', @@ -451,14 +458,14 @@ def _update_dicts(self): '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), + 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), + 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', @@ -467,32 +474,32 @@ def _update_dicts(self): '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'] = { + 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), + 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), + 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), + 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), + 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', @@ -502,14 +509,14 @@ def _update_dicts(self): # 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), + 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), + 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', @@ -519,26 +526,26 @@ def _update_dicts(self): '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), + 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), + 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), + 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), + 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', @@ -547,14 +554,14 @@ def _update_dicts(self): '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), + 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), + 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', @@ -563,39 +570,39 @@ def _update_dicts(self): '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'] = { + 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), + 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), + 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), + 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), + self.output_coords_dict['offglac_snowpack_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), ('time', self.time_values)]) self.output_attrs_dict['offglac_snowpack_monthly_mad'] = { 'long_name': 'off-glacier-wide snowpack, in water equivalent, median absolute deviation', 'units': 'm3', 'temporal_resolution': 'monthly', 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze'} - + @dataclass class binned_stats(single_glacier): @@ -603,7 +610,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 +627,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. @@ -657,7 +664,7 @@ def _update_dicts(self): 'long_name': 'binned glacier area', 'units': 'm2', 'temporal_resolution': 'annual', - 'comment': 'binned area at start of the year'} + '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'] = { @@ -686,7 +693,7 @@ def _update_dicts(self): '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'] = ( @@ -710,7 +717,7 @@ def _update_dicts(self): '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'] = ( @@ -752,7 +759,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), @@ -764,11 +771,11 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): '97.5%': lambda x: np.nanpercentile(x, 97.5, axis=1), '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] - + # 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..daa293b1 100755 --- a/pygem/pygem_modelsetup.py +++ b/pygem/pygem_modelsetup.py @@ -9,18 +9,22 @@ """ # 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'], +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. @@ -267,7 +271,7 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py 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_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'], @@ -323,7 +327,7 @@ 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: @@ -349,8 +353,8 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' 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] + + 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: @@ -358,7 +362,7 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' else: 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) @@ -411,7 +415,7 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' # 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] - + # Remove glaciers below threshold glacier_table = glacier_table.loc[glacier_table['Area'] > min_glac_area_km2,:] glacier_table.reset_index(inplace=True, drop=True) @@ -447,14 +451,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 @@ -479,19 +483,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: nbatch = 0 - + lst_batches[nbatch].append(x) - + nbatch += 1 if group_thousands: @@ -513,10 +517,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..6398ea43 100755 --- a/pygem/scraps/dummy_task_module.py +++ b/pygem/scraps/dummy_task_module.py @@ -1,7 +1,8 @@ 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__) 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..9412f77f 100644 --- a/pygem/setup/config.py +++ b/pygem/setup/config.py @@ -7,6 +7,7 @@ """ import os import shutil + import ruamel.yaml __all__ = ["ConfigManager"] @@ -28,7 +29,7 @@ def __init__(self, config_filename='config.yaml', base_dir=None, overwrite=False 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: @@ -40,7 +41,7 @@ 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}") - + def read_config(self, validate=True): """Read the configuration file and return its contents as a dictionary while preserving formatting. Parameters: @@ -65,15 +66,15 @@ 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}") @@ -83,13 +84,13 @@ def update_config(self, updates): 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. """ @@ -110,7 +111,7 @@ def _validate_config(self, config): 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}") - + # expected config types EXPECTED_TYPES = { diff --git a/pygem/shop/debris.py b/pygem/shop/debris.py index 13b3f933..3ba335a3 100755 --- a/pygem/shop/debris.py +++ b/pygem/shop/debris.py @@ -5,19 +5,19 @@ 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,55 +33,55 @@ 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: +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): """Reproject the debris thickness and enhancement factor files to the given glacier directory - + Variables are exported as new files in the glacier directory. - Reprojecting debris data from one map proj to another is done. + Reprojecting debris data from one map proj to another is done. We use bilinear interpolation to reproject the velocities to the local glacier map. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ - + assert os.path.exists(debris_dir), "Error: debris directory does not exist." hd_dir = debris_dir + 'hd_tifs/' + gdir.rgi_region + '/' ed_dir = debris_dir + 'ed_tifs/' + gdir.rgi_region + '/' - + glac_str_nolead = str(int(gdir.rgi_region)) + '.' + gdir.rgi_id.split('-')[1].split('.')[1] - + # 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[dataed_max] = 1 data[data 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..f38a94dd 100644 --- a/pygem/shop/oib.py +++ b/pygem/shop/oib.py @@ -7,12 +7,19 @@ 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 @@ -33,7 +40,7 @@ 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): @@ -63,7 +70,7 @@ def _rgi6torgi7id(self, debug=False): raise IndexError(f'No matching RGI7Id for {self.rgi6id}') elif len(rgi7id)>1: raise IndexError(f'More than one matching RGI7Id for {self.rgi6id}') - + def _rgi7torgi6id(self, debug=False): """ @@ -138,7 +145,7 @@ def _parsediffs(self, filter_count_pctl=10, debug=False): if debug: print(f'OIB survey dates:\n{", ".join([str(dt.year)+"-"+str(dt.month)+"-"+str(dt.day) for dt in list(self.oib_diffs.keys())])}') # get bin centers - self.bin_centers = (np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec']) + + self.bin_centers = (np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec']) + np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'])) / 2 self.bin_area = self.oib_dict['aad_dict']['hist_bin_areas_m2'] # bin_edges = oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec'] @@ -273,4 +280,4 @@ def _filter_on_pixel_count(arr, pctl = 15): def split_by_uppercase(text): # Add space before each uppercase letter (except at the start of the string) - return re.sub(r"(? Date: Mon, 31 Mar 2025 21:03:23 +0000 Subject: [PATCH 09/17] Apply ruff formatter --- docs/conf.py | 42 +- pygem/__init__.py | 1 + pygem/bin/op/duplicate_gdirs.py | 43 +- pygem/bin/op/initialize.py | 28 +- pygem/bin/op/list_failed_simulations.py | 168 +- .../postproc/postproc_binned_monthly_mass.py | 165 +- .../postproc/postproc_compile_simulations.py | 1155 ++++-- pygem/bin/postproc/postproc_distribute_ice.py | 120 +- pygem/bin/postproc/postproc_monthly_mass.py | 101 +- pygem/bin/preproc/preproc_fetch_mbdata.py | 48 +- pygem/bin/preproc/preproc_wgms_estimate_kp.py | 435 ++- pygem/bin/run/run_calibration.py | 2677 ++++++++++---- .../run/run_calibration_frontalablation.py | 3254 +++++++++++------ pygem/bin/run/run_calibration_reg_glena.py | 479 ++- pygem/bin/run/run_mcmc_priors.py | 541 ++- pygem/bin/run/run_simulation.py | 2432 ++++++++---- pygem/class_climate.py | 547 ++- pygem/gcmbiasadj.py | 464 ++- pygem/glacierdynamics.py | 820 +++-- pygem/massbalance.py | 1237 +++++-- pygem/mcmc.py | 328 +- pygem/oggm_compat.py | 188 +- pygem/output.py | 1232 ++++--- pygem/pygem_modelsetup.py | 360 +- pygem/scraps/dummy_task_module.py | 7 +- pygem/scraps/run.py | 14 +- pygem/setup/config.py | 32 +- pygem/shop/debris.py | 150 +- pygem/shop/icethickness.py | 117 +- pygem/shop/mbdata.py | 83 +- pygem/shop/oib.py | 250 +- pygem/tests/test_basics.py | 3 +- pygem/tests/test_config.py | 66 +- pygem/tests/test_notebooks.py | 10 +- pygem/utils/_funcs.py | 25 +- pygem/utils/_funcs_selectglaciers.py | 23 +- setup.py | 2 +- 37 files changed, 11880 insertions(+), 5767 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 81631e2a..afa84cb2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,29 +11,30 @@ import tomllib -sys.path.insert(0, os.path.abspath('../pygem/')) +sys.path.insert(0, os.path.abspath("../pygem/")) # source pyproject.toml to get release with open("../pyproject.toml", "rb") as f: pyproject = tomllib.load(f) -project = 'PyGEM' -copyright = '2023, David Rounce' -author = 'David Rounce' +project = "PyGEM" +copyright = "2023, David Rounce" +author = "David Rounce" 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", @@ -46,21 +47,20 @@ "html_image", ] -#templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - +# templates_path = ['_templates'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_book_theme' -html_static_path = ['_static'] +html_theme = "sphinx_book_theme" +html_static_path = ["_static"] html_theme_options = { "repository_url": "https://github.com/PyGEM-Community/PyGEM", "use_repository_button": True, - "show_nav_level":2, - "navigation_depth":3, - } + "show_nav_level": 2, + "navigation_depth": 3, +} diff --git a/pygem/__init__.py b/pygem/__init__.py index d1aa0ade..65c2ce17 100755 --- a/pygem/__init__.py +++ b/pygem/__init__.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + from importlib.metadata import version try: diff --git a/pygem/bin/op/duplicate_gdirs.py b/pygem/bin/op/duplicate_gdirs.py index 3a9ccbac..ea3d3131 100644 --- a/pygem/bin/op/duplicate_gdirs.py +++ b/pygem/bin/op/duplicate_gdirs.py @@ -7,6 +7,7 @@ duplicate OGGM glacier directories """ + import argparse import os import shutil @@ -19,33 +20,49 @@ # 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}/' + root = pygem_prms["root"] + "/" + pygem_prms["oggm"]["oggm_gdir_relpath"] + sfix = "/per_glacier/" + f"RGI60-{reg}/" + f"RGI60-{reg}.{thous}/" for n in range(num_copies): - nroot = os.path.abspath(root.replace('gdirs',f'gdirs_{n+1}')) + nroot = os.path.abspath(root.replace("gdirs", f"gdirs_{n + 1}")) # duplicate structure - os.makedirs(nroot + sfix + f'RGI60-{reg}.{id}', exist_ok=True) + os.makedirs(nroot + sfix + f"RGI60-{reg}.{id}", exist_ok=True) # copy directory data - shutil.copytree(root + sfix + f'RGI60-{reg}.{id}', nroot + sfix + f'RGI60-{reg}.{id}', dirs_exist_ok=True) + shutil.copytree( + root + sfix + f"RGI60-{reg}.{id}", + nroot + sfix + f"RGI60-{reg}.{id}", + dirs_exist_ok=True, + ) return -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/pygem/bin/op/initialize.py b/pygem/bin/op/initialize.py index 24b95a02..a95cf1aa 100644 --- a/pygem/bin/op/initialize.py +++ b/pygem/bin/op/initialize.py @@ -7,6 +7,7 @@ initialization script (ensure config.yaml and get sample datasets) """ + import os import shutil import zipfile @@ -20,6 +21,7 @@ # read the config pygem_prms = config_manager.read_config() + def print_file_tree(start_path, indent=""): # Loop through all files and directories in the current directory for item in os.listdir(start_path): @@ -32,6 +34,7 @@ def print_file_tree(start_path, indent=""): if os.path.isdir(path): print_file_tree(path, indent + " ") + def get_confirm_token(response): """Extract confirmation token for Google Drive large file download.""" for key, value in response.cookies.items(): @@ -39,6 +42,7 @@ def get_confirm_token(response): return value return None + def save_response_content(response, destination): """Save the response content to a file.""" chunk_size = 32768 @@ -47,6 +51,7 @@ def save_response_content(response, destination): if chunk: # Filter out keep-alive chunks file.write(chunk) + def get_unique_folder_name(dir): """Generate a unique folder name by appending a suffix if the folder already exists.""" counter = 1 @@ -56,6 +61,7 @@ def get_unique_folder_name(dir): counter += 1 return unique_dir + def download_and_unzip_from_google_drive(file_id, output_dir): """ Download a ZIP file from Google Drive and extract its contents. @@ -82,16 +88,22 @@ def download_and_unzip_from_google_drive(file_id, output_dir): response = session.get(base_url, params={"id": file_id}, stream=True) token = get_confirm_token(response) if token: - response = session.get(base_url, params={"id": file_id, "confirm": token}, stream=True) + response = session.get( + base_url, params={"id": file_id, "confirm": token}, stream=True + ) save_response_content(response, zip_path) # Unzip the file - tmppath = os.path.join(output_dir, 'tmp') - with zipfile.ZipFile(zip_path, 'r') as zip_ref: + tmppath = os.path.join(output_dir, "tmp") + with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(tmppath) # get root dir name of zipped files - dir = [item for item in os.listdir(tmppath) if os.path.isdir(os.path.join(tmppath, item))][0] + dir = [ + item + for item in os.listdir(tmppath) + if os.path.isdir(os.path.join(tmppath, item)) + ][0] unzip_dir = os.path.join(tmppath, dir) # get unique name if root dir name already exists in output_dir output_dir = get_unique_folder_name(os.path.join(output_dir, dir)) @@ -104,9 +116,10 @@ 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') + basedir = os.path.join(os.path.expanduser("~"), "PyGEM") # Google Drive file id for sample dataset file_id = "1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ" # download and unzip @@ -121,13 +134,14 @@ def main(): pass else: - print('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() diff --git a/pygem/bin/op/list_failed_simulations.py b/pygem/bin/op/list_failed_simulations.py index 4625c2b3..65785bdf 100644 --- a/pygem/bin/op/list_failed_simulations.py +++ b/pygem/bin/op/list_failed_simulations.py @@ -7,6 +7,7 @@ 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 argparse import glob @@ -27,48 +28,57 @@ def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_endyear): - # define base directory base_dir = simpath + "/" + str(reg).zfill(2) + "/" - # get all glaciers in region to see which fraction ran successfully - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], - rgi_regionsO2='all', rgi_glac_number='all', - glac_no=None, - debug=True) + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + glac_no=None, + debug=True, + ) - glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) + glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) # get list of glacier simulation files if scenario: - sim_dir = base_dir + gcm + '/' + scenario + '/stats/' + sim_dir = base_dir + gcm + "/" + scenario + "/stats/" else: - sim_dir = base_dir + gcm + '/stats/' + sim_dir = base_dir + gcm + "/stats/" # check if gcm has given scenario - assert os.path.isdir(sim_dir), f'Error: simulation path not found, {sim_dir}' + assert os.path.isdir(sim_dir), f"Error: simulation path not found, {sim_dir}" # instantiate list of galcnos that are not in sim_dir failed_glacnos = [] - fps = glob.glob(sim_dir + f'*_{calib_opt}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc') + fps = glob.glob( + sim_dir + f"*_{calib_opt}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc" + ) # Glaciers with successful runs to process - glacno_ran = [x.split('/')[-1].split('_')[0] for x in fps] - glacno_ran = [x.split('.')[0].zfill(2) + '.' + x[-5:] for x in glacno_ran] + glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] + glacno_ran = [x.split(".")[0].zfill(2) + "." + x[-5:] for x in glacno_ran] # print stats of successfully simualated glaciers - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1)] - print(f'{gcm} {str(scenario).replace('None','')} glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0]/main_glac_rgi_all.shape[0]*100,3)}%)') - print(f' - {np.round(main_glac_rgi.Area.sum(),0)} km2 of {np.round(main_glac_rgi_all.Area.sum(),0)} km2 ({np.round(main_glac_rgi.Area.sum()/main_glac_rgi_all.Area.sum()*100,3)}%)') - - glacno_ran = ['{0:0.5f}'.format(float(x)) for x in glacno_ran] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1) + ] + print( + f"{gcm} {str(scenario).replace('None', '')} glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0] / main_glac_rgi_all.shape[0] * 100, 3)}%)" + ) + print( + f" - {np.round(main_glac_rgi.Area.sum(), 0)} km2 of {np.round(main_glac_rgi_all.Area.sum(), 0)} km2 ({np.round(main_glac_rgi.Area.sum() / main_glac_rgi_all.Area.sum() * 100, 3)}%)" + ) + + glacno_ran = ["{0:0.5f}".format(float(x)) for x in glacno_ran] # loop through each glacier in batch list for i, glacno in enumerate(glacno_list_all): # gat glacier string and file name - glacier_str = '{0:0.5f}'.format(float(glacno)) + glacier_str = "{0:0.5f}".format(float(glacno)) if glacier_str not in glacno_ran: failed_glacnos.append(glacier_str) @@ -76,40 +86,78 @@ def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_end def main(): - # Set up CLI parser = argparse.ArgumentParser( - description="""description: script to check for failed PyGEM glacier simulations\n\nexample call: $python list_failed_simulations.py -rgi_region01=1 -gcm_name=CanESM5 -scenrio=ssp585 -outdir=/path/to/output/failed/glaciers/""", - formatter_class=argparse.RawTextHelpFormatter) - requiredNamed = parser.add_argument_group('required named arguments') - requiredNamed.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-gcm_name', type=str, default=None, - help='GCM name to compile results from (ex. ERA5 or CESM2)') - parser.add_argument('-scenario', action='store', type=str, default=None, - help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-option_calibration', action='store', type=str, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ + description="""description: script to check for failed PyGEM glacier simulations\n\nexample call: $python list_failed_simulations.py -rgi_region01=1 -gcm_name=CanESM5 -scenrio=ssp585 -outdir=/path/to/output/failed/glaciers/""", + formatter_class=argparse.RawTextHelpFormatter, + ) + requiredNamed = parser.add_argument_group("required named arguments") + requiredNamed.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-gcm_name", + type=str, + default=None, + help="GCM name to compile results from (ex. ERA5 or CESM2)", + ) + parser.add_argument( + "-scenario", + action="store", + type=str, + default=None, + help="rcp or ssp scenario used for model run (ex. rcp26 or ssp585)", + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help="Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ 1: new prec scheme and temp building on HH2015, \ - 2: HH2015 methods, 3: quantile delta mapping)') - parser.add_argument('-outdir', type=str, default=None, help='directory to output json file containing list of failed glaciers in each RGI region') - parser.add_argument('-v', '--verbose', action='store_true', - help='verbose flag') + 2: HH2015 methods, 3: quantile delta mapping)", + ) + parser.add_argument( + "-outdir", + type=str, + default=None, + help="directory to output json file containing list of failed glaciers in each RGI region", + ) + parser.add_argument("-v", "--verbose", action="store_true", help="verbose flag") args = parser.parse_args() region = args.rgi_region01 scenario = args.scenario gcm_name = args.gcm_name bias_adj = args.option_bias_adjustment - simpath = pygem_prms['root']+'/Output/simulations/' + simpath = pygem_prms["root"] + "/Output/simulations/" - if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: + if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: scenario = None bias_adj = 0 @@ -117,20 +165,38 @@ def main(): region = [region] if args.outdir and not os.path.isdir(args.outdir): - print(f'Specified output path does not exist: {args.outdir}') + print(f"Specified output path does not exist: {args.outdir}") sys.exit(1) for reg in region: - failed_glacs = run(reg, simpath, args.gcm_name, scenario, args.option_calibration, bias_adj, args.gcm_startyear, args.gcm_endyear) - if len(failed_glacs)>0: + failed_glacs = run( + reg, + simpath, + args.gcm_name, + scenario, + args.option_calibration, + bias_adj, + args.gcm_startyear, + args.gcm_endyear, + ) + if len(failed_glacs) > 0: if args.outdir: - fout = os.path.join(args.outdir, f'R{str(reg).zfill(2)}_{args.gcm_name}_{scenario}_{args.gcm_startyear}_{args.gcm_endyear}_failed_rgiids.json').replace('None_','') - with open(fout, 'w') as f: + fout = os.path.join( + args.outdir, + f"R{str(reg).zfill(2)}_{args.gcm_name}_{scenario}_{args.gcm_startyear}_{args.gcm_endyear}_failed_rgiids.json", + ).replace("None_", "") + with open(fout, "w") as f: json.dump(failed_glacs, f) - print(f'List of failed glaciers for {gcm_name} {str(scenario).replace('None','')} exported to: {fout}') + print( + f"List of failed glaciers for {gcm_name} {str(scenario).replace('None', '')} exported to: {fout}" + ) if args.verbose: - print(f'Failed glaciers for RGI region R{str(reg).zfill(2)} {args.gcm_name} {str(scenario).replace('None','')} {args.gcm_startyear}-{args.gcm_endyear}:') + print( + f"Failed glaciers for RGI region R{str(reg).zfill(2)} {args.gcm_name} {str(scenario).replace('None', '')} {args.gcm_startyear}-{args.gcm_endyear}:" + ) print(failed_glacs) else: - print(f'No glaciers failed from R{region}, for {gcm_name} {scenario.replace('None','')}') + 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 b9111bbc..0ee4bde6 100644 --- a/pygem/bin/postproc/postproc_binned_monthly_mass.py +++ b/pygem/bin/postproc/postproc_binned_monthly_mass.py @@ -7,6 +7,7 @@ derive binned monthly ice thickness and mass from PyGEM simulation """ + # Built-in libraries import argparse import collections @@ -27,19 +28,38 @@ # 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 @@ -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']) + 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,14 +129,14 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): flux_div_monthly = np.repeat(flux_div_annual / 12, 12, axis=-1) # get monthly binned change in thickness - delta_h_monthly = dotb_monthly - flux_div_monthly # [m ice per month] + delta_h_monthly = dotb_monthly - flux_div_monthly # [m ice per month] # get binned monthly thickness = running thickness change + initial thickness running_delta_h_monthly = np.cumsum(delta_h_monthly, axis=-1) - h_monthly = running_delta_h_monthly + h_annual[:,:,0][:,:,np.newaxis] + h_monthly = running_delta_h_monthly + h_annual[:, :, 0][:, :, np.newaxis] # convert to mass per unit area - m_spec_monthly = h_monthly * pygem_prms['constants']['density_ice'] + m_spec_monthly = h_monthly * pygem_prms["constants"]["density_ice"] ### get monthly mass ### # note, binned monthly thickness and mass is currently per unit area @@ -120,14 +145,15 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): # so we'll resort to using the annual binned glacier mass and thickness in order to get to binned glacier area ######################## # first convert m_annual to bin_voluma_annual - v_annual = m_annual / pygem_prms['constants']['density_ice'] + v_annual = m_annual / pygem_prms["constants"]["density_ice"] # now get area: use numpy divide where denominator is greater than 0 to avoid divide error # note, indexing of [:,:,1:] so that annual area array has same shape as flux_div_annual a_annual = np.divide( - v_annual[:,:,1:], - h_annual[:,:,1:], - out=np.full(h_annual[:,:,1:].shape, np.nan), - where=h_annual[:,:,1:]>0) + v_annual[:, :, 1:], + h_annual[:, :, 1:], + out=np.full(h_annual[:, :, 1:].shape, np.nan), + where=h_annual[:, :, 1:] > 0, + ) # tile to get monthly area, assuming area is constant thoughout the year a_monthly = np.tile(a_annual, 12) @@ -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)'} - output_attrs_dict['bin_mass_spec_monthly'] = { - 'long_name': 'binned monthly specific ice mass', - 'units': 'kg m^-2', - 'temporal_resolution': 'monthly', - 'comment': 'monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)'} - output_attrs_dict['bin_mass_monthly'] = { - 'long_name': 'binned monthly ice mass', - 'units': 'kg', - 'temporal_resolution': 'monthly', - 'comment': 'monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)'} + output_attrs_dict["bin_thick_monthly"] = { + "long_name": "binned monthly ice thickness", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly ice thickness binned by surface elevation (assuming constant flux divergence throughout a given year)", + } + output_attrs_dict["bin_mass_spec_monthly"] = { + "long_name": "binned monthly specific ice mass", + "units": "kg m^-2", + "temporal_resolution": "monthly", + "comment": "monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)", + } + output_attrs_dict["bin_mass_monthly"] = { + "long_name": "binned monthly ice mass", + "units": "kg", + "temporal_resolution": "monthly", + "comment": "monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)", + } # Add variables to empty dataset and merge together count_vn = 0 encoding = {} for vn in output_coords_dict.keys(): - empty_holder = np.zeros([len(output_coords_dict[vn][i]) for i in list(output_coords_dict[vn].keys())]) - output_ds = xr.Dataset({vn: (list(output_coords_dict[vn].keys()), empty_holder)}, - coords=output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(output_coords_dict[vn][i]) + for i in list(output_coords_dict[vn].keys()) + ] + ) + output_ds = xr.Dataset( + {vn: (list(output_coords_dict[vn].keys()), empty_holder)}, + coords=output_coords_dict[vn], + ) count_vn += 1 # Merge datasets of stats into one output if count_vn == 1: @@ -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() @@ -274,7 +308,7 @@ def main(): 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: @@ -283,11 +317,12 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(ncores) + ' cores...') + print("Processing with " + str(ncores) + " cores...") with multiprocessing.Pool(ncores) as p: - p.map(run,simpath) + p.map(run, simpath) + + print("Total processing time:", time.time() - time_start, "s") - print('Total processing time:', time.time()-time_start, 's') if __name__ == "__main__": main() diff --git a/pygem/bin/postproc/postproc_compile_simulations.py b/pygem/bin/postproc/postproc_compile_simulations.py index 89189ccf..fd8ae3b7 100644 --- a/pygem/bin/postproc/postproc_compile_simulations.py +++ b/pygem/bin/postproc/postproc_compile_simulations.py @@ -7,6 +7,7 @@ compile individual glacier simulations to the regional level """ + # imports import argparse import glob @@ -29,52 +30,67 @@ 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 - print(f'RGI region {reg}') + ( + reg, + simpath, + gcms, + realizations, + scenario, + calibration, + bias_adj, + gcm_startyear, + gcm_endyear, + vars, + ) = args + print(f"RGI region {reg}") # #%% ----- PROCESS DATASETS FOR INDIVIDUAL GLACIERS AND ELEVATION BINS ----- - comppath = simpath + '/compile/' + comppath = simpath + "/compile/" # define base directory base_dir = simpath + "/" + str(reg).zfill(2) + "/" # get all glaciers in region to see which fraction ran successfully - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], - rgi_regionsO2='all', rgi_glac_number='all', - glac_no=None, - debug=True) + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + glac_no=None, + debug=True, + ) - glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) + glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) ### CREATE BATCHES ### # get last glacier number to define number of batches - lastn = int(sorted(glacno_list_all)[-1].split('.')[1]) + lastn = int(sorted(glacno_list_all)[-1].split(".")[1]) # round up to thosand batch_interval = 1000 last_thous = np.ceil(lastn / batch_interval) * batch_interval @@ -82,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]) + 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[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 ### @@ -96,14 +114,28 @@ def run(args): if scenario: # ensure scenario has been run for each gcm for gcm in gcms: - if scenario not in os.listdir(base_dir + '/' + gcm): + if scenario not in os.listdir(base_dir + "/" + gcm): # remove the gcm from our gcm list if the desired scenario is not contained gcms.remove(gcm) - print(f'scenario {scenario} not found for {gcm}, skipping') - fn = glob.glob(base_dir + gcm + "/" + scenario + "/stats/" + f'*{gcm}_{scenario}_{realizations[0]}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_'))[0] + print(f"scenario {scenario} not found for {gcm}, skipping") + fn = glob.glob( + base_dir + + gcm + + "/" + + scenario + + "/stats/" + + f"*{gcm}_{scenario}_{realizations[0]}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + )[0] else: - fn = glob.glob(base_dir + gcm + "/stats/" + f'*{gcm}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc')[0] - nsets = fn.split('/')[-1].split('_')[-4] + fn = glob.glob( + base_dir + + gcm + + "/stats/" + + f"*{gcm}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc" + )[0] + nsets = fn.split("/")[-1].split("_")[-4] ds_glac = xr.open_dataset(fn) year_values = ds_glac.year.values @@ -113,29 +145,31 @@ def run(args): missing_vars = list(set(vars) - set(ds_vars)) if len(missing_vars) > 0: vars = list(set(vars).intersection(ds_vars)) - print(f'Warning: Requested variables are missing: {missing_vars}') + print(f"Warning: Requested variables are missing: {missing_vars}") ############################################################ - print(f'Compiling GCMS: {gcms}') - print(f'Realizations: {realizations}') - print(f'Variables: {vars}') + print(f"Compiling GCMS: {gcms}") + print(f"Realizations: {realizations}") + print(f"Variables: {vars}") ### LEVEL I ### # loop through glacier batches of 1000 for nbatch, glacno_list in enumerate(glacno_list_batches): - print(f'Batch {nbatch}:') + print(f"Batch {nbatch}:") # batch start timer loop_start = time.time() # get batch start and end numbers - batch_start = glacno_list[0].split('.')[1] - batch_end = glacno_list[-1].split('.')[1] + batch_start = glacno_list[0].split(".")[1] + batch_end = glacno_list[-1].split(".")[1] print(nbatch, batch_start, batch_end) # get all glacier info for glaciers in batch - main_glac_rgi_batch = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_list, axis=1)] + main_glac_rgi_batch = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_list, axis=1) + ] # instantiate variables that will hold all concatenated data for GCMs/realizations # monthly vars @@ -157,21 +191,36 @@ def run(args): # 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/' + sim_dir = base_dir + gcm + "/" + scenario + "/stats/" ### LEVEL III ### for realization in realizations: - print(f'GCM: {gcm} {realization}') - fps = glob.glob(sim_dir + f'*{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) + print(f"GCM: {gcm} {realization}") + fps = glob.glob( + sim_dir + + f"*{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) # during 0th batch, print the regional stats of glaciers and area successfully simulated for all regional glaciers for given gcm scenario - if nbatch==0: + if nbatch == 0: # Glaciers with successful runs to process - glacno_ran = [x.split('/')[-1].split('_')[0] for x in fps] - glacno_ran = [x.split('.')[0].zfill(2) + '.' + x[-5:] for x in glacno_ran] - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1)] - print(f'Glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0]/main_glac_rgi_all.shape[0]*100,3)}%)') - print(f' - {np.round(main_glac_rgi.Area.sum(),0)} km2 of {np.round(main_glac_rgi_all.Area.sum(),0)} km2 ({np.round(main_glac_rgi.Area.sum()/main_glac_rgi_all.Area.sum()*100,3)}%)') + glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] + glacno_ran = [ + x.split(".")[0].zfill(2) + "." + x[-5:] for x in glacno_ran + ] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply( + lambda x: x.rgino_str in glacno_ran, axis=1 + ) + ] + print( + f"Glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0] / main_glac_rgi_all.shape[0] * 100, 3)}%)" + ) + print( + f" - {np.round(main_glac_rgi.Area.sum(), 0)} km2 of {np.round(main_glac_rgi_all.Area.sum(), 0)} km2 ({np.round(main_glac_rgi.Area.sum() / main_glac_rgi_all.Area.sum() * 100, 3)}%)" + ) # instantiate variables that will hold concatenated data for the current GCM # monthly vars @@ -189,13 +238,14 @@ def run(args): reg_glac_gcm_area_annual = None reg_glac_gcm_mass_annual = None - ### LEVEL IV ### # loop through each glacier in batch list for i, glacno in enumerate(glacno_list): # get glacier string and file name - glacier_str = '{0:0.5f}'.format(float(glacno)) - glacno_fn = f'{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + glacier_str = "{0:0.5f}".format(float(glacno)) + glacno_fn = f"{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) # try to load all glaciers in region try: # open netcdf file @@ -208,39 +258,51 @@ 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: @@ -250,7 +312,9 @@ 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 @@ -260,340 +324,522 @@ def run(args): # otherwise concatenate existing arrays else: # monthly vars - reg_glac_gcm_runoff_monthly = np.concatenate((reg_glac_gcm_runoff_monthly, glac_runoff_monthly), axis=0) - reg_offglac_gcm_runoff_monthly = np.concatenate((reg_offglac_gcm_runoff_monthly, offglac_runoff_monthly), axis=0) - reg_glac_gcm_acc_monthly = np.concatenate((reg_glac_gcm_acc_monthly, glac_acc_monthly), axis=0) - reg_glac_gcm_melt_monthly = np.concatenate((reg_glac_gcm_melt_monthly, glac_melt_monthly), axis=0) - reg_glac_gcm_refreeze_monthly = np.concatenate((reg_glac_gcm_refreeze_monthly, glac_refreeze_monthly), axis=0) - reg_glac_gcm_frontalablation_monthly = np.concatenate((reg_glac_gcm_frontalablation_monthly, glac_frontalablation_monthly), axis=0) - reg_glac_gcm_massbaltotal_monthly = np.concatenate((reg_glac_gcm_massbaltotal_monthly, glac_massbaltotal_monthly), axis=0) - reg_glac_gcm_prec_monthly = np.concatenate((reg_glac_gcm_prec_monthly, glac_prec_monthly), axis=0) - reg_glac_gcm_mass_monthly = np.concatenate((reg_glac_gcm_mass_monthly, glac_mass_monthly), axis=0) + reg_glac_gcm_runoff_monthly = np.concatenate( + (reg_glac_gcm_runoff_monthly, glac_runoff_monthly), axis=0 + ) + reg_offglac_gcm_runoff_monthly = np.concatenate( + (reg_offglac_gcm_runoff_monthly, offglac_runoff_monthly), + axis=0, + ) + reg_glac_gcm_acc_monthly = np.concatenate( + (reg_glac_gcm_acc_monthly, glac_acc_monthly), axis=0 + ) + reg_glac_gcm_melt_monthly = np.concatenate( + (reg_glac_gcm_melt_monthly, glac_melt_monthly), axis=0 + ) + reg_glac_gcm_refreeze_monthly = np.concatenate( + (reg_glac_gcm_refreeze_monthly, glac_refreeze_monthly), + axis=0, + ) + reg_glac_gcm_frontalablation_monthly = np.concatenate( + ( + reg_glac_gcm_frontalablation_monthly, + glac_frontalablation_monthly, + ), + axis=0, + ) + reg_glac_gcm_massbaltotal_monthly = np.concatenate( + ( + reg_glac_gcm_massbaltotal_monthly, + glac_massbaltotal_monthly, + ), + axis=0, + ) + reg_glac_gcm_prec_monthly = np.concatenate( + (reg_glac_gcm_prec_monthly, glac_prec_monthly), axis=0 + ) + reg_glac_gcm_mass_monthly = np.concatenate( + (reg_glac_gcm_mass_monthly, glac_mass_monthly), axis=0 + ) # annual vars - reg_glac_gcm_area_annual = np.concatenate((reg_glac_gcm_area_annual, glac_area_annual), axis=0) - reg_glac_gcm_mass_annual = np.concatenate((reg_glac_gcm_mass_annual, glac_mass_annual), axis=0) + reg_glac_gcm_area_annual = np.concatenate( + (reg_glac_gcm_area_annual, glac_area_annual), axis=0 + ) + reg_glac_gcm_mass_annual = np.concatenate( + (reg_glac_gcm_mass_annual, glac_mass_annual), axis=0 + ) # aggregate gcms if reg_glac_allgcms_runoff_monthly is None: # monthly vars - reg_glac_allgcms_runoff_monthly = reg_glac_gcm_runoff_monthly[np.newaxis,:,:] - reg_offglac_allgcms_runoff_monthly = reg_offglac_gcm_runoff_monthly[np.newaxis,:,:] - reg_glac_allgcms_acc_monthly = reg_glac_gcm_acc_monthly[np.newaxis,:,:] - reg_glac_allgcms_melt_monthly = reg_glac_gcm_melt_monthly[np.newaxis,:,:] - reg_glac_allgcms_refreeze_monthly = reg_glac_gcm_refreeze_monthly[np.newaxis,:,:] - reg_glac_allgcms_frontalablation_monthly = reg_glac_gcm_frontalablation_monthly[np.newaxis,:,:] - reg_glac_allgcms_massbaltotal_monthly = reg_glac_gcm_massbaltotal_monthly[np.newaxis,:,:] - reg_glac_allgcms_prec_monthly = reg_glac_gcm_prec_monthly[np.newaxis,:,:] - reg_glac_allgcms_mass_monthly = reg_glac_gcm_mass_monthly[np.newaxis,:,:] + reg_glac_allgcms_runoff_monthly = reg_glac_gcm_runoff_monthly[ + np.newaxis, :, : + ] + reg_offglac_allgcms_runoff_monthly = reg_offglac_gcm_runoff_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_acc_monthly = reg_glac_gcm_acc_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_melt_monthly = reg_glac_gcm_melt_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_refreeze_monthly = reg_glac_gcm_refreeze_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_frontalablation_monthly = ( + reg_glac_gcm_frontalablation_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_massbaltotal_monthly = ( + reg_glac_gcm_massbaltotal_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_prec_monthly = reg_glac_gcm_prec_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_mass_monthly = reg_glac_gcm_mass_monthly[ + np.newaxis, :, : + ] # annual vars - reg_glac_allgcms_area_annual = reg_glac_gcm_area_annual[np.newaxis,:,:] - reg_glac_allgcms_mass_annual = reg_glac_gcm_mass_annual[np.newaxis,:,:] + reg_glac_allgcms_area_annual = reg_glac_gcm_area_annual[ + np.newaxis, :, : + ] + reg_glac_allgcms_mass_annual = reg_glac_gcm_mass_annual[ + np.newaxis, :, : + ] else: # monthly vrs - reg_glac_allgcms_runoff_monthly = np.concatenate((reg_glac_allgcms_runoff_monthly, reg_glac_gcm_runoff_monthly[np.newaxis,:,:]), axis=0) - reg_offglac_allgcms_runoff_monthly = np.concatenate((reg_offglac_allgcms_runoff_monthly, reg_offglac_gcm_runoff_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_acc_monthly = np.concatenate((reg_glac_allgcms_acc_monthly, reg_glac_gcm_acc_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_melt_monthly = np.concatenate((reg_glac_allgcms_melt_monthly, reg_glac_gcm_melt_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_refreeze_monthly = np.concatenate((reg_glac_allgcms_refreeze_monthly, reg_glac_gcm_refreeze_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_frontalablation_monthly = np.concatenate((reg_glac_allgcms_frontalablation_monthly, reg_glac_gcm_frontalablation_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_massbaltotal_monthly = np.concatenate((reg_glac_allgcms_massbaltotal_monthly, reg_glac_gcm_massbaltotal_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_prec_monthly = np.concatenate((reg_glac_allgcms_prec_monthly, reg_glac_gcm_prec_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_mass_monthly = np.concatenate((reg_glac_allgcms_mass_monthly, reg_glac_gcm_mass_monthly[np.newaxis,:,:]), axis=0) + reg_glac_allgcms_runoff_monthly = np.concatenate( + ( + reg_glac_allgcms_runoff_monthly, + reg_glac_gcm_runoff_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_offglac_allgcms_runoff_monthly = np.concatenate( + ( + reg_offglac_allgcms_runoff_monthly, + reg_offglac_gcm_runoff_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_acc_monthly = np.concatenate( + ( + reg_glac_allgcms_acc_monthly, + reg_glac_gcm_acc_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_melt_monthly = np.concatenate( + ( + reg_glac_allgcms_melt_monthly, + reg_glac_gcm_melt_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_refreeze_monthly = np.concatenate( + ( + reg_glac_allgcms_refreeze_monthly, + reg_glac_gcm_refreeze_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_frontalablation_monthly = np.concatenate( + ( + reg_glac_allgcms_frontalablation_monthly, + reg_glac_gcm_frontalablation_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_massbaltotal_monthly = np.concatenate( + ( + reg_glac_allgcms_massbaltotal_monthly, + reg_glac_gcm_massbaltotal_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_prec_monthly = np.concatenate( + ( + reg_glac_allgcms_prec_monthly, + reg_glac_gcm_prec_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_mass_monthly = np.concatenate( + ( + reg_glac_allgcms_mass_monthly, + reg_glac_gcm_mass_monthly[np.newaxis, :, :], + ), + axis=0, + ) # annual vars - reg_glac_allgcms_area_annual = np.concatenate((reg_glac_allgcms_area_annual, reg_glac_gcm_area_annual[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_mass_annual = np.concatenate((reg_glac_allgcms_mass_annual, reg_glac_gcm_mass_annual[np.newaxis,:,:]), axis=0) - - - #===== CREATE NETCDF FILES===== + reg_glac_allgcms_area_annual = np.concatenate( + ( + reg_glac_allgcms_area_annual, + reg_glac_gcm_area_annual[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_mass_annual = np.concatenate( + ( + reg_glac_allgcms_mass_annual, + reg_glac_gcm_mass_annual[np.newaxis, :, :], + ), + axis=0, + ) + + # ===== CREATE NETCDF FILES===== # get common attributes - rgiid_list = ['RGI60-' + x for x in glacno_list] + rgiid_list = ["RGI60-" + x for x in glacno_list] cenlon_list = list(main_glac_rgi_batch.CenLon.values) cenlat_list = list(main_glac_rgi_batch.CenLat.values) - attrs_dict = {'Region':str(reg) + ' - ' + rgi_reg_dict[reg], - 'source': f'PyGEMv{pygem.__version__}', - 'institution': pygem_prms['user']['institution'], - 'history': f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + datetime.today().strftime('%Y-%m-%d'), - 'references': 'doi:10.1126/science.abo1324', - 'Conventions': 'CF-1.9', - 'featureType': 'timeSeries'} + attrs_dict = { + "Region": str(reg) + " - " + rgi_reg_dict[reg], + "source": f"PyGEMv{pygem.__version__}", + "institution": pygem_prms["user"]["institution"], + "history": f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + + datetime.today().strftime("%Y-%m-%d"), + "references": "doi:10.1126/science.abo1324", + "Conventions": "CF-1.9", + "featureType": "timeSeries", + } # loop through variables for var in vars: - # get common coords - if 'annual' in var: + if "annual" in var: tvals = year_values else: tvals = time_values if realizations[0]: - coords_dict=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model= (["realization"], realizations), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), - time=tvals, + 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"] + 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, + 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"] + coord_order = ["model", "glacier", "time"] - #glac_runoff_monthly - if var=='glac_runoff_monthly': + # glac_runoff_monthly + if var == "glac_runoff_monthly": ds = xr.Dataset( - data_vars=dict( - glac_runoff_monthly=(coord_order, reg_glac_allgcms_runoff_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_runoff_monthly.attrs['long_name'] = 'glacier-wide runoff' - ds.glac_runoff_monthly.attrs['units'] = 'm3' - ds.glac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_runoff_monthly.attrs['comment'] = 'runoff from the glacier terminus, which moves over time' - ds.glac_runoff_monthly.attrs['grid_mapping'] = 'crs' - - #offglac_runoff_monthly - elif var=='offglac_runoff_monthly': + data_vars=dict( + glac_runoff_monthly=( + coord_order, + reg_glac_allgcms_runoff_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_runoff_monthly.attrs["long_name"] = "glacier-wide runoff" + ds.glac_runoff_monthly.attrs["units"] = "m3" + ds.glac_runoff_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_runoff_monthly.attrs["comment"] = ( + "runoff from the glacier terminus, which moves over time" + ) + ds.glac_runoff_monthly.attrs["grid_mapping"] = "crs" + + # offglac_runoff_monthly + elif var == "offglac_runoff_monthly": ds = xr.Dataset( - data_vars=dict( - offglac_runoff_monthly=(coord_order, reg_offglac_allgcms_runoff_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.offglac_runoff_monthly.attrs['long_name'] = 'off-glacier-wide runoff' - ds.offglac_runoff_monthly.attrs['units'] = 'm3' - ds.offglac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' - ds.offglac_runoff_monthly.attrs['comment'] = 'off-glacier runoff from area where glacier no longer exists' - ds.offglac_runoff_monthly.attrs['grid_mapping'] = 'crs' - - #glac_acc_monthly - elif var=='glac_acc_monthly': + data_vars=dict( + offglac_runoff_monthly=( + coord_order, + reg_offglac_allgcms_runoff_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.offglac_runoff_monthly.attrs["long_name"] = "off-glacier-wide runoff" + ds.offglac_runoff_monthly.attrs["units"] = "m3" + ds.offglac_runoff_monthly.attrs["temporal_resolution"] = "monthly" + ds.offglac_runoff_monthly.attrs["comment"] = ( + "off-glacier runoff from area where glacier no longer exists" + ) + ds.offglac_runoff_monthly.attrs["grid_mapping"] = "crs" + + # glac_acc_monthly + elif var == "glac_acc_monthly": ds = xr.Dataset( - data_vars=dict( - glac_acc_monthly=(coord_order, reg_glac_allgcms_acc_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_acc_monthly.attrs['long_name'] = 'glacier-wide accumulation, in water equivalent' - ds.glac_acc_monthly.attrs['units'] = 'm3' - ds.glac_acc_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_acc_monthly.attrs['comment'] = 'only the solid precipitation' - ds.glac_acc_monthly.attrs['grid_mapping'] = 'crs' - - #glac_melt_monthly - elif var=='glac_melt_monthly': + data_vars=dict( + glac_acc_monthly=(coord_order, reg_glac_allgcms_acc_monthly), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_acc_monthly.attrs["long_name"] = ( + "glacier-wide accumulation, in water equivalent" + ) + ds.glac_acc_monthly.attrs["units"] = "m3" + ds.glac_acc_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_acc_monthly.attrs["comment"] = "only the solid precipitation" + ds.glac_acc_monthly.attrs["grid_mapping"] = "crs" + + # glac_melt_monthly + elif var == "glac_melt_monthly": ds = xr.Dataset( - data_vars=dict( - glac_melt_monthly=(coord_order, reg_glac_allgcms_melt_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_melt_monthly.attrs['long_name'] = 'glacier-wide melt, in water equivalent' - ds.glac_melt_monthly.attrs['units'] = 'm3' - ds.glac_melt_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_melt_monthly.attrs['grid_mapping'] = 'crs' - - #glac_refreeze_monthly - elif var=='glac_refreeze_monthly': + data_vars=dict( + glac_melt_monthly=(coord_order, reg_glac_allgcms_melt_monthly), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_melt_monthly.attrs["long_name"] = ( + "glacier-wide melt, in water equivalent" + ) + ds.glac_melt_monthly.attrs["units"] = "m3" + ds.glac_melt_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_melt_monthly.attrs["grid_mapping"] = "crs" + + # glac_refreeze_monthly + elif var == "glac_refreeze_monthly": ds = xr.Dataset( - data_vars=dict( - glac_refreeze_monthly=(coord_order, reg_glac_allgcms_refreeze_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_refreeze_monthly.attrs['long_name'] = 'glacier-wide refreeze, in water equivalent' - ds.glac_refreeze_monthly.attrs['units'] = 'm3' - ds.glac_refreeze_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_refreeze_monthly.attrs['grid_mapping'] = 'crs' - - #glac_frontalablation_monthly - elif var=='glac_frontalablation_monthly': + data_vars=dict( + glac_refreeze_monthly=( + coord_order, + reg_glac_allgcms_refreeze_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_refreeze_monthly.attrs["long_name"] = ( + "glacier-wide refreeze, in water equivalent" + ) + ds.glac_refreeze_monthly.attrs["units"] = "m3" + ds.glac_refreeze_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_refreeze_monthly.attrs["grid_mapping"] = "crs" + + # glac_frontalablation_monthly + elif var == "glac_frontalablation_monthly": ds = xr.Dataset( - data_vars=dict( - glac_frontalablation_monthly=(coord_order, reg_glac_allgcms_frontalablation_monthly), - crs = np.nan - ), - coords=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model= (["model"], gcms), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), - time=time_values, - ), - attrs=attrs_dict - ) - ds.glac_frontalablation_monthly.attrs['long_name'] = 'glacier-wide frontal ablation, in water equivalent' - ds.glac_frontalablation_monthly.attrs['units'] = 'm3' - ds.glac_frontalablation_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_frontalablation_monthly.attrs['comment'] = 'mass losses from calving, subaerial frontal melting, \ + data_vars=dict( + glac_frontalablation_monthly=( + coord_order, + reg_glac_allgcms_frontalablation_monthly, + ), + crs=np.nan, + ), + coords=dict( + RGIId=(["glacier"], rgiid_list), + Climate_Model=(["model"], gcms), + lon=(["glacier"], cenlon_list), + lat=(["glacier"], cenlat_list), + time=time_values, + ), + attrs=attrs_dict, + ) + ds.glac_frontalablation_monthly.attrs["long_name"] = ( + "glacier-wide frontal ablation, in water equivalent" + ) + ds.glac_frontalablation_monthly.attrs["units"] = "m3" + ds.glac_frontalablation_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_frontalablation_monthly.attrs["comment"] = ( + "mass losses from calving, subaerial frontal melting, \ sublimation above the waterline and subaqueous frontal melting below the waterline; \ - positive values indicate mass lost like melt' - ds.glac_frontalablation_monthly.attrs['grid_mapping'] = 'crs' + positive values indicate mass lost like melt" + ) + ds.glac_frontalablation_monthly.attrs["grid_mapping"] = "crs" - #glac_massbaltotal_monthly - elif var=='glac_massbaltotal_monthly': + # glac_massbaltotal_monthly + elif var == "glac_massbaltotal_monthly": ds = xr.Dataset( - data_vars=dict( - glac_massbaltotal_monthly=(coord_order, reg_glac_allgcms_massbaltotal_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_massbaltotal_monthly.attrs['long_name'] = 'glacier-wide total mass balance, in water equivalent' - ds.glac_massbaltotal_monthly.attrs['units'] = 'm3' - ds.glac_massbaltotal_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_massbaltotal_monthly.attrs['comment'] = 'total mass balance is the sum of the climatic mass balance and frontal ablation' - ds.glac_massbaltotal_monthly.attrs['grid_mapping'] = 'crs' - - - #glac_prec_monthly - elif var=='glac_prec_monthly': + data_vars=dict( + glac_massbaltotal_monthly=( + coord_order, + reg_glac_allgcms_massbaltotal_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_massbaltotal_monthly.attrs["long_name"] = ( + "glacier-wide total mass balance, in water equivalent" + ) + ds.glac_massbaltotal_monthly.attrs["units"] = "m3" + ds.glac_massbaltotal_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_massbaltotal_monthly.attrs["comment"] = ( + "total mass balance is the sum of the climatic mass balance and frontal ablation" + ) + ds.glac_massbaltotal_monthly.attrs["grid_mapping"] = "crs" + + # glac_prec_monthly + elif var == "glac_prec_monthly": ds = xr.Dataset( - data_vars=dict( - glac_prec_monthly=(coord_order, reg_glac_allgcms_prec_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_prec_monthly.attrs['long_name'] = 'glacier-wide precipitation (liquid)' - ds.glac_prec_monthly.attrs['units'] = 'm3' - ds.glac_prec_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_prec_monthly.attrs['comment'] = 'only the liquid precipitation, solid precipitation excluded' - ds.glac_prec_monthly.attrs['grid_mapping'] = 'crs' - - #glac_mass_monthly - elif var=='glac_mass_monthly': + data_vars=dict( + glac_prec_monthly=(coord_order, reg_glac_allgcms_prec_monthly), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_prec_monthly.attrs["long_name"] = ( + "glacier-wide precipitation (liquid)" + ) + ds.glac_prec_monthly.attrs["units"] = "m3" + ds.glac_prec_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_prec_monthly.attrs["comment"] = ( + "only the liquid precipitation, solid precipitation excluded" + ) + ds.glac_prec_monthly.attrs["grid_mapping"] = "crs" + + # glac_mass_monthly + elif var == "glac_mass_monthly": ds = xr.Dataset( - data_vars=dict( - glac_mass_monthly=(coord_order, reg_glac_allgcms_mass_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_mass_monthly.attrs['long_name'] = 'glacier mass' - ds.glac_mass_monthly.attrs['units'] = 'kg' - ds.glac_mass_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_mass_monthly.attrs['comment'] = 'mass of ice based on area and ice thickness at start of the year and the monthly total mass balance' - ds.glac_mass_monthly.attrs['grid_mapping'] = 'crs' - - #glac_area_annual - elif var=='glac_area_annual': + data_vars=dict( + glac_mass_monthly=(coord_order, reg_glac_allgcms_mass_monthly), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_mass_monthly.attrs["long_name"] = "glacier mass" + ds.glac_mass_monthly.attrs["units"] = "kg" + ds.glac_mass_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_mass_monthly.attrs["comment"] = ( + "mass of ice based on area and ice thickness at start of the year and the monthly total mass balance" + ) + ds.glac_mass_monthly.attrs["grid_mapping"] = "crs" + + # glac_area_annual + elif var == "glac_area_annual": ds = xr.Dataset( - data_vars=dict( - glac_area_annual=(coord_order, reg_glac_allgcms_area_annual), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_area_annual.attrs['long_name'] = 'glacier area' - ds.glac_area_annual.attrs['units'] = 'm2' - ds.glac_area_annual.attrs['temporal_resolution'] = 'annual' - ds.glac_area_annual.attrs['comment'] = 'area at start of the year' - ds.glac_area_annual.attrs['grid_mapping'] = 'crs' - - #glac_mass_annual - elif var=='glac_mass_annual': + data_vars=dict( + glac_area_annual=(coord_order, reg_glac_allgcms_area_annual), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_area_annual.attrs["long_name"] = "glacier area" + ds.glac_area_annual.attrs["units"] = "m2" + ds.glac_area_annual.attrs["temporal_resolution"] = "annual" + ds.glac_area_annual.attrs["comment"] = "area at start of the year" + ds.glac_area_annual.attrs["grid_mapping"] = "crs" + + # glac_mass_annual + elif var == "glac_mass_annual": ds = xr.Dataset( - data_vars=dict( - glac_mass_annual=(coord_order, reg_glac_allgcms_mass_annual), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_mass_annual.attrs['long_name'] = 'glacier mass' - ds.glac_mass_annual.attrs['units'] = 'kg' - ds.glac_mass_annual.attrs['temporal_resolution'] = 'annual' - ds.glac_mass_annual.attrs['comment'] = 'mass of ice based on area and ice thickness at start of the year' - ds.glac_mass_annual.attrs['grid_mapping'] = 'crs' + data_vars=dict( + glac_mass_annual=(coord_order, reg_glac_allgcms_mass_annual), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_mass_annual.attrs["long_name"] = "glacier mass" + ds.glac_mass_annual.attrs["units"] = "kg" + ds.glac_mass_annual.attrs["temporal_resolution"] = "annual" + ds.glac_mass_annual.attrs["comment"] = ( + "mass of ice based on area and ice thickness at start of the year" + ) + ds.glac_mass_annual.attrs["grid_mapping"] = "crs" # crs attributes - same for all vars - ds.crs.attrs['grid_mapping_name'] = 'latitude_longitude' - ds.crs.attrs['longitude_of_prime_meridian'] = 0.0 - ds.crs.attrs['semi_major_axis'] = 6378137.0 - ds.crs.attrs['inverse_flattening'] = 298.257223563 - ds.crs.attrs['proj4text'] = '+proj=longlat +datum=WGS84 +no_defs' - ds.crs.attrs['crs_wkt'] = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' + ds.crs.attrs["grid_mapping_name"] = "latitude_longitude" + ds.crs.attrs["longitude_of_prime_meridian"] = 0.0 + ds.crs.attrs["semi_major_axis"] = 6378137.0 + ds.crs.attrs["inverse_flattening"] = 298.257223563 + ds.crs.attrs["proj4text"] = "+proj=longlat +datum=WGS84 +no_defs" + ds.crs.attrs["crs_wkt"] = ( + 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' + ) # time attributes - different for monthly v annual - ds.time.attrs['long_name'] = 'time' - if 'annual' in var: - ds.time.attrs['range'] = str(year_values[0]) + ' - ' + str(year_values[-1]) - ds.time.attrs['comment'] = 'years referring to the start of each year' - elif 'monthly' in var: - ds.time.attrs['range'] = str(time_values[0]) + ' - ' + str(time_values[-1]) - ds.time.attrs['comment'] = 'start of the month' - - ds.RGIId.attrs['long_name'] = 'Randolph Glacier Inventory Id' - ds.RGIId.attrs['comment'] = 'RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)' - ds.RGIId.attrs['cf_role'] = 'timeseries_id' + ds.time.attrs["long_name"] = "time" + if "annual" in var: + ds.time.attrs["range"] = ( + str(year_values[0]) + " - " + str(year_values[-1]) + ) + ds.time.attrs["comment"] = "years referring to the start of each year" + elif "monthly" in var: + ds.time.attrs["range"] = ( + str(time_values[0]) + " - " + str(time_values[-1]) + ) + ds.time.attrs["comment"] = "start of the month" + + ds.RGIId.attrs["long_name"] = "Randolph Glacier Inventory Id" + ds.RGIId.attrs["comment"] = ( + "RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)" + ) + ds.RGIId.attrs["cf_role"] = "timeseries_id" if realizations[0]: - ds.Climate_Model.attrs['long_name'] = f'{gcms[0]} realization' + ds.Climate_Model.attrs["long_name"] = f"{gcms[0]} realization" else: - ds.Climate_Model.attrs['long_name'] = 'General Circulation Model' + ds.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.lon.attrs["standard_name"] = "longitude" + ds.lon.attrs["long_name"] = "longitude of glacier center" + ds.lon.attrs["units"] = "degrees_east" - ds.lat.attrs['standard_name'] = 'latitude' - ds.lat.attrs['long_name'] = 'latitude of glacier center' - ds.lat.attrs['units'] = 'degrees_north' + ds.lat.attrs["standard_name"] = "latitude" + ds.lat.attrs["long_name"] = "latitude of glacier center" + ds.lat.attrs["units"] = "degrees_north" # save batch - vn_fp = f'{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/' + vn_fp = f"{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/" if not os.path.exists(vn_fp): os.makedirs(vn_fp, exist_ok=True) if realizations[0]: - ds_fn = f'R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + ds_fn = f"R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) else: - ds_fn = f'R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + ds_fn = f"R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) ds.to_netcdf(vn_fp + ds_fn) loop_end = time.time() - print(f'Batch {nbatch} runtime:\t{np.round(loop_end - loop_start,2)} seconds') - + print(f"Batch {nbatch} runtime:\t{np.round(loop_end - loop_start, 2)} seconds") ### MERGE BATCHES FOR ANNUAL VARS ### - vns = ['glac_mass_annual', 'glac_area_annual'] + vns = ["glac_mass_annual", "glac_area_annual"] for vn in vns: if vn in vars: - vn_fp = f'{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/' + vn_fp = f"{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/" fn_merge_list_start = [] if realizations[0]: - fn_merge_list = glob.glob(f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{gcms[0]}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) + fn_merge_list = glob.glob( + f"{vn_fp}/R{str(reg).zfill(2)}_{vn}_{gcms[0]}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) else: - fn_merge_list = glob.glob(f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) - fn_merge_list_start = [int(f.split('-')[-2]) for f in fn_merge_list] + fn_merge_list = glob.glob( + f"{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) + fn_merge_list_start = [int(f.split("-")[-2]) for f in fn_merge_list] if len(fn_merge_list) > 0: - fn_merge_list = [x for _,x in sorted(zip(fn_merge_list_start,fn_merge_list))] + fn_merge_list = [ + x for _, x in sorted(zip(fn_merge_list_start, fn_merge_list)) + ] ds = None for fn in fn_merge_list: @@ -604,7 +850,10 @@ def run(args): else: ds = xr.concat([ds, ds_batch], dim="glacier") # save - ds_fn = fn.split('Batch')[0][:-1] + f'_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc' + ds_fn = ( + fn.split("Batch")[0][:-1] + + f"_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc" + ) ds.to_netcdf(ds_fn) ds_batch.close() @@ -618,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) - requiredNamed = parser.add_argument_group('required named arguments') - requiredNamed.add_argument('-rgi_region01', type=int, default=None, required=True, nargs='+', - help='Randoph Glacier Inventory region (can take multiple, e.g. "1 2 3")') - requiredNamed.add_argument('-gcm_name', type=str, default=None, required=True, nargs='+', - help='GCM name for which to compile simulations (can take multiple, ex. "ERA5" or "CESM2")') - parser.add_argument('-scenario', action='store', type=str, default=None, nargs='+', - help='rcp or ssp scenario used for model run (can take multiple, ex. "ssp245 ssp585")') - parser.add_argument('-realization', action='store', type=str, default=None, nargs='+', - help='realization from large ensemble used for model run (cant take multiple, ex. "r1i1p1f1 r2i1p1f1 r3i1p1f1")') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-sim_path', type=str, default=pygem_prms['root'] + '/Output/simulations/', - help='PyGEM simulations filepath') - parser.add_argument('-option_calibration', action='store', type=str, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: 0, "1", "2", "3".\n0: no adjustment\n1: new prec scheme and temp building on HH2015\n2: HH2015 methods\n3: quantile delta mapping)') - parser.add_argument('-vars',type=str, help='comm delimited list of PyGEM variables to compile (can take multiple, ex. "monthly_mass annual_area")', - choices=['glac_runoff_monthly','offglac_runoff_monthly','glac_acc_monthly','glac_melt_monthly','glac_refreeze_monthly','glac_frontalablation_monthly','glac_massbaltotal_monthly','glac_prec_monthly','glac_mass_monthly','glac_mass_annual','glac_area_annual'], - nargs='+') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization') + description="""description: program for compiling regional stats from the python glacier evolution model (PyGEM)\nnote, this script is not embarrassingly parallel\nit is currently set up to be parallelized by splitting into n jobs based on the number of regions and scenarios scecified\nfor example, the call below could be parallelized into 4 jobs (2 regions x 2 scenarios)\n\nexample call: $python compile_simulations -rgi_region 01 02 -scenario ssp345 ssp585 -gcm_startyear2000 -gcm_endyear 2100 -ncores 4 -vars glac_mass_annual glac_area_annual""", + formatter_class=argparse.RawTextHelpFormatter, + ) + requiredNamed = parser.add_argument_group("required named arguments") + requiredNamed.add_argument( + "-rgi_region01", + type=int, + default=None, + required=True, + nargs="+", + help='Randoph Glacier Inventory region (can take multiple, e.g. "1 2 3")', + ) + requiredNamed.add_argument( + "-gcm_name", + type=str, + default=None, + required=True, + nargs="+", + help='GCM name for which to compile simulations (can take multiple, ex. "ERA5" or "CESM2")', + ) + parser.add_argument( + "-scenario", + action="store", + type=str, + default=None, + nargs="+", + help='rcp or ssp scenario used for model run (can take multiple, ex. "ssp245 ssp585")', + ) + parser.add_argument( + "-realization", + action="store", + type=str, + default=None, + nargs="+", + help='realization from large ensemble used for model run (cant take multiple, ex. "r1i1p1f1 r2i1p1f1 r3i1p1f1")', + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-sim_path", + type=str, + default=pygem_prms["root"] + "/Output/simulations/", + help="PyGEM simulations filepath", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help='Bias adjustment option (options: 0, "1", "2", "3".\n0: no adjustment\n1: new prec scheme and temp building on HH2015\n2: HH2015 methods\n3: quantile delta mapping)', + ) + parser.add_argument( + "-vars", + type=str, + help='comm delimited list of PyGEM variables to compile (can take multiple, ex. "monthly_mass annual_area")', + choices=[ + "glac_runoff_monthly", + "offglac_runoff_monthly", + "glac_acc_monthly", + "glac_melt_monthly", + "glac_refreeze_monthly", + "glac_frontalablation_monthly", + "glac_massbaltotal_monthly", + "glac_prec_monthly", + "glac_mass_monthly", + "glac_mass_annual", + "glac_area_annual", + ], + nargs="+", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + ) args = parser.parse_args() simpath = args.sim_path @@ -658,10 +977,10 @@ def main(): vars = args.vars if not simpath: - simpath = pygem_prms['root'] + '/Output/simulations/' + simpath = pygem_prms["root"] + "/Output/simulations/" - if not os.path.exists(simpath + 'compile/'): - os.makedirs(simpath + 'compile/') + if not os.path.exists(simpath + "compile/"): + os.makedirs(simpath + "compile/") if not isinstance(region, list): region = [region] @@ -672,23 +991,41 @@ def main(): if scenarios: if not isinstance(scenarios, list): scenarios = [scenarios] - if set(['ERA5', 'ERA-Interim', 'COAWST']) & set(gcms): - raise ValueError(f'Cannot compile present-day and future data simulataneously. A scenario was specified, which does not exist for one of the specified GCMs.\nGCMs: {gcms}\nScenarios: {scenarios}') + if set(["ERA5", "ERA-Interim", "COAWST"]) & set(gcms): + raise ValueError( + f"Cannot compile present-day and future data simulataneously. A scenario was specified, which does not exist for one of the specified GCMs.\nGCMs: {gcms}\nScenarios: {scenarios}" + ) else: - scenarios = [''] - if set(gcms) - set(['ERA5', 'ERA-Interim', 'COAWST']): - raise ValueError(f'Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}') + scenarios = [""] + if set(gcms) - set(["ERA5", "ERA-Interim", "COAWST"]): + raise ValueError( + f"Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}" + ) if realizations is None: - realizations = [''] + realizations = [""] else: if not isinstance(realizations, list): realizations = [realizations] if len(gcms) > 1: - raise ValueError(f'Script not set up to aggregate multiple GCMs and realizations simultaneously - if aggregating multiple realizations, specify a single GCM at a time\nGCMs: {gcms}\nrealizations: {realizations}') + raise ValueError( + f"Script not set up to aggregate multiple GCMs and realizations simultaneously - if aggregating multiple realizations, specify a single GCM at a time\nGCMs: {gcms}\nrealizations: {realizations}" + ) if not vars: - vars = ['glac_runoff_monthly','offglac_runoff_monthly','glac_acc_monthly','glac_melt_monthly','glac_refreeze_monthly','glac_frontalablation_monthly','glac_massbaltotal_monthly','glac_prec_monthly','glac_mass_monthly','glac_mass_annual','glac_area_annual'] + vars = [ + "glac_runoff_monthly", + "offglac_runoff_monthly", + "glac_acc_monthly", + "glac_melt_monthly", + "glac_refreeze_monthly", + "glac_frontalablation_monthly", + "glac_massbaltotal_monthly", + "glac_prec_monthly", + "glac_mass_monthly", + "glac_mass_annual", + "glac_area_annual", + ] # get number of jobs and split into desired number of cores njobs = int(len(region) * len(scenarios)) @@ -700,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...') + print("Processing with " + str(num_cores) + " cores...") with multiprocessing.Pool(num_cores) as p: 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__': +if __name__ == "__main__": main() diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index 952c7572..c16ca91d 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + # Built-in libraries import argparse import multiprocessing @@ -40,14 +41,25 @@ def getparser(): """ Use argparse to add arguments from the command line """ - parser = argparse.ArgumentParser(description="distrube PyGEM simulated ice thickness to a 2D grid") + parser = argparse.ArgumentParser( + description="distrube PyGEM simulated ice thickness to a 2D grid" + ) # add arguments - parser.add_argument('-simpath', action='store', type=str, nargs='+', - help='path to PyGEM binned simulation (can take multiple)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-simpath", + action="store", + type=str, + nargs="+", + help="path to PyGEM binned simulation (can take multiple)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") return parser @@ -64,32 +76,32 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): area_m2(time, dis_along_flowline): float64 thickness_m (time, dis_along_flowline): float64 """ - yr0,yr1 = pygem_simpath.split('_')[-3:-1] + yr0, yr1 = pygem_simpath.split("_")[-3:-1] pygem_ds = xr.open_dataset(pygem_simpath).sel(year=slice(yr0, yr1)) - time = pygem_ds.coords['year'].values.flatten().astype(float) - distance_along_flowline = pygem_ds['bin_distance'].values.flatten().astype(float) - area = pygem_ds['bin_area_annual'].values[0].astype(float).T - thick = pygem_ds['bin_thick_annual'].values[0].astype(float).T + time = pygem_ds.coords["year"].values.flatten().astype(float) + distance_along_flowline = pygem_ds["bin_distance"].values.flatten().astype(float) + area = pygem_ds["bin_area_annual"].values[0].astype(float).T + thick = pygem_ds["bin_thick_annual"].values[0].astype(float).T vol = area * thick diag_ds = xr.Dataset() - diag_ds.coords['time'] = time - diag_ds.coords['dis_along_flowline'] = distance_along_flowline - diag_ds['area_m2'] = (('time', 'dis_along_flowline'), area) - diag_ds['area_m2'].attrs['description'] = 'Section area' - diag_ds['area_m2'].attrs['unit'] = 'm 2' - diag_ds['thickness_m'] = (('time', 'dis_along_flowline'), thick * np.nan) - diag_ds['thickness_m'].attrs['description'] = 'Section thickness' - diag_ds['thickness_m'].attrs['unit'] = 'm' - diag_ds['volume_m3'] = (('time', 'dis_along_flowline'), vol) - diag_ds['volume_m3'].attrs['description'] = 'Section volume' - diag_ds['volume_m3'].attrs['unit'] = 'm 3' + diag_ds.coords["time"] = time + diag_ds.coords["dis_along_flowline"] = distance_along_flowline + diag_ds["area_m2"] = (("time", "dis_along_flowline"), area) + diag_ds["area_m2"].attrs["description"] = "Section area" + diag_ds["area_m2"].attrs["unit"] = "m 2" + diag_ds["thickness_m"] = (("time", "dis_along_flowline"), thick * np.nan) + diag_ds["thickness_m"].attrs["description"] = "Section thickness" + diag_ds["thickness_m"].attrs["unit"] = "m" + diag_ds["volume_m3"] = (("time", "dis_along_flowline"), vol) + diag_ds["volume_m3"].attrs["description"] = "Section volume" + diag_ds["volume_m3"].attrs["unit"] = "m 3" # diag_ds.to_netcdf(oggm_diag, 'w', group='fl_0') if debug: # plot volume - vol = diag_ds.sum(dim=['dis_along_flowline'])['volume_m3'] - f,ax = plt.subplots(1,figsize=(5,5)) - (vol/vol[0]).plot(ax=ax) + vol = diag_ds.sum(dim=["dis_along_flowline"])["volume_m3"] + f, ax = plt.subplots(1, figsize=(5, 5)) + (vol / vol[0]).plot(ax=ax) plt.show() return diag_ds @@ -97,26 +109,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] + pygem_fn_split = pygem_fn.split("_") + f_suffix = "_".join(pygem_fn_split[1:])[:-3] glac_no = pygem_fn_split[0] - glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[0, :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[ + 0, : + ] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: - if glacier_rgi_table['TermType'] not in [1,5] or not pygem_prms['setup']['include_calving']: + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_calving"] + ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False else: @@ -127,7 +153,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 @@ -141,13 +167,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) @@ -167,11 +196,12 @@ def main(): # set up partial function with debug argument run_with_debug = partial(run, debug=args.debug) # parallel processing - print('Processing with ' + str(ncores) + ' cores...') + print("Processing with " + str(ncores) + " cores...") with multiprocessing.Pool(ncores) as p: p.map(run_with_debug, args.simpath) - print('Total processing time:', time.time()-time_start, 's') + print("Total processing time:", time.time() - time_start, "s") + if __name__ == "__main__": main() diff --git a/pygem/bin/postproc/postproc_monthly_mass.py b/pygem/bin/postproc/postproc_monthly_mass.py index 8a7895b3..18c66e53 100644 --- a/pygem/bin/postproc/postproc_monthly_mass.py +++ b/pygem/bin/postproc/postproc_monthly_mass.py @@ -7,6 +7,7 @@ derive monthly glacierwide mass for PyGEM simulation using annual glacier mass and monthly total mass balance """ + # Built-in libraries import argparse import collections @@ -34,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 @@ -72,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 @@ -106,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'} - + output_attrs_dict["glac_mass_monthly"] = { + "long_name": "glacier mass", + "units": "kg", + "temporal_resolution": "monthly", + "comment": "monthly glacier mass", + } # Add variables to empty dataset and merge together count_vn = 0 encoding = {} for vn in output_coords_dict.keys(): - empty_holder = np.zeros([len(output_coords_dict[vn][i]) for i in list(output_coords_dict[vn].keys())]) - output_ds = xr.Dataset({vn: (list(output_coords_dict[vn].keys()), empty_holder)}, - coords=output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(output_coords_dict[vn][i]) + for i in list(output_coords_dict[vn].keys()) + ] + ) + output_ds = xr.Dataset( + {vn: (list(output_coords_dict[vn].keys()), empty_holder)}, + coords=output_coords_dict[vn], + ) count_vn += 1 # Merge datasets of stats into one output if count_vn == 1: @@ -138,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 @@ -165,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 @@ -177,7 +203,9 @@ 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() @@ -185,7 +213,7 @@ def run(simpath): except: pass else: - print('Simulation not found: ',simpath) + print("Simulation not found: ", simpath) return @@ -197,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 @@ -210,11 +238,12 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(args.ncores) + ' cores...') + print("Processing with " + str(args.ncores) + " cores...") with multiprocessing.Pool(args.ncores) as p: - p.map(run,simpath) + p.map(run, simpath) + + print("Total processing time:", time.time() - time_start, "s") - print('Total processing time:', time.time()-time_start, 's') if __name__ == "__main__": main() diff --git a/pygem/bin/preproc/preproc_fetch_mbdata.py b/pygem/bin/preproc/preproc_fetch_mbdata.py index 1a07e4d0..063ab2d5 100644 --- a/pygem/bin/preproc/preproc_fetch_mbdata.py +++ b/pygem/bin/preproc/preproc_fetch_mbdata.py @@ -7,6 +7,7 @@ Fetch filled Hugonnet reference mass balance data """ + # Built-in libraries import argparse import os @@ -24,7 +25,7 @@ pygem_prms = config_manager.read_config() -def run(fp='', debug=False, overwrite=False): +def run(fp="", debug=False, overwrite=False): """ pull geodetic mass balance data from OGGM The original 'raw' were acquired and combined from https://doi.org/10.6096/13 (time series/dh__rgi60_pergla_rates) @@ -39,43 +40,56 @@ def run(fp='', debug=False, overwrite=False): """ mbdf = utils.get_geodetic_mb_dataframe() if debug: - print('MB data loaded from OGGM:') + print("MB data loaded from OGGM:") print(mbdf.head()) # pull only 2000-2020 period - mbdf_subset = mbdf[mbdf.period=='2000-01-01_2020-01-01'] + mbdf_subset = mbdf[mbdf.period == "2000-01-01_2020-01-01"] # reset the index mbdf_subset = mbdf_subset.reset_index() # sort by the rgiid column - mbdf_subset = mbdf_subset.sort_values(by='rgiid') + mbdf_subset = mbdf_subset.sort_values(by="rgiid") # rename some keys to work with what other scripts/functions expect - mbdf_subset= mbdf_subset.rename(columns={'dmdtda':'mb_mwea', - 'err_dmdtda':'mb_mwea_err'}) + mbdf_subset = mbdf_subset.rename( + columns={"dmdtda": "mb_mwea", "err_dmdtda": "mb_mwea_err"} + ) - if fp[-4:] != '.csv': - fp += '.csv' + if fp[-4:] != ".csv": + fp += ".csv" if os.path.isfile(fp) and not overwrite: - raise FileExistsError(f'The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}') + raise FileExistsError( + f"The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}" + ) mbdf_subset.to_csv(fp, index=False) if debug: - print(f'Filled global geodetic mass balance data saved to: {fp}') + print(f"Filled global geodetic mass balance data saved to: {fp}") print(mbdf_subset.head()) def main(): - parser = argparse.ArgumentParser(description="grab filled Hugonnet et al. 2021 geodetic mass balance data from OGGM and converts to a format PyGEM utilizes") + parser = argparse.ArgumentParser( + description="grab filled Hugonnet et al. 2021 geodetic mass balance data from OGGM and converts to a format PyGEM utilizes" + ) # add arguments - parser.add_argument('-fname', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", - help='Reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing geodetic mass balance data') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-fname", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", + help="Reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-o", + "--overwrite", + action="store_true", + help="Flag to overwrite existing geodetic mass balance data", + ) + parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") args = parser.parse_args() # hugonnet filepath diff --git a/pygem/bin/preproc/preproc_wgms_estimate_kp.py b/pygem/bin/preproc/preproc_wgms_estimate_kp.py index 891f3f30..2245109f 100644 --- a/pygem/bin/preproc/preproc_wgms_estimate_kp.py +++ b/pygem/bin/preproc/preproc_wgms_estimate_kp.py @@ -10,6 +10,7 @@ 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 @@ -32,36 +33,63 @@ import pygem.pygem_modelsetup as modelsetup -def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', wgms_ee_winter_fp='', wgms_ee_winter_fp_subset='', subset_time_value=20000000): +def subset_winter( + wgms_eee_fp="", + wgms_ee_fp="", + wgms_e_fp="", + wgms_id_fp="", + wgms_ee_winter_fp="", + wgms_ee_winter_fp_subset="", + subset_time_value=20000000, +): """ subset winter mass balance data from WGMS """ # Load data - wgms_e_df = pd.read_csv(wgms_e_fp, encoding='unicode_escape') - wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding='unicode_escape') - wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding='unicode_escape') - wgms_id_df = pd.read_csv(wgms_id_fp, encoding='unicode_escape') + wgms_e_df = pd.read_csv(wgms_e_fp, encoding="unicode_escape") + wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding="unicode_escape") + wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding="unicode_escape") + wgms_id_df = pd.read_csv(wgms_id_fp, encoding="unicode_escape") # Map dictionary wgms_id_dict = dict(zip(wgms_id_df.WGMS_ID, wgms_id_df.RGI_ID)) - wgms_ee_df_raw['rgiid_raw'] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=['rgiid_raw']) - wgms_eee_df_raw['rgiid_raw'] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=['rgiid_raw']) + wgms_ee_df_raw["rgiid_raw"] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=["rgiid_raw"]) + wgms_eee_df_raw["rgiid_raw"] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=["rgiid_raw"]) # Link RGIv5.0 with RGIv6.0 - rgi60_fp = pygem_prms['root'] + '/RGI/rgi60/00_rgi60_attribs/' - rgi50_fp = pygem_prms['root'] + '/RGI/00_rgi50_attribs/' + rgi60_fp = pygem_prms["root"] + "/RGI/rgi60/00_rgi60_attribs/" + rgi50_fp = pygem_prms["root"] + "/RGI/00_rgi50_attribs/" # Process each region - regions_str = ['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18'] + regions_str = [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + ] rgi60_df = None rgi50_df = None for reg_str in regions_str: # RGI60 data for i in os.listdir(rgi60_fp): - if i.startswith(reg_str) and i.endswith('.csv'): - rgi60_df_reg = pd.read_csv(rgi60_fp + i, encoding='unicode_escape') + if i.startswith(reg_str) and i.endswith(".csv"): + rgi60_df_reg = pd.read_csv(rgi60_fp + i, encoding="unicode_escape") # append datasets if rgi60_df is None: rgi60_df = rgi60_df_reg @@ -70,8 +98,8 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w # RGI50 data for i in os.listdir(rgi50_fp): - if i.startswith(reg_str) and i.endswith('.csv'): - rgi50_df_reg = pd.read_csv(rgi50_fp + i, encoding='unicode_escape') + if i.startswith(reg_str) and i.endswith(".csv"): + rgi50_df_reg = pd.read_csv(rgi50_fp + i, encoding="unicode_escape") # append datasets if rgi50_df is None: rgi50_df = rgi50_df_reg @@ -80,23 +108,23 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w # Merge based on GLIMSID glims_rgi50_dict = dict(zip(rgi50_df.GLIMSId, rgi50_df.RGIId)) - rgi60_df['RGIId_50'] = rgi60_df.GLIMSId.map(glims_rgi50_dict) - rgi60_df_4dict = rgi60_df.dropna(subset=['RGIId_50']) + rgi60_df["RGIId_50"] = rgi60_df.GLIMSId.map(glims_rgi50_dict) + rgi60_df_4dict = rgi60_df.dropna(subset=["RGIId_50"]) rgi50_rgi60_dict = dict(zip(rgi60_df_4dict.RGIId_50, rgi60_df_4dict.RGIId)) rgi60_self_dict = dict(zip(rgi60_df.RGIId, rgi60_df.RGIId)) rgi50_rgi60_dict.update(rgi60_self_dict) # Add RGIId for version 6 to WGMS - wgms_ee_df_raw['rgiid'] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) - wgms_eee_df_raw['rgiid'] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + wgms_ee_df_raw["rgiid"] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + wgms_eee_df_raw["rgiid"] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) # Drop points without data - wgms_ee_df = wgms_ee_df_raw.dropna(subset=['rgiid']) - wgms_eee_df = wgms_eee_df_raw.dropna(subset=['rgiid']) + wgms_ee_df = wgms_ee_df_raw.dropna(subset=["rgiid"]) + wgms_eee_df = wgms_eee_df_raw.dropna(subset=["rgiid"]) # Winter balances only - wgms_ee_df_winter = wgms_ee_df.dropna(subset=['WINTER_BALANCE']) - wgms_ee_df_winter = wgms_ee_df_winter.sort_values('rgiid') + wgms_ee_df_winter = wgms_ee_df.dropna(subset=["WINTER_BALANCE"]) + wgms_ee_df_winter = wgms_ee_df_winter.sort_values("rgiid") wgms_ee_df_winter.reset_index(inplace=True, drop=True) # Add the winter time period using the E-MASS-BALANCE-OVERVIEW file @@ -107,218 +135,308 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w wgms_ee_df_winter[cn] = np.nan for nrow in np.arange(wgms_ee_df_winter.shape[0]): - if nrow%500 == 0: - print(nrow, 'of', wgms_ee_df_winter.shape[0]) - name = wgms_ee_df_winter.loc[nrow,'NAME'] - wgmsid = wgms_ee_df_winter.loc[nrow,'WGMS_ID'] - year = wgms_ee_df_winter.loc[nrow,'YEAR'] + if nrow % 500 == 0: + print(nrow, "of", wgms_ee_df_winter.shape[0]) + name = wgms_ee_df_winter.loc[nrow, "NAME"] + wgmsid = wgms_ee_df_winter.loc[nrow, "WGMS_ID"] + year = wgms_ee_df_winter.loc[nrow, "YEAR"] try: - e_idx = np.where((wgms_e_df['NAME'] == name) & - (wgms_e_df['WGMS_ID'] == wgmsid) & - (wgms_e_df['Year'] == year))[0][0] + e_idx = np.where( + (wgms_e_df["NAME"] == name) + & (wgms_e_df["WGMS_ID"] == wgmsid) + & (wgms_e_df["Year"] == year) + )[0][0] except: e_idx = None if e_idx is not None: - wgms_ee_df_winter.loc[nrow,wgms_e_cns2add] = wgms_e_df.loc[e_idx,wgms_e_cns2add] + wgms_ee_df_winter.loc[nrow, wgms_e_cns2add] = wgms_e_df.loc[ + e_idx, wgms_e_cns2add + ] wgms_ee_df_winter.to_csv(wgms_ee_winter_fp, index=False) # Export subset of data - wgms_ee_df_winter_subset = wgms_ee_df_winter.loc[wgms_ee_df_winter['BEGIN_PERIOD'] > subset_time_value] - wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna(subset=['END_WINTER']) + wgms_ee_df_winter_subset = wgms_ee_df_winter.loc[ + wgms_ee_df_winter["BEGIN_PERIOD"] > subset_time_value + ] + wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna(subset=["END_WINTER"]) wgms_ee_df_winter_subset.to_csv(wgms_ee_winter_fp_subset, index=False) -def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_stats_fp=''): +def est_kp( + wgms_ee_winter_fp_subset="", wgms_ee_winter_fp_kp="", wgms_reg_kp_stats_fp="" +): """ This is used to estimate the precipitation factor for the bounds of HH2015_mod """ # Load data - assert os.path.exists(wgms_ee_winter_fp_subset), 'wgms_ee_winter_fn_subset does not exist!' - wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding='unicode_escape') + assert os.path.exists(wgms_ee_winter_fp_subset), ( + "wgms_ee_winter_fn_subset does not exist!" + ) + wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding="unicode_escape") # Process dates - wgms_df.loc[:,'BEGIN_PERIOD'] = wgms_df.loc[:,'BEGIN_PERIOD'].values.astype(int).astype(str) - wgms_df['BEGIN_YEAR'] = [int(x[0:4]) for x in wgms_df.loc[:,'BEGIN_PERIOD']] - wgms_df['BEGIN_MONTH'] = [int(x[4:6]) for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df['BEGIN_DAY'] = [int(x[6:]) for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df['BEGIN_YEARMONTH'] = [x[0:6] for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df.loc[:,'END_WINTER'] = wgms_df.loc[:,'END_WINTER'].values.astype(int).astype(str) - wgms_df['END_YEAR'] = [int(x[0:4]) for x in wgms_df.loc[:,'END_WINTER']] - wgms_df['END_MONTH'] = [int(x[4:6]) for x in list(wgms_df.loc[:,'END_WINTER'])] - wgms_df['END_DAY'] = [int(x[6:]) for x in list(wgms_df.loc[:,'END_WINTER'])] - wgms_df['END_YEARMONTH'] = [x[0:6] for x in list(wgms_df.loc[:,'END_WINTER'])] + wgms_df.loc[:, "BEGIN_PERIOD"] = ( + wgms_df.loc[:, "BEGIN_PERIOD"].values.astype(int).astype(str) + ) + wgms_df["BEGIN_YEAR"] = [int(x[0:4]) for x in wgms_df.loc[:, "BEGIN_PERIOD"]] + wgms_df["BEGIN_MONTH"] = [int(x[4:6]) for x in list(wgms_df.loc[:, "BEGIN_PERIOD"])] + wgms_df["BEGIN_DAY"] = [int(x[6:]) for x in list(wgms_df.loc[:, "BEGIN_PERIOD"])] + wgms_df["BEGIN_YEARMONTH"] = [x[0:6] for x in list(wgms_df.loc[:, "BEGIN_PERIOD"])] + wgms_df.loc[:, "END_WINTER"] = ( + wgms_df.loc[:, "END_WINTER"].values.astype(int).astype(str) + ) + wgms_df["END_YEAR"] = [int(x[0:4]) for x in wgms_df.loc[:, "END_WINTER"]] + wgms_df["END_MONTH"] = [int(x[4:6]) for x in list(wgms_df.loc[:, "END_WINTER"])] + wgms_df["END_DAY"] = [int(x[6:]) for x in list(wgms_df.loc[:, "END_WINTER"])] + wgms_df["END_YEARMONTH"] = [x[0:6] for x in list(wgms_df.loc[:, "END_WINTER"])] # ===== PROCESS UNIQUE GLACIERS ===== - rgiids_unique = list(wgms_df['rgiid'].unique()) - glac_no = [x.split('-')[1] for x in rgiids_unique] + rgiids_unique = list(wgms_df["rgiid"].unique()) + glac_no = [x.split("-")[1] for x in rgiids_unique] main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=pygem_prms['climate']['ref_startyear'], endyear=pygem_prms['climate']['ref_endyear'], spinupyears=0, - option_wateryear=pygem_prms['climate']['ref_wateryear']) - dates_table_yearmo = [str(dates_table.loc[x,'year']) + str(dates_table.loc[x,'month']).zfill(2) - for x in range(dates_table.shape[0])] + startyear=pygem_prms["climate"]["ref_startyear"], + endyear=pygem_prms["climate"]["ref_endyear"], + spinupyears=0, + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) + dates_table_yearmo = [ + str(dates_table.loc[x, "year"]) + str(dates_table.loc[x, "month"]).zfill(2) + for x in range(dates_table.shape[0]) + ] # ===== LOAD CLIMATE DATA ===== # Climate class - gcm = class_climate.GCM(name=pygem_prms['climate']['ref_gcm_name']) + gcm = class_climate.GCM(name=pygem_prms["climate"]["ref_gcm_name"]) # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, - dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table + ) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, - dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) # Lapse rate - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) # ===== PROCESS THE OBSERVATIONS ====== - prec_cn = pygem_prms['climate']['ref_gcm_name'] + '_prec' + prec_cn = pygem_prms["climate"]["ref_gcm_name"] + "_prec" wgms_df[prec_cn] = np.nan - wgms_df['kp'] = np.nan - wgms_df['ndays'] = np.nan + wgms_df["kp"] = np.nan + wgms_df["ndays"] = np.nan for glac in range(main_glac_rgi.shape[0]): - print(glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId']) + print(glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"]) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) rgiid = glacier_rgi_table.RGIId - wgms_df_single = (wgms_df.loc[wgms_df['rgiid'] == rgiid]).copy() + wgms_df_single = (wgms_df.loc[wgms_df["rgiid"] == rgiid]).copy() glac_idx = wgms_df_single.index.values wgms_df_single.reset_index(inplace=True, drop=True) wgms_df_single[prec_cn] = np.nan for nobs in range(wgms_df_single.shape[0]): - # Only process good data # - dates are provided and real # - spans more than one month # - positive winter balance (since we don't account for melt) - if ((wgms_df_single.loc[nobs,'BEGIN_MONTH'] >= 1 and wgms_df_single.loc[nobs,'BEGIN_MONTH'] <= 12) and - (wgms_df_single.loc[nobs,'BEGIN_DAY'] >= 1 and wgms_df_single.loc[nobs,'BEGIN_DAY'] <= 31) and - (wgms_df_single.loc[nobs,'END_MONTH'] >= 1 and wgms_df_single.loc[nobs,'END_MONTH'] <= 12) and - (wgms_df_single.loc[nobs,'END_DAY'] >= 1 and wgms_df_single.loc[nobs,'END_DAY'] <= 31) and - (wgms_df_single.loc[nobs,'BEGIN_PERIOD'] < wgms_df_single.loc[nobs,'END_WINTER']) and - (wgms_df_single.loc[nobs,'BEGIN_YEARMONTH'] != wgms_df_single.loc[nobs,'END_YEARMONTH']) and - (wgms_df_single.loc[nobs,'WINTER_BALANCE'] > 0) - ): + if ( + ( + wgms_df_single.loc[nobs, "BEGIN_MONTH"] >= 1 + and wgms_df_single.loc[nobs, "BEGIN_MONTH"] <= 12 + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_DAY"] >= 1 + and wgms_df_single.loc[nobs, "BEGIN_DAY"] <= 31 + ) + and ( + wgms_df_single.loc[nobs, "END_MONTH"] >= 1 + and wgms_df_single.loc[nobs, "END_MONTH"] <= 12 + ) + and ( + wgms_df_single.loc[nobs, "END_DAY"] >= 1 + and wgms_df_single.loc[nobs, "END_DAY"] <= 31 + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_PERIOD"] + < wgms_df_single.loc[nobs, "END_WINTER"] + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_YEARMONTH"] + != wgms_df_single.loc[nobs, "END_YEARMONTH"] + ) + and (wgms_df_single.loc[nobs, "WINTER_BALANCE"] > 0) + ): # Begin index - idx_begin = dates_table_yearmo.index(wgms_df_single.loc[nobs,'BEGIN_YEARMONTH']) - idx_end = dates_table_yearmo.index(wgms_df_single.loc[nobs,'END_YEARMONTH']) + idx_begin = dates_table_yearmo.index( + wgms_df_single.loc[nobs, "BEGIN_YEARMONTH"] + ) + idx_end = dates_table_yearmo.index( + wgms_df_single.loc[nobs, "END_YEARMONTH"] + ) # Fraction of the months to remove - remove_prec_begin = (gcm_prec[glac,idx_begin] * - wgms_df_single.loc[nobs,'BEGIN_DAY'] / dates_table.loc[idx_begin,'daysinmonth']) - remove_prec_end = (gcm_prec[glac,idx_end] * - (1 - wgms_df_single.loc[nobs,'END_DAY'] / dates_table.loc[idx_end,'daysinmonth'])) + remove_prec_begin = ( + gcm_prec[glac, idx_begin] + * wgms_df_single.loc[nobs, "BEGIN_DAY"] + / dates_table.loc[idx_begin, "daysinmonth"] + ) + remove_prec_end = gcm_prec[glac, idx_end] * ( + 1 + - wgms_df_single.loc[nobs, "END_DAY"] + / dates_table.loc[idx_end, "daysinmonth"] + ) # Winter Precipitation - gcm_prec_winter = gcm_prec[glac,idx_begin:idx_end+1].sum() - remove_prec_begin - remove_prec_end - wgms_df_single.loc[nobs,prec_cn] = gcm_prec_winter + gcm_prec_winter = ( + gcm_prec[glac, idx_begin : idx_end + 1].sum() + - remove_prec_begin + - remove_prec_end + ) + wgms_df_single.loc[nobs, prec_cn] = gcm_prec_winter # Number of days - ndays = (dates_table.loc[idx_begin:idx_end,'daysinmonth'].sum() - wgms_df_single.loc[nobs,'BEGIN_DAY'] - - (dates_table.loc[idx_end,'daysinmonth'] - wgms_df_single.loc[nobs,'END_DAY'])) - wgms_df_single.loc[nobs,'ndays'] = ndays + ndays = ( + dates_table.loc[idx_begin:idx_end, "daysinmonth"].sum() + - wgms_df_single.loc[nobs, "BEGIN_DAY"] + - ( + dates_table.loc[idx_end, "daysinmonth"] + - wgms_df_single.loc[nobs, "END_DAY"] + ) + ) + wgms_df_single.loc[nobs, "ndays"] = ndays # Estimate precipitation factors # - assumes no melt and all snow (hence a convservative/underestimated estimate) - wgms_df_single['kp'] = wgms_df_single['WINTER_BALANCE'] / 1000 / wgms_df_single[prec_cn] + wgms_df_single["kp"] = ( + wgms_df_single["WINTER_BALANCE"] / 1000 / wgms_df_single[prec_cn] + ) # Record precipitation, precipitation factors, and number of days in main dataframe - wgms_df.loc[glac_idx,prec_cn] = wgms_df_single[prec_cn].values - wgms_df.loc[glac_idx,'kp'] = wgms_df_single['kp'].values - wgms_df.loc[glac_idx,'ndays'] = wgms_df_single['ndays'].values + wgms_df.loc[glac_idx, prec_cn] = wgms_df_single[prec_cn].values + wgms_df.loc[glac_idx, "kp"] = wgms_df_single["kp"].values + wgms_df.loc[glac_idx, "ndays"] = wgms_df_single["ndays"].values # Drop nan values - wgms_df_wkp = wgms_df.dropna(subset=['kp']).copy() + wgms_df_wkp = wgms_df.dropna(subset=["kp"]).copy() wgms_df_wkp.reset_index(inplace=True, drop=True) - wgms_df_wkp.to_csv(wgms_ee_winter_fp_kp, index=False) # Calculate stats for all and each region - wgms_df_wkp['reg'] = [x.split('-')[1].split('.')[0] for x in wgms_df_wkp['rgiid'].values] - reg_unique = list(wgms_df_wkp['reg'].unique()) + wgms_df_wkp["reg"] = [ + x.split("-")[1].split(".")[0] for x in wgms_df_wkp["rgiid"].values + ] + reg_unique = list(wgms_df_wkp["reg"].unique()) # Output dataframe - reg_kp_cns = ['region', 'count_obs', 'count_glaciers', 'kp_mean', 'kp_std', 'kp_med', 'kp_nmad', 'kp_min', 'kp_max'] - reg_kp_df = pd.DataFrame(np.zeros((len(reg_unique)+1,len(reg_kp_cns))), columns=reg_kp_cns) + reg_kp_cns = [ + "region", + "count_obs", + "count_glaciers", + "kp_mean", + "kp_std", + "kp_med", + "kp_nmad", + "kp_min", + "kp_max", + ] + reg_kp_df = pd.DataFrame( + np.zeros((len(reg_unique) + 1, len(reg_kp_cns))), columns=reg_kp_cns + ) # Only those with at least 1 month of data - wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp['ndays'] >= 30] + wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp["ndays"] >= 30] # All stats - reg_kp_df.loc[0,'region'] = 'all' - reg_kp_df.loc[0,'count_obs'] = wgms_df_wkp.shape[0] - reg_kp_df.loc[0,'count_glaciers'] = len(wgms_df_wkp['rgiid'].unique()) - reg_kp_df.loc[0,'kp_mean'] = np.mean(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_std'] = np.std(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_med'] = np.median(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_nmad'] = median_abs_deviation(wgms_df_wkp.kp.values, scale='normal') - reg_kp_df.loc[0,'kp_min'] = np.min(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_max'] = np.max(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "region"] = "all" + reg_kp_df.loc[0, "count_obs"] = wgms_df_wkp.shape[0] + reg_kp_df.loc[0, "count_glaciers"] = len(wgms_df_wkp["rgiid"].unique()) + reg_kp_df.loc[0, "kp_mean"] = np.mean(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_std"] = np.std(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_med"] = np.median(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_nmad"] = median_abs_deviation( + wgms_df_wkp.kp.values, scale="normal" + ) + reg_kp_df.loc[0, "kp_min"] = np.min(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_max"] = np.max(wgms_df_wkp.kp.values) # Regional stats for nreg, reg in enumerate(reg_unique): - wgms_df_wkp_reg = wgms_df_wkp.loc[wgms_df_wkp['reg'] == reg] - - reg_kp_df.loc[nreg+1,'region'] = reg - reg_kp_df.loc[nreg+1,'count_obs'] = wgms_df_wkp_reg.shape[0] - reg_kp_df.loc[nreg+1,'count_glaciers'] = len(wgms_df_wkp_reg['rgiid'].unique()) - reg_kp_df.loc[nreg+1,'kp_mean'] = np.mean(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_std'] = np.std(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_med'] = np.median(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_nmad'] = median_abs_deviation(wgms_df_wkp_reg.kp.values, scale='normal') - reg_kp_df.loc[nreg+1,'kp_min'] = np.min(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_max'] = np.max(wgms_df_wkp_reg.kp.values) - - - print('region', reg) - print(' count:', wgms_df_wkp_reg.shape[0]) - print(' glaciers:', len(wgms_df_wkp_reg['rgiid'].unique())) - print(' mean:', np.mean(wgms_df_wkp_reg.kp.values)) - print(' std :', np.std(wgms_df_wkp_reg.kp.values)) - print(' med :', np.median(wgms_df_wkp_reg.kp.values)) - print(' nmad:', median_abs_deviation(wgms_df_wkp_reg.kp.values, scale='normal')) - print(' min :', np.min(wgms_df_wkp_reg.kp.values)) - print(' max :', np.max(wgms_df_wkp_reg.kp.values)) + wgms_df_wkp_reg = wgms_df_wkp.loc[wgms_df_wkp["reg"] == reg] + + reg_kp_df.loc[nreg + 1, "region"] = reg + reg_kp_df.loc[nreg + 1, "count_obs"] = wgms_df_wkp_reg.shape[0] + reg_kp_df.loc[nreg + 1, "count_glaciers"] = len( + wgms_df_wkp_reg["rgiid"].unique() + ) + reg_kp_df.loc[nreg + 1, "kp_mean"] = np.mean(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_std"] = np.std(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_med"] = np.median(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_nmad"] = median_abs_deviation( + wgms_df_wkp_reg.kp.values, scale="normal" + ) + reg_kp_df.loc[nreg + 1, "kp_min"] = np.min(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_max"] = np.max(wgms_df_wkp_reg.kp.values) + + print("region", reg) + print(" count:", wgms_df_wkp_reg.shape[0]) + print(" glaciers:", len(wgms_df_wkp_reg["rgiid"].unique())) + print(" mean:", np.mean(wgms_df_wkp_reg.kp.values)) + print(" std :", np.std(wgms_df_wkp_reg.kp.values)) + print(" med :", np.median(wgms_df_wkp_reg.kp.values)) + print( + " nmad:", median_abs_deviation(wgms_df_wkp_reg.kp.values, scale="normal") + ) + print(" min :", np.min(wgms_df_wkp_reg.kp.values)) + print(" max :", np.max(wgms_df_wkp_reg.kp.values)) reg_kp_df.to_csv(wgms_reg_kp_stats_fp, index=False) def main(): - parser = argparse.ArgumentParser(description="estimate precipitation factors from WGMS winter mass balance data") - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing data') + parser = argparse.ArgumentParser( + description="estimate precipitation factors from WGMS winter mass balance data" + ) + parser.add_argument( + "-o", "--overwrite", action="store_true", help="Flag to overwrite existing data" + ) args = parser.parse_args() # ===== WGMS DATA ===== # these are hardcoded for the format downloaded from WGMS for their 2020-08 dataset, would need to be updated for newer data wgms_fp = f"{pygem_prms['root']}/WGMS/" # inputs - wgms_dsn = 'DOI-WGMS-FoG-2020-08/' - wgms_eee_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-EEE-MASS-BALANCE-POINT.csv' - wgms_ee_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-EE-MASS-BALANCE.csv' - wgms_e_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-E-MASS-BALANCE-OVERVIEW.csv' - wgms_id_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-AA-GLACIER_ID_LUT.csv' + wgms_dsn = "DOI-WGMS-FoG-2020-08/" + wgms_eee_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-EEE-MASS-BALANCE-POINT.csv" + wgms_ee_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-EE-MASS-BALANCE.csv" + wgms_e_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-E-MASS-BALANCE-OVERVIEW.csv" + wgms_id_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-AA-GLACIER_ID_LUT.csv" in_fps = [x for x in [wgms_eee_fp, wgms_ee_fp, wgms_e_fp, wgms_id_fp]] # outputs - wgms_ee_winter_fp = wgms_fp+ 'WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv' - wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace('.csv', '-subset.csv') - wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace('.csv', '-subset-kp.csv') - wgms_reg_kp_stats_fp = wgms_fp+ 'WGMS-FoG-2019-12-reg_kp_summary.csv' + wgms_ee_winter_fp = ( + wgms_fp + "WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv" + ) + wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace(".csv", "-subset.csv") + wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace(".csv", "-subset-kp.csv") + wgms_reg_kp_stats_fp = wgms_fp + "WGMS-FoG-2019-12-reg_kp_summary.csv" out_subset_fps = [wgms_ee_winter_fp, wgms_ee_winter_fp_subset] - output_kp_fps = [wgms_ee_winter_fp_kp,wgms_reg_kp_stats_fp] + output_kp_fps = [wgms_ee_winter_fp_kp, wgms_reg_kp_stats_fp] subset_time_value = 20000000 @@ -327,23 +445,30 @@ def main(): missing = False for fp in in_fps: if not os.path.isfile(fp): - print(f'Missing required WGMS datafile: {fp}') + print(f"Missing required WGMS datafile: {fp}") missing = True if missing: sys.exit(1) - subset_winter(wgms_eee_fp=wgms_eee_fp, - wgms_ee_fp=wgms_ee_fp, - wgms_e_fp=wgms_e_fp, - wgms_id_fp=wgms_id_fp, - wgms_ee_winter_fp=wgms_ee_winter_fp, - wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, - subset_time_value=subset_time_value) - - if not all(os.path.exists(filepath) for filepath in output_kp_fps) or args.overwrite: - est_kp(wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, - wgms_ee_winter_fp_kp=wgms_ee_winter_fp_kp, - wgms_reg_kp_stats_fp=wgms_reg_kp_stats_fp) + subset_winter( + wgms_eee_fp=wgms_eee_fp, + wgms_ee_fp=wgms_ee_fp, + wgms_e_fp=wgms_e_fp, + wgms_id_fp=wgms_id_fp, + wgms_ee_winter_fp=wgms_ee_winter_fp, + wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, + subset_time_value=subset_time_value, + ) + + if ( + not all(os.path.exists(filepath) for filepath in output_kp_fps) + or args.overwrite + ): + est_kp( + wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, + wgms_ee_winter_fp_kp=wgms_ee_winter_fp_kp, + wgms_reg_kp_stats_fp=wgms_reg_kp_stats_fp, + ) if __name__ == "__main__": diff --git a/pygem/bin/run/run_calibration.py b/pygem/bin/run/run_calibration.py index 657f5f24..181663dd 100755 --- a/pygem/bin/run/run_calibration.py +++ b/pygem/bin/run/run_calibration.py @@ -7,6 +7,7 @@ Run model calibration """ + # Built-in libraries import argparse import inspect @@ -40,17 +41,18 @@ from pygem import class_climate, mcmc from pygem.massbalance import PyGEMMassBalance -#from pygem.glacierdynamics import MassRedistributionCurveModel +# from pygem.glacierdynamics import MassRedistributionCurveModel from pygem.oggm_compat import ( single_flowline_glacier_directory, single_flowline_glacier_directory_with_calving, ) -#from oggm.core import climate -#from oggm.core.flowline import FluxBasedModel -#from oggm.core.inversion import calving_flux_from_depth +# from oggm.core import climate +# from oggm.core.flowline import FluxBasedModel +# from oggm.core.inversion import calving_flux_from_depth + -#%% FUNCTIONS +# %% FUNCTIONS def getparser(): """ Use argparse to add arguments from the command line @@ -78,38 +80,104 @@ def getparser(): """ parser = argparse.ArgumentParser(description="Run PyGEM calibration") # 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('-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('-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('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use (default is 1, ie. no parallelization)') - 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('-nchains',action='store',type=int,default=pygem_prms['calib']['MCMC_params']['n_chains'], - help='number of chains in MCMC calibration') - parser.add_argument('-chain_length',action='store',type=int,default=pygem_prms['calib']['MCMC_params']['mcmc_sample_no'], - help='number of samples in a chain for MCMC calibration') - parser.add_argument('-burn_pct',action='store',type=int,default=pygem_prms['calib']['MCMC_params']['mcmc_burn_pct'], - help='burn-in percentage for MCMC calibration') + 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( + "-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( + "-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( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use (default is 1, ie. no parallelization)", + ) + 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( + "-nchains", + action="store", + type=int, + default=pygem_prms["calib"]["MCMC_params"]["n_chains"], + help="number of chains in MCMC calibration", + ) + parser.add_argument( + "-chain_length", + action="store", + type=int, + default=pygem_prms["calib"]["MCMC_params"]["mcmc_sample_no"], + help="number of samples in a chain for MCMC calibration", + ) + parser.add_argument( + "-burn_pct", + action="store", + type=int, + default=pygem_prms["calib"]["MCMC_params"]["mcmc_burn_pct"], + help="burn-in percentage for MCMC calibration", + ) # flags - parser.add_argument('-option_ordered', action='store_true', - help='Flag to keep glacier lists ordered (default is false)') - parser.add_argument('-p', '--progress_bar', action='store_true', - help='Flag to show progress bar') - 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 false)", + ) + parser.add_argument( + "-p", "--progress_bar", action="store_true", help="Flag to show progress bar" + ) + parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") return parser @@ -120,8 +188,17 @@ def safe_float(value): return None -def mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=None, t1=None, t2=None, - option_areaconstant=1, return_tbias_mustmelt=False, return_tbias_mustmelt_wmb=False): +def mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=None, + t1=None, + t2=None, + option_areaconstant=1, + return_tbias_mustmelt=False, + return_tbias_mustmelt_wmb=False, +): """ Run the mass balance and calculate the mass balance [mwea] @@ -135,36 +212,47 @@ def mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=None, t1=None, t2=None, mass balance [m w.e. a-1] """ # RUN MASS BALANCE MODEL - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, fls=fls, option_areaconstant=True) - years = np.arange(0, int(gdir.dates_table.shape[0]/12)) + mbmod = PyGEMMassBalance( + gdir, modelprms, glacier_rgi_table, fls=fls, option_areaconstant=True + ) + years = np.arange(0, int(gdir.dates_table.shape[0] / 12)) for year in years: mbmod.get_annual_mb(fls[0].surface_h, fls=fls, fl_id=0, year=year) # Option for must melt condition if return_tbias_mustmelt: # Number of years and bins with negative climatic mass balance - nbinyears_negmbclim = len(np.where(mbmod.glac_bin_massbalclim_annual < 0)[0]) + nbinyears_negmbclim = len(np.where(mbmod.glac_bin_massbalclim_annual < 0)[0]) return nbinyears_negmbclim elif return_tbias_mustmelt_wmb: - nbinyears_negmbclim = len(np.where(mbmod.glac_bin_massbalclim_annual < 0)[0]) - t1_idx = gdir.mbdata['t1_idx'] - t2_idx = gdir.mbdata['t2_idx'] - nyears = gdir.mbdata['nyears'] - mb_mwea = mbmod.glac_wide_massbaltotal[t1_idx:t2_idx+1].sum() / mbmod.glac_wide_area_annual[0] / nyears + nbinyears_negmbclim = len(np.where(mbmod.glac_bin_massbalclim_annual < 0)[0]) + t1_idx = gdir.mbdata["t1_idx"] + t2_idx = gdir.mbdata["t2_idx"] + nyears = gdir.mbdata["nyears"] + mb_mwea = ( + mbmod.glac_wide_massbaltotal[t1_idx : t2_idx + 1].sum() + / mbmod.glac_wide_area_annual[0] + / nyears + ) return nbinyears_negmbclim, mb_mwea # Otherwise return specific mass balance else: # Specific mass balance [mwea] - t1_idx = gdir.mbdata['t1_idx'] - t2_idx = gdir.mbdata['t2_idx'] - nyears = gdir.mbdata['nyears'] - mb_mwea = mbmod.glac_wide_massbaltotal[t1_idx:t2_idx+1].sum() / mbmod.glac_wide_area_annual[0] / nyears + t1_idx = gdir.mbdata["t1_idx"] + t2_idx = gdir.mbdata["t2_idx"] + nyears = gdir.mbdata["nyears"] + mb_mwea = ( + mbmod.glac_wide_massbaltotal[t1_idx : t2_idx + 1].sum() + / mbmod.glac_wide_area_annual[0] + / nyears + ) return mb_mwea # class for Gaussian Process model for mass balance emulator class ExactGPModel(gpytorch.models.ExactGP): - """ Use the simplest form of GP model, exact inference """ + """Use the simplest form of GP model, exact inference""" + def __init__(self, train_x, train_y, likelihood): super(ExactGPModel, self).__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() @@ -189,7 +277,9 @@ def __init__(self, mod, likelihood, X_mean, X_std, y_mean, y_std): # evaluate the emulator for a given set of model paramaters (note, Xtest should be ordered as so: [tbias, kp, ddfsnow]) def eval(self, Xtest): # normalize each parameter - Xtest[:] = [(x - mu) / sigma for x, mu, sigma in zip(Xtest, self.X_mean, self.X_std)] + Xtest[:] = [ + (x - mu) / sigma for x, mu, sigma in zip(Xtest, self.X_mean, self.X_std) + ] # convert to torch tensor Xtest_normed = torch.tensor(np.array([Xtest])).to(torch.float) # pass to mbEmulator.mod() to evaluate normed values @@ -205,16 +295,18 @@ def load(cls, em_mod_path=None): torch.set_num_threads(1) state_dict = torch.load(em_mod_path, weights_only=False) - emulator_extra_fp = em_mod_path.replace('.pth', '_extra.json') - with open(emulator_extra_fp, 'r') as f: + emulator_extra_fp = em_mod_path.replace(".pth", "_extra.json") + with open(emulator_extra_fp, "r") as f: emulator_extra_dict = json.load(f) # convert lists to torch tensors - X_train = torch.stack([torch.tensor(lst) for lst in emulator_extra_dict['X_train']], dim=1) - X_mean = torch.tensor(emulator_extra_dict['X_mean']) - X_std = torch.tensor(emulator_extra_dict['X_std']) - y_train = torch.tensor(emulator_extra_dict['y_train']) - y_mean = torch.tensor(emulator_extra_dict['y_mean']) - y_std = torch.tensor(emulator_extra_dict['y_std']) + X_train = torch.stack( + [torch.tensor(lst) for lst in emulator_extra_dict["X_train"]], dim=1 + ) + X_mean = torch.tensor(emulator_extra_dict["X_mean"]) + X_std = torch.tensor(emulator_extra_dict["X_std"]) + y_train = torch.tensor(emulator_extra_dict["y_train"]) + y_mean = torch.tensor(emulator_extra_dict["y_mean"]) + y_std = torch.tensor(emulator_extra_dict["y_std"]) # initialize likelihood and model likelihood = gpytorch.likelihoods.GaussianLikelihood() @@ -227,9 +319,14 @@ def load(cls, em_mod_path=None): return cls(model, likelihood, X_mean, X_std, y_mean, y_std) -def create_emulator(glacier_str, sims_df, y_cn, - X_cns=['tbias','kp','ddfsnow'], - em_fp=pygem_prms['root'] + '/Output/emulator/', debug=False): +def create_emulator( + glacier_str, + sims_df, + y_cn, + X_cns=["tbias", "kp", "ddfsnow"], + em_fp=pygem_prms["root"] + "/Output/emulator/", + debug=False, +): """ create emulator for calibrating PyGEM model parameters @@ -253,22 +350,22 @@ def create_emulator(glacier_str, sims_df, y_cn, # This is required for the supercomputer such that resources aren't stolen from other cpus torch.set_num_threads(1) - assert y_cn in sims_df.columns, 'emulator error: y_cn not in sims_df' + assert y_cn in sims_df.columns, "emulator error: y_cn not in sims_df" ################### ### get Xy data ### ################### - X = sims_df.loc[:,X_cns] - y = sims_df.loc[:,y_cn] + X = sims_df.loc[:, X_cns] + y = sims_df.loc[:, y_cn] if debug: - print(f'Calibration x-parameters: {", ".join(X_cns)}') - print(f'Calibration y-parametes: {y_cn}') - print(f'X:\n{X}') - print(f'X-shape:\n{X.shape}\n') - print(f'y:\n{y}') - print(f'y-shape:\n{y.shape}') + print(f"Calibration x-parameters: {', '.join(X_cns)}") + print(f"Calibration y-parametes: {y_cn}") + print(f"X:\n{X}") + print(f"X-shape:\n{X.shape}\n") + print(f"y:\n{y}") + print(f"y-shape:\n{y.shape}") ################### # pull values (note order matters here. whenever emulator is evaluated, order should be same as order in X) @@ -285,10 +382,12 @@ def create_emulator(glacier_str, sims_df, y_cn, y_norm = (y - y_mean) / y_std # Split into training and test data and cast to torch tensors - X_train,X_test,y_train,y_test = [torch.tensor(x).to(torch.float) - for x in sklearn.model_selection.train_test_split(X_norm,y_norm)] + X_train, X_test, y_train, y_test = [ + torch.tensor(x).to(torch.float) + for x in sklearn.model_selection.train_test_split(X_norm, y_norm) + ] # Add a small amount of noise - y_train += torch.randn(*y_train.shape)*0.01 + y_train += torch.randn(*y_train.shape) * 0.01 # initialize likelihood and model likelihood = gpytorch.likelihoods.GaussianLikelihood() @@ -298,7 +397,7 @@ def create_emulator(glacier_str, sims_df, y_cn, # Get into evaluation (predictive posterior) mode model.eval() likelihood.eval() - with torch.no_grad():#, gpytorch.settings.fast_pred_var(): + with torch.no_grad(): # , gpytorch.settings.fast_pred_var(): y_pred = likelihood(model(X_test)) idx = np.argsort(y_test.numpy()) @@ -307,8 +406,10 @@ def create_emulator(glacier_str, sims_df, y_cn, if debug: f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], 'k*') - ax.fill_between(y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], alpha=0.5) + ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], "k*") + ax.fill_between( + y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], alpha=0.5 + ) plt.show() # ----- Find optimal model hyperparameters ----- @@ -316,7 +417,9 @@ def create_emulator(glacier_str, sims_df, y_cn, likelihood.train() # Use the adam optimizer - optimizer = torch.optim.Adam(model.parameters(), lr=0.03) # Includes GaussianLikelihood parameters + optimizer = torch.optim.Adam( + model.parameters(), lr=0.03 + ) # Includes GaussianLikelihood parameters # "Loss" for GPs - the marginal log likelihood mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model) @@ -329,16 +432,16 @@ def create_emulator(glacier_str, sims_df, y_cn, # Calc loss and backprop gradients loss = -mll(output, y_train) loss.backward() -# if debug and i%100==0: -# print(i, loss.item(), model.covar_module.base_kernel.lengthscale[0], -# model.likelihood.noise.item()) + # if debug and i%100==0: + # print(i, loss.item(), model.covar_module.base_kernel.lengthscale[0], + # model.likelihood.noise.item()) optimizer.step() # Plot posterior distributions (with test data on x-axis) # Get into evaluation (predictive posterior) mode model.eval() likelihood.eval() - with torch.no_grad():#, gpytorch.settings.fast_pred_var(): + with torch.no_grad(): # , gpytorch.settings.fast_pred_var(): y_pred = likelihood(model(X_test)) idx = np.argsort(y_test.numpy()) @@ -348,32 +451,37 @@ def create_emulator(glacier_str, sims_df, y_cn, if debug: f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], 'k*') - ax.fill_between(y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], - alpha=0.5) + ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], "k*") + ax.fill_between( + y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], alpha=0.5 + ) plt.show() if debug: # Compare user-defined parameter sets within the emulator - tbias_set = (np.arange(-7,4,0.5)).reshape(-1,1) + tbias_set = (np.arange(-7, 4, 0.5)).reshape(-1, 1) kp_set = np.zeros(tbias_set.shape) + 1 ddf_set = np.zeros(tbias_set.shape) + 0.0041 modelprms_set = np.hstack((tbias_set, kp_set, ddf_set)) modelprms_set_norm = (modelprms_set - X_mean) / X_std - y_set_norm = model(torch.tensor(modelprms_set_norm).to(torch.float)).mean.detach().numpy() + y_set_norm = ( + model(torch.tensor(modelprms_set_norm).to(torch.float)) + .mean.detach() + .numpy() + ) y_set = y_set_norm * y_std + y_mean f, ax = plt.subplots(1, 1, figsize=(4, 4)) - kp_1_idx = np.where(sims_df['kp'] == 1)[0] - ax.plot(sims_df.loc[kp_1_idx,'tbias'], sims_df.loc[kp_1_idx,y_cn]) - ax.plot(tbias_set,y_set,'.') - ax.set_xlabel('tbias (degC)') - if y_cn == 'mb_mwea': - ax.set_ylabel('PyGEM MB (mwea)') - elif y_cn == 'nbinyrs_negmbclim': - ax.set_ylabel('nbinyrs_negmbclim (-)') + kp_1_idx = np.where(sims_df["kp"] == 1)[0] + ax.plot(sims_df.loc[kp_1_idx, "tbias"], sims_df.loc[kp_1_idx, y_cn]) + ax.plot(tbias_set, y_set, ".") + ax.set_xlabel("tbias (degC)") + if y_cn == "mb_mwea": + ax.set_ylabel("PyGEM MB (mwea)") + elif y_cn == "nbinyrs_negmbclim": + ax.set_ylabel("nbinyrs_negmbclim (-)") plt.show() # Compare the modeled and emulated mass balances @@ -381,40 +489,42 @@ def create_emulator(glacier_str, sims_df, y_cn, y_em = y_em_norm * y_std + y_mean f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y,y_em,'.') - ax.plot([y.min(),y.max()], [y.min(), y.max()]) - if y_cn == 'mb_mwea': - ax.set_xlabel('emulator MB (mwea)') - ax.set_ylabel('PyGEM MB (mwea)') - ax.set_xlim(-1,1) - ax.set_ylim(-1,1) - elif y_cn == 'nbinyrs_negmbclim': - ax.set_xlabel('emulator nbinyrs_negmbclim (-)') - ax.set_ylabel('PyGEM nbinyrs_negmbclim (-)') + ax.plot(y, y_em, ".") + ax.plot([y.min(), y.max()], [y.min(), y.max()]) + if y_cn == "mb_mwea": + ax.set_xlabel("emulator MB (mwea)") + ax.set_ylabel("PyGEM MB (mwea)") + ax.set_xlim(-1, 1) + ax.set_ylim(-1, 1) + elif y_cn == "nbinyrs_negmbclim": + ax.set_xlabel("emulator nbinyrs_negmbclim (-)") + ax.set_ylabel("PyGEM nbinyrs_negmbclim (-)") plt.show() # ----- EXPORT EMULATOR ----- # Save emulator (model state, x_train, y_train, etc.) - em_mod_fn = glacier_str + '-emulator-' + y_cn + '.pth' - em_mod_fp = em_fp + 'models/' + glacier_str.split('.')[0].zfill(2) + '/' + em_mod_fn = glacier_str + "-emulator-" + y_cn + ".pth" + em_mod_fp = em_fp + "models/" + glacier_str.split(".")[0].zfill(2) + "/" if not os.path.exists(em_mod_fp): os.makedirs(em_mod_fp, exist_ok=True) torch.save(model.state_dict(), em_mod_fp + em_mod_fn) # Extra required datasets (convert to lists to avoid any serialization issues with torch tensors) - em_extra_dict = {'X_train': [X.tolist() for X in X_train.T], - 'X_mean': [X.tolist() for X in X_mean.T], - 'X_std': [X.tolist() for X in X_std.T], - 'y_train': y_train.tolist(), - 'y_mean': float(y_mean), - 'y_std': float(y_std)} - em_extra_fn = em_mod_fn.replace('.pth','_extra.json') - with open(em_mod_fp + em_extra_fn, 'w') as f: + em_extra_dict = { + "X_train": [X.tolist() for X in X_train.T], + "X_mean": [X.tolist() for X in X_mean.T], + "X_std": [X.tolist() for X in X_std.T], + "y_train": y_train.tolist(), + "y_mean": float(y_mean), + "y_std": float(y_std), + } + em_extra_fn = em_mod_fn.replace(".pth", "_extra.json") + with open(em_mod_fp + em_extra_fn, "w") as f: json.dump(em_extra_dict, f) return massbalEmulator(model, likelihood, X_mean, X_std, y_mean, y_std) -#%% +# %% def run(list_packed_vars): """ Model simulation @@ -442,40 +552,60 @@ def run(list_packed_vars): # ===== 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 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, dates_table) - if 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_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 gcm_name in ["ERA5"]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table + ) else: gcm_tempstd = np.zeros(gcm_temp.shape) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) # Lapse rate [degC m-1] - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) # ===== LOOP THROUGH GLACIERS TO RUN CALIBRATION ===== for glac in range(main_glac_rgi.shape[0]): - if debug or glac == 0 or glac == main_glac_rgi.shape[0]: - print(gcm_name,':', main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId']) + print( + gcm_name, + ":", + main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"], + ) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: - if glacier_rgi_table['TermType'] not 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"] + ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False else: @@ -483,227 +613,452 @@ def run(list_packed_vars): gdir = single_flowline_glacier_directory_with_calving(glacier_str) gdir.is_tidewater = True - fls = gdir.read_pickle('inversion_flowlines') + fls = gdir.read_pickle("inversion_flowlines") glacier_area = fls[0].widths_m * fls[0].dx_meter # 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 # ----- Calibration data ----- try: - mbdata_fn = gdir.get_filepath('mb_calib_pygem') + mbdata_fn = gdir.get_filepath("mb_calib_pygem") - with open(mbdata_fn, 'r') as f: + with open(mbdata_fn, "r") as f: gdir.mbdata = json.load(f) # Tidewater glaciers - use climatic mass balance since calving_k already calibrated separately if gdir.is_tidewater: - assert 'mb_clim_mwea' in gdir.mbdata.keys(), 'include_frontalablation is set as true, but fontal ablation has yet to be calibrated.' - mb_obs_mwea = gdir.mbdata['mb_clim_mwea'] - mb_obs_mwea_err = gdir.mbdata['mb_clim_mwea_err'] + assert "mb_clim_mwea" in gdir.mbdata.keys(), ( + "include_frontalablation is set as true, but fontal ablation has yet to be calibrated." + ) + mb_obs_mwea = gdir.mbdata["mb_clim_mwea"] + mb_obs_mwea_err = gdir.mbdata["mb_clim_mwea_err"] # non-tidewater - use geodetic mass balance else: # Load data - mb_obs_mwea = gdir.mbdata['mb_mwea'] - mb_obs_mwea_err = gdir.mbdata['mb_mwea_err'] + mb_obs_mwea = gdir.mbdata["mb_mwea"] + mb_obs_mwea_err = gdir.mbdata["mb_mwea_err"] # Add time indices consistent with dates_table for mb calculations - gdir.mbdata['t1_datetime'] = pd.to_datetime(gdir.mbdata['t1_str']) - gdir.mbdata['t2_datetime'] = pd.to_datetime(gdir.mbdata['t2_str']) - timedelta(days=1) - t1_year = gdir.mbdata['t1_datetime'].year - t1_month = gdir.mbdata['t1_datetime'].month - t2_year = gdir.mbdata['t2_datetime'].year - t2_month = gdir.mbdata['t2_datetime'].month - t1_idx = dates_table[(t1_year == dates_table['year']) & (t1_month == dates_table['month'])].index.values[0] - t2_idx = dates_table[(t2_year == dates_table['year']) & (t2_month == dates_table['month'])].index.values[0] + gdir.mbdata["t1_datetime"] = pd.to_datetime(gdir.mbdata["t1_str"]) + gdir.mbdata["t2_datetime"] = pd.to_datetime( + gdir.mbdata["t2_str"] + ) - timedelta(days=1) + t1_year = gdir.mbdata["t1_datetime"].year + t1_month = gdir.mbdata["t1_datetime"].month + t2_year = gdir.mbdata["t2_datetime"].year + t2_month = gdir.mbdata["t2_datetime"].month + t1_idx = dates_table[ + (t1_year == dates_table["year"]) + & (t1_month == dates_table["month"]) + ].index.values[0] + t2_idx = dates_table[ + (t2_year == dates_table["year"]) + & (t2_month == dates_table["month"]) + ].index.values[0] # Record indices - gdir.mbdata['t1_idx'] = t1_idx - gdir.mbdata['t2_idx'] = t2_idx + gdir.mbdata["t1_idx"] = t1_idx + gdir.mbdata["t2_idx"] = t2_idx if debug: - print(' mb_data (mwea): ' + str(np.round(mb_obs_mwea,2)) + ' +/- ' + str(np.round(mb_obs_mwea_err,2))) + print( + " mb_data (mwea): " + + str(np.round(mb_obs_mwea, 2)) + + " +/- " + + str(np.round(mb_obs_mwea_err, 2)) + ) except Exception as err: gdir.mbdata = None # LOG FAILURE - fail_fp = pygem_prms['root'] + '/Output/cal_fail/' + glacier_str.split('.')[0].zfill(2) + '/' + fail_fp = ( + pygem_prms["root"] + + "/Output/cal_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(f'Error with mass balance data: {err}') + text_file.write(f"Error with mass balance data: {err}") - print('\n' + glacier_str + ' mass balance data missing. Check dataset and column names.\n') + print( + "\n" + + glacier_str + + " mass balance data missing. Check dataset and column names.\n" + ) except: fls = None if debug: - assert os.path.exists(mbdata_fn), 'Mass balance data missing. Check dataset and column names' - + assert os.path.exists(mbdata_fn), ( + "Mass balance data missing. Check dataset and column names" + ) # ----- CALIBRATION OPTIONS ------ if (fls is not None) and (gdir.mbdata is not None) and (glacier_area.sum() > 0): - - modelprms = {'kp': pygem_prms['sim']['params']['kp'], - 'tbias': pygem_prms['sim']['params']['tbias'], - 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], - 'ddfice': pygem_prms['sim']['params']['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'], - 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], - 'precgrad': pygem_prms['sim']['params']['precgrad']} - - #%% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== + modelprms = { + "kp": pygem_prms["sim"]["params"]["kp"], + "tbias": pygem_prms["sim"]["params"]["tbias"], + "ddfsnow": pygem_prms["sim"]["params"]["ddfsnow"], + "ddfice": pygem_prms["sim"]["params"]["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"], + "tsnow_threshold": pygem_prms["sim"]["params"]["tsnow_threshold"], + "precgrad": pygem_prms["sim"]["params"]["precgrad"], + } + + # %% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== # - precipitation factor, temperature bias, degree-day factor of snow - if args.option_calibration == 'emulator': - tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] - tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] - kp_init = pygem_prms['calib']['emulator_params']['kp_init'] - ddfsnow_init = pygem_prms['calib']['emulator_params']['ddfsnow_init'] + if args.option_calibration == "emulator": + tbias_step = pygem_prms["calib"]["emulator_params"]["tbias_step"] + tbias_init = pygem_prms["calib"]["emulator_params"]["tbias_init"] + kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] + ddfsnow_init = pygem_prms["calib"]["emulator_params"]["ddfsnow_init"] # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) - nsims = pygem_prms['calib']['emulator_params']['emulator_sims'] + nsims = pygem_prms["calib"]["emulator_params"]["emulator_sims"] # Load sims df - sims_fp = pygem_prms['root'] + '/Output/emulator/sims/' + glacier_str.split('.')[0].zfill(2) + '/' - sims_fn = glacier_str + '-' + str(nsims) + '_emulator_sims.csv' - - if not os.path.exists(sims_fp + sims_fn) or pygem_prms['calib']['emulator_params']['overwrite_em_sims']: + sims_fp = ( + pygem_prms["root"] + + "/Output/emulator/sims/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + sims_fn = glacier_str + "-" + str(nsims) + "_emulator_sims.csv" + + if ( + not os.path.exists(sims_fp + sims_fn) + or pygem_prms["calib"]["emulator_params"]["overwrite_em_sims"] + ): # ----- Temperature bias bounds (ensure reasonable values) ----- # Tbias lower bound based on some bins having negative climatic mass balance - tbias_maxacc = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) - modelprms['tbias'] = tbias_maxacc - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + tbias_maxacc = ( + -1 + * ( + gdir.historical_climate["temp"] + + gdir.historical_climate["lr"] + * (fls[0].surface_h.min() - gdir.historical_climate["elev"]) + ).max() + ) + modelprms["tbias"] = tbias_maxacc + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) while nbinyears_negmbclim < 10 or mb_mwea > mb_obs_mwea: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3), - 'nbinyears_negmbclim:', nbinyears_negmbclim) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + "nbinyears_negmbclim:", + nbinyears_negmbclim, + ) tbias_stepsmall = 0.05 while nbinyears_negmbclim > 10: - modelprms['tbias'] = modelprms['tbias'] - tbias_stepsmall - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + modelprms["tbias"] = modelprms["tbias"] - tbias_stepsmall + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3), - 'nbinyears_negmbclim:', nbinyears_negmbclim) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + "nbinyears_negmbclim:", + nbinyears_negmbclim, + ) # Tbias lower bound - tbias_bndlow = modelprms['tbias'] + tbias_stepsmall - modelprms['tbias'] = tbias_bndlow - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_all = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + tbias_bndlow = modelprms["tbias"] + tbias_stepsmall + modelprms["tbias"] = tbias_bndlow + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_all = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) # Tbias lower bound & high precipitation factor - modelprms['kp'] = stats.gamma.ppf(0.99, pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1/pygem_prms['calib']['emulator_params']['kp_gamma_beta']) - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["kp"] = stats.gamma.ppf( + 0.99, + pygem_prms["calib"]["emulator_params"]["kp_gamma_alpha"], + scale=1 + / pygem_prms["calib"]["emulator_params"]["kp_gamma_beta"], + ) + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) # Tbias 'mid-point' - modelprms['kp'] = pygem_prms['calib']['emulator_params']['kp_init'] + modelprms["kp"] = pygem_prms["calib"]["emulator_params"]["kp_init"] ncount_tbias = 0 tbias_bndhigh = 10 tbias_middle = tbias_bndlow + tbias_step - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 50: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 50: + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - tbias_middle = modelprms['tbias'] - tbias_step / 2 + tbias_middle = modelprms["tbias"] - tbias_step / 2 ncount_tbias += 1 if debug: - print(ncount_tbias, - 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) + print( + ncount_tbias, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) # Tbias upper bound (run for equal amount of steps above the midpoint) while ncount_tbias > 0: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - tbias_bndhigh = modelprms['tbias'] + tbias_bndhigh = modelprms["tbias"] ncount_tbias -= 1 if debug: - print(ncount_tbias, - 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) + print( + ncount_tbias, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) # ------ RANDOM RUNS ------- # Temperature bias - if pygem_prms['calib']['emulator_params']['tbias_disttype'] == 'uniform': - tbias_random = np.random.uniform(low=tbias_bndlow, high=tbias_bndhigh, - size=nsims) - elif pygem_prms['calib']['emulator_params']['tbias_disttype'] == 'truncnormal': - tbias_zlow = (tbias_bndlow - tbias_middle) / pygem_prms['calib']['emulator_params']['tbias_sigma'] - tbias_zhigh = (tbias_bndhigh - tbias_middle) / pygem_prms['calib']['emulator_params']['tbias_sigma'] - tbias_random = stats.truncnorm.rvs(a=tbias_zlow, b=tbias_zhigh, loc=tbias_middle, - scale=pygem_prms['calib']['emulator_params']['tbias_sigma'], size=nsims) + if ( + pygem_prms["calib"]["emulator_params"]["tbias_disttype"] + == "uniform" + ): + tbias_random = np.random.uniform( + low=tbias_bndlow, high=tbias_bndhigh, size=nsims + ) + elif ( + pygem_prms["calib"]["emulator_params"]["tbias_disttype"] + == "truncnormal" + ): + tbias_zlow = (tbias_bndlow - tbias_middle) / pygem_prms[ + "calib" + ]["emulator_params"]["tbias_sigma"] + tbias_zhigh = (tbias_bndhigh - tbias_middle) / pygem_prms[ + "calib" + ]["emulator_params"]["tbias_sigma"] + tbias_random = stats.truncnorm.rvs( + a=tbias_zlow, + b=tbias_zhigh, + loc=tbias_middle, + scale=pygem_prms["calib"]["emulator_params"]["tbias_sigma"], + size=nsims, + ) if debug: - print('\ntbias random:', tbias_random.mean(), tbias_random.std()) + print( + "\ntbias random:", tbias_random.mean(), tbias_random.std() + ) # Precipitation factor - kp_random = stats.gamma.rvs(pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1/pygem_prms['calib']['emulator_params']['kp_gamma_beta'], - size=nsims) + kp_random = stats.gamma.rvs( + pygem_prms["calib"]["emulator_params"]["kp_gamma_alpha"], + scale=1 + / pygem_prms["calib"]["emulator_params"]["kp_gamma_beta"], + size=nsims, + ) if debug: - print('kp random:', kp_random.mean(), kp_random.std()) + print("kp random:", kp_random.mean(), kp_random.std()) # Degree-day factor of snow - ddfsnow_zlow = (pygem_prms['calib']['emulator_params']['ddfsnow_bndlow'] - pygem_prms['calib']['emulator_params']['ddfsnow_mu']) / pygem_prms['calib']['emulator_params']['ddfsnow_sigma'] - ddfsnow_zhigh = (pygem_prms['calib']['emulator_params']['ddfsnow_bndhigh'] - pygem_prms['calib']['emulator_params']['ddfsnow_mu']) / pygem_prms['calib']['emulator_params']['ddfsnow_sigma'] - ddfsnow_random = stats.truncnorm.rvs(a=ddfsnow_zlow, b=ddfsnow_zhigh, loc=pygem_prms['calib']['emulator_params']['ddfsnow_mu'], - scale=pygem_prms['calib']['emulator_params']['ddfsnow_sigma'], size=nsims) + 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') + print( + "ddfsnow random:", + ddfsnow_random.mean(), + ddfsnow_random.std(), + "\n", + ) # Run through random values for nsim in range(nsims): - modelprms['tbias'] = tbias_random[nsim] - modelprms['kp'] = kp_random[nsim] - modelprms['ddfsnow'] = ddfsnow_random[nsim] - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - - - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["tbias"] = tbias_random[nsim] + modelprms["kp"] = kp_random[nsim] + modelprms["ddfsnow"] = ddfsnow_random[nsim] + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - if debug and nsim%500 == 0: - print(nsim, 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) + if debug and nsim % 500 == 0: + print( + nsim, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) # ----- Export results ----- - sims_df = pd.DataFrame(output_all, columns=['tbias', 'kp', 'ddfsnow', 'mb_mwea', - 'nbinyrs_negmbclim']) + sims_df = pd.DataFrame( + output_all, + columns=[ + "tbias", + "kp", + "ddfsnow", + "mb_mwea", + "nbinyrs_negmbclim", + ], + ) if os.path.exists(sims_fp) == False: os.makedirs(sims_fp, exist_ok=True) sims_df.to_csv(sims_fp + sims_fn, index=False) @@ -713,146 +1068,270 @@ def run(list_packed_vars): sims_df = pd.read_csv(sims_fp + sims_fn) # ----- EMULATOR: Mass balance ----- - em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' - em_mod_fp = pygem_prms['root'] + '/Output/emulator/models/' + glacier_str.split('.')[0].zfill(2) + '/' - if not os.path.exists(em_mod_fp + em_mod_fn) or pygem_prms['calib']['emulator_params']['overwrite_em_sims']: - mbEmulator = create_emulator(glacier_str, sims_df, y_cn='mb_mwea', debug=debug) + em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fp = ( + pygem_prms["root"] + + "/Output/emulator/models/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + if ( + not os.path.exists(em_mod_fp + em_mod_fn) + or pygem_prms["calib"]["emulator_params"]["overwrite_em_sims"] + ): + mbEmulator = create_emulator( + glacier_str, sims_df, y_cn="mb_mwea", debug=debug + ) else: - mbEmulator = massbalEmulator.load(em_mod_path = em_mod_fp + em_mod_fn) + mbEmulator = massbalEmulator.load(em_mod_path=em_mod_fp + em_mod_fn) # ===== HH2015 MODIFIED CALIBRATION USING EMULATOR ===== - if pygem_prms['calib']['emulator_params']['opt_hh2015_mod']: - tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] - tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] - kp_init = pygem_prms['calib']['emulator_params']['kp_init'] - kp_bndlow = pygem_prms['calib']['emulator_params']['kp_bndlow'] - kp_bndhigh = pygem_prms['calib']['emulator_params']['kp_bndhigh'] - ddfsnow_init = pygem_prms['calib']['emulator_params']['ddfsnow_init'] + if pygem_prms["calib"]["emulator_params"]["opt_hh2015_mod"]: + tbias_init = pygem_prms["calib"]["emulator_params"]["tbias_init"] + tbias_step = pygem_prms["calib"]["emulator_params"]["tbias_step"] + kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] + kp_bndlow = pygem_prms["calib"]["emulator_params"]["kp_bndlow"] + kp_bndhigh = pygem_prms["calib"]["emulator_params"]["kp_bndhigh"] + ddfsnow_init = pygem_prms["calib"]["emulator_params"][ + "ddfsnow_init" + ] # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- - def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid, - debug=False): - """ Update bounds for various parameters for the single_param_optimizer """ + def update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=False, + ): + """Update bounds for various parameters for the single_param_optimizer""" # If mass balance less than observation, reduce tbias - if prm2opt == 'kp': + if prm2opt == "kp": if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high + prm_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': + 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 - elif prm2opt == 'tbias': + 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': + if prm2opt == "kp": prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms['tbias'] = modelprms_subset['tbias'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] - elif prm2opt == 'ddfsnow': + modelprms["tbias"] = modelprms_subset["tbias"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + elif prm2opt == "ddfsnow": prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['tbias'] = modelprms_subset['tbias'] - elif prm2opt == 'tbias': + modelprms["kp"] = modelprms_subset["kp"] + modelprms["tbias"] = modelprms_subset["tbias"] + elif prm2opt == "tbias": prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] + modelprms["kp"] = modelprms_subset["kp"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_low = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_low = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) # Upper bound modelprms[prm2opt] = prm_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_high = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_high = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) # Middle bound prm_mid = (prm_bndlow + prm_bndhigh) / 2 modelprms[prm2opt] = prm_mid - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow,2), 'mb_mwea_low:', np.round(mb_mwea_low,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh,2), 'mb_mwea_high:', np.round(mb_mwea_high,2)) - print(prm2opt + '_mid:', np.round(prm_mid,2), 'mb_mwea_mid:', np.round(mb_mwea_mid,3)) + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow, 2), + "mb_mwea_low:", + np.round(mb_mwea_low, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh, 2), + "mb_mwea_high:", + np.round(mb_mwea_high, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid, 3), + ) # Optimize the model parameter if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: modelprms[prm2opt] = prm_bndlow mb_mwea_mid = mb_mwea_low - elif np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: + elif ( + np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold + ): modelprms[prm2opt] = prm_bndhigh mb_mwea_mid = mb_mwea_high else: ncount = 0 - while (np.absolute(mb_mwea_mid - mb_obs_mwea) > mb_mwea_threshold and - np.absolute(mb_mwea_low - mb_mwea_high) > mb_mwea_threshold): + while ( + np.absolute(mb_mwea_mid - mb_obs_mwea) + > mb_mwea_threshold + and np.absolute(mb_mwea_low - mb_mwea_high) + > mb_mwea_threshold + ): if debug: - print('\n ncount:', ncount) - (prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid) = ( - update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, - mb_mwea_low, mb_mwea_high, mb_mwea_mid, debug=debug)) + print("\n ncount:", ncount) + ( + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + ) = update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=debug, + ) ncount += 1 return modelprms, mb_mwea_mid - # ===== SET THINGS UP ====== if debug: - sims_df['mb_em'] = np.nan + sims_df["mb_em"] = np.nan for nidx in sims_df.index.values: - modelprms['tbias'] = sims_df.loc[nidx,'tbias'] - modelprms['kp'] = sims_df.loc[nidx,'kp'] - modelprms['ddfsnow'] = sims_df.loc[nidx,'ddfsnow'] - sims_df.loc[nidx,'mb_em'] = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - sims_df['mb_em_dif'] = sims_df['mb_em'] - sims_df['mb_mwea'] + modelprms["tbias"] = sims_df.loc[nidx, "tbias"] + modelprms["kp"] = sims_df.loc[nidx, "kp"] + modelprms["ddfsnow"] = sims_df.loc[nidx, "ddfsnow"] + sims_df.loc[nidx, "mb_em"] = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) + sims_df["mb_em_dif"] = sims_df["mb_em"] - sims_df["mb_mwea"] # ----- TEMPERATURE BIAS BOUNDS ----- # Selects from emulator sims dataframe - sims_df_subset = sims_df.loc[sims_df['kp']==1, :] - tbias_bndhigh = float(sims_df_subset['tbias'].max()) - tbias_bndlow = float(sims_df_subset['tbias'].min()) + sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] + tbias_bndhigh = float(sims_df_subset["tbias"].max()) + tbias_bndlow = float(sims_df_subset["tbias"].min()) # Adjust tbias_init based on bounds if tbias_init > tbias_bndhigh: @@ -862,128 +1341,228 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, # ----- Mass balance bounds ----- # Upper bound - modelprms['kp'] = kp_bndhigh - modelprms['tbias'] = tbias_bndlow - modelprms['ddfsnow'] = ddfsnow_init - mb_mwea_bndhigh = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndhigh + modelprms["tbias"] = tbias_bndlow + modelprms["ddfsnow"] = ddfsnow_init + mb_mwea_bndhigh = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) # Lower bound - modelprms['kp'] = kp_bndlow - modelprms['tbias'] = tbias_bndhigh - modelprms['ddfsnow'] = ddfsnow_init - mb_mwea_bndlow = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + modelprms["tbias"] = tbias_bndhigh + modelprms["ddfsnow"] = ddfsnow_init + mb_mwea_bndlow = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) if debug: - print('mb_mwea_max:', np.round(mb_mwea_bndhigh,2), - 'mb_mwea_min:', np.round(mb_mwea_bndlow,2)) + print( + "mb_mwea_max:", + np.round(mb_mwea_bndhigh, 2), + "mb_mwea_min:", + np.round(mb_mwea_bndlow, 2), + ) if mb_obs_mwea > mb_mwea_bndhigh: continue_param_search = False tbias_opt = tbias_bndlow - kp_opt= kp_bndhigh - troubleshoot_fp = (pygem_prms['root'] + '/Output/errors/' + - args.option_calibration + '/' + - glacier_str.split('.')[0].zfill(2) + '/') + kp_opt = kp_bndhigh + troubleshoot_fp = ( + pygem_prms["root"] + + "/Output/errors/" + + args.option_calibration + + "/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(troubleshoot_fp): os.makedirs(troubleshoot_fp, exist_ok=True) txt_fn_extrapfail = glacier_str + "-mbs_obs_outside_bnds.txt" - with open(troubleshoot_fp + txt_fn_extrapfail, "w") as text_file: - text_file.write(glacier_str + ' observed mass balance exceeds max accumulation ' + - 'with value of ' + str(np.round(mb_obs_mwea,2)) + ' mwea') + 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') + with open( + troubleshoot_fp + txt_fn_extrapfail, "w" + ) as text_file: + text_file.write( + glacier_str + + " observed mass balance below max loss " + + "with value of " + + str(np.round(mb_obs_mwea, 2)) + + " mwea" + ) else: continue_param_search = True # ===== ADJUST LOWER AND UPPER BOUNDS TO SET UP OPTIMIZATION ====== # Initialize model parameters - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init test_count = 0 test_count_acc = 0 - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) if mb_mwea > mb_obs_mwea: if debug: - print('increase tbias, decrease kp') + print("increase tbias, decrease kp") kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms['kp'] = kp_bndlow - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) while mb_mwea > mb_obs_mwea and test_count < 100: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] + tbias_step + modelprms["tbias"] = modelprms["tbias"] + tbias_step # Update bounds - tbias_bndhigh_opt = modelprms['tbias'] - tbias_bndlow_opt = modelprms['tbias'] - tbias_step + tbias_bndhigh_opt = modelprms["tbias"] + tbias_bndlow_opt = modelprms["tbias"] - tbias_step # Compute mass balance - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 else: if debug: - print('decrease tbias, increase kp') + print("decrease tbias, increase kp") kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms['kp'] = kp_bndhigh - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndhigh + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) while mb_obs_mwea > mb_mwea and test_count < 100: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] - tbias_step + modelprms["tbias"] = modelprms["tbias"] - tbias_step # If temperature bias is at lower limit, then increase precipitation factor - if modelprms['tbias'] <= tbias_bndlow: - modelprms['tbias'] = tbias_bndlow + if modelprms["tbias"] <= tbias_bndlow: + modelprms["tbias"] = tbias_bndlow if test_count_acc > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms['kp'] = kp_bndhigh + modelprms["kp"] = kp_bndhigh test_count_acc += 1 # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms['tbias'] - tbias_bndhigh_opt = modelprms['tbias'] + tbias_step + tbias_bndlow_opt = modelprms["tbias"] + tbias_bndhigh_opt = modelprms["tbias"] + tbias_step # Compute mass balance - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print('Round 1:') - print(glacier_str + ' kp: ' + str(np.round(modelprms['kp'],2)) + - ' ddfsnow: ' + str(np.round(modelprms['ddfsnow'],4)) + - ' tbias: ' + str(np.round(modelprms['tbias'],2))) + print("Round 1:") + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) # Reset parameters - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init # Lower bound - modelprms['kp'] = kp_bndlow - mb_mwea_kp_low = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + mb_mwea_kp_low = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) # Upper bound - modelprms['kp'] = kp_bndhigh - mb_mwea_kp_high = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndhigh + mb_mwea_kp_high = mbEmulator.eval( + [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + ) # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: @@ -994,145 +1573,242 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_kp_high else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_init, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_init, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } kp_bnds = (kp_bndlow, kp_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='kp', kp_bnds=kp_bnds, debug=debug) - kp_opt = modelprms_opt['kp'] + modelprms_subset, + mb_obs_mwea, + prm2opt="kp", + kp_bnds=kp_bnds, + debug=debug, + ) + kp_opt = modelprms_opt["kp"] continue_param_search = False # Update parameter values - modelprms['kp'] = kp_opt + modelprms["kp"] = kp_opt if debug: - print(' kp:', np.round(kp_opt,2), 'mb_mwea:', np.round(mb_mwea,3), - 'obs_mwea:', np.round(mb_obs_mwea,3)) + print( + " kp:", + np.round(kp_opt, 2), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + ) # ===== ROUND 2: TEMPERATURE BIAS ====== if continue_param_search: if debug: - print('Round 2:') + print("Round 2:") # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, 'ddfsnow': ddfsnow_init, - 'tbias': np.mean([tbias_bndlow_opt, tbias_bndhigh_opt])} + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_init, + "tbias": np.mean([tbias_bndlow_opt, tbias_bndhigh_opt]), + } tbias_bnds = (tbias_bndlow_opt, tbias_bndhigh_opt) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug) + modelprms_subset, + mb_obs_mwea, + prm2opt="tbias", + tbias_bnds=tbias_bnds, + debug=debug, + ) # Update parameter values - tbias_opt = modelprms_opt['tbias'] - modelprms['tbias'] = tbias_opt + tbias_opt = modelprms_opt["tbias"] + modelprms["tbias"] = tbias_opt if debug: - print(' tbias:', np.round(tbias_opt,3), 'mb_mwea:', np.round(mb_mwea,3), - 'obs_mwea:', np.round(mb_obs_mwea,3)) + print( + " tbias:", + np.round(tbias_opt, 3), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + ) else: - tbias_opt = modelprms['tbias'] - + tbias_opt = modelprms["tbias"] if debug: - print('\n\ntbias:', np.round(tbias_opt,2), 'kp:', np.round(kp_opt,2), - 'mb_mwea:', np.round(mb_mwea,3), 'obs_mwea:', np.round(mb_obs_mwea,3), '\n\n') + print( + "\n\ntbias:", + np.round(tbias_opt, 2), + "kp:", + np.round(kp_opt, 2), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + "\n\n", + ) modelparams_opt = modelprms - modelparams_opt['kp'] = kp_opt - modelparams_opt['tbias'] = tbias_opt + modelparams_opt["kp"] = kp_opt + modelparams_opt["tbias"] = tbias_opt # Export model parameters modelprms = modelparams_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [float(mb_mwea)] - modelprms['mb_obs_mwea'] = [float(mb_obs_mwea)] - modelprms['mb_obs_mwea_err'] = [float(mb_obs_mwea_err)] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [float(mb_mwea)] + modelprms["mb_obs_mwea"] = [float(mb_obs_mwea)] + modelprms["mb_obs_mwea_err"] = [float(mb_obs_mwea_err)] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) - #%% ===== MCMC CALIBRATION ====== + # %% ===== MCMC CALIBRATION ====== # use MCMC method to determine posterior probability distributions of the three parameters tbias, # ddfsnow and kp. Then create an ensemble of parameter sets evenly sampled from these # distributions, and output these sets of parameters and their corresponding mass balances to be # used in the simulations. - elif args.option_calibration == 'MCMC': - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: + elif args.option_calibration == "MCMC": + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: # load emulator - em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' - em_mod_fp = pygem_prms['root'] + '/Output/emulator/models/' + glacier_str.split('.')[0].zfill(2) + '/' - assert os.path.exists(em_mod_fp + em_mod_fn), f'emulator output does not exist : {em_mod_fp + em_mod_fn}' - mbEmulator = massbalEmulator.load(em_mod_path = em_mod_fp + em_mod_fn) - outpath_sfix = '' # output file path suffix if using emulator + em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fp = ( + pygem_prms["root"] + + "/Output/emulator/models/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + assert os.path.exists(em_mod_fp + em_mod_fn), ( + f"emulator output does not exist : {em_mod_fp + em_mod_fn}" + ) + mbEmulator = massbalEmulator.load(em_mod_path=em_mod_fp + em_mod_fn) + outpath_sfix = "" # output file path suffix if using emulator else: - outpath_sfix = '-fullsim' # output file path suffix if not using emulator + outpath_sfix = ( + "-fullsim" # output file path suffix if not using emulator + ) # --------------------------------- # ----- FUNCTION DECLARATIONS ----- # --------------------------------- # Rough estimate of minimum elevation mass balance function def calc_mb_total_minelev(modelprms): - """ Approximate estimate of the mass balance at minimum elevation """ + """Approximate estimate of the mass balance at minimum elevation""" fl = fls[0] min_elev = fl.surface_h.min() - glacier_gcm_temp = gdir.historical_climate['temp'] - glacier_gcm_prec = gdir.historical_climate['prec'] - glacier_gcm_lr = gdir.historical_climate['lr'] - glacier_gcm_elev = gdir.historical_climate['elev'] + glacier_gcm_temp = gdir.historical_climate["temp"] + glacier_gcm_prec = gdir.historical_climate["prec"] + glacier_gcm_lr = gdir.historical_climate["lr"] + glacier_gcm_elev = gdir.historical_climate["elev"] # Temperature using gcm and glacier lapse rates # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange - T_minelev = (glacier_gcm_temp + glacier_gcm_lr * - (glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']] - glacier_gcm_elev) + - glacier_gcm_lr * - (min_elev - glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]) + - modelprms['tbias']) + T_minelev = ( + glacier_gcm_temp + + glacier_gcm_lr + * ( + glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + - glacier_gcm_elev + ) + + glacier_gcm_lr + * ( + min_elev + - glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + ) + + modelprms["tbias"] + ) # Precipitation using precipitation factor and precipitation gradient # P_bin = P_gcm * prec_factor * (1 + prec_grad * (z_bin - z_ref)) - P_minelev = (glacier_gcm_prec * modelprms['kp'] * (1 + modelprms['precgrad'] * (min_elev - - glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]))) + P_minelev = ( + glacier_gcm_prec + * modelprms["kp"] + * ( + 1 + + modelprms["precgrad"] + * ( + min_elev + - glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + ) + ) + ) # Accumulation using tsnow_threshold Acc_minelev = np.zeros(P_minelev.shape) - Acc_minelev[T_minelev <= modelprms['tsnow_threshold']] = ( - P_minelev[T_minelev <= modelprms['tsnow_threshold']]) + Acc_minelev[T_minelev <= modelprms["tsnow_threshold"]] = P_minelev[ + T_minelev <= modelprms["tsnow_threshold"] + ] # Melt # energy available for melt [degC day] - melt_energy_available = T_minelev * dates_table['daysinmonth'].values + melt_energy_available = ( + T_minelev * dates_table["daysinmonth"].values + ) melt_energy_available[melt_energy_available < 0] = 0 # assume all snow melt because anything more would melt underlying ice in lowermost bin # SNOW MELT [m w.e.] - Melt_minelev = modelprms['ddfsnow'] * melt_energy_available + Melt_minelev = modelprms["ddfsnow"] * melt_energy_available # Total mass balance over entire period at minimum elvation mb_total_minelev = (Acc_minelev - Melt_minelev).sum() return mb_total_minelev def get_priors(priors): - # define distribution based on priors + # define distribution based on priors dists = [] - for param in ['tbias','kp','ddfsnow']: - if priors[param]['type'] == 'normal': - dist = stats.norm(loc=priors[param]['mu'], scale=priors[param]['sigma']) - elif priors[param]['type'] == 'uniform': - dist = stats.uniform(low=priors[param]['low'], high=priors[param]['high']) - elif priors[param]['type'] == 'gamma': - dist = stats.gamma(a=priors[param]['alpha'], scale=1/priors[param]['beta']) - elif priors[param]['type'] == 'truncnormal': - dist = stats.truncnorm(a=(priors[param]['low']-priors[param]['mu'])/priors[param]['sigma'], - b=(priors[param]['high']-priors[param]['mu'])/priors[param]['sigma'], - loc=priors[param]['mu'], scale=priors[param]['sigma']) + for param in ["tbias", "kp", "ddfsnow"]: + if priors[param]["type"] == "normal": + dist = stats.norm( + loc=priors[param]["mu"], scale=priors[param]["sigma"] + ) + elif priors[param]["type"] == "uniform": + dist = stats.uniform( + low=priors[param]["low"], high=priors[param]["high"] + ) + elif priors[param]["type"] == "gamma": + dist = stats.gamma( + a=priors[param]["alpha"], + scale=1 / priors[param]["beta"], + ) + elif priors[param]["type"] == "truncnormal": + dist = stats.truncnorm( + a=(priors[param]["low"] - priors[param]["mu"]) + / priors[param]["sigma"], + b=(priors[param]["high"] - priors[param]["mu"]) + / priors[param]["sigma"], + loc=priors[param]["mu"], + scale=priors[param]["sigma"], + ) dists.append(dist) return dists - def get_initials(dists, threshold=.01): + def get_initials(dists, threshold=0.01): # sample priors - ensure that probability of each sample > .01 initials = None while initials is None: @@ -1147,24 +1823,28 @@ def get_initials(dists, threshold=.01): return initials def mb_max(*args, **kwargs): - """ Model parameters cannot completely melt the glacier (psuedo-likelihood fxn) """ - if kwargs['massbal'] < mb_max_loss: + """Model parameters cannot completely melt the glacier (psuedo-likelihood fxn)""" + if kwargs["massbal"] < mb_max_loss: return -np.inf else: return 0 def must_melt(kp, tbias, ddfsnow, **kwargs): - """ Likelihood function for mass balance [mwea] based on model parametersr (psuedo-likelihood fxn) """ + """Likelihood function for mass balance [mwea] based on model parametersr (psuedo-likelihood fxn)""" modelprms_copy = modelprms.copy() - modelprms_copy['tbias'] = float(tbias) - modelprms_copy['kp'] = float(kp) - modelprms_copy['ddfsnow'] = float(ddfsnow) - modelprms_copy['ddfice'] = modelprms_copy['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms_copy["tbias"] = float(tbias) + modelprms_copy["kp"] = float(kp) + modelprms_copy["ddfsnow"] = float(ddfsnow) + modelprms_copy["ddfice"] = ( + modelprms_copy["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) mb_total_minelev = calc_mb_total_minelev(modelprms_copy) if mb_total_minelev < 0: return 0 else: return -np.inf + # --------------------------------- # --------------------------------- @@ -1172,47 +1852,87 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # --------------------------------- # Maximum mass loss [mwea] (based on consensus ice thickness estimate) # consensus_mass has units of kg - if os.path.exists(gdir.get_filepath('consensus_mass')): - with open(gdir.get_filepath('consensus_mass'), 'rb') as f: + if os.path.exists(gdir.get_filepath("consensus_mass")): + with open(gdir.get_filepath("consensus_mass"), "rb") as f: consensus_mass = pickle.load(f) else: # Mean global ice thickness from Farinotti et al. (2019) used for missing consensus glaciers ice_thickness_constant = 224 - consensus_mass = glacier_rgi_table.Area * 1e6 * ice_thickness_constant * pygem_prms['constants']['density_ice'] - - mb_max_loss = (-1 * consensus_mass / pygem_prms['constants']['density_water'] / gdir.rgi_area_m2 / - (gdir.dates_table.shape[0] / 12)) + consensus_mass = ( + glacier_rgi_table.Area + * 1e6 + * ice_thickness_constant + * pygem_prms["constants"]["density_ice"] + ) + + mb_max_loss = ( + -1 + * consensus_mass + / pygem_prms["constants"]["density_water"] + / gdir.rgi_area_m2 + / (gdir.dates_table.shape[0] / 12) + ) # --------------------------------- # ------------------ # ----- PRIORS ----- # ------------------ # Prior distributions (specified or informed by regions) - if pygem_prms['calib']['priors_reg_fn'] is not None: + if pygem_prms["calib"]["priors_reg_fn"] is not None: # Load priors - priors_df = pd.read_csv(pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn']) - priors_idx = np.where((priors_df.O1Region == glacier_rgi_table['O1Region']) & - (priors_df.O2Region == glacier_rgi_table['O2Region']))[0][0] + priors_df = pd.read_csv( + pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"] + ) + priors_idx = np.where( + (priors_df.O1Region == glacier_rgi_table["O1Region"]) + & (priors_df.O2Region == glacier_rgi_table["O2Region"]) + )[0][0] # Precipitation factor priors - kp_gamma_alpha = float(priors_df.loc[priors_idx, 'kp_alpha']) - kp_gamma_beta = float(priors_df.loc[priors_idx, 'kp_beta']) + kp_gamma_alpha = float(priors_df.loc[priors_idx, "kp_alpha"]) + kp_gamma_beta = float(priors_df.loc[priors_idx, "kp_beta"]) # Temperature bias priors - tbias_mu = float(priors_df.loc[priors_idx, 'tbias_mean']) - tbias_sigma = float(priors_df.loc[priors_idx, 'tbias_std']) + tbias_mu = float(priors_df.loc[priors_idx, "tbias_mean"]) + tbias_sigma = float(priors_df.loc[priors_idx, "tbias_std"]) else: # Precipitation factor priors - kp_gamma_alpha = pygem_prms['calib']['MCMC_params']['kp_gamma_alpha'] - kp_gamma_beta = pygem_prms['calib']['MCMC_params']['kp_gamma_beta'] + kp_gamma_alpha = pygem_prms["calib"]["MCMC_params"][ + "kp_gamma_alpha" + ] + kp_gamma_beta = pygem_prms["calib"]["MCMC_params"]["kp_gamma_beta"] # Temperature bias priors - tbias_mu = pygem_prms['calib']['MCMC_params']['tbias_mu'] - tbias_sigma = pygem_prms['calib']['MCMC_params']['tbias_sigma'] + tbias_mu = pygem_prms["calib"]["MCMC_params"]["tbias_mu"] + tbias_sigma = pygem_prms["calib"]["MCMC_params"]["tbias_sigma"] # put all priors together into a dictionary - priors = { - 'tbias': {'type':pygem_prms['calib']['MCMC_params']['tbias_disttype'], 'mu':float(tbias_mu) , 'sigma':float(tbias_sigma), 'low':safe_float(getattr(pygem_prms,'tbias_bndlow',None)), 'high':safe_float(getattr(pygem_prms,'tbias_bndhigh',None))}, - 'kp': {'type':pygem_prms['calib']['MCMC_params']['kp_disttype'], 'alpha':float(kp_gamma_alpha), 'beta':float(kp_gamma_beta), 'low':safe_float(getattr(pygem_prms,'kp_bndlow',None)), 'high':safe_float(getattr(pygem_prms,'kp_bndhigh',None))}, - 'ddfsnow': {'type':pygem_prms['calib']['MCMC_params']['ddfsnow_disttype'], 'mu':pygem_prms['calib']['MCMC_params']['ddfsnow_mu'], 'sigma':pygem_prms['calib']['MCMC_params']['ddfsnow_sigma'] ,'low':float(pygem_prms['calib']['MCMC_params']['ddfsnow_bndlow']), 'high':float(pygem_prms['calib']['MCMC_params']['ddfsnow_bndhigh'])}, - } + priors = { + "tbias": { + "type": pygem_prms["calib"]["MCMC_params"]["tbias_disttype"], + "mu": float(tbias_mu), + "sigma": float(tbias_sigma), + "low": safe_float(getattr(pygem_prms, "tbias_bndlow", None)), + "high": safe_float(getattr(pygem_prms, "tbias_bndhigh", None)), + }, + "kp": { + "type": pygem_prms["calib"]["MCMC_params"]["kp_disttype"], + "alpha": float(kp_gamma_alpha), + "beta": float(kp_gamma_beta), + "low": safe_float(getattr(pygem_prms, "kp_bndlow", None)), + "high": safe_float(getattr(pygem_prms, "kp_bndhigh", None)), + }, + "ddfsnow": { + "type": pygem_prms["calib"]["MCMC_params"]["ddfsnow_disttype"], + "mu": pygem_prms["calib"]["MCMC_params"]["ddfsnow_mu"], + "sigma": pygem_prms["calib"]["MCMC_params"]["ddfsnow_sigma"], + "low": float( + pygem_prms["calib"]["MCMC_params"]["ddfsnow_bndlow"] + ), + "high": float( + pygem_prms["calib"]["MCMC_params"]["ddfsnow_bndhigh"] + ), + }, + } # define distributions from priors for sampling initials prior_dists = get_priors(priors) # ------------------ @@ -1221,38 +1941,63 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # ----- TEMPERATURE BIAS BOUNDS ----- # ----------------------------------- # note, temperature bias bounds will remain constant across chains if using emulator - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: # Selects from emulator sims dataframe - sims_fp = pygem_prms['root'] + '/Output/emulator/sims/' + glacier_str.split('.')[0].zfill(2) + '/' - sims_fn = glacier_str + '-' + str(pygem_prms['calib']['MCMC_params']['emulator_sims']) + '_emulator_sims.csv' + sims_fp = ( + pygem_prms["root"] + + "/Output/emulator/sims/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + sims_fn = ( + glacier_str + + "-" + + str(pygem_prms["calib"]["MCMC_params"]["emulator_sims"]) + + "_emulator_sims.csv" + ) sims_df = pd.read_csv(sims_fp + sims_fn) - sims_df_subset = sims_df.loc[sims_df['kp']==1, :] - tbias_bndhigh = float(sims_df_subset['tbias'].max()) - tbias_bndlow = float(sims_df_subset['tbias'].min()) + sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] + tbias_bndhigh = float(sims_df_subset["tbias"].max()) + tbias_bndlow = float(sims_df_subset["tbias"].min()) # ----------------------------------- # ------------------- # --- set up MCMC --- # ------------------- # mass balance observation and standard deviation - obs = [(torch.tensor([mb_obs_mwea]),torch.tensor([mb_obs_mwea_err]))] + obs = [(torch.tensor([mb_obs_mwea]), torch.tensor([mb_obs_mwea_err]))] # if there are more observations to calibrate against, simply append a tuple of (obs, variance) to obs list # e.g. obs.append((torch.tensor(dmda_array),torch.tensor(dmda_err_array))) - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: - mbfxn = mbEmulator.eval # returns (mb_mwea) - mbargs = None # no additional arguments for mbEmulator.eval() + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: + mbfxn = mbEmulator.eval # returns (mb_mwea) + mbargs = None # no additional arguments for mbEmulator.eval() else: - mbfxn = mb_mwea_calc # returns (mb_mwea) - mbargs = (gdir, modelprms, glacier_rgi_table, fls) # arguments for mb_mwea_calc() + mbfxn = mb_mwea_calc # returns (mb_mwea) + mbargs = ( + gdir, + modelprms, + glacier_rgi_table, + fls, + ) # arguments for mb_mwea_calc() # instantiate mbPosterior given priors, and observed values # note, mbEmulator.eval expects the modelprms to be ordered like so: [tbias, kp, ddfsnow], so priors and initial guesses must also be ordered as such) - priors = {key: priors[key] for key in ['tbias','kp','ddfsnow'] if key in priors} - mb = mcmc.mbPosterior(obs, priors, mb_func=mbfxn, mb_args=mbargs, potential_fxns=[mb_max, must_melt]) + priors = { + key: priors[key] + for key in ["tbias", "kp", "ddfsnow"] + if key in priors + } + mb = mcmc.mbPosterior( + obs, + priors, + mb_func=mbfxn, + mb_args=mbargs, + potential_fxns=[mb_max, must_melt], + ) # prepare export modelprms dictionary modelprms_export = {} - for k in ['tbias','kp','ddfsnow','ddfice','mb_mwea','ar']: + for k in ["tbias", "kp", "ddfsnow", "ddfice", "mb_mwea", "ar"]: modelprms_export[k] = {} # ------------------- @@ -1261,222 +2006,386 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # -------------------- 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'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/fig/') + fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/fig/" + ) os.makedirs(fp, exist_ok=True) if args.ncores > 1: - show=False + show = False else: - show=True - mcmc.plot_chain(m_primes, m_chain, obs[0], ar, glacier_str, show=show, fpath=f'{fp}/{glacier_str}-chain{n_chain}.png') + show = True + mcmc.plot_chain( + m_primes, + m_chain, + obs[0], + ar, + glacier_str, + show=show, + fpath=f"{fp}/{glacier_str}-chain{n_chain}.png", + ) for i in pred_chain.keys(): - mcmc.plot_resid_hist(obs[i], pred_chain[i], glacier_str, show=show, fpath=f'{fp}/{glacier_str}-chain{n_chain}-residuals-{i}.png') + mcmc.plot_resid_hist( + obs[i], + pred_chain[i], + glacier_str, + show=show, + fpath=f"{fp}/{glacier_str}-chain{n_chain}-residuals-{i}.png", + ) # Store data from model to be exported - chain_str = 'chain_' + str(n_chain) - modelprms_export['tbias'][chain_str] = m_chain[:,0].tolist() - modelprms_export['kp'][chain_str] = m_chain[:,1].tolist() - modelprms_export['ddfsnow'][chain_str] = m_chain[:,2].tolist() - modelprms_export['ddfice'][chain_str] = (m_chain[:,2] / - pygem_prms['sim']['params']['ddfsnow_iceratio']).tolist() - modelprms_export['mb_mwea'][chain_str] = m_chain[:,3].tolist() - modelprms_export['ar'][chain_str] = ar + chain_str = "chain_" + str(n_chain) + modelprms_export["tbias"][chain_str] = m_chain[:, 0].tolist() + modelprms_export["kp"][chain_str] = m_chain[:, 1].tolist() + modelprms_export["ddfsnow"][chain_str] = m_chain[:, 2].tolist() + modelprms_export["ddfice"][chain_str] = ( + m_chain[:, 2] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ).tolist() + modelprms_export["mb_mwea"][chain_str] = m_chain[:, 3].tolist() + modelprms_export["ar"][chain_str] = ar # increment n_chain only if the current iteration was a repeat n_chain += 1 # Export model parameters - modelprms_export['precgrad'] = [pygem_prms['sim']['params']['precgrad']] - modelprms_export['tsnow_threshold'] = [pygem_prms['sim']['params']['tsnow_threshold']] - modelprms_export['mb_obs_mwea'] = [float(mb_obs_mwea)] - modelprms_export['mb_obs_mwea_err'] = [float(mb_obs_mwea_err)] - modelprms_export['priors'] = priors - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = [(pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/')] + modelprms_export["precgrad"] = [ + pygem_prms["sim"]["params"]["precgrad"] + ] + modelprms_export["tsnow_threshold"] = [ + pygem_prms["sim"]["params"]["tsnow_threshold"] + ] + modelprms_export["mb_obs_mwea"] = [float(mb_obs_mwea)] + modelprms_export["mb_obs_mwea_err"] = [float(mb_obs_mwea_err)] + modelprms_export["priors"] = priors + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = [ + ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + ] # if not using emulator (running full model), save output in ./calibration/ and ./calibration-fullsim/ - if not pygem_prms['calib']['MCMC_params']['option_use_emulator']: - modelprms_fp.append(pygem_prms['root'] + f'/Output/calibration{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) - + '/') + if not pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: + modelprms_fp.append( + pygem_prms["root"] + + f"/Output/calibration{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) for fp in modelprms_fp: if not os.path.exists(fp): os.makedirs(fp, exist_ok=True) modelprms_fullfn = fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms_export else: modelprms_dict = {args.option_calibration: modelprms_export} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) # MCMC LOG SUCCESS - mcmc_good_fp = pygem_prms['root'] + f'/Output/mcmc_success{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) + '/' + mcmc_good_fp = ( + pygem_prms["root"] + + f"/Output/mcmc_success{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(mcmc_good_fp): os.makedirs(mcmc_good_fp, exist_ok=True) txt_fn_good = glacier_str + "-mcmc_success.txt" with open(mcmc_good_fp + txt_fn_good, "w") as text_file: - text_file.write(glacier_str + ' successfully exported mcmc results') + text_file.write( + glacier_str + " successfully exported mcmc results" + ) except Exception as err: # MCMC LOG FAILURE - mcmc_fail_fp = pygem_prms['root'] + f'/Output/mcmc_fail{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) + '/' + mcmc_fail_fp = ( + pygem_prms["root"] + + f"/Output/mcmc_fail{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(mcmc_fail_fp): os.makedirs(mcmc_fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-mcmc_fail.txt" with open(mcmc_fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + f' failed to complete MCMC: {err}') + text_file.write( + glacier_str + f" failed to complete MCMC: {err}" + ) # -------------------- - - #%% ===== HUSS AND HOCK (2015) CALIBRATION ===== - elif args.option_calibration == 'HH2015': - tbias_init = float(pygem_prms['calib']['HH2015_params']['tbias_init']) - tbias_step = float(pygem_prms['calib']['HH2015_params']['tbias_step']) - kp_init = float(pygem_prms['calib']['HH2015_params']['kp_init']) - kp_bndlow = float(pygem_prms['calib']['HH2015_params']['kp_bndlow']) - kp_bndhigh = float(pygem_prms['calib']['HH2015_params']['kp_bndhigh']) - ddfsnow_init = float(pygem_prms['calib']['HH2015_params']['ddfsnow_init']) - ddfsnow_bndlow = float(pygem_prms['calib']['HH2015_params']['ddfsnow_bndlow']) - ddfsnow_bndhigh = float(pygem_prms['calib']['HH2015_params']['ddfsnow_bndhigh']) + # %% ===== HUSS AND HOCK (2015) CALIBRATION ===== + elif args.option_calibration == "HH2015": + tbias_init = float(pygem_prms["calib"]["HH2015_params"]["tbias_init"]) + tbias_step = float(pygem_prms["calib"]["HH2015_params"]["tbias_step"]) + kp_init = float(pygem_prms["calib"]["HH2015_params"]["kp_init"]) + kp_bndlow = float(pygem_prms["calib"]["HH2015_params"]["kp_bndlow"]) + kp_bndhigh = float(pygem_prms["calib"]["HH2015_params"]["kp_bndhigh"]) + ddfsnow_init = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_init"] + ) + ddfsnow_bndlow = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndlow"] + ) + ddfsnow_bndhigh = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndhigh"] + ) # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) continue_param_search = True # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- - def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid, - debug=False): + def update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=False, + ): # If mass balance less than observation, reduce tbias - if prm2opt == 'kp': + if prm2opt == "kp": if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) else: prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid - elif prm2opt == 'ddfsnow': + elif prm2opt == "ddfsnow": if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid else: prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high - elif prm2opt == 'tbias': + prm_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' - - if prm2opt == 'kp': + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow_new, 2), + "mb_mwea_low:", + np.round(mb_mwea_low_new, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh_new, 2), + "mb_mwea_high:", + np.round(mb_mwea_high_new, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid_new, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid_new, 3), + ) + + return ( + prm_bndlow_new, + prm_bndhigh_new, + prm_mid_new, + mb_mwea_low_new, + mb_mwea_high_new, + mb_mwea_mid_new, + ) + + def single_param_optimizer( + modelprms_subset, + mb_obs_mwea, + prm2opt=None, + kp_bnds=None, + tbias_bnds=None, + ddfsnow_bnds=None, + mb_mwea_threshold=0.005, + debug=False, + ): + assert prm2opt is not None, ( + "For single_param_optimizer you must specify parameter to optimize" + ) + + if prm2opt == "kp": prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms['tbias'] = modelprms_subset['tbias'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] - elif prm2opt == 'ddfsnow': + modelprms["tbias"] = modelprms_subset["tbias"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + elif prm2opt == "ddfsnow": prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['tbias'] = modelprms_subset['tbias'] - elif prm2opt == 'tbias': + modelprms["kp"] = modelprms_subset["kp"] + modelprms["tbias"] = modelprms_subset["tbias"] + elif prm2opt == "tbias": prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] + modelprms["kp"] = modelprms_subset["kp"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_low = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_low = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound modelprms[prm2opt] = prm_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_high = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_high = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Middle bound prm_mid = (prm_bndlow + prm_bndhigh) / 2 modelprms[prm2opt] = prm_mid - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow,2), 'mb_mwea_low:', np.round(mb_mwea_low,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh,2), 'mb_mwea_high:', np.round(mb_mwea_high,2)) - print(prm2opt + '_mid:', np.round(prm_mid,2), 'mb_mwea_mid:', np.round(mb_mwea_mid,3)) + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow, 2), + "mb_mwea_low:", + np.round(mb_mwea_low, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh, 2), + "mb_mwea_high:", + np.round(mb_mwea_high, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid, 3), + ) # Optimize the model parameter if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: @@ -1487,32 +2396,57 @@ 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)) + print("\n ncount:", ncount) + ( + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + ) = update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=debug, + ) ncount += 1 return modelprms, mb_mwea_mid - # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print('Round 1:') + print("Round 1:") if debug: - print(glacier_str + ' kp: ' + str(np.round(modelprms['kp'],2)) + - ' ddfsnow: ' + str(np.round(modelprms['ddfsnow'],4)) + - ' tbias: ' + str(np.round(modelprms['tbias'],2))) + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) # Lower bound - modelprms['kp'] = kp_bndlow - mb_mwea_kp_low = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["kp"] = kp_bndlow + mb_mwea_kp_low = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound - modelprms['kp'] = kp_bndhigh - mb_mwea_kp_high = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["kp"] = kp_bndhigh + mb_mwea_kp_high = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: @@ -1523,30 +2457,51 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_kp_high else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_init, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_init, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } kp_bnds = (kp_bndlow, kp_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='kp', kp_bnds=kp_bnds, debug=debug) - kp_opt = modelprms_opt['kp'] + modelprms_subset, + mb_obs_mwea, + prm2opt="kp", + kp_bnds=kp_bnds, + debug=debug, + ) + kp_opt = modelprms_opt["kp"] continue_param_search = False # Update parameter values - modelprms['kp'] = kp_opt + modelprms["kp"] = kp_opt if debug: - print(' kp:', np.round(kp_opt,2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " kp:", np.round(kp_opt, 2), "mb_mwea:", np.round(mb_mwea, 2) + ) # ===== ROUND 2: DEGREE-DAY FACTOR OF SNOW ====== if continue_param_search: if debug: - print('Round 2:') + print("Round 2:") # Lower bound - modelprms['ddfsnow'] = ddfsnow_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_ddflow = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfsnow"] = ddfsnow_bndlow + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_ddflow = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound - modelprms['ddfsnow'] = ddfsnow_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_ddfhigh = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfsnow"] = ddfsnow_bndhigh + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_ddfhigh = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Optimal degree-day factor of snow if mb_obs_mwea < mb_mwea_ddfhigh: ddfsnow_opt = ddfsnow_bndhigh @@ -1556,129 +2511,192 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_ddflow else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } ddfsnow_bnds = (ddfsnow_bndlow, ddfsnow_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='ddfsnow', ddfsnow_bnds=ddfsnow_bnds, debug=debug) - ddfsnow_opt = modelprms_opt['ddfsnow'] + modelprms_subset, + mb_obs_mwea, + prm2opt="ddfsnow", + ddfsnow_bnds=ddfsnow_bnds, + debug=debug, + ) + ddfsnow_opt = modelprms_opt["ddfsnow"] continue_param_search = False # Update parameter values - modelprms['ddfsnow'] = ddfsnow_opt - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["ddfsnow"] = ddfsnow_opt + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) if debug: - print(' ddfsnow:', np.round(ddfsnow_opt,4), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " ddfsnow:", + np.round(ddfsnow_opt, 4), + "mb_mwea:", + np.round(mb_mwea, 2), + ) else: - ddfsnow_opt = modelprms['ddfsnow'] + ddfsnow_opt = modelprms["ddfsnow"] # ===== ROUND 3: TEMPERATURE BIAS ====== if continue_param_search: if debug: - print('Round 3:') + print("Round 3:") # ----- TEMPBIAS: max accumulation ----- # Lower temperature bound based on no positive temperatures # Temperature at the lowest bin # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tbias - tbias_max_acc = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) + tbias_max_acc = ( + -1 + * ( + gdir.historical_climate["temp"] + + gdir.historical_climate["lr"] + * (fls[0].surface_h.min() - gdir.historical_climate["elev"]) + ).max() + ) tbias_bndlow = tbias_max_acc - modelprms['tbias'] = tbias_bndlow + modelprms["tbias"] = tbias_bndlow mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) if debug: - print(' tbias_bndlow:', np.round(tbias_bndlow,2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " tbias_bndlow:", + np.round(tbias_bndlow, 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) # Upper bound - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 20: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 20: + modelprms["tbias"] = modelprms["tbias"] + tbias_step + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(' tc:', np.round(modelprms['tbias'],2), 'mb_mwea:', np.round(mb_mwea,2)) - tbias_bndhigh = modelprms['tbias'] + print( + " tc:", + np.round(modelprms["tbias"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) + tbias_bndhigh = modelprms["tbias"] # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, - 'ddfsnow': ddfsnow_opt, - 'tbias': modelprms['tbias'] - tbias_step/2} - tbias_bnds = (tbias_bndhigh-tbias_step, tbias_bndhigh) + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_opt, + "tbias": modelprms["tbias"] - tbias_step / 2, + } + tbias_bnds = (tbias_bndhigh - tbias_step, tbias_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug) + modelprms_subset, + mb_obs_mwea, + prm2opt="tbias", + tbias_bnds=tbias_bnds, + debug=debug, + ) # Update parameter values - tbias_opt = modelprms_opt['tbias'] - modelprms['tbias'] = tbias_opt + tbias_opt = modelprms_opt["tbias"] + modelprms["tbias"] = tbias_opt if debug: - print(' tbias:', np.round(tbias_opt,3), 'mb_mwea:', np.round(mb_mwea,3)) + print( + " tbias:", + np.round(tbias_opt, 3), + "mb_mwea:", + np.round(mb_mwea, 3), + ) else: - tbias_opt = modelprms['tbias'] - + tbias_opt = modelprms["tbias"] # Export model parameters modelprms = modelprms_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [mb_mwea] - modelprms['mb_obs_mwea'] = [mb_obs_mwea] - modelprms['mb_obs_mwea_err'] = [mb_obs_mwea_err] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [mb_mwea] + modelprms["mb_obs_mwea"] = [mb_obs_mwea] + modelprms["mb_obs_mwea_err"] = [mb_obs_mwea_err] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) - - #%% ===== MODIFIED HUSS AND HOCK (2015) CALIBRATION ===== + # %% ===== MODIFIED HUSS AND HOCK (2015) CALIBRATION ===== # used in Rounce et al. (2020; MCMC paper) # - precipitation factor, then temperature bias (no ddfsnow) # - ranges different - elif args.option_calibration == 'HH2015mod': - tbias_init = pygem_prms['calib']['HH2015mod_params']['tbias_init'] - tbias_step = pygem_prms['calib']['HH2015mod_params']['tbias_step'] - kp_init = pygem_prms['calib']['HH2015mod_params']['kp_init'] - kp_bndlow = pygem_prms['calib']['HH2015mod_params']['kp_bndlow'] - kp_bndhigh = pygem_prms['calib']['HH2015mod_params']['kp_bndhigh'] - ddfsnow_init = pygem_prms['calib']['HH2015mod_params']['ddfsnow_init'] + elif args.option_calibration == "HH2015mod": + tbias_init = pygem_prms["calib"]["HH2015mod_params"]["tbias_init"] + tbias_step = pygem_prms["calib"]["HH2015mod_params"]["tbias_step"] + kp_init = pygem_prms["calib"]["HH2015mod_params"]["kp_init"] + kp_bndlow = pygem_prms["calib"]["HH2015mod_params"]["kp_bndlow"] + kp_bndhigh = pygem_prms["calib"]["HH2015mod_params"]["kp_bndhigh"] + ddfsnow_init = pygem_prms["calib"]["HH2015mod_params"]["ddfsnow_init"] # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) # ----- FUNCTIONS ----- def objective(modelprms_subset): - """ Objective function for mass balance data (mimize difference between model and observation). + """Objective function for mass balance data (mimize difference between model and observation). Parameters ---------- modelprms_subset : list of model parameters [kp, ddfsnow, tbias] """ # Subset of model parameters used to reduce number of constraints required - modelprms['kp'] = modelprms_subset[0] - modelprms['tbias'] = tbias_init + modelprms["kp"] = modelprms_subset[0] + modelprms["tbias"] = tbias_init if len(modelprms_subset) > 1: - modelprms['tbias'] = modelprms_subset[1] + modelprms["tbias"] = modelprms_subset[1] # Mass balance mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) # 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 ---------- @@ -1692,38 +2710,60 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, """ # Run the optimization if run_opt: - modelprms_opt = minimize(objective, modelprms_init, method=pygem_prms['calib']['HH2015mod_params']['method_opt'], - bounds=modelprms_bnds, options={'ftol':ftol_opt, 'eps':eps_opt}) + modelprms_opt = minimize( + objective, + modelprms_init, + method=pygem_prms["calib"]["HH2015mod_params"][ + "method_opt" + ], + bounds=modelprms_bnds, + options={"ftol": ftol_opt, "eps": eps_opt}, + ) # Record the optimized parameters modelprms_subset = modelprms_opt.x else: modelprms_subset = modelprms.copy() - modelprms['kp'] = modelprms_subset[0] + modelprms["kp"] = modelprms_subset[0] if len(modelprms_subset) == 2: - modelprms['tbias'] = modelprms_subset[1] + modelprms["tbias"] = modelprms_subset[1] # Re-run the optimized parameters in order to see the mass balance mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) return modelprms, mb_mwea - # ----- Temperature bias bounds ----- tbias_bndhigh = 0 # Tbias lower bound based on no positive temperatures - tbias_bndlow = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) - modelprms['tbias'] = tbias_bndlow + 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 + modelprms["kp"] = kp_bndhigh - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 20: - modelprms['tbias'] = modelprms['tbias'] + 1 + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 20: + modelprms["tbias"] = modelprms["tbias"] + 1 mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) if debug: - print(' tc:', np.round(modelprms['tbias'],2), 'mb_mwea:', np.round(mb_mwea,2)) - tbias_bndhigh = modelprms['tbias'] + print( + " tc:", + np.round(modelprms["tbias"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) + tbias_bndhigh = modelprms["tbias"] # ===== ROUND 1: PRECIPITATION FACTOR ===== # Adjust bounds based on range of temperature bias @@ -1731,67 +2771,100 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, tbias_init = tbias_bndhigh elif tbias_init < tbias_bndlow: tbias_init = tbias_bndlow - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init tbias_bndlow_opt = tbias_init tbias_bndhigh_opt = tbias_init # Constrain bounds of precipitation factor and temperature bias mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - nbinyears_negmbclim = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt=True) + nbinyears_negmbclim = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt=True, + ) if debug: - print('\ntbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "\ntbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) # Adjust lower or upper bound based on the observed mass balance test_count = 0 if mb_mwea > mb_obs_mwea: if debug: - print('increase tbias, decrease kp') + print("increase tbias, decrease kp") kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms['kp'] = kp_bndlow + modelprms["kp"] = kp_bndlow mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) while mb_mwea > mb_obs_mwea and test_count < 20: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] + tbias_step + modelprms["tbias"] = modelprms["tbias"] + tbias_step # Update bounds - tbias_bndhigh_opt = modelprms['tbias'] - tbias_bndlow_opt = modelprms['tbias'] - tbias_step + tbias_bndhigh_opt = modelprms["tbias"] + tbias_bndlow_opt = modelprms["tbias"] - tbias_step # Compute mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 else: if debug: - print('decrease tbias, increase kp') + print("decrease tbias, increase kp") kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms['kp'] = kp_bndhigh + modelprms["kp"] = kp_bndhigh mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) while mb_obs_mwea > mb_mwea and test_count < 20: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] - tbias_step + modelprms["tbias"] = modelprms["tbias"] - tbias_step # If temperature bias is at lower limit, then increase precipitation factor - if modelprms['tbias'] <= tbias_bndlow: - modelprms['tbias'] = tbias_bndlow + if modelprms["tbias"] <= tbias_bndlow: + modelprms["tbias"] = tbias_bndlow if test_count > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms['kp'] = kp_bndhigh + modelprms["kp"] = kp_bndhigh # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms['tbias'] - tbias_bndhigh_opt = modelprms['tbias'] + tbias_step + tbias_bndlow_opt = modelprms["tbias"] + tbias_bndhigh_opt = modelprms["tbias"] + tbias_step # Compute mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 # ----- RUN OPTIMIZATION WITH CONSTRAINED BOUNDS ----- @@ -1802,8 +2875,8 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, tbias_init = np.mean([tbias_bndlow_opt, tbias_bndhigh_opt]) if debug: - print('tbias bounds:', tbias_bnds) - print('kp bounds:', kp_bnds) + print("tbias bounds:", tbias_bnds) + print("kp bounds:", kp_bnds) # Set up optimization for only the precipitation factor if tbias_bndlow_opt == tbias_bndhigh_opt: @@ -1815,46 +2888,75 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, modelprms_bnds = (kp_bnds, tbias_bnds) # Run optimization - modelparams_opt, mb_mwea = run_objective(modelprms_subset, mb_obs_mwea, - modelprms_bnds=modelprms_bnds, ftol_opt=1e-3) - - kp_opt = modelparams_opt['kp'] - tbias_opt = modelparams_opt['tbias'] + modelparams_opt, mb_mwea = run_objective( + modelprms_subset, + mb_obs_mwea, + modelprms_bnds=modelprms_bnds, + ftol_opt=1e-3, + ) + + kp_opt = modelparams_opt["kp"] + tbias_opt = modelparams_opt["tbias"] if debug: - print('mb_mwea:', np.round(mb_mwea,2), 'obs_mb:', np.round(mb_obs_mwea,2), - 'kp:', np.round(kp_opt,2), 'tbias:', np.round(tbias_opt,2), '\n\n') + print( + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mb:", + np.round(mb_obs_mwea, 2), + "kp:", + np.round(kp_opt, 2), + "tbias:", + np.round(tbias_opt, 2), + "\n\n", + ) # Export model parameters modelprms = modelparams_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [mb_mwea] - modelprms['mb_obs_mwea'] = [mb_obs_mwea] - modelprms['mb_obs_mwea_err'] = [mb_obs_mwea_err] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [mb_mwea] + modelprms["mb_obs_mwea"] = [mb_obs_mwea] + modelprms["mb_obs_mwea_err"] = [mb_obs_mwea_err] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) else: # LOG FAILURE - fail_fp = pygem_prms['root'] + '/Outputcal_fail/' + glacier_str.split('.')[0].zfill(2) + '/' + fail_fp = ( + pygem_prms["root"] + + "/Outputcal_fail/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(fail_fp): os.makedirs(fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-cal_fail.txt" with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' had no flowlines or mb_data.') + text_file.write(glacier_str + " had no flowlines or mb_data.") # Global variables for Spyder development if args.ncores == 1: @@ -1862,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() @@ -1875,14 +2977,18 @@ def main(): glac_no = [float(g) for g in glac_no] glac_no = [f"{g:.5f}" if g >= 10 else f"0{g:.5f}" for g in glac_no] elif args.rgi_glac_number_fn is not None: - with open(args.rgi_glac_number_fn, 'r') as f: + with open(args.rgi_glac_number_fn, "r") as f: glac_no = json.load(f) else: main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=args.rgi_region01, rgi_regionsO2=args.rgi_region02, - include_landterm=pygem_prms['setup']['include_landterm'], include_laketerm=pygem_prms['setup']['include_laketerm'], - include_tidewater=pygem_prms['setup']['include_tidewater'], min_glac_area_km2=pygem_prms['setup']['min_glac_area_km2']) - glac_no = list(main_glac_rgi_all['rgino_str'].values) + rgi_regionsO1=args.rgi_region01, + rgi_regionsO2=args.rgi_region02, + include_landterm=pygem_prms["setup"]["include_landterm"], + include_laketerm=pygem_prms["setup"]["include_laketerm"], + include_tidewater=pygem_prms["setup"]["include_tidewater"], + min_glac_area_km2=pygem_prms["setup"]["min_glac_area_km2"], + ) + glac_no = list(main_glac_rgi_all["rgino_str"].values) # Number of cores for parallel processing if args.ncores > 1: @@ -1891,11 +2997,13 @@ def main(): num_cores = 1 # Glacier number lists to pass for parallel processing - glac_no_lsts = modelsetup.split_list(glac_no, n=num_cores, option_ordered=args.option_ordered) + glac_no_lsts = modelsetup.split_list( + glac_no, n=num_cores, option_ordered=args.option_ordered + ) # Read GCM names from argument parser gcm_name = args.ref_gcm_name - print('Processing:', gcm_name) + print("Processing:", gcm_name) # Pack variables for multiprocessing list_packed_vars = [] @@ -1903,16 +3011,17 @@ def main(): list_packed_vars.append([count, glac_no_lst, gcm_name]) # Parallel processing if num_cores > 1: - print('Processing in parallel with ' + str(num_cores) + ' cores...') + print("Processing in parallel with " + str(num_cores) + " cores...") with multiprocessing.Pool(num_cores) as p: - p.map(run,list_packed_vars) + p.map(run, list_packed_vars) # If not in parallel, then only should be one loop else: # Loop through the chunks and export bias adjustments for n in range(len(list_packed_vars)): run(list_packed_vars[n]) - print('Total processing time:', time.time()-time_start, 's') + print("Total processing time:", time.time() - time_start, "s") + if __name__ == "__main__": main() diff --git a/pygem/bin/run/run_calibration_frontalablation.py b/pygem/bin/run/run_calibration_frontalablation.py index 5a3abbe3..9fe0d7d4 100644 --- a/pygem/bin/run/run_calibration_frontalablation.py +++ b/pygem/bin/run/run_calibration_frontalablation.py @@ -7,6 +7,7 @@ Calibrate frontal ablation parameters for tidewater glaciers """ + # Built-in libraries import argparse import glob @@ -43,8 +44,8 @@ ############### ### globals ### ############### -frontal_ablation_Gta_cn = 'fa_gta_obs' -frontal_ablation_Gta_unc_cn = 'fa_gta_obs_unc' +frontal_ablation_Gta_cn = "fa_gta_obs" +frontal_ablation_Gta_unc_cn = "fa_gta_obs_unc" # Frontal ablation calibration parameter (yr-1) calving_k_init = 0.1 @@ -52,42 +53,56 @@ calving_k_bndhigh_gl = 5 calving_k_step_gl = 0.2 -perc_threshold_agreement = 0.05 # Threshold (%) at which to stop optimization and consider good agreement -fa_threshold = 1e-4 # Absolute threshold at which to stop optimization (Gta) - -rgi_reg_dict = {'all':'Global', - 'global':'Global', - 1:'Alaska', - 2:'W Canada/USA', - 3:'Arctic Canada North', - 4:'Arctic Canada South', - 5:'Greenland', - 6:'Iceland', - 7:'Svalbard', - 8:'Scandinavia', - 9:'Russian Arctic', - 10:'North Asia', - 11:'Central Europe', - 12:'Caucasus/Middle East', - 13:'Central Asia', - 14:'South Asia West', - 15:'South Asia East', - 16:'Low Latitudes', - 17:'Southern Andes', - 18:'New Zealand', - 19:'Antarctica/Subantarctic' - } +perc_threshold_agreement = ( + 0.05 # Threshold (%) at which to stop optimization and consider good agreement +) +fa_threshold = 1e-4 # Absolute threshold at which to stop optimization (Gta) + +rgi_reg_dict = { + "all": "Global", + "global": "Global", + 1: "Alaska", + 2: "W Canada/USA", + 3: "Arctic Canada North", + 4: "Arctic Canada South", + 5: "Greenland", + 6: "Iceland", + 7: "Svalbard", + 8: "Scandinavia", + 9: "Russian Arctic", + 10: "North Asia", + 11: "Central Europe", + 12: "Caucasus/Middle East", + 13: "Central Asia", + 14: "South Asia West", + 15: "South Asia East", + 16: "Low Latitudes", + 17: "Southern Andes", + 18: "New Zealand", + 19: "Antarctica/Subantarctic", +} ############### + def mwea_to_gta(mwea, area_m2): - return mwea * pygem_prms['constants']['density_water'] * area_m2 / 1e12 + return mwea * pygem_prms["constants"]["density_water"] * area_m2 / 1e12 -def gta_to_mwea(gta, area_m2): - return gta * 1e12 / pygem_prms['constants']['density_water'] / area_m2 -def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, - ignore_nan=True, invert_standard=False, debug=False, - calc_mb_geo_correction=False, reset_gdir=True): +def gta_to_mwea(gta, area_m2): + return gta * 1e12 / pygem_prms["constants"]["density_water"] / area_m2 + + +def reg_calving_flux( + main_glac_rgi, + calving_k, + args, + fa_glac_data_reg=None, + ignore_nan=True, + invert_standard=False, + debug=False, + calc_mb_geo_correction=False, + reset_gdir=True, +): """ Compute the calving flux for a group of glaciers @@ -109,168 +124,237 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, """ # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=args.ref_endyear, spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) + startyear=args.ref_startyear, + endyear=args.ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) # ===== LOAD CLIMATE DATA ===== # Climate class - assert args.ref_gcm_name in ['ERA5', 'ERA-Interim'], ( - 'Error: Calibration not set up for ' + args.ref_gcm_name) + assert args.ref_gcm_name in ["ERA5", "ERA-Interim"], ( + "Error: Calibration not set up for " + args.ref_gcm_name + ) gcm = class_climate.GCM(name=args.ref_gcm_name) # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table) - if pygem_prms['mb']['option_ablation'] == 2 and args.ref_gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi, dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table + ) + if pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in ["ERA5"]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table + ) else: gcm_tempstd = np.zeros(gcm_temp.shape) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) # Lapse rate [degC m-1] - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) # ===== CALIBRATE ALL THE GLACIERS AT ONCE ===== - output_cns = ['RGIId', 'calving_k', 'calving_thick', 'calving_flux_Gta_inv', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics'] - output_df = pd.DataFrame(np.zeros((main_glac_rgi.shape[0],len(output_cns))), columns=output_cns) - output_df['RGIId'] = main_glac_rgi.RGIId - output_df['calving_k'] = calving_k - output_df['calving_thick'] = np.nan - output_df['calving_flux_Gta'] = np.nan - output_df['oggm_dynamics'] = 0 - output_df['mb_mwea_fa_asl_lost'] = 0. + output_cns = [ + "RGIId", + "calving_k", + "calving_thick", + "calving_flux_Gta_inv", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ] + output_df = pd.DataFrame( + np.zeros((main_glac_rgi.shape[0], len(output_cns))), columns=output_cns + ) + output_df["RGIId"] = main_glac_rgi.RGIId + output_df["calving_k"] = calving_k + output_df["calving_thick"] = np.nan + output_df["calving_flux_Gta"] = np.nan + output_df["oggm_dynamics"] = 0 + output_df["mb_mwea_fa_asl_lost"] = 0.0 for nglac in np.arange(main_glac_rgi.shape[0]): - - if args.verbose: print('\n',main_glac_rgi.loc[main_glac_rgi.index.values[nglac],'RGIId']) + if args.verbose: + print("\n", main_glac_rgi.loc[main_glac_rgi.index.values[nglac], "RGIId"]) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[nglac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + 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 + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True try: - fls = gdir.read_pickle('inversion_flowlines') + fls = gdir.read_pickle("inversion_flowlines") glacier_area = fls[0].widths_m * fls[0].dx_meter - debris.debris_binned(gdir, fl_str='inversion_flowlines', ignore_debris=True) + debris.debris_binned(gdir, fl_str="inversion_flowlines", ignore_debris=True) except: fls = None # Add climate data to glacier directory - gdir.historical_climate = {'elev': gcm_elev[nglac], - 'temp': gcm_temp[nglac,:], - 'tempstd': gcm_tempstd[nglac,:], - 'prec': gcm_prec[nglac,:], - 'lr': gcm_lr[nglac,:]} + gdir.historical_climate = { + "elev": gcm_elev[nglac], + "temp": gcm_temp[nglac, :], + "tempstd": gcm_tempstd[nglac, :], + "prec": gcm_prec[nglac, :], + "lr": gcm_lr[nglac, :], + } gdir.dates_table = dates_table # ----- Invert ice thickness and run simulation ------ if (fls is not None) and (glacier_area.sum() > 0): - # ----- Model parameters ----- # Use the calibrated model parameters (although they were calibrated without accounting for calving) if args.prms_from_glac_cal: - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp + modelprms_fn): # try using regional priors args.prms_from_reg_priors = True break - with open(modelprms_fp + modelprms_fn, 'r') as f: + with open(modelprms_fp + modelprms_fn, "r") as f: modelprms_dict = json.load(f) - modelprms_em = modelprms_dict['emulator'] - kp_value = modelprms_em['kp'][0] - tbias_value = modelprms_em['tbias'][0] + modelprms_em = modelprms_dict["emulator"] + kp_value = modelprms_em["kp"][0] + tbias_value = modelprms_em["tbias"][0] # Use most likely parameters from initial calibration to force the mass balance gradient for the inversion elif args.prms_from_reg_priors: - if pygem_prms['calib']['priors_reg_fn'] is not None: + if pygem_prms["calib"]["priors_reg_fn"] is not None: # Load priors - priors_df = pd.read_csv(pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn']) - priors_idx = np.where((priors_df.O1Region == glacier_rgi_table['O1Region']) & - (priors_df.O2Region == glacier_rgi_table['O2Region']))[0][0] - kp_value = priors_df.loc[priors_idx,'kp_med'] - tbias_value = priors_df.loc[priors_idx,'tbias_med'] - + priors_df = pd.read_csv( + pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"] + ) + priors_idx = np.where( + (priors_df.O1Region == glacier_rgi_table["O1Region"]) + & (priors_df.O2Region == glacier_rgi_table["O2Region"]) + )[0][0] + kp_value = priors_df.loc[priors_idx, "kp_med"] + tbias_value = priors_df.loc[priors_idx, "tbias_med"] # Set model parameters - modelprms = {'kp': kp_value, - 'tbias': tbias_value, - 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], - 'ddfice': pygem_prms['sim']['params']['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'], - 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], - 'precgrad': pygem_prms['sim']['params']['precgrad']} + modelprms = { + "kp": kp_value, + "tbias": tbias_value, + "ddfsnow": pygem_prms["sim"]["params"]["ddfsnow"], + "ddfice": pygem_prms["sim"]["params"]["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"], + "tsnow_threshold": pygem_prms["sim"]["params"]["tsnow_threshold"], + "precgrad": pygem_prms["sim"]["params"]["precgrad"], + } # Calving and dynamic parameters - cfg.PARAMS['calving_k'] = calving_k - cfg.PARAMS['inversion_calving_k'] = cfg.PARAMS['calving_k'] - - if pygem_prms['sim']['oggm_dynamics']['use_reg_glena']: - glena_df = pd.read_csv(pygem_prms['root'] + pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']) - glena_idx = np.where(glena_df.O1Region == glacier_rgi_table.O1Region)[0][0] - glen_a_multiplier = glena_df.loc[glena_idx,'glens_a_multiplier'] - fs = glena_df.loc[glena_idx,'fs'] + cfg.PARAMS["calving_k"] = calving_k + cfg.PARAMS["inversion_calving_k"] = cfg.PARAMS["calving_k"] + + if pygem_prms["sim"]["oggm_dynamics"]["use_reg_glena"]: + glena_df = pd.read_csv( + pygem_prms["root"] + + pygem_prms["sim"]["oggm_dynamics"]["glena_reg_relpath"] + ) + glena_idx = np.where(glena_df.O1Region == glacier_rgi_table.O1Region)[ + 0 + ][0] + glen_a_multiplier = glena_df.loc[glena_idx, "glens_a_multiplier"] + fs = glena_df.loc[glena_idx, "fs"] else: - fs = pygem_prms['sim']['oggm_dynamics']['fs'] - glen_a_multiplier = pygem_prms['sim']['oggm_dynamics']['glen_a_multiplier'] + fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] + glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ + "glen_a_multiplier" + ] # CFL number (may use different values for calving to prevent errors) - if 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'] + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number" + ] else: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number_calving'] + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number_calving" + ] # ----- Mass balance model for ice thickness inversion using OGGM ----- - mbmod_inv = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=False, - inversion_filter=False) + mbmod_inv = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=False, + inversion_filter=False, + ) # ----- CALVING ----- # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' - mb_years=np.arange(nyears) + assert True == False, "Adjust nyears for non-monthly timestep" + mb_years = np.arange(nyears) # Perform inversion # - find_inversion_calving_from_any_mb will do the inversion with calving, but if it fails # then it will do the inversion assuming land-terminating if invert_standard: - apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears)) + apparent_mb_from_any_mb( + gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears) + ) tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + tasks.mass_conservation_inversion( + gdir, glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, fs=fs + ) else: - tasks.find_inversion_calving_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=mb_years, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + tasks.find_inversion_calving_from_any_mb( + gdir, + mb_model=mbmod_inv, + mb_years=mb_years, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + ) # ------ MODEL WITH EVOLVING AREA ------ - tasks.init_present_time_glacier(gdir) # adds bins below - debris.debris_binned(gdir, fl_str='model_flowlines') # add debris enhancement factors to flowlines - nfls = gdir.read_pickle('model_flowlines') + tasks.init_present_time_glacier(gdir) # adds bins below + debris.debris_binned( + gdir, fl_str="model_flowlines" + ) # add debris enhancement factors to flowlines + nfls = gdir.read_pickle("model_flowlines") # Mass balance model - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=nfls, option_areaconstant=True) + mbmod = PyGEMMassBalance( + gdir, modelprms, glacier_rgi_table, fls=nfls, option_areaconstant=True + ) # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle('inversion_input')[-1] - th = cls['hgt'][-1] - vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] + cls = gdir.read_pickle("inversion_input")[-1] + th = cls["hgt"][-1] + vmin, vmax = cfg.PARAMS["free_board_marine_terminating"] water_level = utils.clip_scalar(0, th - vmax, th - vmin) - ev_model = FluxBasedModel(nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + ev_model = FluxBasedModel( + nfls, + y0=0, + mb_model=mbmod, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) try: diag = ev_model.run_until_and_store(nyears) ev_model.mb_model.glac_wide_volume_annual[-1] = diag.volume_m3[-1] @@ -280,152 +364,226 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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) + 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)) + # if debug: + # print('avg calving_m3:', calving_m3_annual.sum() / nyears) + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward['calving_flux'] = calving_m3_annual.sum() / nyears / 1e9 + out_calving_forward["calving_flux"] = ( + calving_m3_annual.sum() / nyears / 1e9 + ) # calving flux (Gt/yr) - calving_flux_Gta = out_calving_forward['calving_flux'] * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] + calving_flux_Gta = ( + out_calving_forward["calving_flux"] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward['calving_front_thick'] = thick[last_idx] + out_calving_forward["calving_front_thick"] = thick[last_idx] # Record in dataframe - output_df.loc[nglac,'calving_flux_Gta'] = calving_flux_Gta - output_df.loc[nglac,'calving_thick'] = out_calving_forward['calving_front_thick'] - output_df.loc[nglac,'no_errors'] = 1 - output_df.loc[nglac,'oggm_dynamics'] = 1 + 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)) + print( + "OGGM dynamics, calving_k:", + np.round(calving_k, 4), + "glen_a:", + np.round(glen_a_multiplier, 2), + ) + print( + " calving front thickness [m]:", + np.round(out_calving_forward["calving_front_thick"], 1), + ) + print( + " calving flux model [Gt/yr]:", + np.round(calving_flux_Gta, 5), + ) except: if gdir.is_tidewater: if args.verbose: - print('OGGM dynamics failed, using mass redistribution curves') - # Mass redistribution curves glacier dynamics model + print("OGGM dynamics failed, using mass redistribution curves") + # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) _, diag = ev_model.run_until_and_store(nyears) ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ( + ev_model.mb_model.glac_bin_frontalablation.sum(0) + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) - - calving_flux_km3a = (ev_model.mb_model.glac_wide_frontalablation.sum() * pygem_prms['constants']['density_water'] / - pygem_prms['constants']['density_ice'] / nyears / 1e9) - -# 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)) + 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)) # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward['calving_flux'] = calving_flux_km3a + out_calving_forward["calving_flux"] = calving_flux_km3a # calving flux (Gt/yr) - calving_flux_Gta = out_calving_forward['calving_flux'] * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] + calving_flux_Gta = ( + out_calving_forward["calving_flux"] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward['calving_front_thick'] = thick[last_idx] + out_calving_forward["calving_front_thick"] = thick[last_idx] # Record in dataframe - output_df.loc[nglac,'calving_flux_Gta'] = calving_flux_Gta - output_df.loc[nglac,'calving_thick'] = out_calving_forward['calving_front_thick'] - output_df.loc[nglac,'no_errors'] = 1 + 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)) + 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 # (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 - output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) + output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() elif ignore_nan: - output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) + output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() rgiids_data = list(fa_glac_data_reg.RGIId.values) rgiids_mod = list(output_df_good.RGIId.values) fa_data_idx = [rgiids_data.index(x) for x in rgiids_mod] - reg_calving_gta_obs_good = fa_glac_data_reg.loc[fa_data_idx,frontal_ablation_Gta_cn].sum() + reg_calving_gta_obs_good = fa_glac_data_reg.loc[ + fa_data_idx, frontal_ablation_Gta_cn + ].sum() else: reg_calving_gta_mod_good = output_df.calving_flux_Gta.sum() reg_calving_gta_obs_good = fa_glac_data_reg[frontal_ablation_Gta_cn].sum() @@ -433,46 +591,65 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, return output_df, reg_calving_gta_mod_good, reg_calving_gta_obs_good -def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_bndhigh, fa_glac_data_ind, - calving_k_step=calving_k_step_gl, - ignore_nan=False, calc_mb_geo_correction=False, nround_max=5): +def run_opt_fa( + main_glac_rgi_ind, + args, + calving_k, + calving_k_bndlow, + calving_k_bndhigh, + fa_glac_data_ind, + calving_k_step=calving_k_step_gl, + ignore_nan=False, + calc_mb_geo_correction=False, + nround_max=5, +): """ Run the optimization of the frontal ablation for an individual glacier """ - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + output_df, reg_calving_gta_mod, reg_calving_gta_obs = reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) calving_k_bndlow_hold = np.copy(calving_k_bndlow) if args.verbose: - print(' fa_model_init [Gt/yr] :', np.round(reg_calving_gta_mod,4)) + print(" fa_model_init [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) # ----- Rough optimizer using calving_k_step to loop through parameters within bounds ------ calving_k_last = calving_k reg_calving_gta_mod_last = reg_calving_gta_mod.copy() if reg_calving_gta_mod < reg_calving_gta_obs: - if args.verbose: - print('\nincrease calving_k') - -# print('reg_calving_gta_mod:', reg_calving_gta_mod) -# print('reg_calving_gta_obs:', reg_calving_gta_obs) -# print('calving_k:', calving_k) -# print('calving_k_bndhigh:', calving_k_bndhigh) -# print('calving_k_bndlow:', calving_k_bndlow) - - while ((reg_calving_gta_mod < reg_calving_gta_obs and np.round(calving_k,2) < calving_k_bndhigh - and calving_k > calving_k_bndlow - and (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement - and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold))): + print("\nincrease calving_k") + + # print('reg_calving_gta_mod:', reg_calving_gta_mod) + # print('reg_calving_gta_obs:', reg_calving_gta_obs) + # print('calving_k:', calving_k) + # print('calving_k_bndhigh:', calving_k_bndhigh) + # print('calving_k_bndlow:', calving_k_bndlow) + + while ( + reg_calving_gta_mod < reg_calving_gta_obs + and np.round(calving_k, 2) < calving_k_bndhigh + and calving_k > calving_k_bndlow + and ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold + ) + ): # Record previous output calving_k_last = np.copy(calving_k) reg_calving_gta_mod_last = reg_calving_gta_mod.copy() if args.verbose: - print(' increase calving_k_step:', calving_k_step) + print(" increase calving_k_step:", calving_k_step) # Increase calving k calving_k += calving_k_step @@ -481,12 +658,17 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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 @@ -496,22 +678,32 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b reg_calving_gta_mod_bndhigh = reg_calving_gta_mod else: - if args.verbose: - print('\ndecrease calving_k') - print('-----') - print('reg_calving_gta_mod:', reg_calving_gta_mod) - print('reg_calving_gta_obs:', reg_calving_gta_obs) - print('calving_k:', calving_k) - print('calving_k_bndlow:', calving_k_bndlow) - print('fa perc:', (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs)) - print('fa thres:', np.abs(reg_calving_gta_mod - reg_calving_gta_obs)) - print('good values:', output_df.loc[0,'calving_flux_Gta']) - - while ((reg_calving_gta_mod > reg_calving_gta_obs and calving_k > calving_k_bndlow - and (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement - and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold)) - and not np.isnan(output_df.loc[0,'calving_flux_Gta'])): + print("\ndecrease calving_k") + print("-----") + print("reg_calving_gta_mod:", reg_calving_gta_mod) + print("reg_calving_gta_obs:", reg_calving_gta_obs) + print("calving_k:", calving_k) + print("calving_k_bndlow:", calving_k_bndlow) + print( + "fa perc:", + ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + ), + ) + print("fa thres:", np.abs(reg_calving_gta_mod - reg_calving_gta_obs)) + print("good values:", output_df.loc[0, "calving_flux_Gta"]) + + while ( + reg_calving_gta_mod > reg_calving_gta_obs + and calving_k > calving_k_bndlow + and ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold + ) + ) and not np.isnan(output_df.loc[0, "calving_flux_Gta"]): # Record previous output calving_k_last = np.copy(calving_k) reg_calving_gta_mod_last = reg_calving_gta_mod.copy() @@ -523,12 +715,17 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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,44 +735,61 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b reg_calving_gta_mod_bndhigh = reg_calving_gta_mod_last if args.verbose: - print('bnds:', calving_k_bndlow, calving_k_bndhigh) - print('bnds gt/yr:', reg_calving_gta_mod_bndlow, reg_calving_gta_mod_bndhigh) + print("bnds:", calving_k_bndlow, calving_k_bndhigh) + print( + "bnds gt/yr:", reg_calving_gta_mod_bndlow, reg_calving_gta_mod_bndhigh + ) # ----- Optimize further using mid-point "bisection" method ----- # Consider replacing with scipy.optimize.brent - if not np.isnan(output_df.loc[0,'calving_flux_Gta']): - + if not np.isnan(output_df.loc[0, "calving_flux_Gta"]): # Check if upper bound causes good fit - if (np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) / reg_calving_gta_obs < perc_threshold_agreement - or np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) < fa_threshold): - + if ( + np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) + / reg_calving_gta_obs + < perc_threshold_agreement + or np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) < fa_threshold + ): # If so, calving_k equals upper bound and re-run to get proper estimates for output calving_k = calving_k_bndhigh # Re-run the regional frontal ablation estimates - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + 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("upper bound:") + print(" calving_k:", np.round(calving_k, 4)) + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) # Check if lower bound causes good fit - elif (np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs < perc_threshold_agreement - or np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) < fa_threshold): - + elif ( + np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) + / reg_calving_gta_obs + < perc_threshold_agreement + or np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) < fa_threshold + ): calving_k = calving_k_bndlow # Re-run the regional frontal ablation estimates - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + 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("lower bound:") + print(" calving_k:", np.round(calving_k, 4)) + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) else: # Calibrate between limited range @@ -583,28 +797,39 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b # Set initial calving_k calving_k = (calving_k_bndlow + calving_k_bndhigh) / 2 -# print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) -# print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) -# print('calving_k_bndlow:', calving_k_bndlow) -# print('nround:', nround, 'nround_max:', nround_max) -# print('calving_k:', calving_k, 'calving_k_bndlow_set:', calving_k_bndlow_hold) - - while ((np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement and - np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold) and nround <= nround_max - and calving_k > calving_k_bndlow_hold): - + # print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) + # print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) + # print('calving_k_bndlow:', calving_k_bndlow) + # print('nround:', nround, 'nround_max:', nround_max) + # print('calving_k:', calving_k, 'calving_k_bndlow_set:', calving_k_bndlow_hold) + + while ( + ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold + ) + and nround <= nround_max + and calving_k > calving_k_bndlow_hold + ): nround += 1 if args.verbose: - print('\nRound', nround) + print("\nRound", nround) # Update calving_k using midpoint calving_k = (calving_k_bndlow + calving_k_bndhigh) / 2 - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + 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: @@ -616,12 +841,12 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b 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 @@ -629,19 +854,31 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b return output_df, calving_k -def merge_data(frontalablation_fp='', overwrite=False, verbose=False): - frontalablation_fn1 = 'Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv' - frontalablation_fn2 = 'frontalablation_glacier_data_minowa2021.csv' - frontalablation_fn3 = 'frontalablation_glacier_data_osmanoglu.csv' - out_fn = frontalablation_fn1.replace('.csv','-w17_19.csv') +def merge_data(frontalablation_fp="", overwrite=False, verbose=False): + frontalablation_fn1 = "Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv" + frontalablation_fn2 = "frontalablation_glacier_data_minowa2021.csv" + frontalablation_fn3 = "frontalablation_glacier_data_osmanoglu.csv" + out_fn = frontalablation_fn1.replace(".csv", "-w17_19.csv") if os.path.isfile(frontalablation_fp + out_fn) and not overwrite: if verbose: - print(f'Combined frontal ablation dataset already exists, pass `-o` to overwrite: {frontalablation_fp+out_fn}') + print( + f"Combined frontal ablation dataset already exists, pass `-o` to overwrite: {frontalablation_fp + out_fn}" + ) return out_fn - fa_glac_data_cns_subset = ['RGIId','fa_gta_obs', 'fa_gta_obs_unc', - 'Romain_gta_mbtot', 'Romain_gta_mbclim','Romain_mwea_mbtot', 'Romain_mwea_mbclim', - 'thick_measured_yn', 'start_date', 'end_date', 'source'] + fa_glac_data_cns_subset = [ + "RGIId", + "fa_gta_obs", + "fa_gta_obs_unc", + "Romain_gta_mbtot", + "Romain_gta_mbclim", + "Romain_mwea_mbtot", + "Romain_mwea_mbclim", + "thick_measured_yn", + "start_date", + "end_date", + "source", + ] # Load datasets fa_glac_data1 = pd.read_csv(frontalablation_fp + frontalablation_fn1) @@ -649,38 +886,51 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): fa_glac_data3 = pd.read_csv(frontalablation_fp + frontalablation_fn3) # Kochtitzky data - fa_data_df1 = pd.DataFrame(np.zeros((fa_glac_data1.shape[0],len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df1['RGIId'] = fa_glac_data1['RGIId'] - fa_data_df1['fa_gta_obs'] = fa_glac_data1['Frontal_ablation_2000_to_2020_gt_per_yr_mean'] - fa_data_df1['fa_gta_obs_unc'] = fa_glac_data1['Frontal_ablation_2000_to_2020_gt_per_yr_mean_err'] - fa_data_df1['Romain_gta_mbtot'] = fa_glac_data1['Romain_gta_mbtot'] - fa_data_df1['Romain_gta_mbclim'] = fa_glac_data1['Romain_gta_mbclim'] - fa_data_df1['Romain_mwea_mbtot'] = fa_glac_data1['Romain_mwea_mbtot'] - fa_data_df1['Romain_mwea_mbclim'] = fa_glac_data1['Romain_mwea_mbclim'] - fa_data_df1['thick_measured_yn'] = fa_glac_data1['thick_measured_yn'] - fa_data_df1['start_date'] = '20009999' - fa_data_df1['end_date'] = '20199999' - fa_data_df1['source'] = 'Kochtitzky et al.' + fa_data_df1 = pd.DataFrame( + np.zeros((fa_glac_data1.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df1["RGIId"] = fa_glac_data1["RGIId"] + fa_data_df1["fa_gta_obs"] = fa_glac_data1[ + "Frontal_ablation_2000_to_2020_gt_per_yr_mean" + ] + fa_data_df1["fa_gta_obs_unc"] = fa_glac_data1[ + "Frontal_ablation_2000_to_2020_gt_per_yr_mean_err" + ] + fa_data_df1["Romain_gta_mbtot"] = fa_glac_data1["Romain_gta_mbtot"] + fa_data_df1["Romain_gta_mbclim"] = fa_glac_data1["Romain_gta_mbclim"] + fa_data_df1["Romain_mwea_mbtot"] = fa_glac_data1["Romain_mwea_mbtot"] + fa_data_df1["Romain_mwea_mbclim"] = fa_glac_data1["Romain_mwea_mbclim"] + fa_data_df1["thick_measured_yn"] = fa_glac_data1["thick_measured_yn"] + fa_data_df1["start_date"] = "20009999" + fa_data_df1["end_date"] = "20199999" + fa_data_df1["source"] = "Kochtitzky et al." # Minowa data - fa_data_df2 = pd.DataFrame(np.zeros((fa_glac_data2.shape[0], len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df2['RGIId'] = fa_glac_data2['RGIId'] - fa_data_df2['fa_gta_obs'] = fa_glac_data2['frontal_ablation_Gta'] - fa_data_df2['fa_gta_obs_unc'] = fa_glac_data2['frontal_ablation_unc_Gta'] - fa_data_df2['start_date'] = fa_glac_data2['start_date'] - fa_data_df2['end_date'] = fa_glac_data2['end_date'] - fa_data_df2['source'] = fa_glac_data2['Source'] - fa_data_df2.sort_values('RGIId', inplace=True) + fa_data_df2 = pd.DataFrame( + np.zeros((fa_glac_data2.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df2["RGIId"] = fa_glac_data2["RGIId"] + fa_data_df2["fa_gta_obs"] = fa_glac_data2["frontal_ablation_Gta"] + fa_data_df2["fa_gta_obs_unc"] = fa_glac_data2["frontal_ablation_unc_Gta"] + fa_data_df2["start_date"] = fa_glac_data2["start_date"] + fa_data_df2["end_date"] = fa_glac_data2["end_date"] + fa_data_df2["source"] = fa_glac_data2["Source"] + fa_data_df2.sort_values("RGIId", inplace=True) # Osmanoglu data - fa_data_df3 = pd.DataFrame(np.zeros((fa_glac_data3.shape[0],len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df3['RGIId'] = fa_glac_data3['RGIId'] - fa_data_df3['fa_gta_obs'] = fa_glac_data3['frontal_ablation_Gta'] - fa_data_df3['fa_gta_obs_unc'] = fa_glac_data3['frontal_ablation_unc_Gta'] - fa_data_df3['start_date'] = fa_glac_data3['start_date'] - fa_data_df3['end_date'] = fa_glac_data3['end_date'] - fa_data_df3['source'] = fa_glac_data3['Source'] - fa_data_df3.sort_values('RGIId', inplace=True) + fa_data_df3 = pd.DataFrame( + np.zeros((fa_glac_data3.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df3["RGIId"] = fa_glac_data3["RGIId"] + fa_data_df3["fa_gta_obs"] = fa_glac_data3["frontal_ablation_Gta"] + fa_data_df3["fa_gta_obs_unc"] = fa_glac_data3["frontal_ablation_unc_Gta"] + fa_data_df3["start_date"] = fa_glac_data3["start_date"] + fa_data_df3["end_date"] = fa_glac_data3["end_date"] + fa_data_df3["source"] = fa_glac_data3["Source"] + fa_data_df3.sort_values("RGIId", inplace=True) # Concatenate datasets dfs = [fa_data_df1, fa_data_df2, fa_data_df3] @@ -689,17 +939,28 @@ 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) @@ -707,24 +968,29 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati for reg in [regions]: # skip over any regions we don't have data for - if reg not in fa_glac_data['O1Region'].values.tolist(): + if reg not in fa_glac_data["O1Region"].values.tolist(): continue - output_fn = str(reg) + '-frontalablation_cal_ind.csv' + output_fn = str(reg) + "-frontalablation_cal_ind.csv" # Regional data - fa_glac_data_reg = fa_glac_data.loc[fa_glac_data['O1Region'] == reg, :].copy() + fa_glac_data_reg = fa_glac_data.loc[fa_glac_data["O1Region"] == reg, :].copy() fa_glac_data_reg.reset_index(inplace=True, drop=True) - fa_glac_data_reg['glacno'] = '' + fa_glac_data_reg["glacno"] = "" for nglac, rgiid in enumerate(fa_glac_data_reg.RGIId): # Avoid regional data and observations from multiple RGIIds (len==14) - if not fa_glac_data_reg.loc[nglac,'RGIId'] == 'all' and len(fa_glac_data_reg.loc[nglac,'RGIId']) == 14: - fa_glac_data_reg.loc[nglac,'glacno'] = rgiid[-8:]#(str(int(rgiid.split('-')[1].split('.')[0])) + '.' + - # rgiid.split('-')[1].split('.')[1]) + if ( + not fa_glac_data_reg.loc[nglac, "RGIId"] == "all" + and len(fa_glac_data_reg.loc[nglac, "RGIId"]) == 14 + ): + fa_glac_data_reg.loc[nglac, "glacno"] = rgiid[ + -8: + ] # (str(int(rgiid.split('-')[1].split('.')[0])) + '.' + + # rgiid.split('-')[1].split('.')[1]) # Drop observations that aren't of individual glaciers - fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=['glacno']) + fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=["glacno"]) fa_glac_data_reg.reset_index(inplace=True, drop=True) reg_calving_gta_obs = fa_glac_data_reg[frontal_ablation_Gta_cn].sum() @@ -733,72 +999,116 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati main_glac_rgi_all = modelsetup.selectglaciersrgitable(glac_no=glacno_reg_wdata) # Tidewater glaciers - termtype_list = [1,5] - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all['TermType'].isin(termtype_list)] + termtype_list = [1, 5] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all["TermType"].isin(termtype_list) + ] main_glac_rgi.reset_index(inplace=True, drop=True) # ----- QUALITY CONTROL USING MB_CLIM COMPARED TO REGIONAL MASS BALANCE ----- - mb_data['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in mb_data.rgiid.values] - mb_data_reg = mb_data.loc[mb_data['O1Region'] == reg, :] + mb_data["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) for x in mb_data.rgiid.values + ] + mb_data_reg = mb_data.loc[mb_data["O1Region"] == reg, :] mb_data_reg.reset_index(inplace=True) mb_clim_reg_avg = np.mean(mb_data_reg.mb_mwea) mb_clim_reg_std = np.std(mb_data_reg.mb_mwea) - mb_clim_reg_3std = mb_clim_reg_avg + 3*mb_clim_reg_std + mb_clim_reg_3std = mb_clim_reg_avg + 3 * mb_clim_reg_std mb_clim_reg_max = np.max(mb_data_reg.mb_mwea) - mb_clim_reg_3std_min = mb_clim_reg_avg - 3*mb_clim_reg_std + mb_clim_reg_3std_min = mb_clim_reg_avg - 3 * mb_clim_reg_std if verbose: - print('mb_clim_reg_avg:', np.round(mb_clim_reg_avg,2), '+/-', np.round(mb_clim_reg_std,2)) - print('mb_clim_3std (neg):', np.round(mb_clim_reg_3std_min,2)) - print('mb_clim_3std (pos):', np.round(mb_clim_reg_3std,2)) - print('mb_clim_min:', np.round(mb_data_reg.mb_mwea.min(),2)) - print('mb_clim_max:', np.round(mb_clim_reg_max,2)) + 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: - - 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. + output_cns = [ + "RGIId", + "calving_k", + "calving_k_nmad", + "calving_thick", + "calving_flux_Gta", + "fa_gta_obs", + "fa_gta_obs_unc", + "fa_gta_max", + "calving_flux_Gta_bndlow", + "calving_flux_Gta_bndhigh", + "no_errors", + "oggm_dynamics", + "mb_clim_gta", + "mb_total_gta", + "mb_clim_mwea", + "mb_total_mwea", + ] + + output_df_all = pd.DataFrame( + np.zeros((main_glac_rgi.shape[0], len(output_cns))), columns=output_cns + ) + output_df_all["RGIId"] = main_glac_rgi.RGIId + output_df_all["calving_k_nmad"] = 0.0 # Load observations - fa_obs_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_cn])) - fa_obs_unc_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_unc_cn])) - # fa_glacname_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg.glacier_name)) + fa_obs_dict = dict( + zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_cn]) + ) + fa_obs_unc_dict = dict( + zip( + fa_glac_data_reg.RGIId, + fa_glac_data_reg[frontal_ablation_Gta_unc_cn], + ) + ) + # fa_glacname_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg.glacier_name)) rgi_area_dict = dict(zip(main_glac_rgi.RGIId, main_glac_rgi.Area)) - output_df_all['fa_gta_obs'] = output_df_all['RGIId'].map(fa_obs_dict) - output_df_all['fa_gta_obs_unc'] = output_df_all['RGIId'].map(fa_obs_unc_dict) - # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) - output_df_all['area_km2'] = output_df_all['RGIId'].map(rgi_area_dict) + output_df_all["fa_gta_obs"] = output_df_all["RGIId"].map(fa_obs_dict) + output_df_all["fa_gta_obs_unc"] = output_df_all["RGIId"].map( + fa_obs_unc_dict + ) + # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) + output_df_all["area_km2"] = output_df_all["RGIId"].map(rgi_area_dict) # ----- LOAD DATA ON MB_CLIM CORRECTED FOR FRONTAL ABLATION ----- # use this to assess reasonableness of results and see if calving_k values affected fa_rgiids_list = list(fa_glac_data_reg.RGIId) - output_df_all['mb_total_gta_obs'] = np.nan - output_df_all['mb_clim_gta_obs'] = np.nan - output_df_all['mb_total_mwea_obs'] = np.nan - output_df_all['mb_clim_mwea_obs'] = np.nan -# output_df_all['thick_measured_yn'] = np.nan + output_df_all["mb_total_gta_obs"] = np.nan + output_df_all["mb_clim_gta_obs"] = np.nan + output_df_all["mb_total_mwea_obs"] = np.nan + output_df_all["mb_clim_mwea_obs"] = np.nan + # output_df_all['thick_measured_yn'] = np.nan for nglac, rgiid in enumerate(list(output_df_all.RGIId)): fa_idx = fa_rgiids_list.index(rgiid) - output_df_all.loc[nglac, 'mb_total_gta_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_gta_mbtot'] - output_df_all.loc[nglac, 'mb_clim_gta_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_gta_mbclim'] - output_df_all.loc[nglac, 'mb_total_mwea_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_mwea_mbtot'] - output_df_all.loc[nglac, 'mb_clim_mwea_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_mwea_mbclim'] -# output_df_all.loc[nglac, 'thick_measured_yn'] = fa_glac_data_reg.loc[fa_idx, 'thick_measured_yn'] + output_df_all.loc[nglac, "mb_total_gta_obs"] = fa_glac_data_reg.loc[ + fa_idx, "Romain_gta_mbtot" + ] + output_df_all.loc[nglac, "mb_clim_gta_obs"] = fa_glac_data_reg.loc[ + fa_idx, "Romain_gta_mbclim" + ] + output_df_all.loc[nglac, "mb_total_mwea_obs"] = fa_glac_data_reg.loc[ + fa_idx, "Romain_mwea_mbtot" + ] + output_df_all.loc[nglac, "mb_clim_mwea_obs"] = fa_glac_data_reg.loc[ + fa_idx, "Romain_mwea_mbclim" + ] + # output_df_all.loc[nglac, 'thick_measured_yn'] = fa_glac_data_reg.loc[fa_idx, 'thick_measured_yn'] # ----- CORRECT TOO POSITIVE CLIMATIC MASS BALANCES ----- - output_df_all['mb_clim_gta'] = output_df_all['mb_clim_gta_obs'] - output_df_all['mb_total_gta'] = output_df_all['mb_total_gta_obs'] - output_df_all['mb_clim_mwea'] = output_df_all['mb_clim_mwea_obs'] - output_df_all['mb_total_mwea'] = output_df_all['mb_total_mwea_obs'] - output_df_all['fa_gta_max'] = output_df_all['fa_gta_obs'] - - output_df_badmbclim = output_df_all.loc[output_df_all.mb_clim_mwea_obs > mb_clim_reg_3std] + output_df_all["mb_clim_gta"] = output_df_all["mb_clim_gta_obs"] + output_df_all["mb_total_gta"] = output_df_all["mb_total_gta_obs"] + output_df_all["mb_clim_mwea"] = output_df_all["mb_clim_mwea_obs"] + output_df_all["mb_total_mwea"] = output_df_all["mb_total_mwea_obs"] + output_df_all["fa_gta_max"] = output_df_all["fa_gta_obs"] + + output_df_badmbclim = output_df_all.loc[ + output_df_all.mb_clim_mwea_obs > mb_clim_reg_3std + ] # Correct by using mean + 3std as maximum climatic mass balance if output_df_badmbclim.shape[0] > 0: rgiids_toopos = list(output_df_badmbclim.RGIId) @@ -807,21 +1117,21 @@ 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 @@ -830,136 +1140,250 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati calving_k_step = np.copy(calving_k_step_set) # Select individual glacier - main_glac_rgi_ind = main_glac_rgi.loc[[nglac],:] + main_glac_rgi_ind = main_glac_rgi.loc[[nglac], :] main_glac_rgi_ind.reset_index(inplace=True, drop=True) - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] + rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] - fa_glac_data_ind = fa_glac_data_reg.loc[fa_glac_data_reg.RGIId == rgiid_ind, :] + fa_glac_data_ind = fa_glac_data_reg.loc[ + fa_glac_data_reg.RGIId == rgiid_ind, : + ] fa_glac_data_ind.reset_index(inplace=True, drop=True) # Update the data - fa_gta_max = output_df_all.loc[nglac,'fa_gta_max'] - if fa_glac_data_ind.loc[0,frontal_ablation_Gta_cn] > fa_gta_max: + fa_gta_max = output_df_all.loc[nglac, "fa_gta_max"] + if fa_glac_data_ind.loc[0, frontal_ablation_Gta_cn] > fa_gta_max: reg_calving_gta_obs = fa_gta_max - fa_glac_data_ind.loc[0,frontal_ablation_Gta_cn] = fa_gta_max + fa_glac_data_ind.loc[0, frontal_ablation_Gta_cn] = fa_gta_max # Check bounds bndlow_good = True bndhigh_good = True try: - output_df_bndhigh, reg_calving_gta_mod_bndhigh, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndhigh, args, fa_glac_data_reg=fa_glac_data_ind, ignore_nan=False)) + ( + output_df_bndhigh, + reg_calving_gta_mod_bndhigh, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndhigh, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + ) except: bndhigh_good = False reg_calving_gta_mod_bndhigh = None try: - output_df_bndlow, reg_calving_gta_mod_bndlow, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndlow, args, fa_glac_data_reg=fa_glac_data_ind, ignore_nan=False)) + ( + output_df_bndlow, + reg_calving_gta_mod_bndlow, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndlow, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + ) except: bndlow_good = False reg_calving_gta_mod_bndlow = None # Record bounds - output_df_all.loc[nglac,'calving_flux_Gta_bndlow'] = reg_calving_gta_mod_bndlow - output_df_all.loc[nglac,'calving_flux_Gta_bndhigh'] = reg_calving_gta_mod_bndhigh + output_df_all.loc[nglac, "calving_flux_Gta_bndlow"] = ( + reg_calving_gta_mod_bndlow + ) + output_df_all.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + reg_calving_gta_mod_bndhigh + ) if verbose: - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model_bndlow [Gt/yr] :', reg_calving_gta_mod_bndlow) - print(' fa_model_bndhigh [Gt/yr] :', reg_calving_gta_mod_bndhigh) - + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model_bndlow [Gt/yr] :", reg_calving_gta_mod_bndlow) + print( + " fa_model_bndhigh [Gt/yr] :", reg_calving_gta_mod_bndhigh + ) run_opt = False if bndhigh_good and bndlow_good: if reg_calving_gta_obs < reg_calving_gta_mod_bndlow: - output_df_all.loc[nglac,'calving_k'] = output_df_bndlow.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df_bndlow.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df_bndlow.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df_bndlow.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df_bndlow.loc[0,'oggm_dynamics'] + output_df_all.loc[nglac, "calving_k"] = ( + output_df_bndlow.loc[0, "calving_k"] + ) + output_df_all.loc[nglac, "calving_thick"] = ( + output_df_bndlow.loc[0, "calving_thick"] + ) + output_df_all.loc[nglac, "calving_flux_Gta"] = ( + output_df_bndlow.loc[0, "calving_flux_Gta"] + ) + output_df_all.loc[nglac, "no_errors"] = ( + output_df_bndlow.loc[0, "no_errors"] + ) + output_df_all.loc[nglac, "oggm_dynamics"] = ( + output_df_bndlow.loc[0, "oggm_dynamics"] + ) elif reg_calving_gta_obs > reg_calving_gta_mod_bndhigh: - output_df_all.loc[nglac,'calving_k'] = output_df_bndhigh.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df_bndhigh.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df_bndhigh.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df_bndhigh.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df_bndhigh.loc[0,'oggm_dynamics'] + output_df_all.loc[nglac, "calving_k"] = ( + output_df_bndhigh.loc[0, "calving_k"] + ) + output_df_all.loc[nglac, "calving_thick"] = ( + output_df_bndhigh.loc[0, "calving_thick"] + ) + output_df_all.loc[nglac, "calving_flux_Gta"] = ( + output_df_bndhigh.loc[0, "calving_flux_Gta"] + ) + output_df_all.loc[nglac, "no_errors"] = ( + output_df_bndhigh.loc[0, "no_errors"] + ) + output_df_all.loc[nglac, "oggm_dynamics"] = ( + output_df_bndhigh.loc[0, "oggm_dynamics"] + ) else: run_opt = True else: run_opt = True if run_opt: - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_bndhigh, - fa_glac_data_ind, ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k, + calving_k_bndlow, + calving_k_bndhigh, + fa_glac_data_ind, + ignore_nan=False, + ) calving_k_med = np.copy(calving_k) - output_df_all.loc[nglac,'calving_k'] = output_df.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df.loc[0,'oggm_dynamics'] + output_df_all.loc[nglac, "calving_k"] = output_df.loc[ + 0, "calving_k" + ] + output_df_all.loc[nglac, "calving_thick"] = output_df.loc[ + 0, "calving_thick" + ] + output_df_all.loc[nglac, "calving_flux_Gta"] = output_df.loc[ + 0, "calving_flux_Gta" + ] + output_df_all.loc[nglac, "no_errors"] = output_df.loc[ + 0, "no_errors" + ] + output_df_all.loc[nglac, "oggm_dynamics"] = output_df.loc[ + 0, "oggm_dynamics" + ] # ----- ADD UNCERTAINTY ----- # Upper uncertainty - if verbose: print('\n\n----- upper uncertainty:') + if verbose: + print("\n\n----- upper uncertainty:") fa_glac_data_ind_high = fa_glac_data_ind.copy() - fa_gta_obs_high = fa_glac_data_ind.loc[0,'fa_gta_obs'] + fa_glac_data_ind.loc[0,'fa_gta_obs_unc'] - fa_glac_data_ind_high.loc[0,'fa_gta_obs'] = fa_gta_obs_high + fa_gta_obs_high = ( + fa_glac_data_ind.loc[0, "fa_gta_obs"] + + fa_glac_data_ind.loc[0, "fa_gta_obs_unc"] + ) + fa_glac_data_ind_high.loc[0, "fa_gta_obs"] = fa_gta_obs_high calving_k_bndlow_upper = np.copy(calving_k_med) - 0.01 calving_k_start = np.copy(calving_k_med) - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k_start, calving_k_bndlow_upper, calving_k_bndhigh, - fa_glac_data_ind_high, ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k_start, + calving_k_bndlow_upper, + calving_k_bndhigh, + fa_glac_data_ind_high, + ignore_nan=False, + ) calving_k_nmadhigh = np.copy(calving_k) if verbose: - print('calving_k:', np.round(calving_k,2), 'fa_data high:', np.round(fa_glac_data_ind_high.loc[0,'fa_gta_obs'],4), - 'fa_mod high:', np.round(output_df.loc[0,'calving_flux_Gta'],4)) + print( + "calving_k:", + np.round(calving_k, 2), + "fa_data high:", + np.round(fa_glac_data_ind_high.loc[0, "fa_gta_obs"], 4), + "fa_mod high:", + np.round(output_df.loc[0, "calving_flux_Gta"], 4), + ) # Lower uncertainty - if verbose: print('\n\n----- lower uncertainty:') + if verbose: + print("\n\n----- lower uncertainty:") fa_glac_data_ind_low = fa_glac_data_ind.copy() - fa_gta_obs_low = fa_glac_data_ind.loc[0,'fa_gta_obs'] - fa_glac_data_ind.loc[0,'fa_gta_obs_unc'] + fa_gta_obs_low = ( + fa_glac_data_ind.loc[0, "fa_gta_obs"] + - fa_glac_data_ind.loc[0, "fa_gta_obs_unc"] + ) if fa_gta_obs_low < 0: - calving_k_nmadlow = calving_k_med - abs(calving_k_nmadhigh - calving_k_med) + calving_k_nmadlow = calving_k_med - abs( + calving_k_nmadhigh - calving_k_med + ) if verbose: - print('calving_k:', np.round(calving_k_nmadlow,2), 'fa_data low:', np.round(fa_gta_obs_low,4)) + print( + "calving_k:", + np.round(calving_k_nmadlow, 2), + "fa_data low:", + np.round(fa_gta_obs_low, 4), + ) else: - fa_glac_data_ind_low.loc[0,'fa_gta_obs'] = fa_gta_obs_low + fa_glac_data_ind_low.loc[0, "fa_gta_obs"] = fa_gta_obs_low calving_k_bndhigh_lower = np.copy(calving_k_med) + 0.01 calving_k_start = np.copy(calving_k_med) - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k_start, calving_k_bndlow, calving_k_bndhigh_lower, - fa_glac_data_ind_low, - calving_k_step=(calving_k_med - calving_k_bndlow) / 10, - ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k_start, + calving_k_bndlow, + calving_k_bndhigh_lower, + fa_glac_data_ind_low, + calving_k_step=(calving_k_med - calving_k_bndlow) / 10, + ignore_nan=False, + ) calving_k_nmadlow = np.copy(calving_k) if verbose: - print('calving_k:', np.round(calving_k,2), 'fa_data low:', np.round(fa_glac_data_ind_low.loc[0,'fa_gta_obs'],4), - 'fa_mod low:', np.round(output_df.loc[0,'calving_flux_Gta'],4)) - - - calving_k_nmad = np.mean([abs(calving_k_nmadhigh - calving_k_med), abs(calving_k_nmadlow - calving_k_med)]) + print( + "calving_k:", + np.round(calving_k, 2), + "fa_data low:", + np.round( + fa_glac_data_ind_low.loc[0, "fa_gta_obs"], 4 + ), + "fa_mod low:", + np.round(output_df.loc[0, "calving_flux_Gta"], 4), + ) + + calving_k_nmad = np.mean( + [ + abs(calving_k_nmadhigh - calving_k_med), + abs(calving_k_nmadlow - calving_k_med), + ] + ) # Final if verbose: - print('----- final -----') - print(rgiid, 'calving_k (med/high/low/nmad):', np.round(calving_k_med,2), - np.round(calving_k_nmadhigh,2), np.round(calving_k_nmadlow,2), np.round(calving_k_nmad,2)) - - output_df_all.loc[nglac,'calving_k_nmad'] = calving_k_nmad + print("----- final -----") + print( + rgiid, + "calving_k (med/high/low/nmad):", + np.round(calving_k_med, 2), + np.round(calving_k_nmadhigh, 2), + np.round(calving_k_nmadlow, 2), + np.round(calving_k_nmad, 2), + ) + + output_df_all.loc[nglac, "calving_k_nmad"] = calving_k_nmad except: failed_glacs.append(glacier_str) pass -# # Glaciers at bounds, have calving_k_nmad based on regional mean -# output_df_all_subset = output_df_all.loc[output_df_all.calving_k_nmad > 0, :] -# calving_k_nmad = 1.4826 * median_abs_deviation(output_df_all_subset.calving_k) -# output_df_all.loc[output_df_all['calving_k_nmad']==0,'calving_k_nmad'] = calving_k_nmad + # # Glaciers at bounds, have calving_k_nmad based on regional mean + # output_df_all_subset = output_df_all.loc[output_df_all.calving_k_nmad > 0, :] + # calving_k_nmad = 1.4826 * median_abs_deviation(output_df_all_subset.calving_k) + # output_df_all.loc[output_df_all['calving_k_nmad']==0,'calving_k_nmad'] = calving_k_nmad # ----- EXPORT MODEL RESULTS ----- output_df_all.to_csv(output_fp + output_fn, index=False) # Write list of failed glaciers - if len(failed_glacs)>0: + 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") @@ -970,226 +1394,418 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # ----- 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)) + 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() + output_df_all["calving_flux_Gta_rnd1"] = output_df_all[ + "calving_flux_Gta" + ].copy() + output_df_all["calving_k_rnd1"] = output_df_all["calving_k"].copy() # ----- PLOT RESULTS FOR EACH GLACIER ----- - if len(rgiids_good)>0: - with np.errstate(all='ignore'): - plot_max_raw = np.max([output_df_all_good.calving_flux_Gta.max(), output_df_all_good.fa_gta_obs.max()]) - plot_max = 10**np.ceil(np.log10(plot_max_raw)) - - 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 len(rgiids_good) > 0: + with np.errstate(all="ignore"): + plot_max_raw = np.max( + [ + output_df_all_good.calving_flux_Gta.max(), + output_df_all_good.fa_gta_obs.max(), + ] + ) + plot_max = 10 ** np.ceil(np.log10(plot_max_raw)) + + plot_min_raw = np.max( + [ + output_df_all_good.calving_flux_Gta.min(), + output_df_all_good.fa_gta_obs.min(), + ] + ) + plot_min = 10 ** np.floor(np.log10(plot_min_raw)) if plot_min < 1e-3: plot_min = 1e-4 x_min, x_max = plot_min, plot_max - fig, ax = plt.subplots(2, 2, squeeze=False, gridspec_kw = {'wspace':0.4, 'hspace':0.4}) + fig, ax = plt.subplots( + 2, 2, squeeze=False, gridspec_kw={"wspace": 0.4, "hspace": 0.4} + ) # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all_good['area_km2'].values - s_sizes = [10,50,250,1000] + glac_area_all = output_df_all_good["area_km2"].values + s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - sc = ax[0,0].scatter(output_df_all_good['fa_gta_obs'], output_df_all_good['calving_flux_Gta'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) + sc = ax[0, 0].scatter( + output_df_all_good["fa_gta_obs"], + output_df_all_good["calving_flux_Gta"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) # Labels - ax[0,0].set_xlabel('Observed $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_ylabel('Modeled $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_xlim(x_min,x_max) - ax[0,0].set_ylim(x_min,x_max) - ax[0,0].plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) + ax[0, 0].set_xlabel("Observed $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_ylabel("Modeled $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_xlim(x_min, x_max) + ax[0, 0].set_ylim(x_min, x_max) + ax[0, 0].plot( + [x_min, x_max], [x_min, x_max], color="k", linewidth=0.5, zorder=1 + ) # Log scale - ax[0,0].set_xscale('log') - ax[0,0].set_yscale('log') + ax[0, 0].set_xscale("log") + ax[0, 0].set_yscale("log") # Legend - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] for nlabel, obs_label in enumerate(obs_labels): - ax[0,0].scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax[0,0].text(0.06, 0.98, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax[0,0].transAxes, color='grey') - leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') + ax[0, 0].scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax[0, 0].text( + 0.06, + 0.98, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax[0, 0].transAxes, + color="grey", + ) + leg = ax[0, 0].legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) # ----- Histogram ----- - # nbins = 25 - # ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') - vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) - hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) - ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - align='center', edgecolor='black', color='grey') - ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - ax[0,1].set_xticks(vn_bins, minor=True) - ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # nbins = 25 + # ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') + vn_bins = np.arange( + 0, np.max([1, output_df_all_good.calving_k.max()]) + 0.1, 0.1 + ) + hist, bins = np.histogram( + output_df_all_good.loc[ + output_df_all_good["no_errors"] == 1, "calving_k" + ], + bins=vn_bins, + ) + ax[0, 1].bar( + x=vn_bins[:-1] + 0.1 / 2, + height=hist, + width=(bins[1] - bins[0]), + align="center", + edgecolor="black", + color="grey", + ) + ax[0, 1].set_xticks(np.arange(0, np.max([1, vn_bins.max()]) + 0.1, 1)) + ax[0, 1].set_xticks(vn_bins, minor=True) + ax[0, 1].set_xlim(vn_bins.min(), np.max([1, vn_bins.max()])) if hist.max() < 40: y_major_interval = 5 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) elif hist.max() > 40: y_major_interval = 10 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) # Labels - ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[0,1].set_ylabel('Count (glaciers)', size=12) + ax[0, 1].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[0, 1].set_ylabel("Count (glaciers)", size=12) # ----- CALVING_K VS MB_CLIM ----- - ax[1,0].scatter(output_df_all_good['calving_k'], output_df_all_good['mb_clim_mwea'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,0].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[1,0].set_ylabel('$B_{clim}$ (mwea)', size=12) + ax[1, 0].scatter( + output_df_all_good["calving_k"], + output_df_all_good["mb_clim_mwea"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 0].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[1, 0].set_ylabel("$B_{clim}$ (mwea)", size=12) # ----- CALVING_K VS AREA ----- - ax[1,1].scatter(output_df_all_good['area_km2'], output_df_all_good['calving_k'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,1].set_xlabel('Area (km2)', size=12) - ax[1,1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) + ax[1, 1].scatter( + output_df_all_good["area_km2"], + output_df_all_good["calving_k"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 1].set_xlabel("Area (km2)", size=12) + ax[1, 1].set_ylabel("$k_{f}$ (yr$^{-1}$)", size=12) # Correlation - slope, intercept, r_value, p_value, std_err = linregress(output_df_all_good['area_km2'], - output_df_all_good['calving_k'],) + slope, intercept, r_value, p_value, std_err = linregress( + output_df_all_good["area_km2"], + output_df_all_good["calving_k"], + ) if verbose: - print(' r_value =', np.round(r_value,2), 'slope = ', np.round(slope,5), - 'intercept = ', np.round(intercept,5), 'p_value = ', np.round(p_value,6)) + print( + " r_value =", + np.round(r_value, 2), + "slope = ", + np.round(slope, 5), + "intercept = ", + np.round(intercept, 5), + "p_value = ", + np.round(p_value, 6), + ) area_min = 0 area_max = output_df_all_good.area_km2.max() - ax[1,1].plot([area_min, area_max], [intercept+slope*area_min, intercept+slope*area_max], color='k') + ax[1, 1].plot( + [area_min, area_max], + [intercept + slope * area_min, intercept + slope * area_max], + color="k", + ) # Save figure - fig.set_size_inches(6,6) - fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind-good.png' - fig.savefig(fig_fullfn, bbox_inches='tight', dpi=300) + fig.set_size_inches(6, 6) + fig_fullfn = ( + output_fp + str(reg) + "-frontalablation_glac_compare-cal_ind-good.png" + ) + fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) # ----- REPLACE UPPER BOUND CALVING_K WITH MEDIAN CALVING_K ----- - rgiids_bndhigh = list(output_df_all.loc[output_df_all['calving_k'] == calving_k_bndhigh_set,'RGIId'].values) + rgiids_bndhigh = list( + output_df_all.loc[ + output_df_all["calving_k"] == calving_k_bndhigh_set, "RGIId" + ].values + ) for nglac, rgiid in enumerate(output_df_all.RGIId): if rgiid in rgiids_bndhigh: # Estimate frontal ablation for poor glaciers extrapolated from good ones - main_glac_rgi_ind = main_glac_rgi.loc[main_glac_rgi.RGIId == rgiid,:] + main_glac_rgi_ind = main_glac_rgi.loc[main_glac_rgi.RGIId == rgiid, :] main_glac_rgi_ind.reset_index(inplace=True, drop=True) - fa_glac_data_ind = fa_glac_data_reg.loc[fa_glac_data_reg.RGIId == rgiid, :] + fa_glac_data_ind = fa_glac_data_reg.loc[ + fa_glac_data_reg.RGIId == rgiid, : + ] fa_glac_data_ind.reset_index(inplace=True, drop=True) calving_k = np.median(output_df_all_good.calving_k) -# calving_k = intercept + slope * main_glac_rgi_ind.loc[0,'Area'] -# if calving_k > output_df_all_good.calving_k.max(): -# calving_k = output_df_all_good.calving_k.max() - - 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) + glac_no_missing = [x.split("-")[1] for x in rgiids_missing] + main_glac_rgi_missing = modelsetup.selectglaciersrgitable( + glac_no=glac_no_missing + ) - if verbose: print(reg, len(glac_no_missing), main_glac_rgi_missing.Area.sum(), glac_no_missing) + if verbose: + print( + reg, + len(glac_no_missing), + main_glac_rgi_missing.Area.sum(), + glac_no_missing, + ) if not os.path.exists(output_fp + output_fn_missing) or overwrite: - # Add regions for median subsets - output_df_all['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in output_df_all.RGIId] + output_df_all["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) for x in output_df_all.RGIId + ] # Update mass balance data - output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) - output_df_missing['RGIId'] = rgiids_missing - output_df_missing['fa_gta_obs'] = np.nan - rgi_area_dict = dict(zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area)) - output_df_missing['area_km2'] = output_df_missing['RGIId'].map(rgi_area_dict) - rgi_mbobs_dict = dict(zip(mb_data['rgiid'],mb_data['mb_mwea'])) - output_df_missing['mb_clim_mwea_obs'] = output_df_missing['RGIId'].map(rgi_mbobs_dict) - output_df_missing['mb_clim_gta_obs'] = [mwea_to_gta(output_df_missing.loc[x,'mb_clim_mwea_obs'], - output_df_missing.loc[x,'area_km2']*1e6) for x in output_df_missing.index] - output_df_missing['mb_total_mwea_obs'] = output_df_missing['mb_clim_mwea_obs'] - output_df_missing['mb_total_gta_obs'] = output_df_missing['mb_total_gta_obs'] + output_df_missing = pd.DataFrame( + np.zeros((len(rgiids_missing), len(output_df_all.columns))), + columns=output_df_all.columns, + ) + output_df_missing["RGIId"] = rgiids_missing + output_df_missing["fa_gta_obs"] = np.nan + rgi_area_dict = dict( + zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area) + ) + output_df_missing["area_km2"] = output_df_missing["RGIId"].map( + rgi_area_dict + ) + rgi_mbobs_dict = dict(zip(mb_data["rgiid"], mb_data["mb_mwea"])) + output_df_missing["mb_clim_mwea_obs"] = output_df_missing["RGIId"].map( + rgi_mbobs_dict + ) + output_df_missing["mb_clim_gta_obs"] = [ + mwea_to_gta( + output_df_missing.loc[x, "mb_clim_mwea_obs"], + output_df_missing.loc[x, "area_km2"] * 1e6, + ) + for x in output_df_missing.index + ] + output_df_missing["mb_total_mwea_obs"] = output_df_missing[ + "mb_clim_mwea_obs" + ] + output_df_missing["mb_total_gta_obs"] = output_df_missing[ + "mb_total_gta_obs" + ] # Start with median value - calving_k_med = np.median(output_df_all.loc[output_df_all['O1Region']==reg,'calving_k']) + calving_k_med = np.median( + output_df_all.loc[output_df_all["O1Region"] == reg, "calving_k"] + ) failed_glacs = [] for nglac, rgiid in enumerate(rgiids_missing): - glacier_str = rgiid.split('-')[1] + glacier_str = rgiid.split("-")[1] try: - main_glac_rgi_ind = modelsetup.selectglaciersrgitable(glac_no=[rgiid.split('-')[1]]) + 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_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_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 @@ -1198,150 +1814,367 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati calving_k_step = np.copy(calving_k_step_set) # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] + 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("\n\n\n-------") + print( + "mb_clim_obs:", + np.round( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ], + 2, + ), + ) + print( + "mb_clim_fa_corrected:", + np.round(mb_clim_fa_corrected, 2), + ) + + calving_k_step_missing = ( + calving_k_med - calving_k_bndlow + ) / 20 calving_k_next = calving_k - calving_k_step_missing - while output_df.loc[0,'mb_mwea_fa_asl_lost'] > fa_mwea_max and calving_k_next > 0: + 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 @@ -1349,8 +2182,10 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati output_df_missing.to_csv(output_fp + output_fn_missing, index=False) # Write list of failed glaciers - if len(failed_glacs)>0: - with open(output_fp + output_fn_missing[:-4] + "-failed.txt", "w") as f: + if len(failed_glacs) > 0: + with open( + output_fp + output_fn_missing[:-4] + "-failed.txt", "w" + ) as f: for item in failed_glacs: f.write(f"{item}\n") else: @@ -1358,95 +2193,153 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # ----- CORRECTION FOR TOTAL MASS BALANCE IN ANTARCTICA/SUBANTARCTIC ----- if reg in [19]: - -# #%% STATISTICS FOR CALIBRATED GLACIERS -# for nglac, rgiid in enumerate(output_df_all.RGIId.values): -# mb_rgiids = list(mb_data_reg.RGIId.values) -# mb_idx = mb_rgiids.index(rgiid) -# output_df_all.loc[nglac,'mb_clim_mwea_obs'] = mb_data_reg.loc[mb_idx,'mb_mwea'] -# output_df_all.loc[nglac,'mb_clim_gta_obs'] = mwea_to_gta(output_df_all.loc[nglac,'mb_clim_mwea_obs'], -# output_df_all.loc[nglac,'area_km2']*1e6) -# output_df_all.loc[nglac,'mb_total_gta_obs'] = output_df_all.loc[nglac,'mb_clim_gta_obs'] - output_df_all.loc[nglac,'calving_flux_Gta'] -# output_df_all.loc[nglac,'mb_total_mwea_obs'] = gta_to_mwea(output_df_all.loc[nglac,'mb_total_gta_obs'], -# output_df_all.loc[nglac,'area_km2']*1e6) -# print(mb_data_reg.loc[mb_idx,'RGIId'], rgiid) -# -## mb_clim_reg_avg_1std = mb_clim_reg_avg + mb_clim_reg_std -## print('clim threshold:', np.round(mb{})) -# #%% - - output_fn_missing = output_fn.replace('.csv','-missing.csv') - - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], rgi_regionsO2='all', - rgi_glac_number='all', - include_landterm=False, include_laketerm=False, - include_tidewater=True) + # #%% STATISTICS FOR CALIBRATED GLACIERS + # for nglac, rgiid in enumerate(output_df_all.RGIId.values): + # mb_rgiids = list(mb_data_reg.RGIId.values) + # mb_idx = mb_rgiids.index(rgiid) + # output_df_all.loc[nglac,'mb_clim_mwea_obs'] = mb_data_reg.loc[mb_idx,'mb_mwea'] + # output_df_all.loc[nglac,'mb_clim_gta_obs'] = mwea_to_gta(output_df_all.loc[nglac,'mb_clim_mwea_obs'], + # output_df_all.loc[nglac,'area_km2']*1e6) + # output_df_all.loc[nglac,'mb_total_gta_obs'] = output_df_all.loc[nglac,'mb_clim_gta_obs'] - output_df_all.loc[nglac,'calving_flux_Gta'] + # output_df_all.loc[nglac,'mb_total_mwea_obs'] = gta_to_mwea(output_df_all.loc[nglac,'mb_total_gta_obs'], + # output_df_all.loc[nglac,'area_km2']*1e6) + # print(mb_data_reg.loc[mb_idx,'RGIId'], rgiid) + # + ## mb_clim_reg_avg_1std = mb_clim_reg_avg + mb_clim_reg_std + ## print('clim threshold:', np.round(mb{})) + # #%% + + output_fn_missing = output_fn.replace(".csv", "-missing.csv") + + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + include_landterm=False, + include_laketerm=False, + include_tidewater=True, + ) rgiids_processed = list(output_df_all.RGIId) rgiids_all = list(main_glac_rgi_all.RGIId) rgiids_missing = [x for x in rgiids_all if x not in rgiids_processed] - glac_no_missing = [x.split('-')[1] for x in rgiids_missing] - main_glac_rgi_missing = modelsetup.selectglaciersrgitable(glac_no=glac_no_missing) + glac_no_missing = [x.split("-")[1] for x in rgiids_missing] + main_glac_rgi_missing = modelsetup.selectglaciersrgitable( + glac_no=glac_no_missing + ) - if verbose: print(reg, len(glac_no_missing), main_glac_rgi_missing.Area.sum(), glac_no_missing) + if verbose: + print( + reg, + len(glac_no_missing), + main_glac_rgi_missing.Area.sum(), + glac_no_missing, + ) if not os.path.exists(output_fp + output_fn_missing) or overwrite: - # Add regions for median subsets - output_df_all['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in output_df_all.RGIId] + output_df_all["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) for x in output_df_all.RGIId + ] # Update mass balance data - output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) - output_df_missing['RGIId'] = rgiids_missing - output_df_missing['fa_gta_obs'] = np.nan - rgi_area_dict = dict(zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area)) - output_df_missing['area_km2'] = output_df_missing['RGIId'].map(rgi_area_dict) - rgi_mbobs_dict = dict(zip(mb_data['rgiid'],mb_data['mb_mwea'])) - output_df_missing['mb_clim_mwea_obs'] = output_df_missing['RGIId'].map(rgi_mbobs_dict) - output_df_missing['mb_clim_gta_obs'] = [mwea_to_gta(output_df_missing.loc[x,'mb_clim_mwea_obs'], - output_df_missing.loc[x,'area_km2']*1e6) for x in output_df_missing.index] - output_df_missing['mb_total_mwea_obs'] = output_df_missing['mb_clim_mwea_obs'] - output_df_missing['mb_total_gta_obs'] = output_df_missing['mb_clim_gta_obs'] + output_df_missing = pd.DataFrame( + np.zeros((len(rgiids_missing), len(output_df_all.columns))), + columns=output_df_all.columns, + ) + output_df_missing["RGIId"] = rgiids_missing + output_df_missing["fa_gta_obs"] = np.nan + rgi_area_dict = dict( + zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area) + ) + output_df_missing["area_km2"] = output_df_missing["RGIId"].map( + rgi_area_dict + ) + rgi_mbobs_dict = dict(zip(mb_data["rgiid"], mb_data["mb_mwea"])) + output_df_missing["mb_clim_mwea_obs"] = output_df_missing["RGIId"].map( + rgi_mbobs_dict + ) + output_df_missing["mb_clim_gta_obs"] = [ + mwea_to_gta( + output_df_missing.loc[x, "mb_clim_mwea_obs"], + output_df_missing.loc[x, "area_km2"] * 1e6, + ) + for x in output_df_missing.index + ] + output_df_missing["mb_total_mwea_obs"] = output_df_missing[ + "mb_clim_mwea_obs" + ] + output_df_missing["mb_total_gta_obs"] = output_df_missing[ + "mb_clim_gta_obs" + ] # Uncertainty with calving_k based on regional calibration -# calving_k_nmad_missing = 1.4826 * median_abs_deviation(output_df_all.calving_k) + # calving_k_nmad_missing = 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 + 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) @@ -1454,8 +2347,11 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # 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) @@ -1464,114 +2360,215 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati calving_k_step = np.copy(calving_k_step_set) # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] + rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] # fa_glac_data_ind = pd.DataFrame(np.zeros((1,len(fa_glac_data_reg.columns))), # columns=fa_glac_data_reg.columns) - fa_glac_data_ind = pd.DataFrame(columns=fa_glac_data_reg.columns) - fa_glac_data_ind.loc[0,'RGIId'] = rgiid_ind + fa_glac_data_ind = pd.DataFrame( + columns=fa_glac_data_reg.columns + ) + fa_glac_data_ind.loc[0, "RGIId"] = rgiid_ind # Check bounds bndlow_good = True bndhigh_good = True try: - output_df_bndhigh, reg_calving_gta_mod_bndhigh, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndhigh, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndhigh, + reg_calving_gta_mod_bndhigh, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndhigh, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndhigh_good = False reg_calving_gta_mod_bndhigh = None try: - output_df_bndlow, reg_calving_gta_mod_bndlow, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndlow, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndlow, + reg_calving_gta_mod_bndlow, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndlow, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndlow_good = False reg_calving_gta_mod_bndlow = None # Record bounds - output_df_missing.loc[nglac,'calving_flux_Gta_bndlow'] = reg_calving_gta_mod_bndlow - output_df_missing.loc[nglac,'calving_flux_Gta_bndhigh'] = reg_calving_gta_mod_bndhigh + output_df_missing.loc[nglac, "calving_flux_Gta_bndlow"] = ( + reg_calving_gta_mod_bndlow + ) + output_df_missing.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + reg_calving_gta_mod_bndhigh + ) if verbose: - print(' fa_model_bndlow [mwea] :', np.round(gta_to_mwea(reg_calving_gta_mod_bndlow, area_km2*1e6),2)) - print(' fa_model_bndhigh [mwea] :', np.round(gta_to_mwea(reg_calving_gta_mod_bndhigh,area_km2*1e6),2)) - print(' fa_mwea_cal [mwea]:', np.round(fa_mwea_max,2)) + print( + " fa_model_bndlow [mwea] :", + np.round( + gta_to_mwea( + reg_calving_gta_mod_bndlow, area_km2 * 1e6 + ), + 2, + ), + ) + print( + " fa_model_bndhigh [mwea] :", + np.round( + gta_to_mwea( + reg_calving_gta_mod_bndhigh, area_km2 * 1e6 + ), + 2, + ), + ) + print(" fa_mwea_cal [mwea]:", np.round(fa_mwea_max, 2)) if bndhigh_good and bndlow_good: if verbose: - print('\n-------') - print('mb_clim_mwea:', np.round(mb_clim_mwea,2)) + print("\n-------") + print("mb_clim_mwea:", np.round(mb_clim_mwea, 2)) - calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 + calving_k_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)) + ( + 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) + 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 @@ -1580,176 +2577,265 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati output_df_missing = pd.read_csv(output_fp + output_fn_missing) # ----- PLOT RESULTS FOR EACH GLACIER ----- - with np.errstate(all='ignore'): - plot_max_raw = np.max([output_df_all.calving_flux_Gta.max(), output_df_all.fa_gta_obs.max()]) - plot_max = 10**np.ceil(np.log10(plot_max_raw)) - - plot_min_raw = np.max([output_df_all.calving_flux_Gta.min(), output_df_all.fa_gta_obs.min()]) - plot_min = 10**np.floor(np.log10(plot_min_raw)) + with np.errstate(all="ignore"): + plot_max_raw = np.max( + [output_df_all.calving_flux_Gta.max(), output_df_all.fa_gta_obs.max()] + ) + plot_max = 10 ** np.ceil(np.log10(plot_max_raw)) + + plot_min_raw = np.max( + [output_df_all.calving_flux_Gta.min(), output_df_all.fa_gta_obs.min()] + ) + plot_min = 10 ** np.floor(np.log10(plot_min_raw)) if plot_min < 1e-3: plot_min = 1e-4 x_min, x_max = plot_min, plot_max - fig, ax = plt.subplots(2, 2, squeeze=False, gridspec_kw = {'wspace':0.3, 'hspace':0.3}) + fig, ax = plt.subplots( + 2, 2, squeeze=False, gridspec_kw={"wspace": 0.3, "hspace": 0.3} + ) # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all['area_km2'].values - s_sizes = [10,50,250,1000] + glac_area_all = output_df_all["area_km2"].values + s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - sc = ax[0,0].scatter(output_df_all['fa_gta_obs'], output_df_all['calving_flux_Gta'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) + sc = ax[0, 0].scatter( + output_df_all["fa_gta_obs"], + output_df_all["calving_flux_Gta"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) # Labels - ax[0,0].set_xlabel('Observed $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_ylabel('Modeled $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_xlim(x_min,x_max) - ax[0,0].set_ylim(x_min,x_max) - ax[0,0].plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) + ax[0, 0].set_xlabel("Observed $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_ylabel("Modeled $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_xlim(x_min, x_max) + ax[0, 0].set_ylim(x_min, x_max) + ax[0, 0].plot( + [x_min, x_max], [x_min, x_max], color="k", linewidth=0.5, zorder=1 + ) # Log scale - ax[0,0].set_xscale('log') - ax[0,0].set_yscale('log') + ax[0, 0].set_xscale("log") + ax[0, 0].set_yscale("log") # Legend - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] for nlabel, obs_label in enumerate(obs_labels): - ax[0,0].scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax[0,0].text(0.06, 0.98, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax[0,0].transAxes, color='grey') - leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') -# ax[0,0].text(1.08, 0.97, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', -# transform=ax[0,0].transAxes) -# leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, -# handletextpad=1, borderpad=0.25, labelspacing=1, bbox_to_anchor=(1.035, 0.9)) + ax[0, 0].scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax[0, 0].text( + 0.06, + 0.98, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax[0, 0].transAxes, + color="grey", + ) + leg = ax[0, 0].legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) + # ax[0,0].text(1.08, 0.97, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', + # transform=ax[0,0].transAxes) + # leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, + # handletextpad=1, borderpad=0.25, labelspacing=1, bbox_to_anchor=(1.035, 0.9)) # ----- Histogram ----- -# nbins = 25 -# ax[0,1].hist(output_df_all['calving_k'], bins=nbins, color='grey', edgecolor='k') - vn_bins = np.arange(0, np.max([1,output_df_all.calving_k.max()]) + 0.1, 0.1) - hist, bins = np.histogram(output_df_all.loc[output_df_all['no_errors'] == 1, 'calving_k'], bins=vn_bins) - ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - align='center', edgecolor='black', color='grey') - ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - ax[0,1].set_xticks(vn_bins, minor=True) - ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # nbins = 25 + # ax[0,1].hist(output_df_all['calving_k'], bins=nbins, color='grey', edgecolor='k') + vn_bins = np.arange(0, np.max([1, output_df_all.calving_k.max()]) + 0.1, 0.1) + hist, bins = np.histogram( + output_df_all.loc[output_df_all["no_errors"] == 1, "calving_k"], + bins=vn_bins, + ) + ax[0, 1].bar( + x=vn_bins[:-1] + 0.1 / 2, + height=hist, + width=(bins[1] - bins[0]), + align="center", + edgecolor="black", + color="grey", + ) + ax[0, 1].set_xticks(np.arange(0, np.max([1, vn_bins.max()]) + 0.1, 1)) + ax[0, 1].set_xticks(vn_bins, minor=True) + ax[0, 1].set_xlim(vn_bins.min(), np.max([1, vn_bins.max()])) if hist.max() < 40: y_major_interval = 5 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) elif hist.max() > 40: y_major_interval = 10 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) # Labels - ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[0,1].set_ylabel('Count (glaciers)', size=12) + ax[0, 1].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[0, 1].set_ylabel("Count (glaciers)", size=12) # ----- CALVING_K VS MB_CLIM ----- - ax[1,0].scatter(output_df_all['calving_k'], output_df_all['mb_clim_mwea'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,0].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[1,0].set_ylabel('$B_{clim}$ (mwea)', size=12) + ax[1, 0].scatter( + output_df_all["calving_k"], + output_df_all["mb_clim_mwea"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 0].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[1, 0].set_ylabel("$B_{clim}$ (mwea)", size=12) # ----- CALVING_K VS AREA ----- - ax[1,1].scatter(output_df_all['area_km2'], output_df_all['calving_k'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,1].set_xlabel('Area (km2)', size=12) - ax[1,1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) - + ax[1, 1].scatter( + output_df_all["area_km2"], + output_df_all["calving_k"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 1].set_xlabel("Area (km2)", size=12) + ax[1, 1].set_ylabel("$k_{f}$ (yr$^{-1}$)", size=12) # Save figure - fig.set_size_inches(6,6) - fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind.png' - fig.savefig(fig_fullfn, bbox_inches='tight', dpi=300) + fig.set_size_inches(6, 6) + fig_fullfn = output_fp + str(reg) + "-frontalablation_glac_compare-cal_ind.png" + fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) plt.close(fig) # ----- MERGE CALIBRATED CALVING DATASETS ----- -def merge_ind_calving_k(regions=list(range(1,20)), output_fp='', merged_calving_k_fn='', verbose=False): +def merge_ind_calving_k( + regions=list(range(1, 20)), output_fp="", merged_calving_k_fn="", verbose=False +): # get list of all regional frontal ablation calibration file names - output_reg_fns = sorted(glob.glob(f'{output_fp}/*-frontalablation_cal_ind.csv')) - output_reg_fns = [x.split('/')[-1] for x in output_reg_fns] + output_reg_fns = sorted(glob.glob(f"{output_fp}/*-frontalablation_cal_ind.csv")) + output_reg_fns = [x.split("/")[-1] for x in output_reg_fns] # loop through and merge for nreg, output_fn_reg in enumerate(output_reg_fns): - # Load quality controlled frontal ablation data output_df_reg = pd.read_csv(output_fp + output_fn_reg) - if 'calving_k_nmad' not in list(output_df_reg.columns): - output_df_reg['calving_k_nmad'] = 0 + if "calving_k_nmad" not in list(output_df_reg.columns): + output_df_reg["calving_k_nmad"] = 0 if nreg == 0: output_df_all = output_df_reg else: output_df_all = pd.concat([output_df_all, output_df_reg], axis=0) - output_fn_reg_missing = output_fn_reg.replace('.csv','-missing.csv') + output_fn_reg_missing = output_fn_reg.replace(".csv", "-missing.csv") if os.path.exists(output_fp + output_fn_reg_missing): - # Check if second correction exists - output_fn_reg_missing_v2 = output_fn_reg_missing.replace('.csv','_wmbtotal_correction.csv') + 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_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 'calving_k_nmad' not in list(output_df_reg_missing.columns): - output_df_reg_missing['calving_k_nmad'] = 0 + if "calving_k_nmad" not in list(output_df_reg_missing.columns): + output_df_reg_missing["calving_k_nmad"] = 0 output_df_all = pd.concat([output_df_all, output_df_reg_missing], axis=0) output_df_all.to_csv(output_fp + merged_calving_k_fn, index=0) if verbose: - print(f'Merged calving calibration exported: {output_fp+merged_calving_k_fn}') + print(f"Merged calving calibration exported: {output_fp + merged_calving_k_fn}") return # ----- UPDATE MASS BALANCE DATA WITH FRONTAL ABLATION ESTIMATES ----- -def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablation_fn='', hugonnet2021_fp='', hugonnet2021_facorr_fp='', ncores=1, overwrite=False, verbose=False): +def update_mbdata( + regions=list(range(1, 20)), + frontalablation_fp="", + frontalablation_fn="", + hugonnet2021_fp="", + hugonnet2021_facorr_fp="", + ncores=1, + overwrite=False, + verbose=False, +): # Load calving glacier data (already quality controlled during calibration) - assert os.path.exists(frontalablation_fp + frontalablation_fn), 'Calibrated frontal ablation output dataset does not exist' + assert os.path.exists(frontalablation_fp + frontalablation_fn), ( + "Calibrated frontal ablation output dataset does not exist" + ) fa_glac_data = pd.read_csv(frontalablation_fp + frontalablation_fn) # check if fa corrected mass balance data already exists if os.path.exists(hugonnet2021_facorr_fp): - assert overwrite, f'Frontal ablation corrected mass balance dataset already exists!\t{hugonnet2021_facorr_fp}\nPass `-o` to overwrite, or pass a different filename for `hugonnet2021_facorrected_fn`' + assert overwrite, ( + f"Frontal ablation corrected mass balance dataset already exists!\t{hugonnet2021_facorr_fp}\nPass `-o` to overwrite, or pass a different filename for `hugonnet2021_facorrected_fn`" + ) mb_data = pd.read_csv(hugonnet2021_facorr_fp) else: mb_data = pd.read_csv(hugonnet2021_fp) mb_rgiids = list(mb_data.rgiid) # Record prior data - mb_data['mb_romain_mwea'] = mb_data['mb_mwea'] - mb_data['mb_romain_mwea_err'] = mb_data['mb_mwea_err'] - mb_data['mb_clim_mwea'] = mb_data['mb_mwea'] - mb_data['mb_clim_mwea_err'] = mb_data['mb_mwea_err'] + mb_data["mb_romain_mwea"] = mb_data["mb_mwea"] + mb_data["mb_romain_mwea_err"] = mb_data["mb_mwea_err"] + mb_data["mb_clim_mwea"] = mb_data["mb_mwea"] + mb_data["mb_clim_mwea_err"] = mb_data["mb_mwea_err"] # Update mass balance data for nglac, rgiid in enumerate(fa_glac_data.RGIId): - - O1region = int(rgiid.split('-')[1].split('.')[0]) + O1region = int(rgiid.split("-")[1].split(".")[0]) if O1region in regions: - # Update the mass balance data in Romain's file mb_idx = mb_rgiids.index(rgiid) - mb_data.loc[mb_idx,'mb_mwea'] = fa_glac_data.loc[nglac,'mb_total_mwea'] - mb_data.loc[mb_idx,'mb_clim_mwea'] = fa_glac_data.loc[nglac,'mb_clim_mwea'] + mb_data.loc[mb_idx, "mb_mwea"] = fa_glac_data.loc[nglac, "mb_total_mwea"] + mb_data.loc[mb_idx, "mb_clim_mwea"] = fa_glac_data.loc[ + nglac, "mb_clim_mwea" + ] if verbose: - print(rgiid, 'mb_mwea:', np.round(mb_data.loc[mb_idx,'mb_mwea'],2), - 'mb_clim:', np.round(mb_data.loc[mb_idx,'mb_clim_mwea'],2), - 'mb_romain:', np.round(mb_data.loc[mb_idx,'mb_romain_mwea'],2)) + print( + rgiid, + "mb_mwea:", + np.round(mb_data.loc[mb_idx, "mb_mwea"], 2), + "mb_clim:", + np.round(mb_data.loc[mb_idx, "mb_clim_mwea"], 2), + "mb_romain:", + np.round(mb_data.loc[mb_idx, "mb_romain_mwea"], 2), + ) # Export the updated dataset mb_data.to_csv(hugonnet2021_facorr_fp, index=False) @@ -1757,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]) + O1region = int(rgiid.split("-")[1].split(".")[0]) if O1region in regions: - # Select subsets of data - glacier_str = rgiid.split('-')[1] + glacier_str = rgiid.split("-")[1] glac_strs.append(glacier_str) # paralllelize - func_ = partial(single_flowline_glacier_directory_with_calving, reset=True, facorrected=True) + func_ = partial( + single_flowline_glacier_directory_with_calving, reset=True, facorrected=True + ) with multiprocessing.Pool(ncores) as p: p.map(func_, glac_strs) @@ -1773,38 +2859,36 @@ def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablat # plot calving_k by region -def plot_calving_k_allregions(output_fp=''): - +def plot_calving_k_allregions(output_fp=""): fig = plt.figure() - gs = fig.add_gridspec(nrows=3,ncols=3,wspace=0.4,hspace=0.4) - ax1 = fig.add_subplot(gs[0,0]) - ax2 = fig.add_subplot(gs[0,1]) - ax3 = fig.add_subplot(gs[0,2]) - ax4 = fig.add_subplot(gs[1,0]) - ax5 = fig.add_subplot(gs[1,1]) - ax6 = fig.add_subplot(gs[1,2]) - ax7 = fig.add_subplot(gs[2,0]) - ax8 = fig.add_subplot(gs[2,1]) - ax9 = fig.add_subplot(gs[2,2]) - - regions_ordered = [1,3,4,5,7,9,17,19] - for nax, ax in enumerate([ax1,ax2,ax3,ax4,ax5,ax6,ax7,ax8, ax9]): - + gs = fig.add_gridspec(nrows=3, ncols=3, wspace=0.4, hspace=0.4) + ax1 = fig.add_subplot(gs[0, 0]) + ax2 = fig.add_subplot(gs[0, 1]) + ax3 = fig.add_subplot(gs[0, 2]) + ax4 = fig.add_subplot(gs[1, 0]) + ax5 = fig.add_subplot(gs[1, 1]) + ax6 = fig.add_subplot(gs[1, 2]) + ax7 = fig.add_subplot(gs[2, 0]) + ax8 = fig.add_subplot(gs[2, 1]) + ax9 = fig.add_subplot(gs[2, 2]) + + regions_ordered = [1, 3, 4, 5, 7, 9, 17, 19] + for nax, ax in enumerate([ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9]): if ax not in [ax9]: reg = regions_ordered[nax] - calving_k_fn = str(reg) + '-frontalablation_cal_ind.csv' - if not os.path.isfile(output_fp+calving_k_fn): + calving_k_fn = str(reg) + "-frontalablation_cal_ind.csv" + if not os.path.isfile(output_fp + calving_k_fn): continue output_df_all_good = pd.read_csv(output_fp + calving_k_fn) # ----- 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 @@ -1812,126 +2896,222 @@ def plot_calving_k_allregions(output_fp=''): # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all_good['area_km2'].values - s_sizes = [10,40,120,240] + glac_area_all = output_df_all_good["area_km2"].values + s_sizes = [10, 40, 120, 240] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - sc = ax.scatter(output_df_all_good['fa_gta_obs'], output_df_all_good['calving_flux_Gta'], - color='k', marker='o', linewidth=0.5, facecolor='none', - s=s_byarea, clip_on=True) - - ax.plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) - - ax.text(0.98, 1.02, rgi_reg_dict[reg], size=10, horizontalalignment='right', - verticalalignment='bottom', transform=ax.transAxes) + sc = ax.scatter( + output_df_all_good["fa_gta_obs"], + output_df_all_good["calving_flux_Gta"], + color="k", + marker="o", + linewidth=0.5, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + + ax.plot([x_min, x_max], [x_min, x_max], color="k", linewidth=0.5, zorder=1) + + ax.text( + 0.98, + 1.02, + rgi_reg_dict[reg], + size=10, + horizontalalignment="right", + verticalalignment="bottom", + transform=ax.transAxes, + ) # Labels - ax.set_xlim(x_min,x_max) - ax.set_ylim(x_min,x_max) + ax.set_xlim(x_min, x_max) + ax.set_ylim(x_min, x_max) # Log scale - ax.set_xscale('log') - ax.set_yscale('log') - - ax.tick_params(axis='both', which='major', direction='inout', right=True) - ax.tick_params(axis='both', which='minor', direction='in', right=True) - - # # ----- Histogram ----- - ## nbins = 25 - ## ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') - # vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) - # hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) - # ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - # align='center', edgecolor='black', color='grey') - # ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - # ax[0,1].set_xticks(vn_bins, minor=True) - # ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) - # if hist.max() < 40: - # y_major_interval = 5 - # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - # elif hist.max() > 40: - # y_major_interval = 10 - # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - # - # # Labels - # ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - # ax[0,1].set_ylabel('Count (glaciers)', size=12) - - # Plot - # ax.plot(years, reg_vol_med_norm, color=temp_colordict[deg_group], linestyle='-', - # linewidth=1, zorder=4, label=deg_group) - # ax.plot(years, reg_vol_med_norm_nocalving, color=temp_colordict[deg_group], linestyle=':', - # linewidth=1, zorder=3, label=None) - # - # if ax in [ax1, ax4, ax7]: - # ax.set_ylabel('Mass (rel. to 2015)') - # ax.set_xlim(startyear, endyear) - # ax.xaxis.set_major_locator(MultipleLocator(40)) - # ax.xaxis.set_minor_locator(MultipleLocator(10)) - # ax.set_ylim(0,1.1) - # ax.yaxis.set_major_locator(MultipleLocator(0.2)) - # ax.yaxis.set_minor_locator(MultipleLocator(0.1)) + ax.set_xscale("log") + ax.set_yscale("log") + + ax.tick_params(axis="both", which="major", direction="inout", right=True) + ax.tick_params(axis="both", which="minor", direction="in", right=True) + + # # ----- Histogram ----- + ## nbins = 25 + ## ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') + # vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) + # hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) + # ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), + # align='center', edgecolor='black', color='grey') + # ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) + # ax[0,1].set_xticks(vn_bins, minor=True) + # ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # if hist.max() < 40: + # y_major_interval = 5 + # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval + # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + # elif hist.max() > 40: + # y_major_interval = 10 + # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval + # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + # + # # Labels + # ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) + # ax[0,1].set_ylabel('Count (glaciers)', size=12) + + # Plot + # ax.plot(years, reg_vol_med_norm, color=temp_colordict[deg_group], linestyle='-', + # linewidth=1, zorder=4, label=deg_group) + # ax.plot(years, reg_vol_med_norm_nocalving, color=temp_colordict[deg_group], linestyle=':', + # linewidth=1, zorder=3, label=None) + # + # if ax in [ax1, ax4, ax7]: + # ax.set_ylabel('Mass (rel. to 2015)') + # ax.set_xlim(startyear, endyear) + # ax.xaxis.set_major_locator(MultipleLocator(40)) + # ax.xaxis.set_minor_locator(MultipleLocator(10)) + # ax.set_ylim(0,1.1) + # ax.yaxis.set_major_locator(MultipleLocator(0.2)) + # ax.yaxis.set_minor_locator(MultipleLocator(0.1)) # Legend if ax in [ax9]: - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] for nlabel, obs_label in enumerate(obs_labels): - ax.scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax.text(0.1, 1.06, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax.transAxes, color='grey') - leg = ax.legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') - - ax.spines['top'].set_visible(False) - ax.spines['right'].set_visible(False) - ax.spines['bottom'].set_visible(False) - ax.spines['left'].set_visible(False) + ax.scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax.text( + 0.1, + 1.06, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax.transAxes, + color="grey", + ) + leg = ax.legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) + + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) ax.get_xaxis().set_ticks([]) ax.get_yaxis().set_ticks([]) # Labels - fig.text(0.5,0.04,'Observed frontal ablation (Gt yr$^{-1}$)', fontsize=12, horizontalalignment='center', verticalalignment='bottom') - fig.text(0.04,0.5,'Modeled frontal ablation (Gt yr$^{-1}$)', size=12, horizontalalignment='center', verticalalignment='center', rotation=90) + fig.text( + 0.5, + 0.04, + "Observed frontal ablation (Gt yr$^{-1}$)", + fontsize=12, + horizontalalignment="center", + verticalalignment="bottom", + ) + fig.text( + 0.04, + 0.5, + "Modeled frontal ablation (Gt yr$^{-1}$)", + size=12, + horizontalalignment="center", + verticalalignment="center", + rotation=90, + ) # Save figure - fig_fn = ('allregions_calving_ObsMod.png') - fig.set_size_inches(6.5,5.5) - fig.savefig(output_fp + fig_fn, bbox_inches='tight', dpi=300) + fig_fn = "allregions_calving_ObsMod.png" + fig.set_size_inches(6.5, 5.5) + fig.savefig(output_fp + fig_fn, bbox_inches="tight", dpi=300) plt.close(fig) return def main(): - - parser = argparse.ArgumentParser(description="Calibrate frontal ablation against reference calving datasets and update the reference mass balance data accordingly") + parser = argparse.ArgumentParser( + description="Calibrate frontal ablation against reference calving datasets and update the reference mass balance data accordingly" + ) # add arguments - parser.add_argument('-rgi_region01', type=int, default=list(range(1,20)), - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-hugonnet2021_fn', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", - help='reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-hugonnet2021_facorrected_fn', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn']}", - help='reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization') - parser.add_argument('-prms_from_reg_priors', action='store_true', - help='Take model parameters from regional priors (default False and use calibrated glacier parameters)') - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing calibrated frontal ablation datasets') - parser.add_argument('-v', '--verbose', action='store_true', - help='Flag for verbose') + parser.add_argument( + "-rgi_region01", + type=int, + default=list(range(1, 20)), + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-hugonnet2021_fn", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", + help="reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-hugonnet2021_facorrected_fn", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn']}", + help="reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + ) + parser.add_argument( + "-prms_from_reg_priors", + action="store_true", + help="Take model parameters from regional priors (default False and use calibrated glacier parameters)", + ) + parser.add_argument( + "-o", + "--overwrite", + action="store_true", + help="Flag to overwrite existing calibrated frontal ablation datasets", + ) + parser.add_argument("-v", "--verbose", action="store_true", help="Flag for verbose") args = parser.parse_args() args.prms_from_glac_cal = not args.prms_from_reg_priors @@ -1943,28 +3123,56 @@ def main(): # data paths frontalablation_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['frontalablation']['frontalablation_relpath']}" - frontalablation_cal_fn = pygem_prms['calib']['data']['frontalablation']['frontalablation_cal_fn'] - output_fp = frontalablation_fp + '/analysis/' + frontalablation_cal_fn = pygem_prms["calib"]["data"]["frontalablation"][ + "frontalablation_cal_fn" + ] + output_fp = frontalablation_fp + "/analysis/" hugonnet2021_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}/{args.hugonnet2021_fn}" hugonnet2021_facorr_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}/{args.hugonnet2021_facorrected_fn}" - os.makedirs(output_fp,exist_ok=True) + os.makedirs(output_fp, exist_ok=True) # marge input calving datasets - merged_calving_data_fn = merge_data(frontalablation_fp=frontalablation_fp, overwrite=args.overwrite, verbose=args.verbose) + merged_calving_data_fn = merge_data( + frontalablation_fp=frontalablation_fp, + overwrite=args.overwrite, + verbose=args.verbose, + ) # calibrate each individual glacier's calving_k parameter - calib_ind_calving_k_partial = partial(calib_ind_calving_k, args=args, frontalablation_fp=frontalablation_fp, frontalablation_fn=merged_calving_data_fn, output_fp=output_fp, hugonnet2021_fp=hugonnet2021_fp) + calib_ind_calving_k_partial = partial( + calib_ind_calving_k, + args=args, + frontalablation_fp=frontalablation_fp, + frontalablation_fn=merged_calving_data_fn, + output_fp=output_fp, + hugonnet2021_fp=hugonnet2021_fp, + ) with multiprocessing.Pool(args.ncores) as p: p.map(calib_ind_calving_k_partial, args.rgi_region01) # merge all individual calving_k calibration results by region - merge_ind_calving_k(regions=args.rgi_region01, output_fp=output_fp, merged_calving_k_fn=frontalablation_cal_fn, verbose=args.verbose) + merge_ind_calving_k( + regions=args.rgi_region01, + output_fp=output_fp, + merged_calving_k_fn=frontalablation_cal_fn, + verbose=args.verbose, + ) # update reference mass balance data accordingly - update_mbdata(regions=args.rgi_region01, frontalablation_fp=output_fp, frontalablation_fn=frontalablation_cal_fn, hugonnet2021_fp=hugonnet2021_fp, hugonnet2021_facorr_fp=hugonnet2021_facorr_fp, ncores=args.ncores, overwrite=args.overwrite, verbose=args.verbose) + update_mbdata( + regions=args.rgi_region01, + frontalablation_fp=output_fp, + frontalablation_fn=frontalablation_cal_fn, + hugonnet2021_fp=hugonnet2021_fp, + hugonnet2021_facorr_fp=hugonnet2021_facorr_fp, + ncores=args.ncores, + overwrite=args.overwrite, + verbose=args.verbose, + ) # # plot results plot_calving_k_allregions(output_fp=output_fp) + if __name__ == "__main__": main() diff --git a/pygem/bin/run/run_calibration_reg_glena.py b/pygem/bin/run/run_calibration_reg_glena.py index cf96cf0c..7e13dae4 100644 --- a/pygem/bin/run/run_calibration_reg_glena.py +++ b/pygem/bin/run/run_calibration_reg_glena.py @@ -7,6 +7,7 @@ Find the optimal values of glens_a_multiplier to match the consensus ice thickness estimates """ + # Built-in libraries import argparse import json @@ -39,7 +40,7 @@ from pygem.oggm_compat import single_flowline_glacier_directory -#%% FUNCTIONS +# %% FUNCTIONS def getparser(): """ Use argparse to add arguments from the command line @@ -65,32 +66,85 @@ def getparser(): """ parser = argparse.ArgumentParser(description="run calibration in parallel") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-rgi_glac_number_fn', action='store', type=str, default=None, - help='Filename containing list of rgi_glac_number, helpful for running batches on spc') - parser.add_argument('-rgi_glac_number', action='store', type=float, default=pygem_prms['setup']['glac_no'], nargs='+', - help='Randoph Glacier Inventory glacier number (can take multiple)') - parser.add_argument('-fs', action='store', type=float, default=pygem_prms['out']['fs'], - help='Sliding parameter') - parser.add_argument('-a_multiplier', action='store', type=float, default=pygem_prms['out']['glen_a_multiplier'], - help="Glen’s creep parameter A multiplier") - parser.add_argument('-a_multiplier_bndlow', action='store', type=float, default=0.1, - help="Glen’s creep parameter A multiplier, low bound (default 0.1)") - parser.add_argument('-a_multiplier_bndhigh', action='store', type=float, default=10, - help="Glen’s creep parameter A multiplier, upper bound (default 10)") + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-rgi_glac_number_fn", + action="store", + type=str, + default=None, + help="Filename containing list of rgi_glac_number, helpful for running batches on spc", + ) + parser.add_argument( + "-rgi_glac_number", + action="store", + type=float, + default=pygem_prms["setup"]["glac_no"], + nargs="+", + help="Randoph Glacier Inventory glacier number (can take multiple)", + ) + parser.add_argument( + "-fs", + action="store", + type=float, + default=pygem_prms["out"]["fs"], + help="Sliding parameter", + ) + parser.add_argument( + "-a_multiplier", + action="store", + type=float, + default=pygem_prms["out"]["glen_a_multiplier"], + help="Glen’s creep parameter A multiplier", + ) + parser.add_argument( + "-a_multiplier_bndlow", + action="store", + type=float, + default=0.1, + help="Glen’s creep parameter A multiplier, low bound (default 0.1)", + ) + parser.add_argument( + "-a_multiplier_bndhigh", + action="store", + type=float, + default=10, + help="Glen’s creep parameter A multiplier, upper bound (default 10)", + ) # flags - parser.add_argument('-option_ordered', action='store_true', - help='Flag to keep glacier lists ordered (default is off)') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-option_ordered", + action="store_true", + help="Flag to keep glacier lists ordered (default is off)", + ) + parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") return parser @@ -115,39 +169,42 @@ def plot_nfls_section(nfls): # Plot histo posax = ax.get_position() - posax = [posax.x0 + 2 * posax.width / 3.0, - posax.y0, posax.width / 3.0, - posax.height] + posax = [ + posax.x0 + 2 * posax.width / 3.0, + posax.y0, + posax.width / 3.0, + posax.height, + ] axh = fig.add_axes(posax, frameon=False) - axh.hist(height, orientation='horizontal', range=ylim, bins=20, - alpha=0.3, weights=area) + axh.hist( + height, orientation="horizontal", range=ylim, bins=20, alpha=0.3, weights=area + ) axh.invert_xaxis() axh.xaxis.tick_top() - axh.set_xlabel('Area incl. tributaries (km$^2$)') - axh.xaxis.set_label_position('top') + axh.set_xlabel("Area incl. tributaries (km$^2$)") + axh.xaxis.set_label_position("top") axh.set_ylim(ylim) - axh.yaxis.set_ticks_position('right') + axh.yaxis.set_ticks_position("right") axh.set_yticks([]) - axh.axhline(y=ylim[1], color='black', alpha=1) # qick n dirty trick + axh.axhline(y=ylim[1], color="black", alpha=1) # qick n dirty trick # plot Centerlines cls = nfls[-1] x = np.arange(cls.nx) * cls.dx * cls.map_dx # Plot the bed - ax.plot(x, cls.bed_h, color='k', linewidth=2.5, label='Bed (Parab.)') + ax.plot(x, cls.bed_h, color="k", linewidth=2.5, label="Bed (Parab.)") # Where trapezoid change color - if hasattr(cls, '_do_trapeze') and cls._do_trapeze: + if hasattr(cls, "_do_trapeze") and cls._do_trapeze: bed_t = cls.bed_h * np.nan pt = cls.is_trapezoid & (~cls.is_rectangular) bed_t[pt] = cls.bed_h[pt] - ax.plot(x, bed_t, color='rebeccapurple', linewidth=2.5, - label='Bed (Trap.)') + ax.plot(x, bed_t, color="rebeccapurple", linewidth=2.5, label="Bed (Trap.)") bed_t = cls.bed_h * np.nan bed_t[cls.is_rectangular] = cls.bed_h[cls.is_rectangular] - ax.plot(x, bed_t, color='crimson', linewidth=2.5, label='Bed (Rect.)') + ax.plot(x, bed_t, color="crimson", linewidth=2.5, label="Bed (Rect.)") # Plot glacier def surf_to_nan(surf_h, thick): @@ -159,30 +216,34 @@ def surf_to_nan(surf_h, thick): return surf_h surfh = surf_to_nan(cls.surface_h, cls.thick) - ax.plot(x, surfh, color='#003399', linewidth=2, label='Glacier') + ax.plot(x, surfh, color="#003399", linewidth=2, label="Glacier") ax.set_ylim(ylim) - ax.spines['top'].set_color('none') - ax.xaxis.set_ticks_position('bottom') - ax.set_xlabel('Distance along flowline (m)') - ax.set_ylabel('Altitude (m)') + ax.spines["top"].set_color("none") + ax.xaxis.set_ticks_position("bottom") + ax.set_xlabel("Distance along flowline (m)") + ax.set_ylabel("Altitude (m)") # Legend handles, labels = ax.get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) - ax.legend(list(by_label.values()), list(by_label.keys()), - bbox_to_anchor=(0.5, 1.0), - frameon=False) + ax.legend( + list(by_label.values()), + list(by_label.keys()), + bbox_to_anchor=(0.5, 1.0), + frameon=False, + ) plt.show() + def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False): - """ Calculate the modeled volume [km3] and consensus volume [km3] for the given set of glaciers """ + """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] @@ -190,28 +251,32 @@ def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False) apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears)) tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*a_multiplier, fs=fs) - tasks.init_present_time_glacier(gdir) # adds bins below - nfls = gdir.read_pickle('model_flowlines') + tasks.mass_conservation_inversion( + gdir, glen_a=cfg.PARAMS["glen_a"] * a_multiplier, fs=fs + ) + tasks.init_present_time_glacier(gdir) # adds bins below + nfls = gdir.read_pickle("model_flowlines") # Load consensus volume - if os.path.exists(gdir.get_filepath('consensus_mass')): - consensus_fn = gdir.get_filepath('consensus_mass') - with open(consensus_fn, 'rb') as f: - consensus_km3 = pickle.load(f) / pygem_prms['constants']['density_ice'] / 1e9 + if os.path.exists(gdir.get_filepath("consensus_mass")): + consensus_fn = gdir.get_filepath("consensus_mass") + with open(consensus_fn, "rb") as f: + consensus_km3 = ( + pickle.load(f) / pygem_prms["constants"]["density_ice"] / 1e9 + ) reg_vol_km3_consensus += consensus_km3 reg_vol_km3_modeled += nfls[0].volume_km3 if debug: plot_nfls_section(nfls) - print('\n\n Modeled vol [km3]: ', nfls[0].volume_km3) - print(' Consensus vol [km3]:', consensus_km3,'\n\n') + print("\n\n Modeled vol [km3]: ", nfls[0].volume_km3) + print(" Consensus vol [km3]:", consensus_km3, "\n\n") return reg_vol_km3_modeled, reg_vol_km3_consensus -#%% +# %% def main(): parser = getparser() args = parser.parse_args() @@ -224,57 +289,85 @@ def main(): # Calibrate each region for reg in args.rgi_region01: - - print('Region:', reg) + print("Region:", reg) # ===== LOAD GLACIERS ===== main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=[reg], rgi_regionsO2='all', rgi_glac_number='all', - include_landterm=True,include_laketerm=True, include_tidewater=True) - - - main_glac_rgi_all = main_glac_rgi_all.sort_values('Area', ascending=False) + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + include_landterm=True, + include_laketerm=True, + include_tidewater=True, + ) + + main_glac_rgi_all = main_glac_rgi_all.sort_values("Area", ascending=False) main_glac_rgi_all.reset_index(inplace=True, drop=True) - main_glac_rgi_all['Area_cum'] = np.cumsum(main_glac_rgi_all['Area']) - main_glac_rgi_all['Area_cum_frac'] = main_glac_rgi_all['Area_cum'] / main_glac_rgi_all.Area.sum() - - glac_idx = np.where(main_glac_rgi_all.Area_cum_frac > pygem_prms['calib']['icethickness_cal_frac_byarea'])[0][0] + main_glac_rgi_all["Area_cum"] = np.cumsum(main_glac_rgi_all["Area"]) + main_glac_rgi_all["Area_cum_frac"] = ( + main_glac_rgi_all["Area_cum"] / main_glac_rgi_all.Area.sum() + ) + + glac_idx = np.where( + main_glac_rgi_all.Area_cum_frac + > pygem_prms["calib"]["icethickness_cal_frac_byarea"] + )[0][0] main_glac_rgi_subset = main_glac_rgi_all.loc[0:glac_idx, :] - main_glac_rgi_subset = main_glac_rgi_subset.sort_values('O1Index', ascending=True) + main_glac_rgi_subset = main_glac_rgi_subset.sort_values( + "O1Index", ascending=True + ) main_glac_rgi_subset.reset_index(inplace=True, drop=True) - print(f'But only the largest {int(100*pygem_prms['calib']['icethickness_cal_frac_byarea'])}% of the glaciers by area, which includes', main_glac_rgi_subset.shape[0], 'glaciers.') + print( + f"But only the largest {int(100 * pygem_prms['calib']['icethickness_cal_frac_byarea'])}% of the glaciers by area, which includes", + main_glac_rgi_subset.shape[0], + "glaciers.", + ) # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=args.ref_endyear, spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) + startyear=args.ref_startyear, + endyear=args.ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) # ===== LOAD CLIMATE DATA ===== # Climate class gcm_name = args.ref_gcm_name - assert gcm_name in ['ERA5', 'ERA-Interim'], 'Error: Calibration not set up for ' + gcm_name + assert gcm_name in ["ERA5", "ERA-Interim"], ( + "Error: Calibration not set up for " + gcm_name + ) gcm = class_climate.GCM(name=gcm_name) # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi_subset, dates_table) - if pygem_prms['mbmod']['option_ablation'] == 2 and gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi_subset, dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi_subset, dates_table + ) + if pygem_prms["mbmod"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi_subset, dates_table + ) else: gcm_tempstd = np.zeros(gcm_temp.shape) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi_subset, dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi_subset, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi_subset) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi_subset + ) # Lapse rate [degC m-1] - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi_subset, dates_table) + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi_subset, dates_table + ) # ===== RUN MASS BALANCE ===== # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' + assert True == False, "Adjust nyears for non-monthly timestep" reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 @@ -282,10 +375,12 @@ def main(): gdirs = [] for glac in range(main_glac_rgi_subset.shape[0]): # Select subsets of data - glacier_rgi_table = main_glac_rgi_subset.loc[main_glac_rgi_subset.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + 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) ===== @@ -293,82 +388,115 @@ def main(): gdir = single_flowline_glacier_directory(glacier_str) # Flowlines - fls = gdir.read_pickle('inversion_flowlines') + fls = gdir.read_pickle("inversion_flowlines") # Add climate data to glacier directory - gdir.historical_climate = {'elev': gcm_elev[glac], - 'temp': gcm_temp[glac,:], - 'tempstd': gcm_tempstd[glac,:], - 'prec': gcm_prec[glac,:], - 'lr': gcm_lr[glac,:]} + gdir.historical_climate = { + "elev": gcm_elev[glac], + "temp": gcm_temp[glac, :], + "tempstd": gcm_tempstd[glac, :], + "prec": gcm_prec[glac, :], + "lr": gcm_lr[glac, :], + } gdir.dates_table = dates_table glacier_area_km2 = fls[0].widths_m * fls[0].dx_meter / 1e6 if (fls is not None) and (glacier_area_km2.sum() > 0): - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) + '/' + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) modelprms_fullfn = modelprms_fp + modelprms_fn - assert os.path.exists(modelprms_fullfn), glacier_str + ' calibrated parameters do not exist.' - with open(modelprms_fullfn, 'r') as f: + assert os.path.exists(modelprms_fullfn), ( + glacier_str + " calibrated parameters do not exist." + ) + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) - assert 'emulator' in modelprms_dict, ('Error: ' + glacier_str + ' emulator not in modelprms_dict') - modelprms_all = modelprms_dict['emulator'] + assert "emulator" in modelprms_dict, ( + "Error: " + glacier_str + " emulator not in modelprms_dict" + ) + modelprms_all = modelprms_dict["emulator"] # Loop through model parameters - modelprms = {'kp': modelprms_all['kp'][0], - 'tbias': modelprms_all['tbias'][0], - 'ddfsnow': modelprms_all['ddfsnow'][0], - 'ddfice': modelprms_all['ddfice'][0], - 'tsnow_threshold': modelprms_all['tsnow_threshold'][0], - 'precgrad': modelprms_all['precgrad'][0]} + modelprms = { + "kp": modelprms_all["kp"][0], + "tbias": modelprms_all["tbias"][0], + "ddfsnow": modelprms_all["ddfsnow"][0], + "ddfice": modelprms_all["ddfice"][0], + "tsnow_threshold": modelprms_all["tsnow_threshold"][0], + "precgrad": modelprms_all["precgrad"][0], + } # ----- ICE THICKNESS INVERSION using OGGM ----- # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms['mbmod']['include_debris']: + if pygem_prms["mbmod"]["include_debris"]: inversion_filter = True else: inversion_filter = False # Perform inversion based on PyGEM MB - mbmod_inv = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True, - inversion_filter=inversion_filter) - - # if debug: - # h, w = gdir.get_inversion_flowline_hw() - # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * - # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) - # plt.plot(mb_t0, h, '.') - # plt.ylabel('Elevation') - # plt.xlabel('Mass balance (mwea)') - # plt.show() + mbmod_inv = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + inversion_filter=inversion_filter, + ) + + # if debug: + # h, w = gdir.get_inversion_flowline_hw() + # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * + # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + # plt.plot(mb_t0, h, '.') + # plt.ylabel('Elevation') + # plt.xlabel('Mass balance (mwea)') + # plt.show() mbmods.append(mbmod_inv) gdirs.append(gdir) except: - print(glacier_str + ' failed - likely no gdir') + print(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) - - print('Region:', reg) - print('Consensus [km3] :', reg_vol_km3_con) - print('Model [km3] :', reg_vol_km3_mod) - print('Model bndlow [km3] :', reg_vol_km3_mod_bndlow) - print('Model bndhigh [km3]:', reg_vol_km3_mod_bndhigh) + reg_vol_km3_mod_bndhigh, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=args.a_multiplier_bndhigh, + fs=args.fs, + debug=debug, + ) + + print("Region:", reg) + print("Consensus [km3] :", reg_vol_km3_con) + print("Model [km3] :", reg_vol_km3_mod) + print("Model bndlow [km3] :", reg_vol_km3_mod_bndlow) + print("Model bndhigh [km3]:", reg_vol_km3_mod_bndhigh) # ===== OPTIMIZATION ===== # Check consensus is within bounds @@ -378,34 +506,71 @@ def main(): a_multiplier_opt = args.a_multiplier_bndhigh # If so, then find optimal glens_a_multiplier else: + def to_minimize(a_multiplier): """Objective function to minimize""" - reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=a_multiplier, fs=args.fs, - debug=debug) + reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=a_multiplier, + fs=args.fs, + debug=debug, + ) return reg_vol_km3_mod - reg_vol_km3_con + # Brentq minimization - a_multiplier_opt, r = brentq(to_minimize, args.a_multiplier_bndlow, args.a_multiplier_bndhigh, rtol=1e-2, - full_output=True) + a_multiplier_opt, r = brentq( + to_minimize, + args.a_multiplier_bndlow, + args.a_multiplier_bndhigh, + rtol=1e-2, + full_output=True, + ) # Re-run to get estimates - reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=a_multiplier_opt, fs=args.fs, - debug=debug) - - print('\n\nOptimized:\n glens_a_multiplier:', np.round(a_multiplier_opt,3)) - print(' Consensus [km3]:', reg_vol_km3_con) - print(' Model [km3] :', reg_vol_km3_mod) + reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=a_multiplier_opt, + fs=args.fs, + debug=debug, + ) + + print( + "\n\nOptimized:\n glens_a_multiplier:", np.round(a_multiplier_opt, 3) + ) + print(" Consensus [km3]:", reg_vol_km3_con) + print(" Model [km3] :", reg_vol_km3_mod) # ===== EXPORT RESULTS ===== - glena_cns = ['O1Region', 'count', 'glens_a_multiplier', 'fs', 'reg_vol_km3_consensus', 'reg_vol_km3_modeled'] - glena_df_single = pd.DataFrame(np.zeros((1,len(glena_cns))), columns=glena_cns) - glena_df_single.loc[0,:] = [reg, main_glac_rgi_subset.shape[0], a_multiplier_opt, args.fs, reg_vol_km3_con, reg_vol_km3_mod] + glena_cns = [ + "O1Region", + "count", + "glens_a_multiplier", + "fs", + "reg_vol_km3_consensus", + "reg_vol_km3_modeled", + ] + glena_df_single = pd.DataFrame(np.zeros((1, len(glena_cns))), columns=glena_cns) + glena_df_single.loc[0, :] = [ + reg, + main_glac_rgi_subset.shape[0], + a_multiplier_opt, + args.fs, + reg_vol_km3_con, + reg_vol_km3_mod, + ] try: - glena_df = pd.read_csv(f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}") + glena_df = pd.read_csv( + f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}" + ) # Add or overwrite existing file glena_idx = np.where((glena_df.O1Region == reg))[0] if len(glena_idx) > 0: - glena_df.loc[glena_idx,:] = glena_df_single.values + glena_df.loc[glena_idx, :] = glena_df_single.values else: glena_df = pd.concat([glena_df, glena_df_single], axis=0) @@ -413,13 +578,17 @@ def to_minimize(a_multiplier): glena_df = glena_df_single except Exception as err: - print(f'Error saving results: {err}') + print(f"Error saving results: {err}") - glena_df = glena_df.sort_values('O1Region', ascending=True) + 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) + 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") - print('\n\n------\nTotal processing time:', time.time()-time_start, 's') if __name__ == "__main__": main() diff --git a/pygem/bin/run/run_mcmc_priors.py b/pygem/bin/run/run_mcmc_priors.py index 88e5a3ce..69a0cee8 100644 --- a/pygem/bin/run/run_mcmc_priors.py +++ b/pygem/bin/run/run_mcmc_priors.py @@ -1,4 +1,4 @@ -""" Export regional priors """ +"""Export regional priors""" import argparse import json @@ -22,29 +22,47 @@ import pygem.pygem_modelsetup as modelsetup # Region dictionary for titles -reg_dict = {1:'Alaska', - 2:'W CA/USA', - 3:'Arctic CA N', - 4:'Arctic CA S', - 5:'Greenland', - 6:'Iceland', - 7:'Svalbard', - 8:'Scandinavia', - 9:'Russian Arctic', - 10:'N Asia', - 11:'C Europe', - 12:'Caucasus/Middle East', - 13:'C Asia', - 14:'S Asia W', - 15:'S Asia E', - 16:'Low Latitudes', - 17:'S Andes', - 18:'New Zealand', - 19:'Antarctica'} +reg_dict = { + 1: "Alaska", + 2: "W CA/USA", + 3: "Arctic CA N", + 4: "Arctic CA S", + 5: "Greenland", + 6: "Iceland", + 7: "Svalbard", + 8: "Scandinavia", + 9: "Russian Arctic", + 10: "N Asia", + 11: "C Europe", + 12: "Caucasus/Middle East", + 13: "C Asia", + 14: "S Asia W", + 15: "S Asia E", + 16: "Low Latitudes", + 17: "S Andes", + 18: "New Zealand", + 19: "Antarctica", +} # list of prior fields -priors_cn = ['O1Region', 'O2Region', 'count', - 'kp_mean', 'kp_std', 'kp_med', 'kp_min', 'kp_max', 'kp_alpha', 'kp_beta', - 'tbias_mean', 'tbias_std', 'tbias_med', 'tbias_min', 'tbias_max'] +priors_cn = [ + "O1Region", + "O2Region", + "count", + "kp_mean", + "kp_std", + "kp_med", + "kp_min", + "kp_max", + "kp_alpha", + "kp_beta", + "tbias_mean", + "tbias_std", + "tbias_med", + "tbias_min", + "tbias_max", +] + + # FUNCTIONS def getparser(): """ @@ -52,93 +70,158 @@ def getparser(): """ parser = argparse.ArgumentParser(description="run calibration in parallel") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-option_calibration', action='store', type=str, default='emulator', - help='calibration option (defaultss to "emulator")') - parser.add_argument('-priors_reg_outpath', action='store', type=str, default=pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn'], - help='output path') + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default="emulator", + help='calibration option (defaultss to "emulator")', + ) + parser.add_argument( + "-priors_reg_outpath", + action="store", + type=str, + default=pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"], + help="output path", + ) # flags - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') - parser.add_argument('-p', '--plot', action='store_true', - help='Flag for plotting regional priors') + parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") + parser.add_argument( + "-p", "--plot", action="store_true", help="Flag for plotting regional priors" + ) return parser -def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): +def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=""): # EXPORT PRIORS if os.path.exists(priors_reg_outpath): priors_df = pd.read_csv(priors_reg_outpath) # Add or overwrite existing priors - priors_idx = np.where((priors_df.O1Region == reg) & (priors_df.O2Region == regO2))[0] + priors_idx = np.where( + (priors_df.O1Region == reg) & (priors_df.O2Region == regO2) + )[0] if len(priors_idx) > 0: - priors_df.loc[priors_idx,:] = priors_df_single.values + priors_df.loc[priors_idx, :] = priors_df_single.values else: priors_df = pd.concat([priors_df, priors_df_single], axis=0) else: priors_df = priors_df_single - priors_df = priors_df.sort_values(['O1Region', 'O2Region'], ascending=[True, True]) + priors_df = priors_df.sort_values(["O1Region", "O2Region"], ascending=[True, True]) priors_df.reset_index(inplace=True, drop=True) priors_df.to_csv(priors_reg_outpath, index=False) return priors_df -def plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2=''): - # Histograms and record model parameter statistics - fig, ax = plt.subplots(1,2, figsize=(6,4), gridspec_kw = {'wspace':0.3, 'hspace':0.3}) - labelsize = 1 - fig.text(0.5,0.9, 'Region ' + str(reg) + ' (subregion: ' + str(regO2) + ')'.replace(' (subregion: )', '(all subregions)'), ha='center', size=14) +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)') + 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) + 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 ncols = 3 - nrows = int(np.ceil(len(rgi_regionsO2)/ncols)) - priors_df_regO1 = priors_df.loc[priors_df['O1Region'] == reg] + 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): - priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] - kp_values = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, 'kp'].values + priors_df_regO2 = priors_df_regO1.loc[priors_df["O2Region"] == regO2] + kp_values = main_glac_rgi.loc[main_glac_rgi["O2Region"] == regO2, "kp"].values nglaciers = kp_values.shape[0] # Plot histogram - counts, bins, patches = ax[nrow,ncol].hist(kp_values, facecolor='grey', edgecolor='grey', - linewidth=0.1, bins=nbins, density=True) + counts, bins, patches = ax[nrow, ncol].hist( + kp_values, + facecolor="grey", + edgecolor="grey", + linewidth=0.1, + bins=nbins, + density=True, + ) # Plot gamma distribution alpha = priors_df_regO2.kp_alpha.values[0] beta = priors_df_regO2.kp_beta.values[0] - rv = stats.gamma(alpha, scale=1/beta) - ax[nrow,ncol].plot(bins, rv.pdf(bins), color='k') + rv = stats.gamma(alpha, scale=1 / beta) + ax[nrow, ncol].plot(bins, rv.pdf(bins), color="k") # add alpha and beta as text - gammatext = (r'$\alpha$=' + str(np.round(alpha,2)) + '\n' + r'$\beta$=' + str(np.round(beta,2)) - + '\n$n$=' + str(nglaciers)) - ax[nrow,ncol].text(0.98, 0.95, gammatext, size=10, horizontalalignment='right', - verticalalignment='top', transform=ax[nrow,ncol].transAxes) + gammatext = ( + r"$\alpha$=" + + str(np.round(alpha, 2)) + + "\n" + + r"$\beta$=" + + str(np.round(beta, 2)) + + "\n$n$=" + + str(nglaciers) + ) + ax[nrow, ncol].text( + 0.98, + 0.95, + gammatext, + size=10, + horizontalalignment="right", + verticalalignment="top", + transform=ax[nrow, ncol].transAxes, + ) # Subplot title - title_str = reg_dict[reg] + ' (' + str(regO2) + ')' - ax[nrow,ncol].text(0.5, 1.01, title_str, size=10, horizontalalignment='center', - verticalalignment='bottom', transform=ax[nrow,ncol].transAxes) + title_str = reg_dict[reg] + " (" + str(regO2) + ")" + ax[nrow, ncol].text( + 0.5, + 1.01, + title_str, + size=10, + horizontalalignment="center", + verticalalignment="bottom", + transform=ax[nrow, ncol].transAxes, + ) # Adjust row and column ncol += 1 @@ -147,48 +230,90 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): ncol = 0 # Remove extra plots - if len(rgi_regionsO2)%ncols > 0: - n_extras = ncols-len(rgi_regionsO2)%ncols + if len(rgi_regionsO2) % ncols > 0: + n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: - for nextra in np.arange(0,n_extras): - ax[nrow,ncol].axis('off') + for nextra in np.arange(0, n_extras): + ax[nrow, ncol].axis("off") ncol += 1 # Labels - fig.text(0.04, 0.5, 'Probability Density', va='center', ha='center', rotation='vertical', size=12) - fig.text(0.5, 0.04, '$k_{p}$ (-)', va='center', ha='center', size=12) + fig.text( + 0.04, + 0.5, + "Probability Density", + va="center", + ha="center", + rotation="vertical", + size=12, + ) + fig.text(0.5, 0.04, "$k_{p}$ (-)", va="center", ha="center", size=12) fig.set_size_inches(6, 6) - fig.savefig(fig_fp + 'priors_kp_O2Regions-' + str(reg) + '.png', bbox_inches='tight', dpi=300) + fig.savefig( + fig_fp + "priors_kp_O2Regions-" + str(reg) + ".png", + bbox_inches="tight", + dpi=300, + ) # ===== REGIONAL PRIOR: TEMPERATURE BIAS ====== - fig, ax = plt.subplots(nrows, ncols, squeeze=False, gridspec_kw={'wspace':0.3, 'hspace':0.3}) + fig, ax = plt.subplots( + nrows, ncols, squeeze=False, gridspec_kw={"wspace": 0.3, "hspace": 0.3} + ) nrow = 0 ncol = 0 for nreg, regO2 in enumerate(rgi_regionsO2): - - priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] - tbias_values = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, 'tbias'].values + priors_df_regO2 = priors_df_regO1.loc[priors_df["O2Region"] == regO2] + tbias_values = main_glac_rgi.loc[ + main_glac_rgi["O2Region"] == regO2, "tbias" + ].values nglaciers = tbias_values.shape[0] # Plot histogram - counts, bins, patches = ax[nrow,ncol].hist(tbias_values, facecolor='grey', edgecolor='grey', - linewidth=0.1, bins=nbins, density=True) + counts, bins, patches = ax[nrow, ncol].hist( + tbias_values, + facecolor="grey", + edgecolor="grey", + linewidth=0.1, + bins=nbins, + density=True, + ) # Plot gamma distribution mu = priors_df_regO2.tbias_mean.values[0] sigma = priors_df_regO2.tbias_std.values[0] rv = stats.norm(loc=mu, scale=sigma) - ax[nrow,ncol].plot(bins, rv.pdf(bins), color='k') + ax[nrow, ncol].plot(bins, rv.pdf(bins), color="k") # add alpha and beta as text - normtext = (r'$\mu$=' + str(np.round(mu,2)) + '\n' + r'$\sigma$=' + str(np.round(sigma,2)) - + '\n$n$=' + str(nglaciers)) - ax[nrow,ncol].text(0.98, 0.95, normtext, size=10, horizontalalignment='right', - verticalalignment='top', transform=ax[nrow,ncol].transAxes) + normtext = ( + r"$\mu$=" + + str(np.round(mu, 2)) + + "\n" + + r"$\sigma$=" + + str(np.round(sigma, 2)) + + "\n$n$=" + + str(nglaciers) + ) + ax[nrow, ncol].text( + 0.98, + 0.95, + normtext, + size=10, + horizontalalignment="right", + verticalalignment="top", + transform=ax[nrow, ncol].transAxes, + ) # Title - title_str = reg_dict[reg] + ' (' + str(regO2) + ')' - ax[nrow,ncol].text(0.5, 1.01, title_str, size=10, horizontalalignment='center', - verticalalignment='bottom', transform=ax[nrow,ncol].transAxes) + title_str = reg_dict[reg] + " (" + str(regO2) + ")" + ax[nrow, ncol].text( + 0.5, + 1.01, + title_str, + size=10, + horizontalalignment="center", + verticalalignment="bottom", + transform=ax[nrow, ncol].transAxes, + ) # Adjust row and column ncol += 1 @@ -197,78 +322,100 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): ncol = 0 # Remove extra plots - if len(rgi_regionsO2)%ncols > 0: - n_extras = ncols-len(rgi_regionsO2)%ncols + if len(rgi_regionsO2) % ncols > 0: + n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: - for nextra in np.arange(0,n_extras): - ax[nrow,ncol].axis('off') + for nextra in np.arange(0, n_extras): + ax[nrow, ncol].axis("off") ncol += 1 # Labels - fig.text(0.04, 0.5, 'Probability Density', va='center', ha='center', rotation='vertical', size=12) - fig.text(0.5, 0.04, r'$T_{bias}$ ($^\circ$C)', va='center', ha='center', size=12) + fig.text( + 0.04, + 0.5, + "Probability Density", + va="center", + ha="center", + rotation="vertical", + size=12, + ) + fig.text(0.5, 0.04, r"$T_{bias}$ ($^\circ$C)", va="center", ha="center", size=12) fig.set_size_inches(6, 6) - fig.savefig(fig_fp + 'priors_tbias_O2Regions-' + str(reg) + '.png', bbox_inches='tight', dpi=300) + fig.savefig( + fig_fp + "priors_tbias_O2Regions-" + str(reg) + ".png", + bbox_inches="tight", + dpi=300, + ) -def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, plot=False): - +def run( + reg, option_calibration="emulator", priors_reg_outpath="", debug=False, plot=False +): # Calibration filepath - modelprms_fp = pygem_prms['root'] + '/Output/calibration/' + str(reg).zfill(2) + '/' + modelprms_fp = pygem_prms["root"] + "/Output/calibration/" + str(reg).zfill(2) + "/" # Load glaciers - glac_list = [x.split('-')[0] for x in os.listdir(modelprms_fp) if x.endswith('-modelprms_dict.json')] + glac_list = [ + x.split("-")[0] + for x in os.listdir(modelprms_fp) + if x.endswith("-modelprms_dict.json") + ] glac_list = sorted(glac_list) main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_list) # Add model parameters to main dataframe - main_glac_rgi['kp'] = np.nan - main_glac_rgi['tbias'] = np.nan - main_glac_rgi['ddfsnow'] = np.nan - main_glac_rgi['mb_mwea'] = np.nan - main_glac_rgi['kp_em'] = np.nan - main_glac_rgi['tbias_em'] = np.nan - main_glac_rgi['ddfsnow_em'] = np.nan - main_glac_rgi['mb_mwea_em'] = np.nan + main_glac_rgi["kp"] = np.nan + main_glac_rgi["tbias"] = np.nan + main_glac_rgi["ddfsnow"] = np.nan + main_glac_rgi["mb_mwea"] = np.nan + main_glac_rgi["kp_em"] = np.nan + main_glac_rgi["tbias_em"] = np.nan + main_glac_rgi["ddfsnow_em"] = np.nan + main_glac_rgi["mb_mwea_em"] = np.nan for nglac, rgino_str in enumerate(list(main_glac_rgi.rgino_str.values)): - - glac_str = str(int(rgino_str.split('.')[0])) + '.' + rgino_str.split('.')[1] + glac_str = str(int(rgino_str.split(".")[0])) + "." + rgino_str.split(".")[1] # Load model parameters - modelprms_fn = glac_str + '-modelprms_dict.json' - with open(modelprms_fp + modelprms_fn, 'r') as f: + modelprms_fn = glac_str + "-modelprms_dict.json" + with open(modelprms_fp + modelprms_fn, "r") as f: modelprms_dict = json.load(f) - assert option_calibration in list(modelprms_dict.keys()), f'{glac_str}: {option_calibration} not in calibration data.' + 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] + main_glac_rgi.loc[nglac, "kp"] = modelprms["kp"][0] + main_glac_rgi.loc[nglac, "tbias"] = modelprms["tbias"][0] + main_glac_rgi.loc[nglac, "ddfsnow"] = modelprms["ddfsnow"][0] + main_glac_rgi.loc[nglac, "mb_mwea"] = modelprms["mb_mwea"][0] + main_glac_rgi.loc[nglac, "mb_obs_mwea"] = modelprms["mb_obs_mwea"][0] # get regional difference between calibrated mb_mwea and observed - main_glac_rgi['mb_dif_obs_cal'] = main_glac_rgi['mb_obs_mwea'] - main_glac_rgi['mb_mwea'] + main_glac_rgi["mb_dif_obs_cal"] = ( + main_glac_rgi["mb_obs_mwea"] - main_glac_rgi["mb_mwea"] + ) # define figure output path if plot: - fig_fp = os.path.split(priors_reg_outpath)[0] + '/figs/' + fig_fp = os.path.split(priors_reg_outpath)[0] + "/figs/" os.makedirs(fig_fp, exist_ok=True) # Priors for each subregion if reg not in [19]: rgi_regionsO2 = np.unique(main_glac_rgi.O2Region.values) for regO2 in rgi_regionsO2: - main_glac_rgi_subset = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, :] + main_glac_rgi_subset = main_glac_rgi.loc[ + main_glac_rgi["O2Region"] == regO2, : + ] if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2) # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset['kp']) - kp_std = np.std(main_glac_rgi_subset['kp']) - kp_med = np.median(main_glac_rgi_subset['kp']) - kp_min = np.min(main_glac_rgi_subset['kp']) - kp_max = np.max(main_glac_rgi_subset['kp']) + kp_mean = np.mean(main_glac_rgi_subset["kp"]) + kp_std = np.std(main_glac_rgi_subset["kp"]) + kp_med = np.median(main_glac_rgi_subset["kp"]) + kp_min = np.min(main_glac_rgi_subset["kp"]) + kp_max = np.max(main_glac_rgi_subset["kp"]) # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: @@ -278,30 +425,57 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, kp_alpha = kp_mean * kp_beta # Temperature bias - tbias_mean = main_glac_rgi_subset['tbias'].mean() - tbias_std = main_glac_rgi_subset['tbias'].std() - tbias_med = np.median(main_glac_rgi_subset['tbias']) - tbias_min = np.min(main_glac_rgi_subset['tbias']) - tbias_max = np.max(main_glac_rgi_subset['tbias']) + tbias_mean = main_glac_rgi_subset["tbias"].mean() + tbias_std = main_glac_rgi_subset["tbias"].std() + tbias_med = np.median(main_glac_rgi_subset["tbias"]) + tbias_min = np.min(main_glac_rgi_subset["tbias"]) + tbias_max = np.max(main_glac_rgi_subset["tbias"]) # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 if debug: - print('\n', reg, '(' + str(regO2) + ')') - print('kp (mean/std/med/min/max):', np.round(kp_mean,2), np.round(kp_std,2), - np.round(kp_med,2), np.round(kp_min,2), np.round(kp_max,2)) - print(' alpha/beta:', np.round(kp_alpha,2), np.round(kp_beta,2)) - print('tbias (mean/std/med/min/max):', np.round(tbias_mean,2), np.round(tbias_std,2), - np.round(tbias_med,2), np.round(tbias_min,2), np.round(tbias_max,2)) + print("\n", reg, "(" + str(regO2) + ")") + print( + "kp (mean/std/med/min/max):", + np.round(kp_mean, 2), + np.round(kp_std, 2), + np.round(kp_med, 2), + np.round(kp_min, 2), + np.round(kp_max, 2), + ) + print(" alpha/beta:", np.round(kp_alpha, 2), np.round(kp_beta, 2)) + print( + "tbias (mean/std/med/min/max):", + np.round(tbias_mean, 2), + np.round(tbias_std, 2), + np.round(tbias_med, 2), + np.round(tbias_min, 2), + np.round(tbias_max, 2), + ) # export results - priors_df_single = pd.DataFrame(np.zeros((1,len(priors_cn))), columns=priors_cn) - priors_df_single.loc[0,:] = ( - [reg, regO2, main_glac_rgi_subset.shape[0], - kp_mean, kp_std, kp_med, kp_min, kp_max, kp_alpha, kp_beta, - tbias_mean, tbias_std, tbias_med, tbias_min, tbias_max]) + priors_df_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) @@ -311,11 +485,11 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg) # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset['kp']) - kp_std = np.std(main_glac_rgi_subset['kp']) - kp_med = np.median(main_glac_rgi_subset['kp']) - kp_min = np.min(main_glac_rgi_subset['kp']) - kp_max = np.max(main_glac_rgi_subset['kp']) + kp_mean = np.mean(main_glac_rgi_subset["kp"]) + kp_std = np.std(main_glac_rgi_subset["kp"]) + kp_med = np.median(main_glac_rgi_subset["kp"]) + kp_min = np.min(main_glac_rgi_subset["kp"]) + kp_max = np.max(main_glac_rgi_subset["kp"]) # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: @@ -325,36 +499,64 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, kp_alpha = kp_mean * kp_beta # Temperature bias - tbias_mean = main_glac_rgi_subset['tbias'].mean() - tbias_std = main_glac_rgi_subset['tbias'].std() - tbias_med = np.median(main_glac_rgi_subset['tbias']) - tbias_min = np.min(main_glac_rgi_subset['tbias']) - tbias_max = np.max(main_glac_rgi_subset['tbias']) + tbias_mean = main_glac_rgi_subset["tbias"].mean() + tbias_std = main_glac_rgi_subset["tbias"].std() + tbias_med = np.median(main_glac_rgi_subset["tbias"]) + tbias_min = np.min(main_glac_rgi_subset["tbias"]) + tbias_max = np.max(main_glac_rgi_subset["tbias"]) # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 if debug: - print('\n', reg, '(all subregions)') - print('kp (mean/std/med/min/max):', np.round(kp_mean,2), np.round(kp_std,2), - np.round(kp_med,2), np.round(kp_min,2), np.round(kp_max,2)) - print(' alpha/beta:', np.round(kp_alpha,2), np.round(kp_beta,2)) - print('tbias (mean/std/med/min/max):', np.round(tbias_mean,2), np.round(tbias_std,2), - np.round(tbias_med,2), np.round(tbias_min,2), np.round(tbias_max,2)) + print("\n", reg, "(all subregions)") + print( + "kp (mean/std/med/min/max):", + np.round(kp_mean, 2), + np.round(kp_std, 2), + np.round(kp_med, 2), + np.round(kp_min, 2), + np.round(kp_max, 2), + ) + print(" alpha/beta:", np.round(kp_alpha, 2), np.round(kp_beta, 2)) + print( + "tbias (mean/std/med/min/max):", + np.round(tbias_mean, 2), + np.round(tbias_std, 2), + np.round(tbias_med, 2), + np.round(tbias_min, 2), + np.round(tbias_max, 2), + ) for regO2 in rgi_regionsO2: # export results - priors_df_single = pd.DataFrame(np.zeros((1,len(priors_cn))), columns=priors_cn) - priors_df_single.loc[0,:] = ( - [reg, regO2, main_glac_rgi_subset.shape[0], - kp_mean, kp_std, kp_med, kp_min, kp_max, kp_alpha, kp_beta, - tbias_mean, tbias_std, tbias_med, tbias_min, tbias_max]) + priors_df_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,12 +569,19 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(ncores) + ' cores...') - partial_function = partial(run, option_calibration=args.option_calibration, priors_reg_outpath=args.priors_reg_outpath, debug=args.debug, plot=args.plot) + print("Processing with " + str(ncores) + " cores...") + partial_function = partial( + run, + option_calibration=args.option_calibration, + priors_reg_outpath=args.priors_reg_outpath, + debug=args.debug, + plot=args.plot, + ) with multiprocessing.Pool(ncores) as p: p.map(partial_function, args.rgi_region01) - print('\n\n------\nTotal processing time:', time.time()-time_start, 's') + print("\n\n------\nTotal processing time:", time.time() - time_start, "s") + if __name__ == "__main__": main() diff --git a/pygem/bin/run/run_simulation.py b/pygem/bin/run/run_simulation.py index 2819151f..1219bbd2 100755 --- a/pygem/bin/run/run_simulation.py +++ b/pygem/bin/run/run_simulation.py @@ -56,9 +56,10 @@ from pygem.output import calc_stats_array from pygem.shop import debris -cfg.PARAMS['hydro_month_nh']=1 -cfg.PARAMS['hydro_month_sh']=1 -cfg.PARAMS['trapezoid_lambdas'] = 1 +cfg.PARAMS["hydro_month_nh"] = 1 +cfg.PARAMS["hydro_month_sh"] = 1 +cfg.PARAMS["trapezoid_lambdas"] = 1 + # ----- FUNCTIONS ----- def none_or_value(value): @@ -67,6 +68,7 @@ def none_or_value(value): return None return value + def getparser(): """ Use argparse to add arguments from the command line @@ -103,77 +105,236 @@ def getparser(): """ parser = argparse.ArgumentParser(description="Run PyGEM simulation") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-rgi_region02', type=str, default=pygem_prms['setup']['rgi_region02'], nargs='+', - help='Randoph Glacier Inventory subregion (either `all` or multiple spaced integers, e.g. `-run_region02 1 2 3`)') - parser.add_argument('-rgi_glac_number', action='store', type=float, default=pygem_prms['setup']['glac_no'], nargs='+', - help='Randoph Glacier Inventory glacier number (can take multiple)') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-rgi_glac_number_fn', action='store', type=str, default=None, - help='filepath containing list of rgi_glac_number, helpful for running batches on spc') - parser.add_argument('-gcm_list_fn', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='text file full of commands to run (ex. CanESM2 or CESM2)') - parser.add_argument('-gcm_name', action='store', type=str, default=pygem_prms['climate']['gcm_name'], - help='GCM name used for model run') - parser.add_argument('-scenario', action='store', type=none_or_value, default=pygem_prms['climate']['scenario'], - help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)') - parser.add_argument('-realization', action='store', type=str, default=None, - help='realization from large ensemble used for model run (ex. 1011.001 or 1301.020)') - parser.add_argument('-realization_list', action='store', type=str, default=None, - help='text file full of realizations to run') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-mcmc_burn_pct', action='store', type=int, default=0, - help='percent of MCMC chain to burn off from beginning (defaults to 0, assuming that burn in was performed in calibration)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-batch_number', action='store', type=int, default=None, - help='Batch number used to differentiate output on supercomputer') - parser.add_argument('-kp', action='store', type=float, default=pygem_prms['sim']['params']['kp'], - help='Precipitation bias') - parser.add_argument('-tbias', action='store', type=float, default=pygem_prms['sim']['params']['tbias'], - help='Temperature bias') - parser.add_argument('-ddfsnow', action='store', type=float, default=pygem_prms['sim']['params']['ddfsnow'], - help='Degree-day factor of snow') - parser.add_argument('-oggm_working_dir', action='store', type=str, default=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}", - help='Specify OGGM working dir - useful if performing a grid search and have duplicated glacier directories') - parser.add_argument('-option_calibration', action='store', type=none_or_value, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_dynamics', action='store', type=none_or_value, default=pygem_prms['sim']['option_dynamics'], - help='glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)') - parser.add_argument('-use_reg_glena', action='store', type=bool, default=pygem_prms['sim']['oggm_dynamics']['use_reg_glena'], - help='Take the glacier flow parameterization from regionally calibrated priors (boolean: `0` or `1`, `True` or `False`)') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-rgi_region02", + type=str, + default=pygem_prms["setup"]["rgi_region02"], + nargs="+", + help="Randoph Glacier Inventory subregion (either `all` or multiple spaced integers, e.g. `-run_region02 1 2 3`)", + ) + parser.add_argument( + "-rgi_glac_number", + action="store", + type=float, + default=pygem_prms["setup"]["glac_no"], + nargs="+", + help="Randoph Glacier Inventory glacier number (can take multiple)", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-rgi_glac_number_fn", + action="store", + type=str, + default=None, + help="filepath containing list of rgi_glac_number, helpful for running batches on spc", + ) + parser.add_argument( + "-gcm_list_fn", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="text file full of commands to run (ex. CanESM2 or CESM2)", + ) + parser.add_argument( + "-gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["gcm_name"], + help="GCM name used for model run", + ) + parser.add_argument( + "-scenario", + action="store", + type=none_or_value, + default=pygem_prms["climate"]["scenario"], + help="rcp or ssp scenario used for model run (ex. rcp26 or ssp585)", + ) + parser.add_argument( + "-realization", + action="store", + type=str, + default=None, + help="realization from large ensemble used for model run (ex. 1011.001 or 1301.020)", + ) + parser.add_argument( + "-realization_list", + action="store", + type=str, + default=None, + help="text file full of realizations to run", + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-mcmc_burn_pct", + action="store", + type=int, + default=0, + help="percent of MCMC chain to burn off from beginning (defaults to 0, assuming that burn in was performed in calibration)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument( + "-batch_number", + action="store", + type=int, + default=None, + help="Batch number used to differentiate output on supercomputer", + ) + parser.add_argument( + "-kp", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["kp"], + help="Precipitation bias", + ) + parser.add_argument( + "-tbias", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["tbias"], + help="Temperature bias", + ) + parser.add_argument( + "-ddfsnow", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["ddfsnow"], + help="Degree-day factor of snow", + ) + parser.add_argument( + "-oggm_working_dir", + action="store", + type=str, + default=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}", + help="Specify OGGM working dir - useful if performing a grid search and have duplicated glacier directories", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=none_or_value, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_dynamics", + action="store", + type=none_or_value, + default=pygem_prms["sim"]["option_dynamics"], + help="glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)", + ) + parser.add_argument( + "-use_reg_glena", + action="store", + type=bool, + default=pygem_prms["sim"]["oggm_dynamics"]["use_reg_glena"], + help="Take the glacier flow parameterization from regionally calibrated priors (boolean: `0` or `1`, `True` or `False`)", + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help="Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ 1: new prec scheme and temp building on HH2015, \ - 2: HH2015 methods, 3: quantile delta mapping)') - parser.add_argument('-nsims', action='store', type=int, default=pygem_prms['sim']['nsims'], - help='number of simulations (note, defaults to 1 if `option_calibration` != `MCMC`)') - parser.add_argument('-modelprms_fp', action='store', type=str, default=None, - help='model parameters filepath') - 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 @@ -198,12 +359,12 @@ def run(list_packed_vars): gcm_name = list_packed_vars[2] realization = list_packed_vars[3] if (gcm_name != args.ref_gcm_name) and (args.scenario is None): - scenario = os.path.basename(args.gcm_list_fn).split('_')[1] + scenario = os.path.basename(args.gcm_list_fn).split("_")[1] else: scenario = args.scenario debug = args.debug if debug: - print(f'scenario:{scenario}') + print(f"scenario:{scenario}") # ===== LOAD GLACIERS ===== main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) @@ -213,29 +374,35 @@ def run(list_packed_vars): # adjust end year in event that gcm_end year precedes ref_startyear ref_endyear = min([args.ref_endyear, args.gcm_endyear]) dates_table_ref = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=ref_endyear, - spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) + startyear=args.ref_startyear, + endyear=ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) # GCM Full Period (includes reference and simulation periods) dates_table_full = modelsetup.datesmodelrun( - startyear=min([args.ref_startyear,args.gcm_startyear]), - endyear=args.gcm_endyear, spinupyears=pygem_prms['climate']['gcm_spinupyears'], - option_wateryear=pygem_prms['climate']['gcm_wateryear']) + startyear=min([args.ref_startyear, args.gcm_startyear]), + endyear=args.gcm_endyear, + spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + option_wateryear=pygem_prms["climate"]["gcm_wateryear"], + ) # GCM Simulation Period dates_table = modelsetup.datesmodelrun( - startyear=args.gcm_startyear, endyear=args.gcm_endyear, - spinupyears=pygem_prms['climate']['gcm_spinupyears'], - option_wateryear=pygem_prms['climate']['gcm_wateryear']) + startyear=args.gcm_startyear, + endyear=args.gcm_endyear, + spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + option_wateryear=pygem_prms["climate"]["gcm_wateryear"], + ) if debug: - print('ref years:', args.ref_startyear, ref_endyear) - print('sim years:', args.gcm_startyear, args.gcm_endyear) + print("ref years:", args.ref_startyear, ref_endyear) + print("sim years:", args.gcm_startyear, args.gcm_endyear) # ===== LOAD CLIMATE DATA ===== # Climate class - if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: + if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: gcm = class_climate.GCM(name=gcm_name) ref_gcm = gcm dates_table_ref = dates_table_full @@ -244,426 +411,646 @@ def run(list_packed_vars): if realization is None: gcm = class_climate.GCM(name=gcm_name, scenario=scenario) else: - gcm = class_climate.GCM(name=gcm_name, scenario=scenario, realization=realization) + gcm = class_climate.GCM( + name=gcm_name, scenario=scenario, realization=realization + ) # Reference GCM ref_gcm = class_climate.GCM(name=args.ref_gcm_name) # ----- Select Temperature and Precipitation Data ----- # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, - dates_table_full) - ref_temp, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.temp_fn, ref_gcm.temp_vn, - main_glac_rgi, dates_table_ref) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table_full + ) + ref_temp, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.temp_fn, ref_gcm.temp_vn, main_glac_rgi, dates_table_ref + ) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, - dates_table_full) - ref_prec, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.prec_fn, ref_gcm.prec_vn, - main_glac_rgi, dates_table_ref) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table_full + ) + ref_prec, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.prec_fn, ref_gcm.prec_vn, main_glac_rgi, dates_table_ref + ) # Elevation [m asl] try: - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) except: gcm_elev = None - ref_elev = ref_gcm.importGCMfxnearestneighbor_xarray(ref_gcm.elev_fn, ref_gcm.elev_vn, main_glac_rgi) + ref_elev = ref_gcm.importGCMfxnearestneighbor_xarray( + ref_gcm.elev_fn, ref_gcm.elev_vn, main_glac_rgi + ) # ----- Temperature and Precipitation Bias Adjustments ----- # No adjustments if args.option_bias_adjustment == 0 or gcm_name == args.ref_gcm_name: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table_full[dates_cn].to_list().index(args.gcm_startyear) gcm_elev_adj = gcm_elev - gcm_temp_adj = gcm_temp[:,sim_idx_start:] - gcm_prec_adj = gcm_prec[:,sim_idx_start:] + gcm_temp_adj = gcm_temp[:, sim_idx_start:] + gcm_prec_adj = gcm_prec[:, sim_idx_start:] # Bias correct based on reference climate data else: # OPTION 1: Adjust temp using Huss and Hock (2015), prec similar but addresses for variance and outliers if args.option_bias_adjustment == 1: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_opt1( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # OPTION 2: Adjust temp and prec using Huss and Hock (2015) elif args.option_bias_adjustment == 2: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_HH2015( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # OPTION 3: Adjust temp and prec using quantile delta mapping, Cannon et al. (2015) elif args.option_bias_adjustment == 3: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) - + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_QDM( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_QDM( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # assert that the gcm_elev_adj is not None - assert gcm_elev_adj is not None, 'No GCM elevation data' + assert gcm_elev_adj is not None, "No GCM elevation data" # ----- Other Climate Datasets (Air temperature variability [degC] and Lapse rate [K m-1]) # Air temperature variability [degC] - if pygem_prms['mb']['option_ablation'] != 2: - gcm_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) - ref_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table_ref.shape[0])) - elif pygem_prms['mb']['option_ablation'] == 2 and gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi, dates_table) + if pygem_prms["mb"]["option_ablation"] != 2: + gcm_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) + ref_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table_ref.shape[0])) + elif pygem_prms["mb"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table + ) ref_tempstd = gcm_tempstd - elif pygem_prms['mb']['option_ablation'] == 2 and args.ref_gcm_name in ['ERA5']: + elif pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in ["ERA5"]: # Compute temp std based on reference climate data - ref_tempstd, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.tempstd_fn, ref_gcm.tempstd_vn, - main_glac_rgi, dates_table_ref) + ref_tempstd, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.tempstd_fn, ref_gcm.tempstd_vn, main_glac_rgi, dates_table_ref + ) # Monthly average from reference climate data - gcm_tempstd = gcmbiasadj.monthly_avg_array_rolled(ref_tempstd, dates_table_ref, dates_table_full) + gcm_tempstd = gcmbiasadj.monthly_avg_array_rolled( + ref_tempstd, dates_table_ref, dates_table_full + ) else: - gcm_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) - ref_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table_ref.shape[0])) + gcm_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) + ref_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table_ref.shape[0])) # Lapse rate - if gcm_name in ['ERA-Interim', 'ERA5']: - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + if gcm_name in ["ERA-Interim", "ERA5"]: + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) ref_lr = gcm_lr else: # Compute lapse rates based on reference climate data - ref_lr, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.lr_fn, ref_gcm.lr_vn, main_glac_rgi, - dates_table_ref) + ref_lr, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.lr_fn, ref_gcm.lr_vn, main_glac_rgi, dates_table_ref + ) # Monthly average from reference climate data - gcm_lr = gcmbiasadj.monthly_avg_array_rolled(ref_lr, dates_table_ref, dates_table_full, args.gcm_startyear, args.ref_startyear) - + gcm_lr = gcmbiasadj.monthly_avg_array_rolled( + ref_lr, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ) # ===== RUN MASS BALANCE ===== # Number of simulations - if args.option_calibration == 'MCMC': + if args.option_calibration == "MCMC": nsims = args.nsims else: nsims = 1 # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) - nyears_ref = int(dates_table_ref.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) + nyears_ref = int(dates_table_ref.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' + assert True == False, "Adjust nyears for non-monthly timestep" for glac in range(main_glac_rgi.shape[0]): if glac == 0: - print(gcm_name,':', main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId']) + print( + gcm_name, + ":", + main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"], + ) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_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 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) + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + gdir = single_flowline_glacier_directory( + glacier_str, working_dir=args.oggm_working_dir + ) gdir.is_tidewater = False calving_k = None else: - gdir = single_flowline_glacier_directory_with_calving(glacier_str, working_dir=args.oggm_working_dir) + gdir = single_flowline_glacier_directory_with_calving( + glacier_str, working_dir=args.oggm_working_dir + ) gdir.is_tidewater = True - cfg.PARAMS['use_kcalving_for_inversion'] = True - cfg.PARAMS['use_kcalving_for_run'] = True + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True # Flowlines - fls = gdir.read_pickle('inversion_flowlines') + fls = gdir.read_pickle("inversion_flowlines") # Reference gdir for ice thickness inversion gdir_ref = copy.deepcopy(gdir) - gdir_ref.historical_climate = {'elev': ref_elev[glac], - 'temp': ref_temp[glac,:], - 'tempstd': ref_tempstd[glac,:], - 'prec': ref_prec[glac,:], - 'lr': ref_lr[glac,:]} + gdir_ref.historical_climate = { + "elev": ref_elev[glac], + "temp": ref_temp[glac, :], + "tempstd": ref_tempstd[glac, :], + "prec": ref_prec[glac, :], + "lr": ref_lr[glac, :], + } gdir_ref.dates_table = dates_table_ref # Add climate data to glacier directory - if pygem_prms['climate']['hindcast'] == True: + if pygem_prms["climate"]["hindcast"] == True: gcm_temp_adj = gcm_temp_adj[::-1] gcm_tempstd = gcm_tempstd[::-1] - gcm_prec_adj= gcm_prec_adj[::-1] + gcm_prec_adj = gcm_prec_adj[::-1] gcm_lr = gcm_lr[::-1] - gdir.historical_climate = {'elev': gcm_elev_adj[glac], - 'temp': gcm_temp_adj[glac,:], - 'tempstd': gcm_tempstd[glac,:], - 'prec': gcm_prec_adj[glac,:], - 'lr': gcm_lr[glac,:]} + gdir.historical_climate = { + "elev": gcm_elev_adj[glac], + "temp": gcm_temp_adj[glac, :], + "tempstd": gcm_tempstd[glac, :], + "prec": gcm_prec_adj[glac, :], + "lr": gcm_lr[glac, :], + } gdir.dates_table = dates_table glacier_area_km2 = fls[0].widths_m * fls[0].dx_meter / 1e6 if (fls is not None) and (glacier_area_km2.sum() > 0): - # Load model parameters if args.option_calibration: modelprms_fp = args.modelprms_fp if not modelprms_fp: - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms_fn - - assert os.path.exists(modelprms_fp), 'Calibrated parameters do not exist.' - with open(modelprms_fp, 'r') as f: + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + modelprms_fn + + assert os.path.exists(modelprms_fp), ( + "Calibrated parameters do not exist." + ) + with open(modelprms_fp, "r") as f: modelprms_dict = json.load(f) - assert args.option_calibration in modelprms_dict, ('Error: ' + args.option_calibration + - ' not in modelprms_dict') + assert args.option_calibration in modelprms_dict, ( + "Error: " + args.option_calibration + " not in modelprms_dict" + ) modelprms_all = modelprms_dict[args.option_calibration] # MCMC needs model parameters to be selected - if args.option_calibration == 'MCMC': + if args.option_calibration == "MCMC": if nsims == 1: - modelprms_all = {'kp': [np.median(modelprms_all['kp']['chain_0'])], - 'tbias': [np.median(modelprms_all['tbias']['chain_0'])], - 'ddfsnow': [np.median(modelprms_all['ddfsnow']['chain_0'])], - 'ddfice': [np.median(modelprms_all['ddfice']['chain_0'])], - 'tsnow_threshold': modelprms_all['tsnow_threshold'], - 'precgrad': modelprms_all['precgrad']} + modelprms_all = { + "kp": [np.median(modelprms_all["kp"]["chain_0"])], + "tbias": [np.median(modelprms_all["tbias"]["chain_0"])], + "ddfsnow": [ + np.median(modelprms_all["ddfsnow"]["chain_0"]) + ], + "ddfice": [ + np.median(modelprms_all["ddfice"]["chain_0"]) + ], + "tsnow_threshold": modelprms_all["tsnow_threshold"], + "precgrad": modelprms_all["precgrad"], + } else: # Select every kth iteration to use for the ensemble - mcmc_sample_no = len(modelprms_all['kp']['chain_0']) - sims_burn = int(args.mcmc_burn_pct/100*mcmc_sample_no) + mcmc_sample_no = len(modelprms_all["kp"]["chain_0"]) + sims_burn = int(args.mcmc_burn_pct / 100 * mcmc_sample_no) mp_spacing = int((mcmc_sample_no - sims_burn) / nsims) mp_idx_start = np.arange(sims_burn, sims_burn + mp_spacing) np.random.shuffle(mp_idx_start) mp_idx_start = mp_idx_start[0] - mp_idx_all = np.arange(mp_idx_start, mcmc_sample_no, mp_spacing) + mp_idx_all = np.arange( + mp_idx_start, mcmc_sample_no, mp_spacing + ) modelprms_all = { - 'kp': [modelprms_all['kp']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'tbias': [modelprms_all['tbias']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'ddfsnow': [modelprms_all['ddfsnow']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'ddfice': [modelprms_all['ddfice']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'tsnow_threshold': modelprms_all['tsnow_threshold'] * nsims, - 'precgrad': modelprms_all['precgrad'] * nsims} + "kp": [ + modelprms_all["kp"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "tbias": [ + modelprms_all["tbias"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "ddfsnow": [ + modelprms_all["ddfsnow"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "ddfice": [ + modelprms_all["ddfice"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "tsnow_threshold": modelprms_all["tsnow_threshold"] + * nsims, + "precgrad": modelprms_all["precgrad"] * nsims, + } else: nsims = 1 # Calving parameter - if glacier_rgi_table['TermType'] not 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' + assert os.path.exists(fp), ( + "Calibrated calving dataset does not exist" + ) calving_df = pd.read_csv(fp) calving_rgiids = list(calving_df.RGIId) # Use calibrated value if individual data available if rgiid in calving_rgiids: calving_idx = calving_rgiids.index(rgiid) - calving_k = calving_df.loc[calving_idx, 'calving_k'] - calving_k_nmad = calving_df.loc[calving_idx, 'calving_k_nmad'] + calving_k = calving_df.loc[calving_idx, "calving_k"] + calving_k_nmad = calving_df.loc[ + calving_idx, "calving_k_nmad" + ] # Otherwise, use region's median value else: - calving_df['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in calving_df.RGIId.values] - calving_df_reg = calving_df.loc[calving_df['O1Region'] == int(reg_str), :] + calving_df["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) + for x in calving_df.RGIId.values + ] + calving_df_reg = calving_df.loc[ + calving_df["O1Region"] == int(reg_str), : + ] calving_k = np.median(calving_df_reg.calving_k) calving_k_nmad = 0 if nsims == 1: calving_k_values = np.array([calving_k]) else: - calving_k_values = calving_k + np.random.normal(loc=0, scale=calving_k_nmad, size=nsims) + calving_k_values = calving_k + np.random.normal( + loc=0, scale=calving_k_nmad, size=nsims + ) calving_k_values[calving_k_values < 0.001] = 0.001 calving_k_values[calving_k_values > 5] = 5 -# calving_k_values[:] = calving_k + # 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) + 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)) + # 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' + 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_values:", + np.mean(calving_k_values), + np.std(calving_k_values), + "\n", + calving_k_values, + ) else: - modelprms_all = {'kp': [args.kp], - 'tbias': [args.tbias], - 'ddfsnow': [args.ddfsnow], - 'ddfice': [args.ddfsnow / pygem_prms['sim']['params']['ddfsnow_iceratio']], - 'tsnow_threshold': [pygem_prms['sim']['params']['tsnow_threshold']], - 'precgrad': [pygem_prms['sim']['params']['precgrad']]} - calving_k = np.zeros(nsims) + pygem_prms['sim']['params']['calving_k'] + modelprms_all = { + "kp": [args.kp], + "tbias": [args.tbias], + "ddfsnow": [args.ddfsnow], + "ddfice": [ + args.ddfsnow + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ], + "tsnow_threshold": [ + pygem_prms["sim"]["params"]["tsnow_threshold"] + ], + "precgrad": [pygem_prms["sim"]["params"]["precgrad"]], + } + calving_k = ( + np.zeros(nsims) + pygem_prms["sim"]["params"]["calving_k"] + ) calving_k_values = calving_k if debug and gdir.is_tidewater: - print('calving_k:', calving_k) - + print("calving_k:", calving_k) # Load OGGM glacier dynamics parameters (if necessary) - if args.option_dynamics in ['OGGM', 'MassRedistributionCurves']: - + if args.option_dynamics in ["OGGM", "MassRedistributionCurves"]: # CFL number (may use different values for calving to prevent errors) - if 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'] + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number" + ] else: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number_calving'] - + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number_calving" + ] if debug: - print('cfl number:', cfg.PARAMS['cfl_number']) + print("cfl number:", cfg.PARAMS["cfl_number"]) if args.use_reg_glena: - glena_df = pd.read_csv(f"{pygem_prms['root']}/{pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']}") + glena_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'] + fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] + glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ + "glen_a_multiplier" + ] # Time attributes and values - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - annual_columns = np.unique(dates_table['wateryear'].values)[0:int(dates_table.shape[0]/12)] + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + annual_columns = np.unique(dates_table["wateryear"].values)[ + 0 : int(dates_table.shape[0] / 12) + ] else: - annual_columns = np.unique(dates_table['year'].values)[0:int(dates_table.shape[0]/12)] + annual_columns = np.unique(dates_table["year"].values)[ + 0 : int(dates_table.shape[0] / 12) + ] # append additional year to year_values to account for mass and area at end of period - year_values = annual_columns[pygem_prms['climate']['gcm_spinupyears']:annual_columns.shape[0]] - year_values = np.concatenate((year_values, np.array([annual_columns[-1] + 1]))) - output_glac_temp_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_prec_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_acc_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_refreeze_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_melt_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_frontalablation_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_massbaltotal_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_runoff_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_snowline_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_area_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_bsl_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_change_ignored_annual = np.zeros((year_values.shape[0], nsims)) - output_glac_ELA_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_offglac_prec_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_refreeze_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_melt_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_snowpack_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_runoff_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan + year_values = annual_columns[ + pygem_prms["climate"]["gcm_spinupyears"] : annual_columns.shape[0] + ] + year_values = np.concatenate( + (year_values, np.array([annual_columns[-1] + 1])) + ) + output_glac_temp_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_prec_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_acc_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_refreeze_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_melt_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_frontalablation_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_massbaltotal_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_runoff_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_snowline_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_area_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_bsl_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_change_ignored_annual = np.zeros( + (year_values.shape[0], nsims) + ) + output_glac_ELA_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_offglac_prec_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_refreeze_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_melt_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_snowpack_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_runoff_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) output_glac_bin_icethickness_annual = None # Loop through model parameters count_exceed_boundary_errors = 0 mb_em_sims = [] for n_iter in range(nsims): - if debug: - print('n_iter:', n_iter) + print("n_iter:", n_iter) if calving_k is not None: calving_k = calving_k_values[n_iter] - cfg.PARAMS['calving_k'] = calving_k - cfg.PARAMS['inversion_calving_k'] = calving_k + cfg.PARAMS["calving_k"] = calving_k + cfg.PARAMS["inversion_calving_k"] = calving_k # successful_run used to continue runs when catching specific errors successful_run = True - modelprms = {'kp': modelprms_all['kp'][n_iter], - 'tbias': modelprms_all['tbias'][n_iter], - 'ddfsnow': modelprms_all['ddfsnow'][n_iter], - 'ddfice': modelprms_all['ddfice'][n_iter], - 'tsnow_threshold': modelprms_all['tsnow_threshold'][n_iter], - 'precgrad': modelprms_all['precgrad'][n_iter]} + 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))) - - #%% + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) + + # %% # ----- ICE THICKNESS INVERSION using OGGM ----- if args.option_dynamics is not None: # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms['mb']['include_debris']: + if pygem_prms["mb"]["include_debris"]: inversion_filter = True else: inversion_filter = False # Perform inversion based on PyGEM MB using reference directory - mbmod_inv = PyGEMMassBalance(gdir_ref, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True, - inversion_filter=inversion_filter) -# if debug: -# h, w = gdir.get_inversion_flowline_hw() -# mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * -# pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) -# plt.plot(mb_t0, h, '.') -# plt.ylabel('Elevation') -# plt.xlabel('Mass balance (mwea)') -# plt.show() + mbmod_inv = PyGEMMassBalance( + gdir_ref, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + inversion_filter=inversion_filter, + ) + # if debug: + # h, w = gdir.get_inversion_flowline_hw() + # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * + # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + # plt.plot(mb_t0, h, '.') + # plt.ylabel('Elevation') + # plt.xlabel('Mass balance (mwea)') + # plt.show() # Non-tidewater glaciers - if not gdir.is_tidewater or not pygem_prms['setup']['include_frontalablation']: + if ( + not gdir.is_tidewater + or not pygem_prms["setup"]["include_frontalablation"] + ): # Arbitrariliy shift the MB profile up (or down) until mass balance is zero (equilibrium for inversion) - apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref)) + apparent_mb_from_any_mb( + gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref) + ) tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + tasks.mass_conservation_inversion( + gdir, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + ) # Tidewater glaciers else: - cfg.PARAMS['use_kcalving_for_inversion'] = True - cfg.PARAMS['use_kcalving_for_run'] = True - tasks.find_inversion_calving_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref), - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True + tasks.find_inversion_calving_from_any_mb( + gdir, + mb_model=mbmod_inv, + mb_years=np.arange(nyears_ref), + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + ) # ----- INDENTED TO BE JUST WITH DYNAMICS ----- - tasks.init_present_time_glacier(gdir) # adds bins below - if pygem_prms['mb']['include_debris']: - debris.debris_binned(gdir, fl_str='model_flowlines') # add debris enhancement factors to flowlines + tasks.init_present_time_glacier(gdir) # adds bins below + if pygem_prms["mb"]["include_debris"]: + debris.debris_binned( + gdir, fl_str="model_flowlines" + ) # add debris enhancement factors to flowlines try: - nfls = gdir.read_pickle('model_flowlines') + nfls = gdir.read_pickle("model_flowlines") except FileNotFoundError as e: - if 'model_flowlines.pkl' in str(e): + if "model_flowlines.pkl" in str(e): tasks.compute_downstream_line(gdir) tasks.compute_downstream_bedshape(gdir) - tasks.init_present_time_glacier(gdir) # adds bins below - nfls = gdir.read_pickle('model_flowlines') + tasks.init_present_time_glacier(gdir) # adds bins below + nfls = gdir.read_pickle("model_flowlines") else: raise # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle('inversion_input')[-1] - th = cls['hgt'][-1] - vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] + cls = gdir.read_pickle("inversion_input")[-1] + th = cls["hgt"][-1] + vmin, vmax = cfg.PARAMS["free_board_marine_terminating"] water_level = utils.clip_scalar(0, th - vmax, th - vmin) # No ice dynamics options @@ -675,21 +1062,30 @@ def run(list_packed_vars): # ------ MODEL WITH EVOLVING AREA ------ # Mass balance model - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=nfls, option_areaconstant=False) + mbmod = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=nfls, + option_areaconstant=False, + ) # Glacier dynamics model - if args.option_dynamics == 'OGGM': + if args.option_dynamics == "OGGM": if debug: - print('OGGM GLACIER DYNAMICS!') + print("OGGM GLACIER DYNAMICS!") # new numerical scheme is SemiImplicitModel() but doesn't have frontal ablation yet # FluxBasedModel is old numerical scheme but includes frontal ablation - ev_model = FluxBasedModel(nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + ev_model = FluxBasedModel( + nfls, + y0=0, + mb_model=mbmod, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) if debug: graphics.plot_modeloutput_section(ev_model) @@ -697,355 +1093,670 @@ def run(list_packed_vars): try: diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual[-1] = diag.volume_m3[-1] - ev_model.mb_model.glac_wide_area_annual[-1] = diag.area_m2[-1] + ev_model.mb_model.glac_wide_volume_annual[-1] = ( + diag.volume_m3[-1] + ) + ev_model.mb_model.glac_wide_area_annual[-1] = diag.area_m2[ + -1 + ] # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Glacier-wide frontal ablation (m3 w.e.) # - note: diag.calving_m3 is cumulative calving if debug: - print('\n\ndiag.calving_m3:', diag.calving_m3.values) - print('calving_m3_since_y0:', ev_model.calving_m3_since_y0) - calving_m3_annual = ((diag.calving_m3.values[1:] - diag.calving_m3.values[0:-1]) * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + print( + "\n\ndiag.calving_m3:", diag.calving_m3.values + ) + print( + "calving_m3_since_y0:", + ev_model.calving_m3_since_y0, + ) + calving_m3_annual = ( + ( + diag.calving_m3.values[1:] + - diag.calving_m3.values[0:-1] + ) + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) for n in np.arange(calving_m3_annual.shape[0]): - ev_model.mb_model.glac_wide_frontalablation[12*n+11] = calving_m3_annual[n] + ev_model.mb_model.glac_wide_frontalablation[ + 12 * n + 11 + ] = calving_m3_annual[n] # Glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg calving_m3:', calving_m3_annual.sum() / nyears) - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg calving_m3:", + calving_m3_annual.sum() / nyears, + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"]["density_ice"] + / 1e12 + / nyears, + 4, + ), + ) except RuntimeError as e: - if 'Glacier exceeds domain boundaries' in repr(e): + if "Glacier exceeds domain boundaries" in repr(e): count_exceed_boundary_errors += 1 successful_run = False # LOG FAILURE - fail_domain_fp = (pygem_prms['root'] + '/Output/simulations/fail-exceed_domain/' + reg_str + '/' - + gcm_name + '/') - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_domain_fp += scenario + '/' + fail_domain_fp = ( + pygem_prms["root"] + + "/Output/simulations/fail-exceed_domain/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + fail_domain_fp += scenario + "/" if not os.path.exists(fail_domain_fp): os.makedirs(fail_domain_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" - with open(fail_domain_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' failed to complete ' + - str(count_exceed_boundary_errors) + ' simulations') + with open( + fail_domain_fp + txt_fn_fail, "w" + ) as text_file: + text_file.write( + glacier_str + + " failed to complete " + + str(count_exceed_boundary_errors) + + " simulations" + ) elif gdir.is_tidewater: if debug: - print('OGGM dynamics failed, using mass redistribution curves') + print( + "OGGM dynamics failed, using mass redistribution curves" + ) # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level, - spinupyears=pygem_prms['climate']['ref_spinupyears'] - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + spinupyears=pygem_prms["climate"][ + "ref_spinupyears" + ], + ) _, diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ( + ev_model.mb_model.glac_bin_frontalablation.sum(0) + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"]["density_ice"] + / 1e12 + / nyears, + 4, + ), + ) except: if gdir.is_tidewater: if debug: - print('OGGM dynamics failed, using mass redistribution curves') - # Mass redistribution curves glacier dynamics model + print( + "OGGM dynamics failed, using mass redistribution curves" + ) + # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) _, diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ( + ev_model.mb_model.glac_bin_frontalablation.sum(0) + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"]["density_ice"] + / 1e12 + / nyears, + 4, + ), + ) else: raise # Mass redistribution model - elif args.option_dynamics == 'MassRedistributionCurves': + elif args.option_dynamics == "MassRedistributionCurves": if debug: - print('MASS REDISTRIBUTION CURVES!') + print("MASS REDISTRIBUTION CURVES!") ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, -# water_level=gdir.get_diagnostics().get('calving_water_level', None) - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + # water_level=gdir.get_diagnostics().get('calving_water_level', None) + water_level=water_level, + ) if debug: - print('New glacier vol', ev_model.volume_m3) + print("New glacier vol", ev_model.volume_m3) graphics.plot_modeloutput_section(ev_model) plt.show() try: _, diag = ev_model.run_until_and_store(nyears) -# print('shape of volume:', ev_model.mb_model.glac_wide_volume_annual.shape, diag.volume_m3.shape) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values + # print('shape of volume:', ev_model.mb_model.glac_wide_volume_annual.shape, diag.volume_m3.shape) + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ( + ev_model.mb_model.glac_bin_frontalablation.sum(0) + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"]["density_ice"] + / 1e12 + / nyears, + 4, + ), + ) except RuntimeError as e: - if 'Glacier exceeds domain boundaries' in repr(e): + if "Glacier exceeds domain boundaries" in repr(e): count_exceed_boundary_errors += 1 successful_run = False # LOG FAILURE - fail_domain_fp = (pygem_prms['root'] + '/Output/simulations/fail-exceed_domain/' + reg_str + '/' - + gcm_name + '/') - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_domain_fp += scenario + '/' + fail_domain_fp = ( + pygem_prms["root"] + + "/Output/simulations/fail-exceed_domain/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + fail_domain_fp += scenario + "/" if not os.path.exists(fail_domain_fp): os.makedirs(fail_domain_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" - with open(fail_domain_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' failed to complete ' + - str(count_exceed_boundary_errors) + ' simulations') + with open( + fail_domain_fp + txt_fn_fail, "w" + ) as text_file: + text_file.write( + glacier_str + + " failed to complete " + + str(count_exceed_boundary_errors) + + " simulations" + ) else: raise - - - elif args.option_dynamics is None: # Mass balance model ev_model = None diag = xr.Dataset() - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True) + mbmod = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + ) # ----- MODEL RUN WITH CONSTANT GLACIER AREA ----- years = np.arange(args.gcm_startyear, args.gcm_endyear + 1) mb_all = [] for year in years - years[0]: - mb_annual = mbmod.get_annual_mb(nfls[0].surface_h, fls=nfls, fl_id=0, year=year, - debug=True) - mb_mwea = (mb_annual * 365 * 24 * 3600 * pygem_prms['constants']['density_ice'] / - pygem_prms['constants']['density_water']) - glac_wide_mb_mwea = ((mb_mwea * mbmod.glacier_area_initial).sum() / - mbmod.glacier_area_initial.sum()) + mb_annual = mbmod.get_annual_mb( + nfls[0].surface_h, + fls=nfls, + fl_id=0, + year=year, + debug=True, + ) + mb_mwea = ( + mb_annual + * 365 + * 24 + * 3600 + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) + glac_wide_mb_mwea = ( + mb_mwea * mbmod.glacier_area_initial + ).sum() / mbmod.glacier_area_initial.sum() mb_all.append(glac_wide_mb_mwea) mbmod.glac_wide_area_annual[-1] = mbmod.glac_wide_area_annual[0] - mbmod.glac_wide_volume_annual[-1] = mbmod.glac_wide_volume_annual[0] - diag['area_m2'] = mbmod.glac_wide_area_annual - diag['volume_m3'] = mbmod.glac_wide_volume_annual - diag['volume_bsl_m3'] = 0 + mbmod.glac_wide_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_dx_meter = getattr(ev_model.fls[0], "dx_meter", None) + fl_widths_m = getattr(ev_model.fls[0], "widths_m", None) + fl_section = getattr(ev_model.fls[0], "section", None) else: - fl_dx_meter = getattr(nfls[0], 'dx_meter', None) - fl_widths_m = getattr(nfls[0], 'widths_m', None) - fl_section = getattr(nfls[0],'section',None) + fl_dx_meter = getattr(nfls[0], "dx_meter", None) + fl_widths_m = getattr(nfls[0], "widths_m", None) + fl_section = getattr(nfls[0], "section", None) if fl_section is not None and fl_widths_m is not None: # thickness icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] - output_glac_bin_icethickness_annual_sim[:,-1,0] = icethickness_t0 + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] + / fl_widths_m[fl_widths_m > 0] + ) + output_glac_bin_icethickness_annual_sim[:, -1, 0] = ( + icethickness_t0 + ) # mass - glacier_vol_t0 = fl_widths_m * fl_dx_meter * icethickness_t0 - output_glac_bin_mass_annual_sim[:,-1,0] = glacier_vol_t0 * pygem_prms['constants']['density_ice'] - output_glac_bin_area_annual = output_glac_bin_area_annual_sim - output_glac_bin_mass_annual = output_glac_bin_mass_annual_sim - output_glac_bin_icethickness_annual = output_glac_bin_icethickness_annual_sim - output_glac_bin_massbalclim_annual_sim = np.zeros(mbmod.glac_bin_icethickness_annual.shape) - output_glac_bin_massbalclim_annual_sim[:,:-1] = mbmod.glac_bin_massbalclim_annual - output_glac_bin_massbalclim_annual = output_glac_bin_massbalclim_annual_sim[:,:,np.newaxis] - output_glac_bin_massbalclim_monthly_sim = np.zeros(mbmod.glac_bin_massbalclim.shape) - output_glac_bin_massbalclim_monthly_sim = mbmod.glac_bin_massbalclim - output_glac_bin_massbalclim_monthly = output_glac_bin_massbalclim_monthly_sim[:,:,np.newaxis] + glacier_vol_t0 = ( + fl_widths_m * fl_dx_meter * icethickness_t0 + ) + output_glac_bin_mass_annual_sim[:, -1, 0] = ( + glacier_vol_t0 + * pygem_prms["constants"]["density_ice"] + ) + output_glac_bin_area_annual = ( + output_glac_bin_area_annual_sim + ) + output_glac_bin_mass_annual = ( + output_glac_bin_mass_annual_sim + ) + output_glac_bin_icethickness_annual = ( + output_glac_bin_icethickness_annual_sim + ) + output_glac_bin_massbalclim_annual_sim = np.zeros( + mbmod.glac_bin_icethickness_annual.shape + ) + output_glac_bin_massbalclim_annual_sim[:, :-1] = ( + mbmod.glac_bin_massbalclim_annual + ) + output_glac_bin_massbalclim_annual = ( + output_glac_bin_massbalclim_annual_sim[:, :, np.newaxis] + ) + output_glac_bin_massbalclim_monthly_sim = np.zeros( + mbmod.glac_bin_massbalclim.shape + ) + output_glac_bin_massbalclim_monthly_sim = ( + mbmod.glac_bin_massbalclim + ) + output_glac_bin_massbalclim_monthly = ( + output_glac_bin_massbalclim_monthly_sim[ + :, :, np.newaxis + ] + ) # accum - output_glac_bin_acc_monthly_sim = np.zeros(mbmod.bin_acc.shape) - output_glac_bin_acc_monthly_sim = mbmod.bin_acc - output_glac_bin_acc_monthly = output_glac_bin_acc_monthly_sim[:,:,np.newaxis] + output_glac_bin_acc_monthly_sim = np.zeros( + mbmod.bin_acc.shape + ) + output_glac_bin_acc_monthly_sim = mbmod.bin_acc + output_glac_bin_acc_monthly = ( + output_glac_bin_acc_monthly_sim[:, :, np.newaxis] + ) # refreeze - output_glac_bin_refreeze_monthly_sim = np.zeros(mbmod.glac_bin_refreeze.shape) - output_glac_bin_refreeze_monthly_sim = mbmod.glac_bin_refreeze - output_glac_bin_refreeze_monthly = output_glac_bin_refreeze_monthly_sim[:,:,np.newaxis] + output_glac_bin_refreeze_monthly_sim = np.zeros( + mbmod.glac_bin_refreeze.shape + ) + output_glac_bin_refreeze_monthly_sim = ( + mbmod.glac_bin_refreeze + ) + output_glac_bin_refreeze_monthly = ( + output_glac_bin_refreeze_monthly_sim[:, :, np.newaxis] + ) # melt - output_glac_bin_melt_monthly_sim = np.zeros(mbmod.glac_bin_melt.shape) - output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt - output_glac_bin_melt_monthly = output_glac_bin_melt_monthly_sim[:,:,np.newaxis] + output_glac_bin_melt_monthly_sim = np.zeros( + mbmod.glac_bin_melt.shape + ) + output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt + output_glac_bin_melt_monthly = ( + output_glac_bin_melt_monthly_sim[:, :, np.newaxis] + ) else: # Update the latest thickness and volume - output_glac_bin_area_annual_sim = mbmod.glac_bin_area_annual[:,:,np.newaxis] - output_glac_bin_mass_annual_sim = (mbmod.glac_bin_area_annual * - mbmod.glac_bin_icethickness_annual * - pygem_prms['constants']['density_ice'])[:,:,np.newaxis] - output_glac_bin_icethickness_annual_sim = (mbmod.glac_bin_icethickness_annual)[:,:,np.newaxis] + output_glac_bin_area_annual_sim = ( + mbmod.glac_bin_area_annual[:, :, np.newaxis] + ) + output_glac_bin_mass_annual_sim = ( + mbmod.glac_bin_area_annual + * mbmod.glac_bin_icethickness_annual + * pygem_prms["constants"]["density_ice"] + )[:, :, np.newaxis] + output_glac_bin_icethickness_annual_sim = ( + mbmod.glac_bin_icethickness_annual + )[:, :, np.newaxis] if ev_model is not None: - fl_dx_meter = getattr(ev_model.fls[0], 'dx_meter', None) - fl_widths_m = getattr(ev_model.fls[0], 'widths_m', None) - fl_section = getattr(ev_model.fls[0],'section',None) + fl_dx_meter = getattr(ev_model.fls[0], "dx_meter", None) + fl_widths_m = getattr(ev_model.fls[0], "widths_m", None) + fl_section = getattr(ev_model.fls[0], "section", None) else: - fl_dx_meter = getattr(nfls[0], 'dx_meter', None) - fl_widths_m = getattr(nfls[0], 'widths_m', None) - fl_section = getattr(nfls[0],'section',None) + fl_dx_meter = getattr(nfls[0], "dx_meter", None) + fl_widths_m = getattr(nfls[0], "widths_m", None) + fl_section = getattr(nfls[0], "section", None) if fl_section is not None and fl_widths_m is not None: # thickness icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] - output_glac_bin_icethickness_annual_sim[:,-1,0] = icethickness_t0 + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] + / fl_widths_m[fl_widths_m > 0] + ) + output_glac_bin_icethickness_annual_sim[:, -1, 0] = ( + icethickness_t0 + ) # mass - glacier_vol_t0 = fl_widths_m * fl_dx_meter * icethickness_t0 - output_glac_bin_mass_annual_sim[:,-1,0] = glacier_vol_t0 * pygem_prms['constants']['density_ice'] - output_glac_bin_area_annual = np.append(output_glac_bin_area_annual, - output_glac_bin_area_annual_sim, axis=2) - output_glac_bin_mass_annual = np.append(output_glac_bin_mass_annual, - output_glac_bin_mass_annual_sim, axis=2) - output_glac_bin_icethickness_annual = np.append(output_glac_bin_icethickness_annual, - output_glac_bin_icethickness_annual_sim, - axis=2) - output_glac_bin_massbalclim_annual_sim = np.zeros(mbmod.glac_bin_icethickness_annual.shape) - output_glac_bin_massbalclim_annual_sim[:,:-1] = mbmod.glac_bin_massbalclim_annual - output_glac_bin_massbalclim_annual = np.append(output_glac_bin_massbalclim_annual, - output_glac_bin_massbalclim_annual_sim[:,:,np.newaxis], - axis=2) - output_glac_bin_massbalclim_monthly_sim = np.zeros(mbmod.glac_bin_massbalclim.shape) - output_glac_bin_massbalclim_monthly_sim = mbmod.glac_bin_massbalclim - output_glac_bin_massbalclim_monthly = np.append(output_glac_bin_massbalclim_monthly, - output_glac_bin_massbalclim_monthly_sim[:,:,np.newaxis], - axis=2) + glacier_vol_t0 = ( + fl_widths_m * fl_dx_meter * icethickness_t0 + ) + output_glac_bin_mass_annual_sim[:, -1, 0] = ( + glacier_vol_t0 + * pygem_prms["constants"]["density_ice"] + ) + output_glac_bin_area_annual = np.append( + output_glac_bin_area_annual, + output_glac_bin_area_annual_sim, + axis=2, + ) + output_glac_bin_mass_annual = np.append( + output_glac_bin_mass_annual, + output_glac_bin_mass_annual_sim, + axis=2, + ) + output_glac_bin_icethickness_annual = np.append( + output_glac_bin_icethickness_annual, + output_glac_bin_icethickness_annual_sim, + axis=2, + ) + output_glac_bin_massbalclim_annual_sim = np.zeros( + mbmod.glac_bin_icethickness_annual.shape + ) + output_glac_bin_massbalclim_annual_sim[:, :-1] = ( + mbmod.glac_bin_massbalclim_annual + ) + output_glac_bin_massbalclim_annual = np.append( + output_glac_bin_massbalclim_annual, + output_glac_bin_massbalclim_annual_sim[ + :, :, np.newaxis + ], + axis=2, + ) + output_glac_bin_massbalclim_monthly_sim = np.zeros( + mbmod.glac_bin_massbalclim.shape + ) + output_glac_bin_massbalclim_monthly_sim = ( + mbmod.glac_bin_massbalclim + ) + output_glac_bin_massbalclim_monthly = np.append( + output_glac_bin_massbalclim_monthly, + output_glac_bin_massbalclim_monthly_sim[ + :, :, np.newaxis + ], + axis=2, + ) # accum - output_glac_bin_acc_monthly_sim = np.zeros(mbmod.bin_acc.shape) - output_glac_bin_acc_monthly_sim = mbmod.bin_acc - output_glac_bin_acc_monthly = np.append(output_glac_bin_acc_monthly, - output_glac_bin_acc_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_acc_monthly_sim = np.zeros( + mbmod.bin_acc.shape + ) + output_glac_bin_acc_monthly_sim = mbmod.bin_acc + output_glac_bin_acc_monthly = np.append( + output_glac_bin_acc_monthly, + output_glac_bin_acc_monthly_sim[:, :, np.newaxis], + axis=2, + ) # melt - output_glac_bin_melt_monthly_sim = np.zeros(mbmod.glac_bin_melt.shape) - output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt - output_glac_bin_melt_monthly = np.append(output_glac_bin_melt_monthly, - output_glac_bin_melt_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_melt_monthly_sim = np.zeros( + mbmod.glac_bin_melt.shape + ) + output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt + output_glac_bin_melt_monthly = np.append( + output_glac_bin_melt_monthly, + output_glac_bin_melt_monthly_sim[:, :, np.newaxis], + axis=2, + ) # refreeze - output_glac_bin_refreeze_monthly_sim = np.zeros(mbmod.glac_bin_refreeze.shape) - output_glac_bin_refreeze_monthly_sim = mbmod.glac_bin_refreeze - output_glac_bin_refreeze_monthly = np.append(output_glac_bin_refreeze_monthly, - output_glac_bin_refreeze_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_refreeze_monthly_sim = np.zeros( + mbmod.glac_bin_refreeze.shape + ) + output_glac_bin_refreeze_monthly_sim = ( + mbmod.glac_bin_refreeze + ) + output_glac_bin_refreeze_monthly = np.append( + output_glac_bin_refreeze_monthly, + output_glac_bin_refreeze_monthly_sim[:, :, np.newaxis], + axis=2, + ) # ===== Export Results ===== if count_exceed_boundary_errors < nsims: @@ -1053,257 +1764,511 @@ 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 + '/' - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_fp += scenario + '/' + fail_fp = ( + pygem_prms["root"] + + "/Output/simulations/failed/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + fail_fp += scenario + "/" if not os.path.exists(fail_fp): os.makedirs(fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + f' failed to complete simulation: {err}') + text_file.write(glacier_str + f" failed to complete simulation: {err}") # Global variables for Spyder development if args.ncores == 1: @@ -1311,17 +2276,21 @@ def run(list_packed_vars): main_vars = inspect.currentframe().f_locals -#%% PARALLEL PROCESSING +# %% PARALLEL PROCESSING def main(): time_start = time.time() parser = getparser() args = parser.parse_args() # date range check try: - assert args.ref_startyear < args.ref_endyear, f"ref_startyear [{args.ref_startyear}] must be less than ref_endyear [{args.ref_endyear}]" - assert args.gcm_startyear < args.gcm_endyear, f"gcm_startyear [{args.gcm_startyear}] must be less than gcm_endyear [{args.gcm_endyear}]" + assert args.ref_startyear < args.ref_endyear, ( + f"ref_startyear [{args.ref_startyear}] must be less than ref_endyear [{args.ref_endyear}]" + ) + assert args.gcm_startyear < args.gcm_endyear, ( + f"gcm_startyear [{args.gcm_startyear}] must be less than gcm_endyear [{args.gcm_endyear}]" + ) except AssertionError as err: - print('error: ', err) + print("error: ", err) sys.exit(1) # RGI glacier number if args.rgi_glac_number: @@ -1330,14 +2299,18 @@ def main(): glac_no = [float(g) for g in glac_no] glac_no = [f"{g:.5f}" if g >= 10 else f"0{g:.5f}" for g in glac_no] elif args.rgi_glac_number_fn is not None: - with open(args.rgi_glac_number_fn, 'r') as f: + with open(args.rgi_glac_number_fn, "r") as f: glac_no = json.load(f) else: main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=args.rgi_region01, rgi_regionsO2=args.rgi_region02, - include_landterm=pygem_prms['setup']['include_landterm'], include_laketerm=pygem_prms['setup']['include_laketerm'], - include_tidewater=pygem_prms['setup']['include_tidewater'], min_glac_area_km2=pygem_prms['setup']['min_glac_area_km2']) - glac_no = list(main_glac_rgi_all['rgino_str'].values) + rgi_regionsO1=args.rgi_region01, + rgi_regionsO2=args.rgi_region02, + include_landterm=pygem_prms["setup"]["include_landterm"], + include_laketerm=pygem_prms["setup"]["include_laketerm"], + include_tidewater=pygem_prms["setup"]["include_tidewater"], + min_glac_area_km2=pygem_prms["setup"]["min_glac_area_km2"], + ) + glac_no = list(main_glac_rgi_all["rgino_str"].values) # Number of cores for parallel processing if args.ncores > 1: @@ -1346,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 @@ -1357,18 +2332,18 @@ def main(): gcm_list = [args.ref_gcm_name] scenario = args.scenario else: - with open(args.gcm_list_fn, 'r') as gcm_fn: + with open(args.gcm_list_fn, "r") as gcm_fn: gcm_list = gcm_fn.read().splitlines() - scenario = os.path.basename(args.gcm_list_fn).split('_')[1] - print('Found %d gcms to process'%(len(gcm_list))) + scenario = os.path.basename(args.gcm_list_fn).split("_")[1] + print("Found %d gcms to process" % (len(gcm_list))) # Read realizations from argument parser if args.realization is not None: realizations = [args.realization] elif args.realization_list is not None: - with open(args.realization_list, 'r') as real_fn: + with open(args.realization_list, "r") as real_fn: realizations = list(real_fn.read().splitlines()) - print('Found %d realizations to process'%(len(realizations))) + print("Found %d realizations to process" % (len(realizations))) else: realizations = None @@ -1379,9 +2354,9 @@ def main(): # Loop through all GCMs for gcm_name in gcm_list: if args.scenario is None: - print('Processing:', gcm_name) + print("Processing:", gcm_name) elif args.scenario is not None: - print('Processing:', gcm_name, scenario) + print("Processing:", gcm_name, scenario) # Pack variables for multiprocessing list_packed_vars = [] if realizations is not None: @@ -1392,18 +2367,19 @@ def main(): 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...') + 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() diff --git a/pygem/class_climate.py b/pygem/class_climate.py index 1b8f1cb7..99b4a5eb 100755 --- a/pygem/class_climate.py +++ b/pygem/class_climate.py @@ -7,6 +7,7 @@ class of climate data and functions associated with manipulating the dataset to be in the proper format """ + import os import numpy as np @@ -22,7 +23,8 @@ class of climate data and functions associated with manipulating the dataset to # 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. @@ -35,16 +37,16 @@ class GCM(): realization : str realization from large ensemble (example: '1011.001' or '1301.020') """ - def __init__(self, - name=str(), - scenario=str(), - realization=None): + + def __init__(self, name=str(), scenario=str(), realization=None): """ Add variable name and specific properties associated with each gcm. """ - if pygem_prms['rgi']['rgi_lon_colname'] not in ['CenLon_360']: - assert 1==0, 'Longitude does not use 360 degrees. Check how negative values are handled!' + if pygem_prms["rgi"]["rgi_lon_colname"] not in ["CenLon_360"]: + assert 1 == 0, ( + "Longitude does not use 360 degrees. Check how negative values are handled!" + ) # Source of climate data self.name = name @@ -57,150 +59,261 @@ def __init__(self, self.realization = realization # Set parameters for CESM2 Large Ensemble - if self.name == 'smbb.f09_g17.LE2': + if self.name == "smbb.f09_g17.LE2": # Standardized CESM2 Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + scenario + '_' + name + '-' + realization + '.cam.h0.1980-2100.nc' - self.prec_fn = self.prec_vn + '_mon_' + scenario + '_' + name + '-' + realization + '.cam.h0.1980-2100.nc' - self.elev_fn = self.elev_vn + '_fx_' + scenario + '_' + name + '.cam.h0.nc' + self.temp_fn = ( + self.temp_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + ".cam.h0.1980-2100.nc" + ) + self.prec_fn = ( + self.prec_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + ".cam.h0.1980-2100.nc" + ) + self.elev_fn = ( + self.elev_vn + "_fx_" + scenario + "_" + name + ".cam.h0.nc" + ) # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario + pygem_prms['climate']['paths']['cesm2_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario + pygem_prms['climate']['paths']['cesm2_fp_fx_ending'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cesm2_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cesm2_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cesm2_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cesm2_fp_fx_ending"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario # Set parameters for GFDL SPEAR Large Ensemble - elif self.name == 'GFDL-SPEAR-MED': + elif self.name == "GFDL-SPEAR-MED": # Standardized GFDL SPEAR Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'zsurf' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "zsurf" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + scenario + '_' + name + '-' + realization + 'i1p1f1_gr3_1980-2100.nc' - self.prec_fn = self.prec_vn + '_mon_' + scenario + '_' + name + '-' + realization + 'i1p1f1_gr3_1980-2100.nc' - self.elev_fn = self.elev_vn + '_fx_' + scenario + '_' + name + '.nc' + self.temp_fn = ( + self.temp_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + "i1p1f1_gr3_1980-2100.nc" + ) + self.prec_fn = ( + self.prec_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + "i1p1f1_gr3_1980-2100.nc" + ) + self.elev_fn = self.elev_vn + "_fx_" + scenario + "_" + name + ".nc" # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario + pygem_prms['climate']['paths']['gfdl_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario + pygem_prms['climate']['paths']['gfdl_fp_fx_ending'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["gfdl_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["gfdl_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["gfdl_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["gfdl_fp_fx_ending"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario else: self.realization = [] # Set parameters for ERA5, ERA-Interim, and CMIP5 netcdf files - if self.name == 'ERA5': + if self.name == "ERA5": # Variable names - self.temp_vn = 't2m' - self.tempstd_vn = 't2m_std' - self.prec_vn = 'tp' - self.elev_vn = 'z' - self.lat_vn = 'latitude' - self.lon_vn = 'longitude' - self.time_vn = 'time' - self.lr_vn = 'lapserate' + self.temp_vn = "t2m" + self.tempstd_vn = "t2m_std" + self.prec_vn = "tp" + self.elev_vn = "z" + self.lat_vn = "latitude" + self.lon_vn = "longitude" + self.time_vn = "time" + self.lr_vn = "lapserate" # Variable filenames - self.temp_fn = pygem_prms['climate']['paths']['era5_temp_fn'] - self.tempstd_fn = pygem_prms['climate']['paths']['era5_tempstd_fn'] - self.prec_fn = pygem_prms['climate']['paths']['era5_prec_fn'] - self.elev_fn = pygem_prms['climate']['paths']['era5_elev_fn'] - self.lr_fn = pygem_prms['climate']['paths']['era5_lr_fn'] + self.temp_fn = pygem_prms["climate"]["paths"]["era5_temp_fn"] + self.tempstd_fn = pygem_prms["climate"]["paths"]["era5_tempstd_fn"] + self.prec_fn = pygem_prms["climate"]["paths"]["era5_prec_fn"] + self.elev_fn = pygem_prms["climate"]["paths"]["era5_elev_fn"] + self.lr_fn = pygem_prms["climate"]["paths"]["era5_lr_fn"] # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['era5_relpath'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['era5_relpath'] + self.var_fp = ( + pygem_prms["root"] + pygem_prms["climate"]["paths"]["era5_relpath"] + ) + self.fx_fp = ( + pygem_prms["root"] + pygem_prms["climate"]["paths"]["era5_relpath"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] - elif self.name == 'ERA-Interim': + elif self.name == "ERA-Interim": # Variable names - self.temp_vn = 't2m' - self.prec_vn = 'tp' - self.elev_vn = 'z' - self.lat_vn = 'latitude' - self.lon_vn = 'longitude' - self.time_vn = 'time' - self.lr_vn = 'lapserate' + self.temp_vn = "t2m" + self.prec_vn = "tp" + self.elev_vn = "z" + self.lat_vn = "latitude" + self.lon_vn = "longitude" + self.time_vn = "time" + self.lr_vn = "lapserate" # Variable filenames - self.temp_fn = pygem_prms['climate']['paths']['eraint_temp_fn'] - self.prec_fn = pygem_prms['climate']['paths']['eraint_prec_fn'] - self.elev_fn = pygem_prms['climate']['paths']['eraint_elev_fn'] - self.lr_fn = pygem_prms['climate']['paths']['eraint_lr_fn'] + self.temp_fn = pygem_prms["climate"]["paths"]["eraint_temp_fn"] + self.prec_fn = pygem_prms["climate"]["paths"]["eraint_prec_fn"] + self.elev_fn = pygem_prms["climate"]["paths"]["eraint_elev_fn"] + self.lr_fn = pygem_prms["climate"]["paths"]["eraint_lr_fn"] # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['eraint_relpath'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['eraint_relpath'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["eraint_relpath"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["eraint_relpath"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] # Standardized CMIP5 format (GCM/RCP) - elif 'rcp' in scenario: + elif "rcp" in scenario: # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' - self.prec_fn = self.prec_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' - self.elev_fn = self.elev_vn + '_fx_' + name + '_' + scenario + '_r0i0p0.nc' + self.temp_fn = ( + self.temp_vn + "_mon_" + name + "_" + scenario + "_r1i1p1_native.nc" + ) + self.prec_fn = ( + self.prec_vn + "_mon_" + name + "_" + scenario + "_r1i1p1_native.nc" + ) + self.elev_fn = ( + self.elev_vn + "_fx_" + name + "_" + scenario + "_r0i0p0.nc" + ) # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario + pygem_prms['climate']['paths']['cmip5_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario + pygem_prms['climate']['paths']['cmip5_fp_fx_ending'] - if not os.path.exists(self.var_fp) and os.path.exists(pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/'): - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' - if not os.path.exists(self.fx_fp) and os.path.exists(pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/'): - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cmip5_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cmip5_fp_fx_ending"] + ) + if not os.path.exists(self.var_fp) and os.path.exists( + pygem_prms["climate"]["paths"]["cmip5_relpath"] + name + "/" + ): + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ) + if not os.path.exists(self.fx_fp) and os.path.exists( + pygem_prms["climate"]["paths"]["cmip5_relpath"] + name + "/" + ): + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario # Standardized CMIP6 format (GCM/SSP) - elif 'ssp' in scenario: + elif "ssp" in scenario: # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = name + '_' + scenario + '_r1i1p1f1_' + self.temp_vn + '.nc' - self.prec_fn = name + '_' + scenario + '_r1i1p1f1_' + self.prec_vn + '.nc' - self.elev_fn = name + '_' + self.elev_vn + '.nc' + self.temp_fn = ( + name + "_" + scenario + "_r1i1p1f1_" + self.temp_vn + ".nc" + ) + self.prec_fn = ( + name + "_" + scenario + "_r1i1p1f1_" + self.prec_vn + ".nc" + ) + self.elev_fn = name + "_" + self.elev_vn + ".nc" # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip6_relpath'] + name + '/' - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip6_relpath'] + name + '/' + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip6_relpath"] + + name + + "/" + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip6_relpath"] + + name + + "/" + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario - def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): """ Import time invariant (constant) variables and extract nearest neighbor. @@ -225,25 +338,42 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): data = xr.open_dataset(self.fx_fp + filename) glac_variable = np.zeros(main_glac_rgi.shape[0]) # If time dimension included, then set the time index (required for ERA Interim, but not for CMIP5 or COAWST) - if 'time' in data[vn].coords: + if "time" in data[vn].coords: time_idx = 0 # ERA Interim has only 1 value of time, so index is 0 # Find Nearest Neighbor - if self.name == 'COAWST': + if self.name == "COAWST": for glac in range(main_glac_rgi.shape[0]): - latlon_dist = (((data[self.lat_vn].values - main_glac_rgi[self.rgi_lat_colname].values[glac])**2 + - (data[self.lon_vn].values - main_glac_rgi[self.rgi_lon_colname].values[glac])**2)**0.5) - latlon_nearidx = [x[0] for x in np.where(latlon_dist == latlon_dist.min())] + latlon_dist = ( + ( + data[self.lat_vn].values + - main_glac_rgi[self.rgi_lat_colname].values[glac] + ) + ** 2 + + ( + data[self.lon_vn].values + - main_glac_rgi[self.rgi_lon_colname].values[glac] + ) + ** 2 + ) ** 0.5 + latlon_nearidx = [ + x[0] for x in np.where(latlon_dist == latlon_dist.min()) + ] lat_nearidx = latlon_nearidx[0] lon_nearidx = latlon_nearidx[1] - glac_variable[glac] = ( - data[vn][latlon_nearidx[0], latlon_nearidx[1]].values) + glac_variable[glac] = data[vn][ + latlon_nearidx[0], latlon_nearidx[1] + ].values else: # argmin() finds the minimum distance between the glacier lat/lon and the GCM pixel - lat_nearidx = (np.abs(main_glac_rgi[self.rgi_lat_colname].values[:,np.newaxis] - - data.variables[self.lat_vn][:].values).argmin(axis=1)) - lon_nearidx = (np.abs(main_glac_rgi[self.rgi_lon_colname].values[:,np.newaxis] - - data.variables[self.lon_vn][:].values).argmin(axis=1)) + lat_nearidx = np.abs( + main_glac_rgi[self.rgi_lat_colname].values[:, np.newaxis] + - data.variables[self.lat_vn][:].values + ).argmin(axis=1) + lon_nearidx = np.abs( + main_glac_rgi[self.rgi_lon_colname].values[:, np.newaxis] + - data.variables[self.lon_vn][:].values + ).argmin(axis=1) latlon_nearidx = list(zip(lat_nearidx, lon_nearidx)) latlon_nearidx_unique = list(set(latlon_nearidx)) @@ -251,7 +381,9 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): 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 @@ -260,20 +392,26 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): # Correct units if necessary (CMIP5 already in m a.s.l., ERA Interim is geopotential [m2 s-2]) if vn == self.elev_vn: # If the variable has units associated with geopotential, then convert to m.a.s.l (ERA Interim) - if 'units' in data[vn].attrs and (data[vn].attrs['units'] == 'm**2 s**-2'): + if "units" in data[vn].attrs and (data[vn].attrs["units"] == "m**2 s**-2"): # Convert m2 s-2 to m by dividing by gravity (ERA Interim states to use 9.80665) glac_variable = glac_variable / 9.80665 # Elseif units already in m.a.s.l., then continue - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': + elif "units" in data[vn].attrs and data[vn].attrs["units"] == "m": pass # Otherwise, provide warning else: - print('Check units of elevation from GCM is m.') + print("Check units of elevation from GCM is m.") return glac_variable - - def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_table, realizations=['r1i1p1f1','r4i1p1f1']): + def importGCMvarnearestneighbor_xarray( + self, + filename, + vn, + main_glac_rgi, + dates_table, + realizations=["r1i1p1f1", "r4i1p1f1"], + ): """ Import time series of variables and extract nearest neighbor. @@ -303,26 +441,39 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ """ # Import netcdf file if not os.path.exists(self.var_fp + filename): - if os.path.exists(self.var_fp + filename.replace('r1i1p1f1','r4i1p1f1')): - filename = filename.replace('r1i1p1f1','r4i1p1f1') - if os.path.exists(self.var_fp + filename.replace('_native','')): - filename = filename.replace('_native','') + if os.path.exists(self.var_fp + filename.replace("r1i1p1f1", "r4i1p1f1")): + filename = filename.replace("r1i1p1f1", "r4i1p1f1") + if os.path.exists(self.var_fp + filename.replace("_native", "")): + filename = filename.replace("_native", "") data = xr.open_dataset(self.var_fp + filename) - glac_variable_series = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) + glac_variable_series = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) # Check GCM provides required years of data - years_check = pd.Series(data['time']).apply(lambda x: int(x.strftime('%Y'))) - assert years_check.max() >= dates_table.year.max(), self.name + ' does not provide data out to ' + str(dates_table.year.max()) - assert years_check.min() <= dates_table.year.min(), self.name + ' does not provide data back to ' + str(dates_table.year.min()) + years_check = pd.Series(data["time"]).apply(lambda x: int(x.strftime("%Y"))) + assert years_check.max() >= dates_table.year.max(), ( + self.name + " does not provide data out to " + str(dates_table.year.max()) + ) + assert years_check.min() <= dates_table.year.min(), ( + self.name + " does not provide data back to " + str(dates_table.year.min()) + ) # Determine the correct time indices - if self.timestep == 'monthly': - start_idx = (np.where(pd.Series(data[self.time_vn]).apply(lambda x: x.strftime('%Y-%m')) == - dates_table['date'].apply(lambda x: x.strftime('%Y-%m'))[0]))[0][0] - end_idx = (np.where(pd.Series(data[self.time_vn]).apply(lambda x: x.strftime('%Y-%m')) == - dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m'))[dates_table.shape[0] - 1]))[0][0] + if self.timestep == "monthly": + start_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply(lambda x: x.strftime("%Y-%m")) + == dates_table["date"].apply(lambda x: x.strftime("%Y-%m"))[0] + ) + )[0][0] + end_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply(lambda x: x.strftime("%Y-%m")) + == dates_table["date"].apply(lambda x: x.strftime("%Y-%m"))[ + dates_table.shape[0] - 1 + ] + ) + )[0][0] # np.where finds the index position where to values are equal # pd.Series(data.variables[gcm_time_varname]) creates a pandas series of the time variable associated with # the netcdf @@ -336,79 +487,119 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ # 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 # information - elif self.timestep == 'daily': - start_idx = (np.where(pd.Series(data[self.time_vn]) - .apply(lambda x: x.strftime('%Y-%m-%d')) == dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m-%d'))[0]))[0][0] - end_idx = (np.where(pd.Series(data[self.time_vn]) - .apply(lambda x: x.strftime('%Y-%m-%d')) == dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m-%d'))[dates_table.shape[0] - 1]))[0][0] + elif self.timestep == "daily": + start_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + == dates_table["date"].apply(lambda x: x.strftime("%Y-%m-%d"))[0] + ) + )[0][0] + end_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + == dates_table["date"].apply(lambda x: x.strftime("%Y-%m-%d"))[ + dates_table.shape[0] - 1 + ] + ) + )[0][0] # Extract the time series - time_series = pd.Series(data[self.time_vn][start_idx:end_idx+1]) + time_series = pd.Series(data[self.time_vn][start_idx : end_idx + 1]) # Find Nearest Neighbor - if self.name == 'COAWST': + if self.name == "COAWST": for glac in range(main_glac_rgi.shape[0]): - latlon_dist = (((data[self.lat_vn].values - main_glac_rgi[self.rgi_lat_colname].values[glac])**2 + - (data[self.lon_vn].values - main_glac_rgi[self.rgi_lon_colname].values[glac])**2)**0.5) - latlon_nearidx = [x[0] for x in np.where(latlon_dist == latlon_dist.min())] + latlon_dist = ( + ( + data[self.lat_vn].values + - main_glac_rgi[self.rgi_lat_colname].values[glac] + ) + ** 2 + + ( + data[self.lon_vn].values + - main_glac_rgi[self.rgi_lon_colname].values[glac] + ) + ** 2 + ) ** 0.5 + latlon_nearidx = [ + x[0] for x in np.where(latlon_dist == latlon_dist.min()) + ] lat_nearidx = latlon_nearidx[0] lon_nearidx = latlon_nearidx[1] - glac_variable_series[glac,:] = ( - data[vn][start_idx:end_idx+1, latlon_nearidx[0], latlon_nearidx[1]].values) + glac_variable_series[glac, :] = data[vn][ + start_idx : end_idx + 1, latlon_nearidx[0], latlon_nearidx[1] + ].values else: # argmin() finds the minimum distance between the glacier lat/lon and the GCM pixel; .values is used to # extract the position's value as opposed to having an array - lat_nearidx = (np.abs(main_glac_rgi[self.rgi_lat_colname].values[:,np.newaxis] - - data.variables[self.lat_vn][:].values).argmin(axis=1)) - lon_nearidx = (np.abs(main_glac_rgi[self.rgi_lon_colname].values[:,np.newaxis] - - data.variables[self.lon_vn][:].values).argmin(axis=1)) + lat_nearidx = np.abs( + main_glac_rgi[self.rgi_lat_colname].values[:, np.newaxis] + - data.variables[self.lat_vn][:].values + ).argmin(axis=1) + lon_nearidx = np.abs( + main_glac_rgi[self.rgi_lon_colname].values[:, np.newaxis] + - data.variables[self.lon_vn][:].values + ).argmin(axis=1) # Find unique latitude/longitudes latlon_nearidx = list(zip(lat_nearidx, lon_nearidx)) latlon_nearidx_unique = list(set(latlon_nearidx)) # Create dictionary of time series for each unique latitude/longitude glac_variable_dict = {} for latlon in latlon_nearidx_unique: - if 'expver' in data.keys(): + if "expver" in data.keys(): expver_idx = 0 - glac_variable_dict[latlon] = data[vn][start_idx:end_idx+1, expver_idx, latlon[0], latlon[1]].values + glac_variable_dict[latlon] = data[vn][ + start_idx : end_idx + 1, expver_idx, latlon[0], latlon[1] + ].values else: - glac_variable_dict[latlon] = data[vn][start_idx:end_idx+1, latlon[0], latlon[1]].values + glac_variable_dict[latlon] = data[vn][ + start_idx : end_idx + 1, latlon[0], latlon[1] + ].values # Convert to series - glac_variable_series = np.array([glac_variable_dict[x] for x in latlon_nearidx]) + glac_variable_series = np.array( + [glac_variable_dict[x] for x in latlon_nearidx] + ) # Perform corrections to the data if necessary # Surface air temperature corrections - if vn in ['tas', 't2m', 'T2']: - if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'K': + if vn in ["tas", "t2m", "T2"]: + if "units" in data[vn].attrs and data[vn].attrs["units"] == "K": # Convert from K to deg C glac_variable_series = glac_variable_series - 273.15 else: - print('Check units of air temperature from GCM is degrees C.') - elif vn in ['t2m_std']: - if 'units' in data[vn].attrs and data[vn].attrs['units'] not in ['C', 'K']: - print('Check units of air temperature standard deviation from GCM is degrees C or K') + print("Check units of air temperature from GCM is degrees C.") + elif vn in ["t2m_std"]: + if "units" in data[vn].attrs and data[vn].attrs["units"] not in ["C", "K"]: + print( + "Check units of air temperature standard deviation from GCM is degrees C or K" + ) # Precipitation corrections # If the variable is precipitation - elif vn in ['pr', 'tp', 'TOTPRECIP']: + elif vn in ["pr", "tp", "TOTPRECIP"]: # If the variable has units and those units are meters (ERA Interim) - if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': + if "units" in data[vn].attrs and data[vn].attrs["units"] == "m": pass # Elseif the variable has units and those units are kg m-2 s-1 (CMIP5/CMIP6) - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'kg m-2 s-1': + elif "units" in data[vn].attrs and data[vn].attrs["units"] == "kg m-2 s-1": # Convert from kg m-2 s-1 to m day-1 - glac_variable_series = glac_variable_series/1000*3600*24 + glac_variable_series = glac_variable_series / 1000 * 3600 * 24 # (1 kg m-2 s-1) * (1 m3/1000 kg) * (3600 s / hr) * (24 hr / day) = (m day-1) # Elseif the variable has units and those units are mm (COAWST) - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'mm': - glac_variable_series = glac_variable_series/1000 + elif "units" in data[vn].attrs and data[vn].attrs["units"] == "mm": + glac_variable_series = glac_variable_series / 1000 # Else check the variables units else: - print('Check units of precipitation from GCM is meters per day.') - if self.timestep == 'monthly' and self.name != 'COAWST': + print("Check units of precipitation from GCM is meters per day.") + if self.timestep == "monthly" and self.name != "COAWST": # Convert from meters per day to meters per month (COAWST data already 'monthly accumulated precipitation') - if 'daysinmonth' in dates_table.columns: - glac_variable_series = glac_variable_series * dates_table['daysinmonth'].values[np.newaxis,:] + if "daysinmonth" in dates_table.columns: + glac_variable_series = ( + glac_variable_series + * dates_table["daysinmonth"].values[np.newaxis, :] + ) elif vn != self.lr_vn: - print('Check units of air temperature or precipitation') + print("Check units of air temperature or precipitation") return glac_variable_series, time_series diff --git a/pygem/gcmbiasadj.py b/pygem/gcmbiasadj.py index 09f40e89..029a8995 100755 --- a/pygem/gcmbiasadj.py +++ b/pygem/gcmbiasadj.py @@ -7,6 +7,7 @@ Run bias adjustments a given climate dataset """ + # Built-in libraries import math @@ -22,37 +23,62 @@ # 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() - - -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): + 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, +): """ Huss and Hock (2015) temperature bias correction based on mean and interannual variability @@ -79,25 +105,37 @@ 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 # 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 @@ -111,53 +149,83 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_temp = gcm_temp[:,sim_idx_start:] + bc_temp = gcm_temp[:, sim_idx_start:] # Monthly temperature bias adjusted according to monthly average # This is where the actual bias adjustment of temperature values occurs. # All steps before this are preliminary steps (e.g., formatting, # determining additive factor and std adjustment). - t_mt = bc_temp + np.tile(gcm_temp_monthly_adj, int(bc_temp.shape[1]/12)) + t_mt = bc_temp + np.tile(gcm_temp_monthly_adj, int(bc_temp.shape[1] / 12)) # Mean monthly temperature bias adjusted according to monthly average # t_m25avg is the avg monthly temp in a 25-year period around the given year N = 25 t_m_Navg = np.zeros(t_mt.shape) - for month in range(0,12): - t_m_subset = t_mt[:,month::12] + for month in range(0, 12): + t_m_subset = t_mt[:, month::12] # Uniform filter computes running average and uses 'reflects' values at borders - t_m_Navg_subset = uniform_filter(t_m_subset,size=(1,N)) - t_m_Navg[:,month::12] = t_m_Navg_subset + t_m_Navg_subset = uniform_filter(t_m_subset, size=(1, N)) + t_m_Navg[:, month::12] = t_m_Navg_subset - gcm_temp_biasadj = t_m_Navg + (t_mt - t_m_Navg) * np.tile(variability_monthly_std, int(bc_temp.shape[1]/12)) + gcm_temp_biasadj = t_m_Navg + (t_mt - t_m_Navg) * np.tile( + variability_monthly_std, int(bc_temp.shape[1] / 12) + ) # Update elevation gcm_elev_biasadj = ref_elev # Assert that mean temperatures for all the glaciers must be more-or-less equal - gcm_temp_biasadj_subset = ( - gcm_temp_biasadj[:,gcm_subset_idx_start:gcm_subset_idx_end+1][:,ref_spinupyears*12:]) + gcm_temp_biasadj_subset = gcm_temp_biasadj[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ][:, ref_spinupyears * 12 :] if gcm_startyear == ref_startyear: if debug: - print((np.mean(gcm_temp_biasadj_subset, axis=1) - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) - assert np.max(np.abs(np.mean(gcm_temp_biasadj_subset, axis=1) - - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) < 1, ( - 'Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree') + print( + ( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) + assert ( + np.max( + np.abs( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) + < 1 + ), ( + "Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree" + ) else: if debug: - print((np.mean(gcm_temp_biasadj_subset, axis=1) - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) + print( + ( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) return gcm_temp_biasadj, gcm_elev_biasadj -def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0): +def prec_biasadj_HH2015( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, +): """ Huss and Hock (2015) precipitation bias correction based on mean (multiplicative) @@ -180,21 +248,29 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab 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 @@ -203,33 +279,52 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] + bc_prec = gcm_prec[:, sim_idx_start:] # Bias adjusted precipitation accounting for differences in monthly mean - gcm_prec_biasadj = bc_prec * np.tile(bias_adj_prec_monthly, int(bc_prec.shape[1]/12)) + gcm_prec_biasadj = bc_prec * np.tile( + bias_adj_prec_monthly, int(bc_prec.shape[1] / 12) + ) # Update elevation gcm_elev_biasadj = ref_elev # Assertion that bias adjustment does not drastically modify the precipitation and are reasonable - gcm_prec_biasadj_subset = ( - gcm_prec_biasadj[:,gcm_subset_idx_start:gcm_subset_idx_end+1][:,gcm_spinupyears*12:]) - gcm_prec_biasadj_frac = gcm_prec_biasadj_subset.sum(axis=1) / ref_prec_nospinup.sum(axis=1) + 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 @@ -252,21 +347,29 @@ 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 @@ -275,65 +378,109 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] + bc_prec = gcm_prec[:, sim_idx_start:] # Bias adjusted precipitation accounting for differences in monthly mean - gcm_prec_biasadj_raw = bc_prec * np.tile(bias_adj_prec_monthly, int(bc_prec.shape[1]/12)) + gcm_prec_biasadj_raw = bc_prec * np.tile( + bias_adj_prec_monthly, int(bc_prec.shape[1] / 12) + ) # Adjust variance based on zscore and reference standard deviation - ref_prec_monthly_std = np.roll(monthly_std_2darray(ref_prec_nospinup), roll_amt, axis=1) - gcm_prec_biasadj_raw_monthly_avg = monthly_avg_2darray(gcm_prec_biasadj_raw[:,0:ref_prec.shape[1]]) - gcm_prec_biasadj_raw_monthly_std = monthly_std_2darray(gcm_prec_biasadj_raw[:,0:ref_prec.shape[1]]) + ref_prec_monthly_std = np.roll( + monthly_std_2darray(ref_prec_nospinup), roll_amt, axis=1 + ) + gcm_prec_biasadj_raw_monthly_avg = monthly_avg_2darray( + gcm_prec_biasadj_raw[:, 0 : ref_prec.shape[1]] + ) + gcm_prec_biasadj_raw_monthly_std = monthly_std_2darray( + gcm_prec_biasadj_raw[:, 0 : ref_prec.shape[1]] + ) # Calculate value compared to mean and standard deviation gcm_prec_biasadj_zscore = ( - (gcm_prec_biasadj_raw - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1]/12))) / - np.tile(gcm_prec_biasadj_raw_monthly_std, int(bc_prec.shape[1]/12))) - gcm_prec_biasadj = ( - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1]/12)) + - gcm_prec_biasadj_zscore * np.tile(ref_prec_monthly_std, int(bc_prec.shape[1]/12))) + gcm_prec_biasadj_raw + - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1] / 12)) + ) / np.tile(gcm_prec_biasadj_raw_monthly_std, int(bc_prec.shape[1] / 12)) + gcm_prec_biasadj = np.tile( + gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1] / 12) + ) + gcm_prec_biasadj_zscore * np.tile( + ref_prec_monthly_std, int(bc_prec.shape[1] / 12) + ) gcm_prec_biasadj[gcm_prec_biasadj < 0] = 0 # Identify outliers using reference's monthly maximum adjusted for future increases - ref_prec_monthly_max = np.roll((ref_prec_nospinup.reshape(-1,12).transpose() - .reshape(-1,int(ref_prec_nospinup.shape[1]/12)).max(1).reshape(12,-1).transpose()), - roll_amt, axis=1) - gcm_prec_max_check = np.tile(ref_prec_monthly_max, int(gcm_prec_biasadj.shape[1]/12)) + ref_prec_monthly_max = np.roll( + ( + ref_prec_nospinup.reshape(-1, 12) + .transpose() + .reshape(-1, int(ref_prec_nospinup.shape[1] / 12)) + .max(1) + .reshape(12, -1) + .transpose() + ), + roll_amt, + axis=1, + ) + gcm_prec_max_check = np.tile( + ref_prec_monthly_max, int(gcm_prec_biasadj.shape[1] / 12) + ) # For wetter years in future, adjust monthly max by the annual increase in precipitation gcm_prec_annual = annual_sum_2darray(bc_prec) - gcm_prec_annual_norm = gcm_prec_annual / gcm_prec_annual.mean(1)[:,np.newaxis] - gcm_prec_annual_norm_repeated = np.repeat(gcm_prec_annual_norm, 12).reshape(gcm_prec_biasadj.shape) + gcm_prec_annual_norm = gcm_prec_annual / gcm_prec_annual.mean(1)[:, np.newaxis] + gcm_prec_annual_norm_repeated = np.repeat(gcm_prec_annual_norm, 12).reshape( + gcm_prec_biasadj.shape + ) gcm_prec_max_check_adj = gcm_prec_max_check * gcm_prec_annual_norm_repeated gcm_prec_max_check_adj[gcm_prec_max_check_adj < gcm_prec_max_check] = ( - gcm_prec_max_check[gcm_prec_max_check_adj < gcm_prec_max_check]) + gcm_prec_max_check[gcm_prec_max_check_adj < gcm_prec_max_check] + ) # Replace outliers with monthly mean adjusted for the normalized annual variation - outlier_replacement = (gcm_prec_annual_norm_repeated * - np.tile(ref_prec_monthly_avg, int(gcm_prec_biasadj.shape[1]/12))) - 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 @@ -369,13 +516,17 @@ 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), @@ -383,38 +534,44 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_temp = gcm_temp[:,sim_idx_start:] + bc_temp = gcm_temp[:, sim_idx_start:] # create an empty array for the bias-corrected GCM data # gcm_temp_biasadj = np.zeros(bc_temp.size) - loop_years = 20 # number of years used for each bias-correction period - loop_months = loop_years * 12 # number of months used for each bias-correction period + loop_years = 20 # number of years used for each bias-correction period + loop_months = ( + loop_years * 12 + ) # number of months used for each bias-correction period # convert to Kelvin to better handle Celsius values around 0) bc_temp = bc_temp + 273.15 # bc_temp = bc_temp[0] - all_gcm_temp_biasadj =[] # empty list for all glaciers + all_gcm_temp_biasadj = [] # empty list for all glaciers for j in range(0, len(bc_temp)): - gcm_temp_biasadj = [] # empty list for bias-corrected data - bc_loops = len(bc_temp[j])/loop_months # determine number of loops needed for bias-correction + gcm_temp_biasadj = [] # empty list for bias-corrected data + bc_loops = ( + len(bc_temp[j]) / loop_months + ) # determine number of loops needed for bias-correction # loop through however many times are required to bias-correct the entire time period # using smaller time periods (typically 20-30 years) to better capture the # quantiles and extremes at different points in the future for i in range(0, math.ceil(bc_loops)): - bc_temp_loop = bc_temp[j][i*loop_months:(i+1)*loop_months] + 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 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) + bias_correction_factor = np.percentile( + ref_temp_nospinup, percentile + ) / np.percentile(gcm_temp_nospinup, percentile) bc_temp_loop_corrected[ival] = projected_value * bias_correction_factor # append the values from each time period to a list gcm_temp_biasadj.append(bc_temp_loop_corrected) @@ -437,8 +594,17 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, 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 @@ -475,13 +641,17 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, """ # 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), @@ -489,36 +659,42 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] + bc_prec = gcm_prec[:, sim_idx_start:] # create an empty array for the bias-corrected GCM data # gcm_prec_biasadj = np.zeros(bc_prec.size) - loop_years = 20 # number of years used for each bias-correction period - loop_months = loop_years * 12 # number of months used for each bias-correction period + loop_years = 20 # number of years used for each bias-correction period + loop_months = ( + loop_years * 12 + ) # number of months used for each bias-correction period # bc_prec = bc_prec[0] - all_gcm_prec_biasadj =[] # empty list for all glaciers + all_gcm_prec_biasadj = [] # empty list for all glaciers for j in range(0, len(bc_prec)): - gcm_prec_biasadj = [] # empty list for bias-corrected data - bc_loops = len(bc_prec[j])/loop_months # determine number of loops needed for bias-correction + gcm_prec_biasadj = [] # empty list for bias-corrected data + bc_loops = ( + len(bc_prec[j]) / loop_months + ) # determine number of loops needed for bias-correction # loop through however many times are required to bias-correct the entire time period # using smaller time periods (typically 20-30 years) to better capture the # quantiles and extremes at different points in the future for i in range(0, math.ceil(bc_loops)): - bc_prec_loop = bc_prec[j][i*loop_months:(i+1)*loop_months] + bc_prec_loop = bc_prec[j][i * loop_months : (i + 1) * loop_months] bc_prec_loop_corrected = np.zeros(bc_prec_loop.size) # now loop through each individual value within the time period for bias correction for ival, projected_value in enumerate(bc_prec_loop): percentile = percentileofscore(bc_prec_loop, projected_value) - bias_correction_factor = np.percentile(ref_prec_nospinup, percentile)/np.percentile(gcm_prec_nospinup, percentile) + 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) @@ -535,8 +711,10 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, 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 +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 ---------- @@ -553,22 +731,24 @@ def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_starty 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) if gcm_startyear != ref_startyear: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - gcm_array = gcm_array[:,sim_idx_start:] + gcm_array = gcm_array[:, sim_idx_start:] return gcm_array diff --git a/pygem/glacierdynamics.py b/pygem/glacierdynamics.py index a604b7c7..16daad2a 100755 --- a/pygem/glacierdynamics.py +++ b/pygem/glacierdynamics.py @@ -5,13 +5,14 @@ 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 pandas as pd +# import netCDF4 import xarray as xr from oggm import __version__, cfg, utils from oggm.core.flowline import FlowlineModel @@ -27,22 +28,31 @@ 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 ---------- @@ -67,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 @@ -77,20 +93,21 @@ def __init__(self, flowlines, mb_model=None, y0=0., self.is_tidewater = is_tidewater self.water_level = water_level -# widths_t0 = flowlines[0].widths_m -# area_v1 = widths_t0 * flowlines[0].dx_meter -# print('area v1:', area_v1.sum()) -# area_v2 = np.copy(area_v1) -# area_v2[flowlines[0].thick == 0] = 0 -# print('area v2:', area_v2.sum()) + # widths_t0 = flowlines[0].widths_m + # area_v1 = widths_t0 * flowlines[0].dx_meter + # print('area v1:', area_v1.sum()) + # area_v2 = np.copy(area_v1) + # area_v2[flowlines[0].thick == 0] = 0 + # print('area v2:', area_v2.sum()) # HERE IS THE STUFF TO RECORD FOR EACH FLOWLINE! if self.is_tidewater: - self.calving_k = cfg.PARAMS['calving_k'] - self.calving_m3_since_y0 = 0. # total calving since time y0 - - assert len(flowlines) == 1, 'MassRedistributionCurveModel is not set up for multiple flowlines' + self.calving_k = cfg.PARAMS["calving_k"] + self.calving_m3_since_y0 = 0.0 # total calving since time y0 + assert len(flowlines) == 1, ( + "MassRedistributionCurveModel is not set up for multiple flowlines" + ) def run_until(self, y1, run_single_year=False): """Runs the model from the current year up to a given year date y1. @@ -116,17 +133,17 @@ def run_until(self, y1, run_single_year=False): # Check for domain bounds if self.check_for_boundaries: if self.fls[-1].thick[-1] > 10: - raise RuntimeError('Glacier exceeds domain boundaries, ' - 'at year: {}'.format(self.yr)) + raise RuntimeError( + "Glacier exceeds domain boundaries, at year: {}".format(self.yr) + ) # Check for NaNs for fl in self.fls: if np.any(~np.isfinite(fl.thick)): - raise FloatingPointError('NaN in numerical solution.') - + raise FloatingPointError("NaN in numerical solution.") - - def run_until_and_store(self, y1, run_path=None, diag_path=None, - store_monthly_step=None): + def run_until_and_store( + self, y1, run_path=None, diag_path=None, store_monthly_step=None + ): """Runs the model and returns intermediate steps in xarray datasets. This function repeatedly calls FlowlineModel.run_until for either @@ -159,29 +176,31 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, """ if int(y1) != y1: - raise InvalidParamsError('run_until_and_store only accepts ' - 'integer year dates.') + raise InvalidParamsError( + "run_until_and_store only accepts integer year dates." + ) if not self.mb_model.hemisphere: - raise InvalidParamsError('run_until_and_store needs a ' - 'mass-balance model with an unambiguous ' - 'hemisphere.') + raise InvalidParamsError( + "run_until_and_store needs a " + "mass-balance model with an unambiguous " + "hemisphere." + ) # time - yearly_time = np.arange(np.floor(self.yr), np.floor(y1)+1) + yearly_time = np.arange(np.floor(self.yr), np.floor(y1) + 1) if store_monthly_step is None: - store_monthly_step = self.mb_step == 'monthly' + store_monthly_step = self.mb_step == "monthly" if store_monthly_step: monthly_time = utils.monthly_timeseries(self.yr, y1) else: - monthly_time = np.arange(np.floor(self.yr), np.floor(y1)+1) + monthly_time = np.arange(np.floor(self.yr), np.floor(y1) + 1) - sm = cfg.PARAMS['hydro_month_' + self.mb_model.hemisphere] + sm = cfg.PARAMS["hydro_month_" + self.mb_model.hemisphere] yrs, months = utils.floatyear_to_date(monthly_time) - cyrs, cmonths = utils.hydrodate_to_calendardate(yrs, months, - start_month=sm) + cyrs, cmonths = utils.hydrodate_to_calendardate(yrs, months, start_month=sm) # init output if run_path is not None: @@ -199,73 +218,70 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, diag_ds = xr.Dataset() # Global attributes - diag_ds.attrs['description'] = 'OGGM model output' - diag_ds.attrs['oggm_version'] = __version__ - diag_ds.attrs['calendar'] = '365-day no leap' - diag_ds.attrs['creation_date'] = strftime("%Y-%m-%d %H:%M:%S", - gmtime()) - diag_ds.attrs['hemisphere'] = self.mb_model.hemisphere - diag_ds.attrs['water_level'] = self.water_level + diag_ds.attrs["description"] = "OGGM model output" + diag_ds.attrs["oggm_version"] = __version__ + diag_ds.attrs["calendar"] = "365-day no leap" + diag_ds.attrs["creation_date"] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + diag_ds.attrs["hemisphere"] = self.mb_model.hemisphere + diag_ds.attrs["water_level"] = self.water_level # Coordinates - diag_ds.coords['time'] = ('time', monthly_time) - diag_ds.coords['hydro_year'] = ('time', yrs) - diag_ds.coords['hydro_month'] = ('time', months) - diag_ds.coords['calendar_year'] = ('time', cyrs) - diag_ds.coords['calendar_month'] = ('time', cmonths) - - diag_ds['time'].attrs['description'] = 'Floating hydrological year' - diag_ds['hydro_year'].attrs['description'] = 'Hydrological year' - diag_ds['hydro_month'].attrs['description'] = 'Hydrological month' - diag_ds['calendar_year'].attrs['description'] = 'Calendar year' - diag_ds['calendar_month'].attrs['description'] = 'Calendar month' + diag_ds.coords["time"] = ("time", monthly_time) + diag_ds.coords["hydro_year"] = ("time", yrs) + diag_ds.coords["hydro_month"] = ("time", months) + diag_ds.coords["calendar_year"] = ("time", cyrs) + diag_ds.coords["calendar_month"] = ("time", cmonths) + + diag_ds["time"].attrs["description"] = "Floating hydrological year" + diag_ds["hydro_year"].attrs["description"] = "Hydrological year" + diag_ds["hydro_month"].attrs["description"] = "Hydrological month" + diag_ds["calendar_year"].attrs["description"] = "Calendar year" + diag_ds["calendar_month"].attrs["description"] = "Calendar month" # Variables and attributes - diag_ds['volume_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_m3'].attrs['description'] = 'Total glacier volume' - diag_ds['volume_m3'].attrs['unit'] = 'm 3' - diag_ds['volume_bsl_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_bsl_m3'].attrs['description'] = ('Glacier volume ' - 'below ' - 'sea-level') - diag_ds['volume_bsl_m3'].attrs['unit'] = 'm 3' - diag_ds['volume_bwl_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_bwl_m3'].attrs['description'] = ('Glacier volume ' - 'below ') - diag_ds['volume_bwl_m3'].attrs['unit'] = 'm 3' - - diag_ds['area_m2'] = ('time', np.zeros(nm) * np.nan) - diag_ds['area_m2'].attrs['description'] = 'Total glacier area' - diag_ds['area_m2'].attrs['unit'] = 'm 2' - diag_ds['length_m'] = ('time', np.zeros(nm) * np.nan) - diag_ds['length_m'].attrs['description'] = 'Glacier length' - diag_ds['length_m'].attrs['unit'] = 'm 3' - diag_ds['ela_m'] = ('time', np.zeros(nm) * np.nan) - diag_ds['ela_m'].attrs['description'] = ('Annual Equilibrium Line ' - 'Altitude (ELA)') - diag_ds['ela_m'].attrs['unit'] = 'm a.s.l' + diag_ds["volume_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_m3"].attrs["description"] = "Total glacier volume" + diag_ds["volume_m3"].attrs["unit"] = "m 3" + diag_ds["volume_bsl_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_bsl_m3"].attrs["description"] = "Glacier volume below sea-level" + diag_ds["volume_bsl_m3"].attrs["unit"] = "m 3" + diag_ds["volume_bwl_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_bwl_m3"].attrs["description"] = "Glacier volume below " + diag_ds["volume_bwl_m3"].attrs["unit"] = "m 3" + + diag_ds["area_m2"] = ("time", np.zeros(nm) * np.nan) + diag_ds["area_m2"].attrs["description"] = "Total glacier area" + diag_ds["area_m2"].attrs["unit"] = "m 2" + diag_ds["length_m"] = ("time", np.zeros(nm) * np.nan) + diag_ds["length_m"].attrs["description"] = "Glacier length" + diag_ds["length_m"].attrs["unit"] = "m 3" + diag_ds["ela_m"] = ("time", np.zeros(nm) * np.nan) + diag_ds["ela_m"].attrs["description"] = ( + "Annual Equilibrium Line Altitude (ELA)" + ) + diag_ds["ela_m"].attrs["unit"] = "m a.s.l" if self.is_tidewater: - diag_ds['calving_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['calving_m3'].attrs['description'] = ('Total accumulated ' - 'calving flux') - diag_ds['calving_m3'].attrs['unit'] = 'm 3' - diag_ds['calving_rate_myr'] = ('time', np.zeros(nm) * np.nan) - diag_ds['calving_rate_myr'].attrs['description'] = 'Calving rate' - diag_ds['calving_rate_myr'].attrs['unit'] = 'm yr-1' + diag_ds["calving_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["calving_m3"].attrs["description"] = ( + "Total accumulated calving flux" + ) + diag_ds["calving_m3"].attrs["unit"] = "m 3" + diag_ds["calving_rate_myr"] = ("time", np.zeros(nm) * np.nan) + diag_ds["calving_rate_myr"].attrs["description"] = "Calving rate" + diag_ds["calving_rate_myr"].attrs["unit"] = "m yr-1" # Run j = 0 for i, (yr, mo) in enumerate(zip(yearly_time[:-1], months[:-1])): - # Record initial parameters if i == 0: - diag_ds['volume_m3'].data[i] = self.volume_m3 - diag_ds['area_m2'].data[i] = self.area_m2 - diag_ds['length_m'].data[i] = self.length_m + diag_ds["volume_m3"].data[i] = self.volume_m3 + diag_ds["area_m2"].data[i] = self.area_m2 + diag_ds["length_m"].data[i] = self.length_m if self.is_tidewater: - diag_ds['volume_bsl_m3'].data[i] = self.volume_bsl_m3 - diag_ds['volume_bwl_m3'].data[i] = self.volume_bwl_m3 + diag_ds["volume_bsl_m3"].data[i] = self.volume_bsl_m3 + diag_ds["volume_bwl_m3"].data[i] = self.volume_bwl_m3 self.run_until(yr, run_single_year=True) # Model run @@ -280,64 +296,61 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, pass j += 1 # Diagnostics - diag_ds['volume_m3'].data[i+1] = self.volume_m3 - diag_ds['area_m2'].data[i+1] = self.area_m2 - diag_ds['length_m'].data[i+1] = self.length_m + diag_ds["volume_m3"].data[i + 1] = self.volume_m3 + diag_ds["area_m2"].data[i + 1] = self.area_m2 + diag_ds["length_m"].data[i + 1] = self.length_m if self.is_tidewater: - diag_ds['calving_m3'].data[i+1] = self.calving_m3_since_y0 - diag_ds['calving_rate_myr'].data[i+1] = self.calving_rate_myr - diag_ds['volume_bsl_m3'].data[i+1] = self.volume_bsl_m3 - diag_ds['volume_bwl_m3'].data[i+1] = self.volume_bwl_m3 + diag_ds["calving_m3"].data[i + 1] = self.calving_m3_since_y0 + diag_ds["calving_rate_myr"].data[i + 1] = self.calving_rate_myr + diag_ds["volume_bsl_m3"].data[i + 1] = self.volume_bsl_m3 + diag_ds["volume_bwl_m3"].data[i + 1] = self.volume_bwl_m3 # to datasets run_ds = [] - for (s, w, b) in zip(sects, widths, bucket): + for s, w, b in zip(sects, widths, bucket): ds = xr.Dataset() - ds.attrs['description'] = 'OGGM model output' - ds.attrs['oggm_version'] = __version__ - ds.attrs['calendar'] = '365-day no leap' - ds.attrs['creation_date'] = strftime("%Y-%m-%d %H:%M:%S", - gmtime()) - ds.coords['time'] = yearly_time - ds['time'].attrs['description'] = 'Floating hydrological year' - varcoords = OrderedDict(time=('time', yearly_time), - year=('time', yearly_time)) - ds['ts_section'] = xr.DataArray(s, dims=('time', 'x'), - coords=varcoords) - ds['ts_width_m'] = xr.DataArray(w, dims=('time', 'x'), - coords=varcoords) + ds.attrs["description"] = "OGGM model output" + ds.attrs["oggm_version"] = __version__ + ds.attrs["calendar"] = "365-day no leap" + ds.attrs["creation_date"] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + ds.coords["time"] = yearly_time + ds["time"].attrs["description"] = "Floating hydrological year" + varcoords = OrderedDict( + time=("time", yearly_time), year=("time", yearly_time) + ) + ds["ts_section"] = xr.DataArray(s, dims=("time", "x"), coords=varcoords) + ds["ts_width_m"] = xr.DataArray(w, dims=("time", "x"), coords=varcoords) if self.is_tidewater: - ds['ts_calving_bucket_m3'] = xr.DataArray(b, dims=('time', ), - coords=varcoords) + ds["ts_calving_bucket_m3"] = xr.DataArray( + b, dims=("time",), coords=varcoords + ) run_ds.append(ds) # write output? if run_path is not None: - encode = {'ts_section': {'zlib': True, 'complevel': 5}, - 'ts_width_m': {'zlib': True, 'complevel': 5}, - } + encode = { + "ts_section": {"zlib": True, "complevel": 5}, + "ts_width_m": {"zlib": True, "complevel": 5}, + } for i, ds in enumerate(run_ds): - ds.to_netcdf(run_path, 'a', group='fl_{}'.format(i), - encoding=encode) + ds.to_netcdf(run_path, "a", group="fl_{}".format(i), encoding=encode) # Add other diagnostics - diag_ds.to_netcdf(run_path, 'a') + diag_ds.to_netcdf(run_path, "a") if diag_path is not None: diag_ds.to_netcdf(diag_path) return run_ds, diag_ds - def updategeometry(self, year, debug=False): """Update geometry for a given year""" if debug: - print('year:', year) + print("year:", year) # Loop over flowlines for fl_id, fl in enumerate(self.fls): - # Flowline state heights = self.fls[fl_id].surface_h.copy() section_t0 = self.fls[fl_id].section.copy() @@ -346,46 +359,62 @@ def updategeometry(self, year, debug=False): # 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) + 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 init:", np.round(vol_init)) + print(" volume final:", np.round(vol_init - fa_m3)) # First, remove volume lost to frontal ablation # changes to _t0 not _t1, since t1 will be done in the mass redistribution - glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] + glac_idx_bsl = np.where( + (thick_t0 > 0) & (fl.bed_h < self.water_level) + )[0] while fa_m3 > 0 and len(glac_idx_bsl) > 0: if debug: - print('fa_m3_remaining:', fa_m3) + print("fa_m3_remaining:", fa_m3) # OGGM code -# glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] + # glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] last_idx = glac_idx_bsl[-1] if debug: - print('before:', np.round(self.fls[fl_id].section[last_idx],0), - np.round(self.fls[fl_id].thick[last_idx],0), - np.round(heights[last_idx],0)) + print( + "before:", + np.round(self.fls[fl_id].section[last_idx], 0), + np.round(self.fls[fl_id].thick[last_idx], 0), + np.round(heights[last_idx], 0), + ) vol_last = section_t0[last_idx] * fl.dx_meter # If frontal ablation more than bin volume, remove entire bin if fa_m3 > vol_last: # Record frontal ablation (m3 w.e.) in mass balance model for output - self.mb_model.glac_bin_frontalablation[last_idx,int(12*(year+1)-1)] = ( - vol_last * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + self.mb_model.glac_bin_frontalablation[ + last_idx, int(12 * (year + 1) - 1) + ] = ( + vol_last + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # Update ice thickness and section area section_t0[last_idx] = 0 self.fls[fl_id].section = section_t0 @@ -395,11 +424,18 @@ def updategeometry(self, year, debug=False): # 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 + 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 @@ -410,60 +446,84 @@ def updategeometry(self, year, debug=False): 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) @@ -479,12 +539,14 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter - fl_widths_m = getattr(fl, 'widths_m', None) - fl_section = getattr(fl,'section',None) + fl_widths_m = getattr(fl, "widths_m", None) + fl_section = getattr(fl, "section", None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + ) else: icethickness_t0 = None @@ -500,22 +562,31 @@ 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] + 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): + if fl.bed_h[last_above_wl] < self.water_level: # Volume [m3] and bed elevation [masl] of each bin if debug: - print('\nyear:', year, '\n sea level:', self.water_level, 'bed elev:', np.round(fl.bed_h[last_above_wl], 2)) - print(' estimate frontal ablation') - print(' min elevation:', fl.surface_h[last_above_wl]) + print( + "\nyear:", + year, + "\n sea level:", + self.water_level, + "bed elev:", + np.round(fl.bed_h[last_above_wl], 2), + ) + print(" estimate frontal ablation") + print(" min elevation:", fl.surface_h[last_above_wl]) # --- The rest is for calving only --- - self.calving_rate_myr = 0. + self.calving_rate_myr = 0.0 # OK, we're really calving section = fl.section @@ -527,10 +598,12 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, q_calving = k * d * h * fl.widths_m[last_above_wl] # Max frontal ablation is removing all bins with bed below water level - glac_idx_bsl = np.where((fl.thick > 0) & (fl.bed_h < self.water_level))[0] + glac_idx_bsl = np.where( + (fl.thick > 0) & (fl.bed_h < self.water_level) + )[0] q_calving_max = np.sum(section[glac_idx_bsl]) * fl.dx_meter - if q_calving > q_calving_max + pygem_prms['constants']['tolerance']: + if q_calving > q_calving_max + pygem_prms["constants"]["tolerance"]: q_calving = q_calving_max # Add to the bucket and the diagnostics @@ -539,10 +612,19 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, 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 @@ -579,15 +661,17 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb # Annual glacier-wide volume change [m3] # units: [m ice / s] * [s] * [m2] = m3 ice - glacier_volumechange = (glac_bin_massbalclim_annual * sec_in_year * glacier_area_t0).sum() + glacier_volumechange = ( + glac_bin_massbalclim_annual * sec_in_year * glacier_area_t0 + ).sum() # For hindcast simulations, volume change is the opposite if hindcast == 1: glacier_volumechange = -1 * glacier_volumechange if debug: - print('\nDebugging Mass Redistribution Huss function\n') - print('glacier volume change:', glacier_volumechange) + print("\nDebugging Mass Redistribution Huss function\n") + print("glacier volume change:", glacier_volumechange) # If volume loss is more than the glacier volume, melt everything and stop here glacier_volume_total = (self.fls[0].section * self.fls[0].dx_meter).sum() @@ -602,49 +686,79 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb # Compute ice thickness [m ice], glacier area [m2], ice thickness change [m ice] after redistribution icethickness_change, glacier_volumechange_remaining = ( - self._massredistributioncurveHuss(section_t0, thick_t0, width_t0, glac_idx_t0, - glacier_volumechange, glac_bin_massbalclim_annual, - heights, debug=False)) + self._massredistributioncurveHuss( + section_t0, + thick_t0, + width_t0, + glac_idx_t0, + glacier_volumechange, + glac_bin_massbalclim_annual, + heights, + debug=False, + ) + ) if debug: - print('\nmax icethickness change:', np.round(icethickness_change.max(),3), - '\nmin icethickness change:', np.round(icethickness_change.min(),3), - '\nvolume remaining:', glacier_volumechange_remaining) + print( + "\nmax icethickness change:", + np.round(icethickness_change.max(), 3), + "\nmin icethickness change:", + np.round(icethickness_change.min(), 3), + "\nvolume remaining:", + glacier_volumechange_remaining, + ) nloop = 0 # Glacier retreat # if glacier retreats (ice thickness == 0), volume change needs to be redistributed over glacier again while glacier_volumechange_remaining < 0: - if debug: - print('\n\nGlacier retreating (loop ' + str(nloop) + '):') + print("\n\nGlacier retreating (loop " + str(nloop) + "):") section_t0_retreated = self.fls[0].section.copy() thick_t0_retreated = self.fls[0].thick.copy() width_t0_retreated = self.fls[0].widths_m.copy() - glacier_volumechange_remaining_retreated = glacier_volumechange_remaining.copy() + 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 # 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) + massbalclim_retreat[glac_idx_t0_retreated] = ( + glacier_volumechange_remaining + / glacier_area_t0_retreated.sum() + / sec_in_year + ) # Mass redistribution # apply mass redistribution using Huss' empirical geometry change equations icethickness_change, glacier_volumechange_remaining = ( self._massredistributioncurveHuss( - section_t0_retreated, thick_t0_retreated, width_t0_retreated, glac_idx_t0_retreated, - glacier_volumechange_remaining_retreated, massbalclim_retreat, heights, debug=False)) + section_t0_retreated, + thick_t0_retreated, + width_t0_retreated, + glac_idx_t0_retreated, + glacier_volumechange_remaining_retreated, + massbalclim_retreat, + heights, + debug=False, + ) + ) # Avoid rounding errors that get loop stuck if abs(glacier_volumechange_remaining) < 1: glacier_volumechange_remaining = 0 if debug: - print('ice thickness change:', icethickness_change) - print('\nmax icethickness change:', np.round(icethickness_change.max(),3), - '\nmin icethickness change:', np.round(icethickness_change.min(),3), - '\nvolume remaining:', glacier_volumechange_remaining) + print("ice thickness change:", icethickness_change) + print( + "\nmax icethickness change:", + np.round(icethickness_change.max(), 3), + "\nmin icethickness change:", + np.round(icethickness_change.min(), 3), + "\nvolume remaining:", + glacier_volumechange_remaining, + ) nloop += 1 # Glacier advances @@ -654,9 +768,11 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb # 2. If additional volume after adding new bin, then redistribute mass gain across all bins again, # i.e., increase the ice thickness and width # 3. Repeat adding a new bin and redistributing the mass until no addiitonal volume is left - while (icethickness_change > pygem_prms['sim']['icethickness_advancethreshold']).any() == True: + while ( + icethickness_change > pygem_prms["sim"]["icethickness_advancethreshold"] + ).any() == True: if debug: - print('advancing glacier') + print("advancing glacier") # Record glacier area and ice thickness before advance corrections applied section_t0_raw = self.fls[0].section.copy() @@ -665,25 +781,35 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb glacier_area_t0_raw = width_t0_raw * self.fls[0].dx_meter if debug: - print('\n\nthickness t0:', thick_t0_raw) - print('glacier area t0:', glacier_area_t0_raw) - print('width_t0_raw:', width_t0_raw,'\n\n') + print("\n\nthickness t0:", thick_t0_raw) + print("glacier area t0:", glacier_area_t0_raw) + print("width_t0_raw:", width_t0_raw, "\n\n") # Index bins that are advancing - icethickness_change[icethickness_change <= pygem_prms['sim']['icethickness_advancethreshold']] = 0 + icethickness_change[ + icethickness_change + <= pygem_prms["sim"]["icethickness_advancethreshold"] + ] = 0 glac_idx_advance = icethickness_change.nonzero()[0] # Update ice thickness based on maximum advance threshold [m ice] - self.fls[0].thick[glac_idx_advance] = (self.fls[0].thick[glac_idx_advance] - - (icethickness_change[glac_idx_advance] - pygem_prms['sim']['icethickness_advancethreshold'])) + self.fls[0].thick[glac_idx_advance] = self.fls[0].thick[ + glac_idx_advance + ] - ( + icethickness_change[glac_idx_advance] + - pygem_prms["sim"]["icethickness_advancethreshold"] + ) glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter # Advance volume [m3] - advance_volume = ((glacier_area_t0_raw[glac_idx_advance] * thick_t0_raw[glac_idx_advance]).sum() - - (glacier_area_t1[glac_idx_advance] * self.fls[0].thick[glac_idx_advance]).sum()) + advance_volume = ( + glacier_area_t0_raw[glac_idx_advance] * thick_t0_raw[glac_idx_advance] + ).sum() - ( + glacier_area_t1[glac_idx_advance] * self.fls[0].thick[glac_idx_advance] + ).sum() if debug: - print('advance volume [m3]:', advance_volume) + print("advance volume [m3]:", advance_volume) # Set the cross sectional area of the next bin advance_section = advance_volume / self.fls[0].dx_meter @@ -697,7 +823,6 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb # 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]) @@ -707,17 +832,33 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb if debug: print(self.fls[0].surface_h[glac_idx_t0]) - print(glac_idx_t0_term, 'height:', self.fls[0].surface_h[glac_idx_t0_term], - 'thickness:', self.fls[0].thick[glac_idx_t0_term]) - print(np.where(self.fls[0].surface_h[glac_idx_t0] > self.fls[0].surface_h[glac_idx_t0_term])[0]) - print('advance section:', advance_section) + print( + glac_idx_t0_term, + "height:", + self.fls[0].surface_h[glac_idx_t0_term], + "thickness:", + self.fls[0].thick[glac_idx_t0_term], + ) + print( + np.where( + self.fls[0].surface_h[glac_idx_t0] + > self.fls[0].surface_h[glac_idx_t0_term] + )[0] + ) + print("advance section:", advance_section) thick_prior = np.copy(self.fls[0].thick) section_updated = np.copy(self.fls[0].section) - section_updated[glac_idx_t0_term] = section_updated[glac_idx_t0_term] + advance_section + section_updated[glac_idx_t0_term] = ( + section_updated[glac_idx_t0_term] + advance_section + ) if debug: - print(self.fls[0].section[glac_idx_t0_term], self.fls[0].surface_h[glac_idx_t0_term], self.fls[0].thick[glac_idx_t0_term]) + print( + self.fls[0].section[glac_idx_t0_term], + self.fls[0].surface_h[glac_idx_t0_term], + self.fls[0].thick[glac_idx_t0_term], + ) self.fls[0].section = section_updated @@ -726,91 +867,133 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb 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("thick_reduction:", thick_reduction) + print( + "----\nprior to correction:", + self.fls[0].thick[glac_idx_t0_term], + self.fls[0].section[glac_idx_t0_term], + ) + + self.fls[0].thick[glac_idx_t0_term] = ( + self.fls[0].thick[glac_idx_t0_term] - thick_reduction + ) glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter # Advance volume [m3] - advance_volume = ((glacier_area_t0_raw[glac_idx_t0_term] * thick_t0_raw[glac_idx_t0_term]).sum() - - (glacier_area_t1[glac_idx_t0_term] * self.fls[0].thick[glac_idx_t0_term]).sum()) + advance_volume = ( + glacier_area_t0_raw[glac_idx_t0_term] + * thick_t0_raw[glac_idx_t0_term] + ).sum() - ( + glacier_area_t1[glac_idx_t0_term] + * self.fls[0].thick[glac_idx_t0_term] + ).sum() if debug: - print('post correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term]) - print('surface_h:', self.fls[0].surface_h[glac_idx_t0]) - print('advance_volume:', advance_volume) - print('icethickness_change:', icethickness_change) + print( + "post correction:", + self.fls[0].thick[glac_idx_t0_term], + self.fls[0].section[glac_idx_t0_term], + ) + print("surface_h:", self.fls[0].surface_h[glac_idx_t0]) + print("advance_volume:", advance_volume) + print("icethickness_change:", icethickness_change) # Set icethickness change of terminus to 0 to avoid while loop issues icethickness_change[glac_idx_t0_term] = 0 - if advance_volume > 0: - glac_idx_bin2add = ( - np.where(self.fls[0].surface_h == - self.fls[0].surface_h[np.where(self.fls[0].surface_h < min_elev)[0]].max())[0][0]) + glac_idx_bin2add = np.where( + self.fls[0].surface_h + == self.fls[0] + .surface_h[np.where(self.fls[0].surface_h < min_elev)[0]] + .max() + )[0][0] section_2add = self.fls[0].section.copy() section_2add[glac_idx_bin2add] = advance_section self.fls[0].section = section_2add # 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]) + 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']]) + 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 @@ -835,16 +1018,35 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb 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. @@ -872,7 +1074,7 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ """ if debug: - print('\nDebugging mass redistribution curve Huss\n') + print("\nDebugging mass redistribution curve Huss\n") # Apply Huss redistribution if there are at least 3 elevation bands; otherwise, use the mass balance # Glacier area used to select parameters @@ -893,14 +1095,18 @@ 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) @@ -908,9 +1114,11 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ icethicknesschange_norm[icethicknesschange_norm < 0] = 0 # Huss' ice thickness scaling factor, fs_huss [m ice] # units: m3 / (m2 * [-]) * (1000 m / 1 km) = m ice - fs_huss = glacier_volumechange / (glacier_area_t0 * icethicknesschange_norm).sum() + fs_huss = ( + glacier_volumechange / (glacier_area_t0 * icethicknesschange_norm).sum() + ) if debug: - print('fs_huss:', fs_huss) + print("fs_huss:", fs_huss) # Volume change [m3 ice] bin_volumechange = icethicknesschange_norm * fs_huss * glacier_area_t0 @@ -919,7 +1127,7 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ bin_volumechange = massbalclim_annual * glacier_area_t0 if debug: - print('-----\n') + print("-----\n") vol_before = section_t0 * self.fls[0].dx_meter # Update cross sectional area (updating thickness does not conserve mass in OGGM!) @@ -932,15 +1140,19 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ vol_after = self.fls[0].section * self.fls[0].dx_meter if debug: - print('vol_chg_wanted:', bin_volumechange.sum()) - print('vol_chg:', (vol_after.sum() - vol_before.sum())) - print('\n-----') + print("vol_chg_wanted:", bin_volumechange.sum()) + print("vol_chg:", (vol_after.sum() - vol_before.sum())) + print("\n-----") # Compute the remaining volume change - bin_volumechange_remaining = (bin_volumechange - (self.fls[0].section * self.fls[0].dx_meter - - section_t0 * self.fls[0].dx_meter)) + bin_volumechange_remaining = bin_volumechange - ( + self.fls[0].section * self.fls[0].dx_meter + - section_t0 * self.fls[0].dx_meter + ) # remove values below tolerance to avoid rounding errors - bin_volumechange_remaining[abs(bin_volumechange_remaining) < pygem_prms['constants']['tolerance']] = 0 + bin_volumechange_remaining[ + abs(bin_volumechange_remaining) < pygem_prms["constants"]["tolerance"] + ] = 0 # Glacier volume change remaining - if less than zero, then needed for retreat glacier_volumechange_remaining = bin_volumechange_remaining.sum() diff --git a/pygem/massbalance.py b/pygem/massbalance.py index 8401f53b..c8dcbfc7 100644 --- a/pygem/massbalance.py +++ b/pygem/massbalance.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + # External libraries import numpy as np @@ -19,7 +20,8 @@ # read the config pygem_prms = config_manager.read_config() -#%% + +# %% class PyGEMMassBalance(MassBalanceModel): """Mass-balance computed from the Python Glacier Evolution Model. @@ -27,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 ---------- @@ -55,7 +67,7 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, switch to run the model in reverse or not (may be irrelevant after converting to OGGM's setup) """ if debug: - print('\n\nDEBUGGING MASS BALANCE FUNCTION\n\n') + print("\n\nDEBUGGING MASS BALANCE FUNCTION\n\n") self.debug_refreeze = debug_refreeze self.inversion_filter = inversion_filter @@ -67,11 +79,15 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, self.modelprms = modelprms self.glacier_rgi_table = glacier_rgi_table self.is_tidewater = gdir.is_tidewater - self.icethickness_initial = getattr(fls[fl_id], 'thick', None) + self.icethickness_initial = getattr(fls[fl_id], "thick", None) self.width_initial = fls[fl_id].widths_m self.glacier_area_initial = fls[fl_id].widths_m * fls[fl_id].dx_meter self.heights = fls[fl_id].surface_h - if pygem_prms['mb']['include_debris'] and not ignore_debris and not gdir.is_tidewater: + if ( + pygem_prms["mb"]["include_debris"] + and not ignore_debris + and not gdir.is_tidewater + ): try: self.debris_ed = fls[fl_id].debris_ed except: @@ -83,14 +99,14 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, # Climate data self.dates_table = gdir.dates_table - self.glacier_gcm_temp = gdir.historical_climate['temp'] - self.glacier_gcm_tempstd = gdir.historical_climate['tempstd'] - self.glacier_gcm_prec = gdir.historical_climate['prec'] - self.glacier_gcm_elev = gdir.historical_climate['elev'] - self.glacier_gcm_lrgcm = gdir.historical_climate['lr'] - self.glacier_gcm_lrglac = gdir.historical_climate['lr'] - - if pygem_prms['climate']['hindcast'] == True: + self.glacier_gcm_temp = gdir.historical_climate["temp"] + self.glacier_gcm_tempstd = gdir.historical_climate["tempstd"] + self.glacier_gcm_prec = gdir.historical_climate["prec"] + self.glacier_gcm_elev = gdir.historical_climate["elev"] + self.glacier_gcm_lrgcm = gdir.historical_climate["lr"] + self.glacier_gcm_lrglac = gdir.historical_climate["lr"] + + if pygem_prms["climate"]["hindcast"] == True: self.glacier_gcm_prec = self.glacier_gcm_prec[::-1] self.glacier_gcm_temp = self.glacier_gcm_temp[::-1] self.glacier_gcm_lrgcm = self.glacier_gcm_lrgcm[::-1] @@ -104,31 +120,35 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, 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) @@ -138,17 +158,17 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, self.glac_wide_massbaltotal = np.zeros(self.nmonths) self.glac_wide_runoff = np.zeros(self.nmonths) self.glac_wide_snowline = np.zeros(self.nmonths) - self.glac_wide_area_annual = np.zeros(self.nyears+1) - self.glac_wide_volume_annual = np.zeros(self.nyears+1) + self.glac_wide_area_annual = np.zeros(self.nyears + 1) + self.glac_wide_volume_annual = np.zeros(self.nyears + 1) self.glac_wide_volume_change_ignored_annual = np.zeros(self.nyears) - self.glac_wide_ELA_annual = np.zeros(self.nyears+1) + self.glac_wide_ELA_annual = np.zeros(self.nyears + 1) self.offglac_wide_prec = np.zeros(self.nmonths) self.offglac_wide_refreeze = np.zeros(self.nmonths) self.offglac_wide_melt = np.zeros(self.nmonths) self.offglac_wide_snowpack = np.zeros(self.nmonths) self.offglac_wide_runoff = np.zeros(self.nmonths) - self.dayspermonth = self.dates_table['daysinmonth'].values + self.dayspermonth = self.dates_table["daysinmonth"].values self.surfacetype_ddf = np.zeros((nbins)) # Surface type DDF dictionary (manipulate this function for calibration or for each glacier) @@ -156,31 +176,53 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial(self.heights) # Refreezing specific layers - if pygem_prms['mb']['option_refreezing'] == 'HH2015': + if pygem_prms["mb"]["option_refreezing"] == "HH2015": # Refreezing layers density, volumetric heat capacity, and thermal conductivity - self.rf_dens_expb = (pygem_prms['mb']['HH2015_rf_opts']['rf_dens_bot'] / pygem_prms['mb']['HH2015_rf_opts']['rf_dens_top'])**(1/(pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1)) - self.rf_layers_dens = np.array([pygem_prms['mb']['HH2015_rf_opts']['rf_dens_top'] * self.rf_dens_expb**x - for x in np.arange(0,pygem_prms['mb']['HH2015_rf_opts']['rf_layers'])]) - self.rf_layers_ch = ((1 - self.rf_layers_dens/1000) * pygem_prms['constants']['ch_air'] + self.rf_layers_dens/1000 * - pygem_prms['constants']['ch_ice']) - self.rf_layers_k = ((1 - self.rf_layers_dens/1000) * pygem_prms['constants']['k_air'] + self.rf_layers_dens/1000 * - pygem_prms['constants']['k_ice']) + self.rf_dens_expb = ( + pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_bot"] + / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_top"] + ) ** (1 / (pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] - 1)) + self.rf_layers_dens = np.array( + [ + pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_top"] + * self.rf_dens_expb**x + for x in np.arange( + 0, pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + ) + ] + ) + self.rf_layers_ch = (1 - self.rf_layers_dens / 1000) * pygem_prms[ + "constants" + ]["ch_air"] + self.rf_layers_dens / 1000 * pygem_prms["constants"]["ch_ice"] + self.rf_layers_k = (1 - self.rf_layers_dens / 1000) * pygem_prms[ + "constants" + ]["k_air"] + self.rf_layers_dens / 1000 * pygem_prms["constants"]["k_ice"] # refreeze in each bin self.refr = np.zeros(nbins) # refrezee cold content or "potential" refreeze self.rf_cold = np.zeros(nbins) # layer temp of each elev bin for present time step - self.te_rf = np.zeros((pygem_prms['mb']['HH2015_rf_opts']['rf_layers'],nbins,self.nmonths)) + self.te_rf = np.zeros( + (pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"], nbins, self.nmonths) + ) # layer temp of each elev bin for previous time step - self.tl_rf = np.zeros((pygem_prms['mb']['HH2015_rf_opts']['rf_layers'],nbins,self.nmonths)) + self.tl_rf = np.zeros( + (pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"], nbins, self.nmonths) + ) # Sea level for marine-terminating glaciers self.sea_level = 0 - rgi_region = int(glacier_rgi_table.RGIId.split('-')[1].split('.')[0]) - - - def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, - debug=pygem_prms['debug']['mb'], option_areaconstant=False): + rgi_region = int(glacier_rgi_table.RGIId.split("-")[1].split(".")[0]) + + def get_annual_mb( + self, + heights, + year=None, + fls=None, + fl_id=None, + debug=pygem_prms["debug"]["mb"], + option_areaconstant=False, + ): """FIXED FORMAT FOR THE FLOWLINE MODEL Returns annual climatic mass balance [m ice per second] @@ -199,18 +241,23 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, """ year = int(year) if self.repeat_period: - year = year % (pygem_prms['climate']['gcm_endyear'] - pygem_prms['climate']['gcm_startyear']) + year = year % ( + pygem_prms["climate"]["gcm_endyear"] + - pygem_prms["climate"]["gcm_startyear"] + ) fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter glacier_area_initial = self.glacier_area_initial - fl_widths_m = getattr(fl, 'widths_m', None) - fl_section = getattr(fl,'section',None) + fl_widths_m = getattr(fl, "widths_m", None) + fl_section = getattr(fl, "section", None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + ) else: icethickness_t0 = None @@ -219,7 +266,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=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] @@ -228,53 +275,82 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, nmonths = self.glacier_gcm_temp.shape[0] # Local variables - bin_precsnow = np.zeros((nbins,nmonths)) + bin_precsnow = np.zeros((nbins, nmonths)) # Refreezing specific layers - if pygem_prms['mb']['option_refreezing'] == 'HH2015' and year == 0: - self.te_rf[:,:,0] = 0 # layer temp of each elev bin for present time step - self.tl_rf[:,:,0] = 0 # layer temp of each elev bin for previous time step - elif pygem_prms['mb']['option_refreezing'] == 'Woodward': + if pygem_prms["mb"]["option_refreezing"] == "HH2015" and year == 0: + self.te_rf[:, :, 0] = 0 # layer temp of each elev bin for present time step + self.tl_rf[:, :, 0] = ( + 0 # layer temp of each elev bin for previous time step + ) + elif pygem_prms["mb"]["option_refreezing"] == "Woodward": refreeze_potential = np.zeros(nbins) if self.glacier_area_initial.sum() > 0: -# if len(glac_idx_t0) > 0: + # if len(glac_idx_t0) > 0: # Surface type [0=off-glacier, 1=ice, 2=snow, 3=firn, 4=debris] if year == 0: - self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial(self.heights) - self.glac_bin_surfacetype_annual[:,year] = self.surfacetype + self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial( + self.heights + ) + self.glac_bin_surfacetype_annual[:, year] = self.surfacetype # Off-glacier area and indices if option_areaconstant == False: - self.offglac_bin_area_annual[:,year] = glacier_area_initial - glacier_area_t0 - offglac_idx = np.where(self.offglac_bin_area_annual[:,year] > 0)[0] + self.offglac_bin_area_annual[:, year] = ( + glacier_area_initial - glacier_area_t0 + ) + offglac_idx = np.where(self.offglac_bin_area_annual[:, year] > 0)[0] # Functions currently set up for monthly timestep # only compute mass balance while glacier exists - if (pygem_prms['time']['timestep'] == 'monthly'): -# if (pygem_prms['time']['timestep'] == 'monthly') and (glac_idx_t0.shape[0] != 0): + if pygem_prms["time"]["timestep"] == "monthly": + # if (pygem_prms['time']['timestep'] == 'monthly') and (glac_idx_t0.shape[0] != 0): # AIR TEMPERATURE: Downscale the gcm temperature [deg C] to each bin - if pygem_prms['mb']['option_temp2bins'] == 1: + if pygem_prms["mb"]["option_temp2bins"] == 1: # Downscale using gcm and glacier lapse rates # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange - self.bin_temp[:,12*year:12*(year+1)] = (self.glacier_gcm_temp[12*year:12*(year+1)] + - self.glacier_gcm_lrgcm[12*year:12*(year+1)] * - (self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']] - self.glacier_gcm_elev) + - self.glacier_gcm_lrglac[12*year:12*(year+1)] * (heights - - self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']])[:, np.newaxis] + - self.modelprms['tbias']) + self.bin_temp[:, 12 * year : 12 * (year + 1)] = ( + self.glacier_gcm_temp[12 * year : 12 * (year + 1)] + + self.glacier_gcm_lrgcm[12 * year : 12 * (year + 1)] + * ( + self.glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + - self.glacier_gcm_elev + ) + + self.glacier_gcm_lrglac[12 * year : 12 * (year + 1)] + * ( + heights + - self.glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + )[:, np.newaxis] + + self.modelprms["tbias"] + ) # PRECIPITATION/ACCUMULATION: Downscale the precipitation (liquid and solid) to each bin - if pygem_prms['mb']['option_prec2bins'] == 1: + if pygem_prms["mb"]["option_prec2bins"] == 1: # Precipitation using precipitation factor and precipitation gradient # P_bin = P_gcm * prec_factor * (1 + prec_grad * (z_bin - z_ref)) - bin_precsnow[:,12*year:12*(year+1)] = (self.glacier_gcm_prec[12*year:12*(year+1)] * - self.modelprms['kp'] * (1 + self.modelprms['precgrad'] * (heights - - self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]))[:,np.newaxis]) + bin_precsnow[:, 12 * year : 12 * (year + 1)] = ( + self.glacier_gcm_prec[12 * year : 12 * (year + 1)] + * self.modelprms["kp"] + * ( + 1 + + self.modelprms["precgrad"] + * ( + heights + - self.glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + ) + )[:, np.newaxis] + ) # Option to adjust prec of uppermost 25% of glacier for wind erosion and reduced moisture content - if pygem_prms['mb']['option_preclimit'] == 1: + if pygem_prms["mb"]["option_preclimit"] == 1: # Elevation range based on all flowlines raw_min_elev = [] raw_max_elev = [] @@ -294,220 +370,419 @@ 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 pygem_prms["mb"]["option_accumulation"] == 1: # if temperature above threshold, then rain - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold']]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold']]) + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + ] # if temperature below threshold, then snow - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold']]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold']]) - elif pygem_prms['mb']['option_accumulation'] == 2: + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] + ] + elif pygem_prms["mb"]["option_accumulation"] == 2: # if temperature between min/max, then mix of snow/rain using linear relationship between min/max - self.bin_prec[:,12*year:12*(year+1)] = ( - (0.5 + (self.bin_temp[:,12*year:12*(year+1)] - - self.modelprms['tsnow_threshold']) / 2) * bin_precsnow[:,12*year:12*(year+1)]) - self.bin_acc[:,12*year:12*(year+1)] = ( - bin_precsnow[:,12*year:12*(year+1)] - self.bin_prec[:,12*year:12*(year+1)]) + self.bin_prec[:, 12 * year : 12 * (year + 1)] = ( + 0.5 + + ( + self.bin_temp[:, 12 * year : 12 * (year + 1)] + - self.modelprms["tsnow_threshold"] + ) + / 2 + ) * bin_precsnow[:, 12 * year : 12 * (year + 1)] + self.bin_acc[:, 12 * year : 12 * (year + 1)] = ( + bin_precsnow[:, 12 * year : 12 * (year + 1)] + - self.bin_prec[:, 12 * year : 12 * (year + 1)] + ) # if temperature above maximum threshold, then all rain - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) = 0 + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ) = 0 # if temperature below minimum threshold, then all snow - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) = 0 + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ) = 0 # ENTER MONTHLY LOOP (monthly loop required since surface type changes) - for month in range(0,12): + for month in range(0, 12): # Step is the position as a function of year and month, which improves readability - step = 12*year + month + step = 12 * year + month # ACCUMULATION, MELT, REFREEZE, AND CLIMATIC MASS BALANCE # Snowpack [m w.e.] = snow remaining + new snow if step == 0: - self.bin_snowpack[:,step] = self.bin_acc[:,step] + self.bin_snowpack[:, step] = self.bin_acc[:, step] else: - self.bin_snowpack[:,step] = self.snowpack_remaining[:,step-1] + self.bin_acc[:,step] + self.bin_snowpack[:, step] = ( + self.snowpack_remaining[:, step - 1] + self.bin_acc[:, step] + ) # MELT [m w.e.] # energy available for melt [degC day] - if pygem_prms['mb']['option_ablation'] == 1: + if pygem_prms["mb"]["option_ablation"] == 1: # option 1: energy based on monthly temperature - melt_energy_available = self.bin_temp[:,step]*self.dayspermonth[step] + melt_energy_available = ( + self.bin_temp[:, step] * self.dayspermonth[step] + ) melt_energy_available[melt_energy_available < 0] = 0 - elif pygem_prms['mb']['option_ablation'] == 2: + elif pygem_prms["mb"]["option_ablation"] == 2: # Seed randomness for repeatability, but base it on step to ensure the daily variability is not # the same for every single time step np.random.seed(step) # option 2: monthly temperature superimposed with daily temperature variability # daily temperature variation in each bin for the monthly timestep bin_tempstd_daily = np.repeat( - np.random.normal(loc=0, scale=self.glacier_gcm_tempstd[step], - size=self.dayspermonth[step]) - .reshape(1,self.dayspermonth[step]), heights.shape[0], axis=0) + np.random.normal( + loc=0, + scale=self.glacier_gcm_tempstd[step], + size=self.dayspermonth[step], + ).reshape(1, self.dayspermonth[step]), + heights.shape[0], + axis=0, + ) # daily temperature in each bin for the monthly timestep - bin_temp_daily = self.bin_temp[:,step][:,np.newaxis] + bin_tempstd_daily + bin_temp_daily = ( + self.bin_temp[:, step][:, np.newaxis] + bin_tempstd_daily + ) # remove negative values bin_temp_daily[bin_temp_daily < 0] = 0 # Energy available for melt [degC day] = sum of daily energy available melt_energy_available = bin_temp_daily.sum(axis=1) # SNOW MELT [m w.e.] - self.bin_meltsnow[:,step] = self.surfacetype_ddf_dict[2] * melt_energy_available + self.bin_meltsnow[:, step] = ( + self.surfacetype_ddf_dict[2] * melt_energy_available + ) # snow melt cannot exceed the snow depth - self.bin_meltsnow[self.bin_meltsnow[:,step] > self.bin_snowpack[:,step], step] = ( - self.bin_snowpack[self.bin_meltsnow[:,step] > self.bin_snowpack[:,step], step]) + self.bin_meltsnow[ + self.bin_meltsnow[:, step] > self.bin_snowpack[:, step], step + ] = self.bin_snowpack[ + self.bin_meltsnow[:, step] > self.bin_snowpack[:, step], step + ] # GLACIER MELT (ice and firn) [m w.e.] # energy remaining after snow melt [degC day] melt_energy_available = ( - melt_energy_available - self.bin_meltsnow[:,step] / self.surfacetype_ddf_dict[2]) + melt_energy_available + - self.bin_meltsnow[:, step] / self.surfacetype_ddf_dict[2] + ) # remove low values of energy available caused by rounding errors in the step above - melt_energy_available[abs(melt_energy_available) < pygem_prms['constants']['tolerance']] = 0 + melt_energy_available[ + abs(melt_energy_available) + < pygem_prms["constants"]["tolerance"] + ] = 0 # DDF based on surface type [m w.e. degC-1 day-1] for surfacetype_idx in self.surfacetype_ddf_dict: self.surfacetype_ddf[self.surfacetype == surfacetype_idx] = ( - self.surfacetype_ddf_dict[surfacetype_idx]) + self.surfacetype_ddf_dict[surfacetype_idx] + ) # Debris enhancement factors in ablation area (debris in accumulation area would submerge) - if surfacetype_idx == 1 and pygem_prms['mb']['include_debris']: + if surfacetype_idx == 1 and pygem_prms["mb"]["include_debris"]: self.surfacetype_ddf[self.surfacetype == 1] = ( - self.surfacetype_ddf[self.surfacetype == 1] * self.debris_ed[self.surfacetype == 1]) - self.bin_meltglac[glac_idx_t0,step] = ( - self.surfacetype_ddf[glac_idx_t0] * melt_energy_available[glac_idx_t0]) + self.surfacetype_ddf[self.surfacetype == 1] + * self.debris_ed[self.surfacetype == 1] + ) + self.bin_meltglac[glac_idx_t0, step] = ( + self.surfacetype_ddf[glac_idx_t0] + * melt_energy_available[glac_idx_t0] + ) # TOTAL MELT (snow + glacier) # off-glacier need to include melt of refreeze because there are no glacier dynamics, # but on-glacier do not need to account for this (simply assume refreeze has same surface type) - self.bin_melt[:,step] = self.bin_meltglac[:,step] + self.bin_meltsnow[:,step] + self.bin_melt[:, step] = ( + self.bin_meltglac[:, step] + self.bin_meltsnow[:, step] + ) # REFREEZING - if pygem_prms['mb']['option_refreezing'] == 'HH2015': + if pygem_prms["mb"]["option_refreezing"] == "HH2015": if step > 0: - self.tl_rf[:,:,step] = self.tl_rf[:,:,step-1] - self.te_rf[:,:,step] = self.te_rf[:,:,step-1] + self.tl_rf[:, :, step] = self.tl_rf[:, :, step - 1] + self.te_rf[:, :, step] = self.te_rf[:, :, step - 1] # Refreeze based on heat conduction approach (Huss and Hock 2015) # refreeze time step (s) - rf_dt = 3600 * 24 * self.dayspermonth[step] / pygem_prms['mb']['HH2015_rf_opts']['rf_dsc'] - - if pygem_prms['mb']['HH2015_rf_opts']['option_rf_limit_meltsnow'] == 1: + rf_dt = ( + 3600 + * 24 + * self.dayspermonth[step] + / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dsc"] + ) + + if ( + pygem_prms["mb"]["HH2015_rf_opts"][ + "option_rf_limit_meltsnow" + ] + == 1 + ): bin_meltlimit = self.bin_meltsnow.copy() else: bin_meltlimit = self.bin_melt.copy() # Debug lowest bin if self.debug_refreeze: - gidx_debug = np.where(heights == heights[glac_idx_t0].min())[0] + gidx_debug = np.where( + heights == heights[glac_idx_t0].min() + )[0] # Loop through each elevation bin of glacier for nbin, gidx in enumerate(glac_idx_t0): # COMPUTE HEAT CONDUCTION - BUILD COLD RESERVOIR # If no melt, then build up cold reservoir (compute heat conduction) - if self.bin_melt[gidx,step] < pygem_prms['mb']['HH2015_rf_opts']['rf_meltcrit']: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('\nMonth ' + str(self.dates_table.loc[step,'month']), - 'Computing heat conduction') + if ( + self.bin_melt[gidx, step] + < pygem_prms["mb"]["HH2015_rf_opts"]["rf_meltcrit"] + ): + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "\nMonth " + + str(self.dates_table.loc[step, "month"]), + "Computing heat conduction", + ) # Set refreeze equal to 0 self.refr[gidx] = 0 # Loop through multiple iterations to converge on a solution # -> this will loop through 0, 1, 2 - for h in np.arange(0, pygem_prms['mb']['HH2015_rf_opts']['rf_dsc']): + for h in np.arange( + 0, pygem_prms["mb"]["HH2015_rf_opts"]["rf_dsc"] + ): # Compute heat conduction in layers (loop through rows) # go from 1 to rf_layers-1 to avoid indexing errors with "j-1" and "j+1" # "j+1" is set to zero, which is fine for temperate glaciers but inaccurate for # cold/polythermal glaciers - for j in np.arange(1, pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1): + for j in np.arange( + 1, + pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + - 1, + ): # Assume temperature of first layer equals air temperature # assumption probably wrong, but might still work at annual average # Since next line uses tl_rf for all calculations, set tl_rf[0] to present mean # monthly air temperature to ensure the present calculations are done with the # present time step's air temperature - self.tl_rf[0, gidx,step] = self.bin_temp[gidx,step] + self.tl_rf[0, gidx, step] = self.bin_temp[ + gidx, step + ] # Temperature for each layer - self.te_rf[j,gidx,step] = (self.tl_rf[j,gidx,step] + - rf_dt * self.rf_layers_k[j] / self.rf_layers_ch[j] / pygem_prms['mb']['HH2015_rf_opts']['rf_dz']**2 * - 0.5 * ((self.tl_rf[j-1,gidx,step] - self.tl_rf[j,gidx,step]) - - (self.tl_rf[j,gidx,step] - self.tl_rf[j+1,gidx,step]))) + self.te_rf[j, gidx, step] = self.tl_rf[ + j, gidx, step + ] + rf_dt * self.rf_layers_k[ + j + ] / self.rf_layers_ch[j] / pygem_prms["mb"][ + "HH2015_rf_opts" + ]["rf_dz"] ** 2 * 0.5 * ( + ( + self.tl_rf[j - 1, gidx, step] + - self.tl_rf[j, gidx, step] + ) + - ( + self.tl_rf[j, gidx, step] + - self.tl_rf[j + 1, gidx, step] + ) + ) # Update previous time step - self.tl_rf[:,gidx,step] = self.te_rf[:,gidx,step] - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('tl_rf:', ["{:.2f}".format(x) for x in self.tl_rf[:,gidx,step]]) + self.tl_rf[:, gidx, step] = self.te_rf[ + :, gidx, step + ] + + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "tl_rf:", + [ + "{:.2f}".format(x) + for x in self.tl_rf[:, gidx, step] + ], + ) # COMPUTE REFREEZING - TAP INTO "COLD RESERVOIR" or potential refreezing else: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('\nMonth ' + str(self.dates_table.loc[step,'month']), 'Computing refreeze') + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "\nMonth " + + str(self.dates_table.loc[step, "month"]), + "Computing refreeze", + ) # Refreezing over firn surface - if (self.surfacetype[gidx] == 2) or (self.surfacetype[gidx] == 3): - nlayers = pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1 + if (self.surfacetype[gidx] == 2) or ( + self.surfacetype[gidx] == 3 + ): + nlayers = ( + pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + - 1 + ) # Refreezing over ice surface else: # Approximate number of layers of snow on top of ice - smax = np.round((self.bin_snowpack[gidx,step] / (self.rf_layers_dens[0] / 1000) + - pygem_prms['mb']['HH2015_rf_opts']['pp']) / pygem_prms['mb']['HH2015_rf_opts']['rf_dz'], 0) + smax = np.round( + ( + self.bin_snowpack[gidx, step] + / (self.rf_layers_dens[0] / 1000) + + pygem_prms["mb"]["HH2015_rf_opts"]["pp"] + ) + / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dz"], + 0, + ) # if there is very little snow on the ground (SWE > 0.06 m for pp=0.3), # then still set smax (layers) to 1 - if self.bin_snowpack[gidx,step] > 0 and smax == 0: - smax=1 + if self.bin_snowpack[gidx, step] > 0 and smax == 0: + smax = 1 # if no snow on the ground, then set to rf_cold to NoData value if smax == 0: self.rf_cold[gidx] = 0 # if smax greater than the number of layers, set to max number of layers minus 1 - if smax > pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1: - smax = pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1 + if ( + smax + > pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1 + ): + smax = ( + pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1 + ) nlayers = int(smax) # Compute potential refreeze, "cold reservoir", from temperature in each layer # only calculate potential refreezing first time it starts melting each year - if self.rf_cold[gidx] == 0 and self.tl_rf[:,gidx,step].min() < 0: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('calculating potential refreeze from ' + str(nlayers) + ' layers') - - for j in np.arange(0,nlayers): + if ( + self.rf_cold[gidx] == 0 + and self.tl_rf[:, gidx, step].min() < 0 + ): + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "calculating potential refreeze from " + + str(nlayers) + + " layers" + ) + + for j in np.arange(0, nlayers): j += 1 # units: (degC) * (J K-1 m-3) * (m) * (kg J-1) * (m3 kg-1) - rf_cold_layer = (self.tl_rf[j,gidx,step] * self.rf_layers_ch[j] * - pygem_prms['mb']['HH2015_rf_opts']['rf_dz'] / pygem_prms['constants']['Lh_rf'] / pygem_prms['constants']['density_water']) + rf_cold_layer = ( + self.tl_rf[j, gidx, step] + * self.rf_layers_ch[j] + * pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_dz" + ] + / pygem_prms["constants"]["Lh_rf"] + / pygem_prms["constants"]["density_water"] + ) self.rf_cold[gidx] -= rf_cold_layer - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('j:', j, 'tl_rf @ j:', np.round(self.tl_rf[j,gidx,step],2), - 'ch @ j:', np.round(self.rf_layers_ch[j],2), - 'rf_cold_layer @ j:', np.round(rf_cold_layer,2), - 'rf_cold @ j:', np.round(self.rf_cold[gidx],2)) - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('rf_cold:', np.round(self.rf_cold[gidx],2)) + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "j:", + j, + "tl_rf @ j:", + np.round(self.tl_rf[j, gidx, step], 2), + "ch @ j:", + np.round(self.rf_layers_ch[j], 2), + "rf_cold_layer @ j:", + np.round(rf_cold_layer, 2), + "rf_cold @ j:", + np.round(self.rf_cold[gidx], 2), + ) + + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "rf_cold:", np.round(self.rf_cold[gidx], 2) + ) # Compute refreezing # If melt and liquid prec < potential refreeze, then refreeze all melt and liquid prec - if (bin_meltlimit[gidx,step] + self.bin_prec[gidx,step]) < self.rf_cold[gidx]: - self.refr[gidx] = bin_meltlimit[gidx,step] + self.bin_prec[gidx,step] + if ( + bin_meltlimit[gidx, step] + + self.bin_prec[gidx, step] + ) < self.rf_cold[gidx]: + self.refr[gidx] = ( + bin_meltlimit[gidx, step] + + self.bin_prec[gidx, step] + ) # otherwise, refreeze equals the potential refreeze elif self.rf_cold[gidx] > 0: self.refr[gidx] = self.rf_cold[gidx] @@ -515,116 +790,206 @@ 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)) - - elif pygem_prms['mb']['option_refreezing'] == 'Woodward': + 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) @@ -637,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) + 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 @@ -681,26 +1054,33 @@ def _convert_glacwide_results(self, year, glacier_area, heights, """ # Glacier area glac_idx = glacier_area.nonzero()[0] - glacier_area_monthly = glacier_area[:,np.newaxis].repeat(12,axis=1) + glacier_area_monthly = glacier_area[:, np.newaxis].repeat(12, axis=1) # Check if need to adjust for complete removal of the glacier # - needed for accurate runoff calcs and accurate mass balance components - icethickness_t0 = getattr(fls[fl_id], 'thick', None) + icethickness_t0 = getattr(fls[fl_id], "thick", None) if icethickness_t0 is not None: # Mass loss cannot exceed glacier volume if glacier_area.sum() > 0: - mb_max_loss = (-1 * (glacier_area * icethickness_t0).sum() / glacier_area.sum() * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + mb_max_loss = ( + -1 + * (glacier_area * icethickness_t0).sum() + / glacier_area.sum() + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # Check annual climatic mass balance (mwea) - mb_mwea = ((glacier_area * self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1)).sum() / - glacier_area.sum()) + mb_mwea = ( + glacier_area + * self.glac_bin_massbalclim[:, 12 * year : 12 * (year + 1)].sum(1) + ).sum() / glacier_area.sum() else: mb_max_loss = 0 mb_mwea = 0 if len(glac_idx) > 0: # Quality control for thickness - if hasattr(fls[fl_id], 'thick'): + if hasattr(fls[fl_id], "thick"): thickness = fls[fl_id].thick glacier_area[thickness == 0] = 0 section = fls[fl_id].section @@ -708,107 +1088,143 @@ 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 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] + 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): """ @@ -825,38 +1241,58 @@ def ensure_mass_conservation(self, diag): 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']) + 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]) + 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) + 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): """ @@ -888,33 +1324,48 @@ def _surfacetypebinsinitial(self, elev_bins): """ surfacetype = np.zeros(self.glacier_area_initial.shape) # Option 1 - initial surface type based on the median elevation - if pygem_prms['mb']['option_surfacetype_initial'] == 1: - surfacetype[(elev_bins < self.glacier_rgi_table.loc['Zmed']) & (self.glacier_area_initial > 0)] = 1 - surfacetype[(elev_bins >= self.glacier_rgi_table.loc['Zmed']) & (self.glacier_area_initial > 0)] = 2 + if pygem_prms["mb"]["option_surfacetype_initial"] == 1: + surfacetype[ + (elev_bins < self.glacier_rgi_table.loc["Zmed"]) + & (self.glacier_area_initial > 0) + ] = 1 + surfacetype[ + (elev_bins >= self.glacier_rgi_table.loc["Zmed"]) + & (self.glacier_area_initial > 0) + ] = 2 # Option 2 - initial surface type based on the mean elevation - elif pygem_prms['mb']['option_surfacetype_initial'] ==2: - surfacetype[(elev_bins < self.glacier_rgi_table['Zmean']) & (self.glacier_area_initial > 0)] = 1 - surfacetype[(elev_bins >= self.glacier_rgi_table['Zmean']) & (self.glacier_area_initial > 0)] = 2 + elif pygem_prms["mb"]["option_surfacetype_initial"] == 2: + surfacetype[ + (elev_bins < self.glacier_rgi_table["Zmean"]) + & (self.glacier_area_initial > 0) + ] = 1 + surfacetype[ + (elev_bins >= self.glacier_rgi_table["Zmean"]) + & (self.glacier_area_initial > 0) + ] = 2 else: - print("This option for 'option_surfacetype' does not exist. Please choose an option that exists. " - + "Exiting model run.\n") + print( + "This option for 'option_surfacetype' does not exist. Please choose an option that exists. " + + "Exiting model run.\n" + ) exit() # Compute firnline index try: # firn in bins >= firnline_idx - firnline_idx = np.where(surfacetype==2)[0][0] + firnline_idx = np.where(surfacetype == 2)[0][0] except: # avoid errors if there is no firn, i.e., the entire glacier is melting - firnline_idx = np.where(surfacetype!=0)[0][-1] + firnline_idx = np.where(surfacetype != 0)[0][-1] # If firn is included, then specify initial firn conditions - if pygem_prms['mb']['include_firn'] == 1: + if pygem_prms["mb"]["include_firn"] == 1: surfacetype[surfacetype == 2] = 3 # everything initially considered snow is considered firn, i.e., the model initially assumes there is no # snow on the surface anywhere. return surfacetype, firnline_idx - - def _surfacetypebinsannual(self, surfacetype, glac_bin_massbalclim_annual, year_index): + def _surfacetypebinsannual( + self, surfacetype, glac_bin_massbalclim_annual, year_index + ): """ Update surface type according to climatic mass balance over the last five years. @@ -958,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: + 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. @@ -1006,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'] + surfacetype_ddf_dict[3] = modelprms["ddfsnow"] elif option_ddf_firn == 1: - surfacetype_ddf_dict[3] = np.mean([modelprms['ddfsnow'],modelprms['ddfice']]) + 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 abd2f98a..267e6836 100644 --- a/pygem/mcmc.py +++ b/pygem/mcmc.py @@ -7,6 +7,7 @@ Markov chain Monte Carlo methods """ + import copy import matplotlib.pyplot as plt @@ -23,17 +24,20 @@ torch.set_default_dtype(torch.float64) plt.rcParams["font.family"] = "arial" -plt.rcParams['font.size'] = 8 -plt.rcParams['legend.fontsize'] = 6 +plt.rcParams["font.size"] = 8 +plt.rcParams["legend.fontsize"] = 6 + # z-normalization functions def z_normalize(params, means, std_devs): return (params - means) / std_devs + # inverse z-normalization -def inverse_z_normalize(z_params, means, std_devs): +def inverse_z_normalize(z_params, means, std_devs): return z_params * std_devs + means + def log_normal_density(x, **kwargs): """ Computes the log probability density of a normal distribution. @@ -46,7 +50,7 @@ def log_normal_density(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma = kwargs['mu'], kwargs['sigma'] + mu, sigma = kwargs["mu"], kwargs["sigma"] # flatten arrays and get dimensionality x = x.flatten() @@ -54,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): """ @@ -72,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): """ @@ -89,7 +102,7 @@ def log_truncated_normal(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma, lo, hi = kwargs['mu'], kwargs['sigma'], kwargs['low'], kwargs['high'] + mu, sigma, lo, hi = kwargs["mu"], kwargs["sigma"], kwargs["low"], kwargs["high"] # Standardize standard_x = (x - mu) / sigma standard_a = (lo - mu) / sigma @@ -106,16 +119,20 @@ def log_truncated_normal(x, **kwargs): return torch.log(pdf) - torch.log(normalization) + # mapper dictionary - maps to appropriate log probability density function for given distribution `type` log_prob_fxn_map = { - 'normal': log_normal_density, - 'gamma': log_gamma_density, - 'truncnormal': log_truncated_normal + "normal": log_normal_density, + "gamma": log_gamma_density, + "truncnormal": log_truncated_normal, } + # mass balance posterior class class mbPosterior: - def __init__(self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kwargs): + def __init__( + self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kwargs + ): # obs will be passed as a list, where each item is a tuple with the first element being the mean observation, and the second being the variance self.obs = obs self.priors = copy.deepcopy(priors) @@ -126,8 +143,8 @@ def __init__(self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kw self.check_priors() # get mean and std for each parameter type - self.means = torch.tensor([params['mu'] for params in self.priors.values()]) - self.stds = torch.tensor([params['sigma'] for params in self.priors.values()]) + self.means = torch.tensor([params["mu"] for params in self.priors.values()]) + self.stds = torch.tensor([params["sigma"] for params in self.priors.values()]) # check priors. remove any subkeys that have a `None` value, and ensure that we have a mean and standard deviation for and gamma distributions def check_priors(self): @@ -137,24 +154,28 @@ def check_priors(self): if value is None: keys_rm.append(i) # Add key to remove list # ensure torch tensor objects - elif isinstance(value,str) and 'inf' in value: + elif isinstance(value, str) and "inf" in value: self.priors[k][i] = torch.tensor([float(value)]) - elif isinstance(value,float): + elif isinstance(value, float): self.priors[k][i] = torch.tensor([self.priors[k][i]]) # Remove the keys outside of the iteration for i in keys_rm: del self.priors[k][i] for k in self.priors.keys(): - if self.priors[k]['type'] == 'gamma' and 'mu' not in self.priors[k].keys(): - self.priors[k]['mu'] = self.priors[k]['alpha'] / self.priors[k]['beta'] - self.priors[k]['sigma'] = float(np.sqrt(self.priors[k]['alpha']) / self.priors[k]['beta']) + if self.priors[k]["type"] == "gamma" and "mu" not in self.priors[k].keys(): + self.priors[k]["mu"] = self.priors[k]["alpha"] / self.priors[k]["beta"] + self.priors[k]["sigma"] = float( + np.sqrt(self.priors[k]["alpha"]) / self.priors[k]["beta"] + ) # update modelprms for evaluation def update_modelprms(self, m): - for i, k in enumerate(['tbias','kp','ddfsnow']): + for i, k in enumerate(["tbias", "kp", "ddfsnow"]): self.mb_args[1][k] = float(m[i]) - self.mb_args[1]['ddfice'] = self.mb_args[1]['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + self.mb_args[1]["ddfice"] = ( + self.mb_args[1]["ddfsnow"] / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) # get mb_pred def get_mb_pred(self, m): @@ -165,14 +186,16 @@ def get_mb_pred(self, m): self.preds = self.mb_func([*m]) if not isinstance(self.preds, tuple): self.preds = [self.preds] - self.preds = [torch.tensor(item) for item in self.preds] # make all preds torch.tensor() objects + self.preds = [ + torch.tensor(item) for item in self.preds + ] # make all preds torch.tensor() objects # get total log prior density def log_prior(self, m): log_prior = [] for i, (key, params) in enumerate(self.priors.items()): params_copy = params.copy() - prior_type = params_copy.pop('type') + prior_type = params_copy.pop("type") function_to_call = log_prob_fxn_map[prior_type] log_prior.append(function_to_call(m[i], **params_copy)) log_prior = torch.stack(log_prior).sum() @@ -182,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: @@ -214,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: @@ -239,17 +267,27 @@ 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)) @@ -258,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 @@ -277,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 @@ -285,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: @@ -320,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. @@ -351,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 = np.correlate(x, x, mode="full") + rho = rho[len(rho) // 2 :] # normalize the autocorrelation values # note: rho[0] is the variance * n_samples, so this is consistent # with the statistics definition of autocorrelation on wikipedia @@ -369,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() @@ -385,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].set_ylabel(r'$K_p$', fontsize=fontsize) - - axes[2].plot(m_primes[:, 2],'.',ms=ms, c='tab:blue') - axes[2].plot(m_chain[:, 2],'.',ms=ms, c='tab:orange') - axes[2].plot([],[],label=f'mean={np.mean(m_chain[:, 2]):.3f}\nstd={np.std(m_chain[:, 2]):.3f}') - l2 = axes[2].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - axes[2].set_ylabel(r'$fsnow$', fontsize=fontsize) - - axes[3].fill_between(np.arange(len(ar)),mb_obs[0]-(2*mb_obs[1]),mb_obs[0]+(2*mb_obs[1]),color='grey',alpha=.3) - axes[3].fill_between(np.arange(len(ar)),mb_obs[0]-mb_obs[1],mb_obs[0]+mb_obs[1],color='grey',alpha=.3) - axes[3].plot(m_primes[:, 3],'.',ms=ms, c='tab:blue') - axes[3].plot(m_chain[:, 3],'.',ms=ms, c='tab:orange') - axes[3].plot([],[],label=f'mean={np.mean(m_chain[:, 3]):.3f}\nstd={np.std(m_chain[:, 3]):.3f}') - l3 = axes[3].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - axes[3].set_ylabel(r'$\dot{{b}}$', fontsize=fontsize) - - axes[4].plot(ar,'tab:orange', lw=1) - axes[4].plot(np.convolve(ar, np.ones(100)/100, mode='valid'), 'k', label='moving avg.', lw=1) - l4 = axes[4].legend(loc='upper left',handlelength=.5, borderaxespad=0, fontsize=fontsize) - axes[4].set_ylabel(r'$AR$', fontsize=fontsize) + axes[0].set_ylabel(r"$T_{bias}$", fontsize=fontsize) + + axes[1].plot(m_primes[:, 1], ".", ms=ms, c="tab:blue") + axes[1].plot(m_chain[:, 1], ".", ms=ms, c="tab:orange") + axes[1].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 1]):.3f}\nstd={np.std(m_chain[:, 1]):.3f}", + ) + l1 = axes[1].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[1].set_ylabel(r"$K_p$", fontsize=fontsize) + + axes[2].plot(m_primes[:, 2], ".", ms=ms, c="tab:blue") + axes[2].plot(m_chain[:, 2], ".", ms=ms, c="tab:orange") + axes[2].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 2]):.3f}\nstd={np.std(m_chain[:, 2]):.3f}", + ) + l2 = axes[2].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[2].set_ylabel(r"$fsnow$", fontsize=fontsize) + + axes[3].fill_between( + np.arange(len(ar)), + mb_obs[0] - (2 * mb_obs[1]), + mb_obs[0] + (2 * mb_obs[1]), + color="grey", + alpha=0.3, + ) + axes[3].fill_between( + np.arange(len(ar)), + mb_obs[0] - mb_obs[1], + mb_obs[0] + mb_obs[1], + color="grey", + alpha=0.3, + ) + axes[3].plot(m_primes[:, 3], ".", ms=ms, c="tab:blue") + axes[3].plot(m_chain[:, 3], ".", ms=ms, c="tab:orange") + axes[3].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 3]):.3f}\nstd={np.std(m_chain[:, 3]):.3f}", + ) + l3 = axes[3].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[3].set_ylabel(r"$\dot{{b}}$", fontsize=fontsize) + + axes[4].plot(ar, "tab:orange", lw=1) + axes[4].plot( + np.convolve(ar, np.ones(100) / 100, mode="valid"), + "k", + label="moving avg.", + lw=1, + ) + l4 = axes[4].legend( + loc="upper left", handlelength=0.5, borderaxespad=0, fontsize=fontsize + ) + axes[4].set_ylabel(r"$AR$", fontsize=fontsize) for i, ax in enumerate(axes): - ax.xaxis.set_ticks_position('both') - ax.yaxis.set_ticks_position('both') - ax.tick_params(axis="both",direction="inout") - if i==4: + ax.xaxis.set_ticks_position("both") + ax.yaxis.set_ticks_position("both") + ax.tick_params(axis="both", direction="inout") + if i == 4: continue - ax.plot([],[],label=f'n_eff={neff[i]}') + ax.plot([], [], label=f"n_eff={neff[i]}") hands, ls = ax.get_legend_handles_labels() - if i==0: - ax.legend(handles=[hands[1],hands[2],hands[3]], labels=[ls[1],ls[2],ls[3]], loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize) + if i == 0: + ax.legend( + handles=[hands[1], hands[2], hands[3]], + labels=[ls[1], ls[2], ls[3]], + loc="upper left", + borderaxespad=0, + handlelength=0, + fontsize=fontsize, + ) else: - ax.legend(handles=[hands[-1]], labels=[ls[-1]], loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize) + ax.legend( + handles=[hands[-1]], + labels=[ls[-1]], + loc="upper left", + borderaxespad=0, + handlelength=0, + fontsize=fontsize, + ) axes[0].add_artist(l0) axes[1].add_artist(l1) @@ -454,16 +559,25 @@ def plot_resid_hist(obs, preds, title, fontsize=8, show=False, fpath=None): # Plot the trace of the parameters fig, axes = plt.subplots(1, 1, figsize=(3, 2)) # subtract obs from preds to get residuals - diffs = np.concatenate([pred.flatten() - obs[0].flatten().numpy() for pred in preds]) + diffs = np.concatenate( + [pred.flatten() - obs[0].flatten().numpy() for pred in preds] + ) # mask nans to avoid error in np.histogram() diffs = diffs[~np.isnan(diffs)] # Calculate histogram counts and bin edges counts, bin_edges = np.histogram(diffs, bins=20) pct = counts / counts.sum() * 100 bin_width = bin_edges[1] - bin_edges[0] - axes.bar(bin_edges[:-1], pct, width=bin_width, edgecolor='black', color='gray', align='edge') - axes.set_xlabel('residuals (pred - obs)', fontsize=fontsize) - axes.set_ylabel('count (%)', fontsize=fontsize) + axes.bar( + bin_edges[:-1], + pct, + width=bin_width, + edgecolor="black", + color="gray", + align="edge", + ) + axes.set_xlabel("residuals (pred - obs)", fontsize=fontsize) + axes.set_ylabel("count (%)", fontsize=fontsize) axes.set_title(title, fontsize=fontsize) plt.tight_layout() plt.subplots_adjust(hspace=0.1, wspace=0) diff --git a/pygem/oggm_compat.py b/pygem/oggm_compat.py index 2337fef0..380e7538 100755 --- a/pygem/oggm_compat.py +++ b/pygem/oggm_compat.py @@ -7,6 +7,7 @@ PYGEM-OGGGM COMPATIBILITY FUNCTIONS """ + import os import netCDF4 @@ -16,14 +17,14 @@ import pandas as pd from oggm import cfg, tasks, utils, workflow -#from oggm import tasks +# from oggm import tasks from oggm.cfg import SEC_IN_YEAR from oggm.core import flowline from oggm.core.massbalance import MassBalanceModel from pygem.setup.config import ConfigManager -#from oggm.shop import rgitopo +# from oggm.shop import rgitopo from pygem.shop import debris, icethickness, mbdata # instantiate ConfigManager @@ -36,9 +37,15 @@ 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,73 +64,83 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - if rgi_id.startswith('RGI60-') == False: - rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] + raise ValueError("We expect rgi_id to be a string") + if rgi_id.startswith("RGI60-") == False: + rgi_id = "RGI60-" + rgi_id.split(".")[0].zfill(2) + "." + rgi_id.split(".")[1] else: - raise ValueError('Check RGIId is correct') + raise ValueError("Check RGIId is correct") # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS['use_multiprocessing'] = False + cfg.PARAMS["use_multiprocessing"] = False # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS['continue_on_error'] = True + cfg.PARAMS["continue_on_error"] = True # Has internet - cfg.PARAMS['has_internet'] = has_internet + cfg.PARAMS["has_internet"] = has_internet # Set border boundary - cfg.PARAMS['border'] = prepro_border + cfg.PARAMS["border"] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS['dl_verify'] = True - cfg.PARAMS['use_multiple_flowlines'] = False + cfg.PARAMS["dl_verify"] = True + cfg.PARAMS["use_multiple_flowlines"] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS['working_dir'] = working_dir + cfg.PATHS["working_dir"] = working_dir # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle('inversion_flowlines') + gdir.read_pickle("inversion_flowlines") except: reset = True if reset: # Start after the prepro task level - base_url = pygem_prms['oggm']['base_url'] + base_url = pygem_prms["oggm"]["base_url"] - cfg.PARAMS['has_internet'] = pygem_prms['oggm']['has_internet'] - gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=2, prepro_border=cfg.PARAMS['border'], - prepro_base_url=base_url, prepro_rgi_version='62')[0] + cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + gdir = workflow.init_glacier_directories( + [rgi_id], + from_prepro_level=2, + prepro_border=cfg.PARAMS["border"], + prepro_base_url=base_url, + prepro_rgi_version="62", + )[0] # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath('consensus_mass')): + if not os.path.isfile(gdir.get_filepath("consensus_mass")): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data - if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): + if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): workflow.execute_entity_task(mbdata.mb_df_to_gdir, gdir) # debris thickness and melt enhancement factors - if not os.path.isfile(gdir.get_filepath('debris_ed')) or os.path.isfile(gdir.get_filepath('debris_hd')): + if not os.path.isfile(gdir.get_filepath("debris_ed")) or os.path.isfile( + gdir.get_filepath("debris_hd") + ): workflow.execute_entity_task(debris.debris_to_gdir, gdir) workflow.execute_entity_task(debris.debris_binned, gdir) return gdir - -def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['oggm']['overwrite_gdirs'], - prepro_border=pygem_prms['oggm']['border'], k_calving=1, - logging_level= pygem_prms['oggm']['logging_level'], - has_internet= pygem_prms['oggm']['has_internet'], - working_dir=pygem_prms['root'] + pygem_prms['oggm']['oggm_gdir_relpath'], - facorrected=pygem_prms['setup']['include_frontalablation']): +def single_flowline_glacier_directory_with_calving( + rgi_id, + reset=pygem_prms["oggm"]["overwrite_gdirs"], + prepro_border=pygem_prms["oggm"]["border"], + k_calving=1, + logging_level=pygem_prms["oggm"]["logging_level"], + has_internet=pygem_prms["oggm"]["has_internet"], + working_dir=pygem_prms["root"] + pygem_prms["oggm"]["oggm_gdir_relpath"], + facorrected=pygem_prms["setup"]["include_frontalablation"], +): """Prepare a GlacierDirectory for PyGEM (single flowline to start with) k_calving is free variable! @@ -143,62 +160,69 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - if rgi_id.startswith('RGI60-') == False: - rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] + raise ValueError("We expect rgi_id to be a string") + if rgi_id.startswith("RGI60-") == False: + rgi_id = "RGI60-" + rgi_id.split(".")[0].zfill(2) + "." + rgi_id.split(".")[1] else: - raise ValueError('Check RGIId is correct') + raise ValueError("Check RGIId is correct") # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS['use_multiprocessing'] = False + cfg.PARAMS["use_multiprocessing"] = False # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS['continue_on_error'] = True + cfg.PARAMS["continue_on_error"] = True # Has internet - cfg.PARAMS['has_internet'] = has_internet + cfg.PARAMS["has_internet"] = has_internet # Set border boundary - cfg.PARAMS['border'] = prepro_border + cfg.PARAMS["border"] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS['dl_verify'] = True - cfg.PARAMS['use_multiple_flowlines'] = False + cfg.PARAMS["dl_verify"] = True + cfg.PARAMS["use_multiple_flowlines"] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS['working_dir'] = working_dir + cfg.PATHS["working_dir"] = working_dir # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle('inversion_flowlines') + gdir.read_pickle("inversion_flowlines") except: reset = True if reset: # Start after the prepro task level - base_url = pygem_prms['oggm']['base_url'] + base_url = pygem_prms["oggm"]["base_url"] - cfg.PARAMS['has_internet'] = pygem_prms['oggm']['has_internet'] - gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=2, prepro_border=cfg.PARAMS['border'], - prepro_base_url=base_url, prepro_rgi_version='62')[0] + cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + gdir = workflow.init_glacier_directories( + [rgi_id], + from_prepro_level=2, + prepro_border=cfg.PARAMS["border"], + prepro_base_url=base_url, + prepro_rgi_version="62", + )[0] if not gdir.is_tidewater: - raise ValueError(f'{rgi_id} is not tidewater!') + raise ValueError(f"{rgi_id} is not tidewater!") # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath('consensus_mass')): + if not os.path.isfile(gdir.get_filepath("consensus_mass")): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data (note facorrected kwarg) - if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): - workflow.execute_entity_task(mbdata.mb_df_to_gdir, gdir, **{"facorrected": facorrected}) + if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): + workflow.execute_entity_task( + mbdata.mb_df_to_gdir, gdir, **{"facorrected": facorrected} + ) return gdir @@ -211,9 +235,12 @@ def l3_proc(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) @@ -232,17 +259,20 @@ def l3_proc(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): @@ -259,8 +289,8 @@ def create_empty_glacier_directory(rgi_id): """ # RGIId check if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - assert rgi_id.startswith('RGI60-'), 'Check RGIId starts with RGI60-' + raise ValueError("We expect rgi_id to be a string") + assert rgi_id.startswith("RGI60-"), "Check RGIId starts with RGI60-" # Create empty directory gdir = CompatGlacDir(rgi_id) @@ -281,7 +311,7 @@ def get_glacier_zwh(gdir): a dataframe with the requested data """ - fls = gdir.read_pickle('model_flowlines') + fls = gdir.read_pickle("model_flowlines") z = np.array([]) w = np.array([]) h = np.array([]) @@ -297,10 +327,10 @@ def get_glacier_zwh(gdir): # Output df = pd.DataFrame() - df['z'] = z - df['w'] = w - df['h'] = h - df['dx'] = dx + df["z"] = z + df["w"] = w + df["h"] = h + df["dx"] = dx return df @@ -318,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 ---------- @@ -339,17 +369,16 @@ def __init__(self, gdir, grad=3., h_perc=60, sigma_ela=100., seed=None): self.valid_bounds = [-1e4, 2e4] # in m self.grad = grad self.sigma_ela = sigma_ela - self.hemisphere = 'nh' + self.hemisphere = "nh" self.rng = np.random.RandomState(seed) # Decide on a reference ELA - grids_file = gdir.get_filepath('gridded_data') + grids_file = gdir.get_filepath("gridded_data") with netCDF4.Dataset(grids_file) as nc: - glacier_mask = nc.variables['glacier_mask'][:] - glacier_topo = nc.variables['topo_smoothed'][:] + glacier_mask = nc.variables["glacier_mask"][:] + glacier_topo = nc.variables["topo_smoothed"][:] - self.orig_ela_h = np.percentile(glacier_topo[glacier_mask == 1], - h_perc) + self.orig_ela_h = np.percentile(glacier_topo[glacier_mask == 1], h_perc) self.ela_h_per_year = dict() # empty dictionary def get_random_ela_h(self, year): @@ -370,10 +399,9 @@ def get_random_ela_h(self, year): return ela_h def get_annual_mb(self, heights, year=None, fl_id=None): - # Compute the mass-balance gradient ela_h = self.get_random_ela_h(year) mb = (np.asarray(heights) - ela_h) * self.grad # Convert to units of [m s-1] (meters of ice per second) - return mb / SEC_IN_YEAR / cfg.PARAMS['ice_density'] + return mb / SEC_IN_YEAR / cfg.PARAMS["ice_density"] diff --git a/pygem/output.py b/pygem/output.py index 7d212e3e..4f0ab67e 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -11,6 +11,7 @@ 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 @@ -32,8 +33,19 @@ # 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", + "set_fn", + "get_fn", + "set_modelprms", + "create_xr_ds", + "get_xr_ds", + "save_xr_ds", + "calc_stats_array", +] + @dataclass class single_glacier: @@ -70,16 +82,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 @@ -98,9 +111,9 @@ 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.outdir = pygem_prms['root'] + '/Output/simulations/' + self.glacier_str = "{0:0.5f}".format(self.glacier_rgi_table["RGIId_float"]) + self.reg_str = str(self.glacier_rgi_table.O1Region).zfill(2) + self.outdir = pygem_prms["root"] + "/Output/simulations/" self.set_fn() self._set_time_vals() self._model_params_record() @@ -116,23 +129,23 @@ def set_fn(self, outfn=None): if outfn: self.outfn = outfn else: - self.outfn = self.glacier_str + '_' + self.gcm_name + '_' + self.outfn = self.glacier_str + "_" + self.gcm_name + "_" if self.scenario: - self.outfn += f'{self.scenario}_' + self.outfn += f"{self.scenario}_" if self.realization: - self.outfn += f'{self.realization}_' + self.outfn += f"{self.realization}_" if self.option_calibration: - self.outfn += f'{self.option_calibration}_' + self.outfn += f"{self.option_calibration}_" else: - self.outfn += f'kp{self.modelprms["kp"]}_ddfsnow{self.modelprms["ddfsnow"]}_tbias{self.modelprms["tbias"]}_' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outfn += f'ba{self.option_bias_adjustment}_' + self.outfn += f"kp{self.modelprms['kp']}_ddfsnow{self.modelprms['ddfsnow']}_tbias{self.modelprms['tbias']}_" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outfn += f"ba{self.option_bias_adjustment}_" else: - self.outfn += 'ba0_' + self.outfn += "ba0_" if self.option_calibration: - self.outfn += 'SETS_' - self.outfn += f'{self.gcm_startyear}_' - self.outfn += f'{self.gcm_endyear}_' + self.outfn += "SETS_" + self.outfn += f"{self.gcm_startyear}_" + self.outfn += f"{self.gcm_endyear}_" def get_fn(self): """Return the output dataset filename.""" @@ -156,34 +169,48 @@ def set_modelprms(self, modelprms): 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)] - elif pygem_prms['climate']['gcm_wateryear'] == 'calendar': - self.year_type = 'calendar year' - self.annual_columns = np.unique(self.dates_table['year'].values)[0:int(self.dates_table.shape[0]/12)] - elif pygem_prms['climate']['gcm_wateryear'] == 'custom': - self.year_type = 'custom year' - self.time_values = self.dates_table.loc[pygem_prms['climate']['gcm_spinupyears']*12:self.dates_table.shape[0]+1,'date'].tolist() - self.time_values = [cftime.DatetimeNoLeap(x.year, x.month, x.day) for x in self.time_values] + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + self.year_type = "water year" + self.annual_columns = np.unique(self.dates_table["wateryear"].values)[ + 0 : int(self.dates_table.shape[0] / 12) + ] + elif pygem_prms["climate"]["gcm_wateryear"] == "calendar": + self.year_type = "calendar year" + self.annual_columns = np.unique(self.dates_table["year"].values)[ + 0 : int(self.dates_table.shape[0] / 12) + ] + elif pygem_prms["climate"]["gcm_wateryear"] == "custom": + self.year_type = "custom year" + self.time_values = self.dates_table.loc[ + pygem_prms["climate"]["gcm_spinupyears"] * 12 : self.dates_table.shape[0] + + 1, + "date", + ].tolist() + self.time_values = [ + cftime.DatetimeNoLeap(x.year, x.month, x.day) for x in self.time_values + ] # append additional year to self.year_values to account for mass and area at end of period - self.year_values = self.annual_columns[pygem_prms['climate']['gcm_spinupyears']:self.annual_columns.shape[0]] - self.year_values = np.concatenate((self.year_values, np.array([self.annual_columns[-1] + 1]))) + self.year_values = self.annual_columns[ + pygem_prms["climate"]["gcm_spinupyears"] : self.annual_columns.shape[0] + ] + self.year_values = np.concatenate( + (self.year_values, np.array([self.annual_columns[-1] + 1])) + ) def _model_params_record(self): """Build model parameters attribute dictionary to be saved to output dataset.""" # get all locally defined variables from the pygem_prms, excluding imports, functions, and classes self.mdl_params_dict = {} # overwrite variables that are possibly different from pygem_input - self.mdl_params_dict['ref_startyear'] = self.ref_startyear - self.mdl_params_dict['ref_endyear'] = self.ref_endyear - self.mdl_params_dict['gcm_startyear'] = self.gcm_startyear - self.mdl_params_dict['gcm_endyear'] = self.gcm_endyear - self.mdl_params_dict['gcm_name'] = self.gcm_name - self.mdl_params_dict['realization'] = self.realization - self.mdl_params_dict['scenario'] = self.scenario - self.mdl_params_dict['option_calibration'] = self.option_calibration - self.mdl_params_dict['option_bias_adjustment'] = self.option_bias_adjustment + self.mdl_params_dict["ref_startyear"] = self.ref_startyear + self.mdl_params_dict["ref_endyear"] = self.ref_endyear + self.mdl_params_dict["gcm_startyear"] = self.gcm_startyear + self.mdl_params_dict["gcm_endyear"] = self.gcm_endyear + self.mdl_params_dict["gcm_name"] = self.gcm_name + self.mdl_params_dict["realization"] = self.realization + self.mdl_params_dict["scenario"] = self.scenario + self.mdl_params_dict["option_calibration"] = self.option_calibration + self.mdl_params_dict["option_bias_adjustment"] = self.option_bias_adjustment # record manually defined modelprms if calibration option is None if not self.option_calibration: self._update_modelparams_record() @@ -196,46 +223,67 @@ 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.""" @@ -244,15 +292,22 @@ def create_xr_ds(self): self.encoding = {} for vn in self.output_coords_dict.keys(): count_vn += 1 - empty_holder = np.zeros([len(self.output_coords_dict[vn][i]) for i in list(self.output_coords_dict[vn].keys())]) - output_xr_ds_ = xr.Dataset({vn: (list(self.output_coords_dict[vn].keys()), empty_holder)}, - coords=self.output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(self.output_coords_dict[vn][i]) + for i in list(self.output_coords_dict[vn].keys()) + ] + ) + output_xr_ds_ = xr.Dataset( + {vn: (list(self.output_coords_dict[vn].keys()), empty_holder)}, + coords=self.output_coords_dict[vn], + ) # Merge datasets of stats into one output if count_vn == 1: self.output_xr_ds = output_xr_ds_ else: self.output_xr_ds = xr.merge((self.output_xr_ds, output_xr_ds_)) - noencoding_vn = ['RGIId'] + noencoding_vn = ["RGIId"] # Add attributes for vn in self.output_xr_ds.variables: try: @@ -262,22 +317,28 @@ def create_xr_ds(self): # Encoding (specify _FillValue, offsets, etc.) if vn not in noencoding_vn: - self.encoding[vn] = {'_FillValue': None, - 'zlib':True, - 'complevel':9 - } - self.output_xr_ds['RGIId'].values = np.array([self.glacier_rgi_table.loc['RGIId']]) - self.output_xr_ds['CenLon'].values = np.array([self.glacier_rgi_table.CenLon]) - self.output_xr_ds['CenLat'].values = np.array([self.glacier_rgi_table.CenLat]) - self.output_xr_ds['O1Region'].values = np.array([self.glacier_rgi_table.O1Region]) - self.output_xr_ds['O2Region'].values = np.array([self.glacier_rgi_table.O2Region]) - self.output_xr_ds['Area'].values = np.array([self.glacier_rgi_table.Area * 1e6]) - - self.output_xr_ds.attrs = {'source': f'PyGEMv{self.pygem_version}', - 'institution': pygem_prms['user']['institution'], - 'history': f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + datetime.today().strftime('%Y-%m-%d'), - 'references': 'doi:10.1126/science.abo1324', - 'model_parameters':json.dumps(self.mdl_params_dict)} + self.encoding[vn] = {"_FillValue": None, "zlib": True, "complevel": 9} + self.output_xr_ds["RGIId"].values = np.array( + [self.glacier_rgi_table.loc["RGIId"]] + ) + self.output_xr_ds["CenLon"].values = np.array([self.glacier_rgi_table.CenLon]) + self.output_xr_ds["CenLat"].values = np.array([self.glacier_rgi_table.CenLat]) + self.output_xr_ds["O1Region"].values = np.array( + [self.glacier_rgi_table.O1Region] + ) + self.output_xr_ds["O2Region"].values = np.array( + [self.glacier_rgi_table.O2Region] + ) + self.output_xr_ds["Area"].values = np.array([self.glacier_rgi_table.Area * 1e6]) + + self.output_xr_ds.attrs = { + "source": f"PyGEMv{self.pygem_version}", + "institution": pygem_prms["user"]["institution"], + "history": f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + + datetime.today().strftime("%Y-%m-%d"), + "references": "doi:10.1126/science.abo1324", + "model_parameters": json.dumps(self.mdl_params_dict), + } def get_xr_ds(self): """Return the xarray dataset.""" @@ -315,293 +376,416 @@ def __post_init__(self): def _set_outdir(self): """Set the output directory path. Create if it does not already exist.""" - self.outdir += self.reg_str + '/' + self.gcm_name + '/' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outdir += self.scenario + '/' - self.outdir += 'stats/' + self.outdir += self.reg_str + "/" + self.gcm_name + "/" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outdir += self.scenario + "/" + self.outdir += "stats/" # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) 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_attrs_dict['glac_runoff_monthly'] = { - 'long_name': 'glacier-wide runoff', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'runoff from the glacier terminus, which moves over time'} - self.output_coords_dict['glac_area_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_area_annual'] = { - 'long_name': 'glacier area', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'area at start of the year'} - self.output_coords_dict['glac_mass_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_annual'] = { - 'long_name': 'glacier mass', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_mass_bsl_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_bsl_annual'] = { - 'long_name': 'glacier mass below sea level', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_ELA_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_ELA_annual'] = { - 'long_name': 'annual equilibrium line altitude above mean sea level', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'equilibrium line altitude is the elevation where the climatic mass balance is zero'} - self.output_coords_dict['offglac_runoff_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_runoff_monthly'] = { - 'long_name': 'off-glacier-wide runoff', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'off-glacier runoff from area where glacier no longer exists'} + self.output_coords_dict["glac_runoff_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_runoff_monthly"] = { + "long_name": "glacier-wide runoff", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "runoff from the glacier terminus, which moves over time", + } + self.output_coords_dict["glac_area_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_area_annual"] = { + "long_name": "glacier area", + "units": "m2", + "temporal_resolution": "annual", + "comment": "area at start of the year", + } + self.output_coords_dict["glac_mass_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_mass_annual"] = { + "long_name": "glacier mass", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_mass_bsl_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_mass_bsl_annual"] = { + "long_name": "glacier mass below sea level", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice below sea level based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_ELA_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_ELA_annual"] = { + "long_name": "annual equilibrium line altitude above mean sea level", + "units": "m", + "temporal_resolution": "annual", + "comment": "equilibrium line altitude is the elevation where the climatic mass balance is zero", + } + self.output_coords_dict["offglac_runoff_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["offglac_runoff_monthly"] = { + "long_name": "off-glacier-wide runoff", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "off-glacier runoff from area where glacier no longer exists", + } # if 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_attrs_dict['glac_runoff_monthly_mad'] = { - 'long_name': 'glacier-wide runoff median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'runoff from the glacier terminus, which moves over time'} - self.output_coords_dict['glac_area_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_area_annual_mad'] = { - 'long_name': 'glacier area median absolute deviation', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'area at start of the year'} - self.output_coords_dict['glac_mass_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_annual_mad'] = { - 'long_name': 'glacier mass median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_mass_bsl_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_bsl_annual_mad'] = { - 'long_name': 'glacier mass below sea level median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_ELA_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_ELA_annual_mad'] = { - 'long_name': 'annual equilibrium line altitude above mean sea level median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'equilibrium line altitude is the elevation where the climatic mass balance is zero'} - self.output_coords_dict['offglac_runoff_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_runoff_monthly_mad'] = { - 'long_name': 'off-glacier-wide runoff median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'off-glacier runoff from area where glacier no longer exists'} + self.output_coords_dict["glac_runoff_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_runoff_monthly_mad"] = { + "long_name": "glacier-wide runoff median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "runoff from the glacier terminus, which moves over time", + } + self.output_coords_dict["glac_area_annual_mad"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_area_annual_mad"] = { + "long_name": "glacier area median absolute deviation", + "units": "m2", + "temporal_resolution": "annual", + "comment": "area at start of the year", + } + self.output_coords_dict["glac_mass_annual_mad"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_mass_annual_mad"] = { + "long_name": "glacier mass median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_mass_bsl_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_bsl_annual_mad"] = { + "long_name": "glacier mass below sea level median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice below sea level based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_ELA_annual_mad"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_ELA_annual_mad"] = { + "long_name": "annual equilibrium line altitude above mean sea level median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "equilibrium line altitude is the elevation where the climatic mass balance is zero", + } + self.output_coords_dict["offglac_runoff_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_runoff_monthly_mad"] = { + "long_name": "off-glacier-wide runoff median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "off-glacier runoff from area where glacier no longer exists", + } # 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_attrs_dict['glac_prec_monthly'] = { - 'long_name': 'glacier-wide precipitation (liquid)', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['glac_temp_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_temp_monthly'] = { - 'standard_name': 'air_temperature', - 'long_name': 'glacier-wide mean air temperature', - 'units': 'K', - 'temporal_resolution': 'monthly', - 'comment': ('each elevation bin is weighted equally to compute the mean temperature, and ' - 'bins where the glacier no longer exists due to retreat have been removed')} - self.output_coords_dict['glac_acc_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_acc_monthly'] = { - 'long_name': 'glacier-wide accumulation, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the solid precipitation'} - self.output_coords_dict['glac_refreeze_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_refreeze_monthly'] = { - 'long_name': 'glacier-wide refreeze, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_melt_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_melt_monthly'] = { - 'long_name': 'glacier-wide melt, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_frontalablation_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_frontalablation_monthly'] = { - 'long_name': 'glacier-wide frontal ablation, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': ( - 'mass losses from calving, subaerial frontal melting, sublimation above the ' - 'waterline and subaqueous frontal melting below the waterline; positive values indicate mass lost like melt')} - self.output_coords_dict['glac_massbaltotal_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_massbaltotal_monthly'] = { - 'long_name': 'glacier-wide total mass balance, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'total mass balance is the sum of the climatic mass balance and frontal ablation'} - self.output_coords_dict['glac_snowline_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_snowline_monthly'] = { - 'long_name': 'transient snowline altitude above mean sea level', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'transient snowline is altitude separating snow from ice/firn'} - self.output_coords_dict['glac_mass_change_ignored_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_change_ignored_annual'] = { - 'long_name': 'glacier mass change ignored', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'glacier mass change ignored due to flux divergence'} - self.output_coords_dict['offglac_prec_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_prec_monthly'] = { - 'long_name': 'off-glacier-wide precipitation (liquid)', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['offglac_refreeze_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_refreeze_monthly'] = { - 'long_name': 'off-glacier-wide refreeze, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['offglac_melt_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_melt_monthly'] = { - 'long_name': 'off-glacier-wide melt, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only melt of snow and refreeze since off-glacier'} - self.output_coords_dict['offglac_snowpack_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_snowpack_monthly'] = { - 'long_name': 'off-glacier-wide snowpack, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze'} + if pygem_prms["sim"]["out"]["export_extra_vars"]: + self.output_coords_dict["glac_prec_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_prec_monthly"] = { + "long_name": "glacier-wide precipitation (liquid)", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["glac_temp_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_temp_monthly"] = { + "standard_name": "air_temperature", + "long_name": "glacier-wide mean air temperature", + "units": "K", + "temporal_resolution": "monthly", + "comment": ( + "each elevation bin is weighted equally to compute the mean temperature, and " + "bins where the glacier no longer exists due to retreat have been removed" + ), + } + self.output_coords_dict["glac_acc_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_acc_monthly"] = { + "long_name": "glacier-wide accumulation, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the solid precipitation", + } + self.output_coords_dict["glac_refreeze_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_refreeze_monthly"] = { + "long_name": "glacier-wide refreeze, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_melt_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_melt_monthly"] = { + "long_name": "glacier-wide melt, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_frontalablation_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_frontalablation_monthly"] = { + "long_name": "glacier-wide frontal ablation, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": ( + "mass losses from calving, subaerial frontal melting, sublimation above the " + "waterline and subaqueous frontal melting below the waterline; positive values indicate mass lost like melt" + ), + } + self.output_coords_dict["glac_massbaltotal_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_massbaltotal_monthly"] = { + "long_name": "glacier-wide total mass balance, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "total mass balance is the sum of the climatic mass balance and frontal ablation", + } + self.output_coords_dict["glac_snowline_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["glac_snowline_monthly"] = { + "long_name": "transient snowline altitude above mean sea level", + "units": "m", + "temporal_resolution": "monthly", + "comment": "transient snowline is altitude separating snow from ice/firn", + } + self.output_coords_dict["glac_mass_change_ignored_annual"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_change_ignored_annual"] = { + "long_name": "glacier mass change ignored", + "units": "kg", + "temporal_resolution": "annual", + "comment": "glacier mass change ignored due to flux divergence", + } + self.output_coords_dict["offglac_prec_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["offglac_prec_monthly"] = { + "long_name": "off-glacier-wide precipitation (liquid)", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["offglac_refreeze_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_refreeze_monthly"] = { + "long_name": "off-glacier-wide refreeze, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["offglac_melt_monthly"] = collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + self.output_attrs_dict["offglac_melt_monthly"] = { + "long_name": "off-glacier-wide melt, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only melt of snow and refreeze since off-glacier", + } + self.output_coords_dict["offglac_snowpack_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_snowpack_monthly"] = { + "long_name": "off-glacier-wide snowpack, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + } # if 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_attrs_dict['glac_temp_monthly_mad'] = { - 'standard_name': 'air_temperature', - 'long_name': 'glacier-wide mean air temperature median absolute deviation', - 'units': 'K', - 'temporal_resolution': 'monthly', - 'comment': ( - 'each elevation bin is weighted equally to compute the mean temperature, and ' - 'bins where the glacier no longer exists due to retreat have been removed')} - self.output_coords_dict['glac_acc_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_acc_monthly_mad'] = { - 'long_name': 'glacier-wide accumulation, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the solid precipitation'} - self.output_coords_dict['glac_refreeze_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_refreeze_monthly_mad'] = { - 'long_name': 'glacier-wide refreeze, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_melt_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_melt_monthly_mad'] = { - 'long_name': 'glacier-wide melt, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_frontalablation_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_frontalablation_monthly_mad'] = { - 'long_name': 'glacier-wide frontal ablation, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': ( - 'mass losses from calving, subaerial frontal melting, sublimation above the ' - 'waterline and subaqueous frontal melting below the waterline')} - self.output_coords_dict['glac_massbaltotal_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_massbaltotal_monthly_mad'] = { - 'long_name': 'glacier-wide total mass balance, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'total mass balance is the sum of the climatic mass balance and frontal ablation'} - self.output_coords_dict['glac_snowline_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_snowline_monthly_mad'] = { - 'long_name': 'transient snowline above mean sea level median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'transient snowline is altitude separating snow from ice/firn'} - self.output_coords_dict['glac_mass_change_ignored_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_change_ignored_annual_mad'] = { - 'long_name': 'glacier mass change ignored median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'glacier mass change ignored due to flux divergence'} - self.output_coords_dict['offglac_prec_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_prec_monthly_mad'] = { - 'long_name': 'off-glacier-wide precipitation (liquid) median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['offglac_refreeze_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_refreeze_monthly_mad'] = { - 'long_name': 'off-glacier-wide refreeze, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['offglac_melt_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_melt_monthly_mad'] = { - 'long_name': 'off-glacier-wide melt, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only melt of snow and refreeze since off-glacier'} - self.output_coords_dict['offglac_snowpack_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_snowpack_monthly_mad'] = { - 'long_name': 'off-glacier-wide snowpack, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze'} + self.output_coords_dict["glac_prec_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_prec_monthly_mad"] = { + "long_name": "glacier-wide precipitation (liquid) median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["glac_temp_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_temp_monthly_mad"] = { + "standard_name": "air_temperature", + "long_name": "glacier-wide mean air temperature median absolute deviation", + "units": "K", + "temporal_resolution": "monthly", + "comment": ( + "each elevation bin is weighted equally to compute the mean temperature, and " + "bins where the glacier no longer exists due to retreat have been removed" + ), + } + self.output_coords_dict["glac_acc_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_acc_monthly_mad"] = { + "long_name": "glacier-wide accumulation, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the solid precipitation", + } + self.output_coords_dict["glac_refreeze_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_refreeze_monthly_mad"] = { + "long_name": "glacier-wide refreeze, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_melt_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_melt_monthly_mad"] = { + "long_name": "glacier-wide melt, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_frontalablation_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_frontalablation_monthly_mad"] = { + "long_name": "glacier-wide frontal ablation, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": ( + "mass losses from calving, subaerial frontal melting, sublimation above the " + "waterline and subaqueous frontal melting below the waterline" + ), + } + self.output_coords_dict["glac_massbaltotal_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_massbaltotal_monthly_mad"] = { + "long_name": "glacier-wide total mass balance, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "total mass balance is the sum of the climatic mass balance and frontal ablation", + } + self.output_coords_dict["glac_snowline_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_snowline_monthly_mad"] = { + "long_name": "transient snowline above mean sea level median absolute deviation", + "units": "m", + "temporal_resolution": "monthly", + "comment": "transient snowline is altitude separating snow from ice/firn", + } + self.output_coords_dict["glac_mass_change_ignored_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_change_ignored_annual_mad"] = { + "long_name": "glacier mass change ignored median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "glacier mass change ignored due to flux divergence", + } + self.output_coords_dict["offglac_prec_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_prec_monthly_mad"] = { + "long_name": "off-glacier-wide precipitation (liquid) median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["offglac_refreeze_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_refreeze_monthly_mad"] = { + "long_name": "off-glacier-wide refreeze, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["offglac_melt_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_melt_monthly_mad"] = { + "long_name": "off-glacier-wide melt, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only melt of snow and refreeze since off-glacier", + } + self.output_coords_dict["offglac_snowpack_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_snowpack_monthly_mad"] = { + "long_name": "off-glacier-wide snowpack, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + } @dataclass @@ -640,110 +824,188 @@ def __post_init__(self): def _set_outdir(self): """Set the output directory path. Create if it does not already exist.""" - self.outdir += self.reg_str + '/' + self.gcm_name + '/' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outdir += self.scenario + '/' - self.outdir += 'binned/' + self.outdir += self.reg_str + "/" + self.gcm_name + "/" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outdir += self.scenario + "/" + self.outdir += "binned/" # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) 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_attrs_dict['bin_distance'] = { - 'long_name': 'distance downglacier', - 'units': 'm', - 'comment': 'horizontal distance calculated from top of glacier moving downglacier'} - self.output_coords_dict['bin_surface_h_initial'] = collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values)]) - self.output_attrs_dict['bin_surface_h_initial'] = { - 'long_name': 'initial binned surface elevation', - 'units': 'm above sea level'} - self.output_coords_dict['bin_area_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_area_annual'] = { - 'long_name': 'binned glacier area', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'binned area at start of the year'} - self.output_coords_dict['bin_mass_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_mass_annual'] = { - 'long_name': 'binned ice mass', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'binned ice mass at start of the year'} - self.output_coords_dict['bin_thick_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_thick_annual'] = { - 'long_name': 'binned ice thickness', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'binned ice thickness at start of the year'} - self.output_coords_dict['bin_massbalclim_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_massbalclim_annual'] = { - 'long_name': 'binned climatic mass balance, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'climatic mass balance is computed before dynamics so can theoretically exceed ice thickness'}, - self.output_coords_dict['bin_massbalclim_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_massbalclim_monthly'] = { - 'long_name': 'binned monthly climatic mass balance, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly climatic mass balance from the PyGEM mass balance module'} + self.output_coords_dict["bin_distance"] = collections.OrderedDict( + [("glac", self.glac_values), ("bin", self.bin_values)] + ) + self.output_attrs_dict["bin_distance"] = { + "long_name": "distance downglacier", + "units": "m", + "comment": "horizontal distance calculated from top of glacier moving downglacier", + } + self.output_coords_dict["bin_surface_h_initial"] = collections.OrderedDict( + [("glac", self.glac_values), ("bin", self.bin_values)] + ) + self.output_attrs_dict["bin_surface_h_initial"] = { + "long_name": "initial binned surface elevation", + "units": "m above sea level", + } + self.output_coords_dict["bin_area_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_area_annual"] = { + "long_name": "binned glacier area", + "units": "m2", + "temporal_resolution": "annual", + "comment": "binned area at start of the year", + } + self.output_coords_dict["bin_mass_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_mass_annual"] = { + "long_name": "binned ice mass", + "units": "kg", + "temporal_resolution": "annual", + "comment": "binned ice mass at start of the year", + } + self.output_coords_dict["bin_thick_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_thick_annual"] = { + "long_name": "binned ice thickness", + "units": "m", + "temporal_resolution": "annual", + "comment": "binned ice thickness at start of the year", + } + self.output_coords_dict["bin_massbalclim_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_massbalclim_annual"] = ( + { + "long_name": "binned climatic mass balance, in water equivalent", + "units": "m", + "temporal_resolution": "annual", + "comment": "climatic mass balance is computed before dynamics so can theoretically exceed ice thickness", + }, + ) + self.output_coords_dict["bin_massbalclim_monthly"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + self.output_attrs_dict["bin_massbalclim_monthly"] = { + "long_name": "binned monthly climatic mass balance, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly climatic mass balance from the PyGEM mass balance module", + } # 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)])) - self.output_attrs_dict['bin_accumulation_monthly'] = { - 'long_name': 'binned monthly accumulation, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly accumulation from the PyGEM mass balance module'} - self.output_coords_dict['bin_melt_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_melt_monthly'] = { - 'long_name': 'binned monthly melt, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly melt from the PyGEM mass balance module'} - self.output_coords_dict['bin_refreeze_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_refreeze_monthly'] = { - 'long_name': 'binned monthly refreeze, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly refreeze from the PyGEM mass balance module'} + self.output_coords_dict["bin_accumulation_monthly"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["bin_accumulation_monthly"] = { + "long_name": "binned monthly accumulation, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly accumulation from the PyGEM mass balance module", + } + self.output_coords_dict["bin_melt_monthly"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + self.output_attrs_dict["bin_melt_monthly"] = { + "long_name": "binned monthly melt, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly melt from the PyGEM mass balance module", + } + self.output_coords_dict["bin_refreeze_monthly"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + self.output_attrs_dict["bin_refreeze_monthly"] = { + "long_name": "binned monthly refreeze, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly refreeze from the PyGEM mass balance module", + } # if 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_attrs_dict['bin_mass_annual_mad'] = { - 'long_name': 'binned ice mass median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['bin_thick_annual_mad'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_thick_annual_mad'] = { - 'long_name': 'binned ice thickness median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'thickness of ice at start of the year'} - self.output_coords_dict['bin_massbalclim_annual_mad'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_massbalclim_annual_mad'] = { - 'long_name': 'binned climatic mass balance, in water equivalent, median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'climatic mass balance is computed before dynamics so can theoretically exceed ice thickness'} - - -def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): + self.output_coords_dict["bin_mass_annual_mad"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_mass_annual_mad"] = { + "long_name": "binned ice mass median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["bin_thick_annual_mad"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_thick_annual_mad"] = { + "long_name": "binned ice thickness median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "thickness of ice at start of the year", + } + self.output_coords_dict["bin_massbalclim_annual_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + ) + self.output_attrs_dict["bin_massbalclim_annual_mad"] = { + "long_name": "binned climatic mass balance, in water equivalent, median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "climatic mass balance is computed before dynamics so can theoretically exceed ice thickness", + } + + +def calc_stats_array(data, stats_cns=pygem_prms["sim"]["out"]["sim_stats"]): """ Calculate stats for a given variable. @@ -762,20 +1024,24 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): # dictionary of functions to call for each stat in `stats_cns` stat_funcs = { - 'mean': lambda x: np.nanmean(x, axis=1), - 'std': lambda x: np.nanstd(x, axis=1), - '2.5%': lambda x: np.nanpercentile(x, 2.5, axis=1), - '25%': lambda x: np.nanpercentile(x, 25, axis=1), - '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') + "mean": lambda x: np.nanmean(x, axis=1), + "std": lambda x: np.nanstd(x, axis=1), + "2.5%": lambda x: np.nanpercentile(x, 2.5, axis=1), + "25%": lambda x: np.nanpercentile(x, 25, axis=1), + "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"), } # 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 diff --git a/pygem/pygem_modelsetup.py b/pygem/pygem_modelsetup.py index daa293b1..72b65f09 100755 --- a/pygem/pygem_modelsetup.py +++ b/pygem/pygem_modelsetup.py @@ -7,6 +7,7 @@ List of functions used to set up different aspects of the model """ + # Built-in libaries import os from datetime import datetime @@ -24,8 +25,12 @@ 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. @@ -46,83 +51,92 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge # Include spinup time in start year startyear_wspinup = startyear - spinupyears # Convert start year into date depending on option_wateryear - if option_wateryear == 'hydro': - startdate = str(startyear_wspinup - 1) + '-10-01' - enddate = str(endyear) + '-09-30' - elif option_wateryear == 'calendar': - startdate = str(startyear_wspinup) + '-01-01' - enddate = str(endyear) + '-12-31' - elif option_wateryear == 'custom': - startdate = str(startyear_wspinup) + '-' + pygem_prms['time']['startmonthday'] - enddate = str(endyear) + '-' + pygem_prms['time']['endmonthday'] + if option_wateryear == "hydro": + startdate = str(startyear_wspinup - 1) + "-10-01" + enddate = str(endyear) + "-09-30" + elif option_wateryear == "calendar": + startdate = str(startyear_wspinup) + "-01-01" + enddate = str(endyear) + "-12-31" + elif option_wateryear == "custom": + startdate = str(startyear_wspinup) + "-" + pygem_prms["time"]["startmonthday"] + enddate = str(endyear) + "-" + pygem_prms["time"]["endmonthday"] else: - assert True==False, "\n\nError: Select an option_wateryear that exists.\n" + assert True == False, "\n\nError: Select an option_wateryear that exists.\n" # Convert input format into proper datetime format - startdate = datetime(*[int(item) for item in startdate.split('-')]) - enddate = datetime(*[int(item) for item in enddate.split('-')]) - if pygem_prms['time']['timestep'] == 'monthly': - startdate = startdate.strftime('%Y-%m') - enddate = enddate.strftime('%Y-%m') - elif pygem_prms['time']['timestep'] == 'daily': - startdate = startdate.strftime('%Y-%m-%d') - enddate = enddate.strftime('%Y-%m-%d') + startdate = datetime(*[int(item) for item in startdate.split("-")]) + enddate = datetime(*[int(item) for item in enddate.split("-")]) + if pygem_prms["time"]["timestep"] == "monthly": + startdate = startdate.strftime("%Y-%m") + enddate = enddate.strftime("%Y-%m") + elif pygem_prms["time"]["timestep"] == "daily": + startdate = startdate.strftime("%Y-%m-%d") + enddate = enddate.strftime("%Y-%m-%d") # Generate dates_table using date_range function - if pygem_prms['time']['timestep'] == 'monthly': + if pygem_prms["time"]["timestep"] == "monthly": # Automatically generate dates from start date to end data using a monthly frequency (MS), which generates # monthly data using the 1st of each month' - dates_table = pd.DataFrame({'date' : pd.date_range(startdate, enddate, freq='MS', unit='s')}) + dates_table = pd.DataFrame( + {"date": pd.date_range(startdate, enddate, freq="MS", unit="s")} + ) # Select attributes of DateTimeIndex (dt.year, dt.month, and dt.daysinmonth) - dates_table['year'] = dates_table['date'].dt.year - dates_table['month'] = dates_table['date'].dt.month - dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth - dates_table['timestep'] = np.arange(len(dates_table['date'])) + dates_table["year"] = dates_table["date"].dt.year + dates_table["month"] = dates_table["date"].dt.month + dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth + dates_table["timestep"] = np.arange(len(dates_table["date"])) # Set date as index - dates_table.set_index('timestep', inplace=True) + dates_table.set_index("timestep", inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms['time']['option_leapyear'] == 0: - mask1 = (dates_table['daysinmonth'] == 29) - dates_table.loc[mask1,'daysinmonth'] = 28 - elif pygem_prms['time']['timestep'] == 'daily': + if pygem_prms["time"]["option_leapyear"] == 0: + mask1 = dates_table["daysinmonth"] == 29 + dates_table.loc[mask1, "daysinmonth"] = 28 + elif pygem_prms["time"]["timestep"] == "daily": # Automatically generate daily (freq = 'D') dates - dates_table = pd.DataFrame({'date' : pd.date_range(startdate, enddate, freq='D')}) + dates_table = pd.DataFrame( + {"date": pd.date_range(startdate, enddate, freq="D")} + ) # Extract attributes for dates_table - dates_table['year'] = dates_table['date'].dt.year - dates_table['month'] = dates_table['date'].dt.month - dates_table['day'] = dates_table['date'].dt.day - dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth + dates_table["year"] = dates_table["date"].dt.year + dates_table["month"] = dates_table["date"].dt.month + dates_table["day"] = dates_table["date"].dt.day + dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth # Set date as index - dates_table.set_index('date', inplace=True) + dates_table.set_index("date", inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms['time']['option_leapyear'] == 0: + if pygem_prms["time"]["option_leapyear"] == 0: # First, change 'daysinmonth' number - mask1 = dates_table['daysinmonth'] == 29 - dates_table.loc[mask1,'daysinmonth'] = 28 + mask1 = dates_table["daysinmonth"] == 29 + dates_table.loc[mask1, "daysinmonth"] = 28 # Next, remove the 29th days from the dates - mask2 = ((dates_table['month'] == 2) & (dates_table['day'] == 29)) + mask2 = (dates_table["month"] == 2) & (dates_table["day"] == 29) dates_table.drop(dates_table[mask2].index, inplace=True) else: - print("\n\nError: Please select 'daily' or 'monthly' for gcm_timestep. Exiting model run now.\n") + print( + "\n\nError: Please select 'daily' or 'monthly' for gcm_timestep. Exiting model run now.\n" + ) exit() # Add column for water year # Water year for northern hemisphere using USGS definition (October 1 - September 30th), # e.g., water year for 2000 is from October 1, 1999 - September 30, 2000 - dates_table['wateryear'] = dates_table['year'] + dates_table["wateryear"] = dates_table["year"] for step in range(dates_table.shape[0]): - if dates_table.loc[step, 'month'] >= 10: - dates_table.loc[step, 'wateryear'] = dates_table.loc[step, 'year'] + 1 + if dates_table.loc[step, "month"] >= 10: + dates_table.loc[step, "wateryear"] = dates_table.loc[step, "year"] + 1 # Add column for seasons # create a season dictionary to assist groupby functions seasondict = {} month_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] season_list = [] for i in range(len(month_list)): - if (month_list[i] >= pygem_prms['time']['summer_month_start'] and month_list[i] < pygem_prms['time']['winter_month_start']): - season_list.append('summer') + if ( + month_list[i] >= pygem_prms["time"]["summer_month_start"] + and month_list[i] < pygem_prms["time"]["winter_month_start"] + ): + season_list.append("summer") seasondict[month_list[i]] = season_list[i] else: - season_list.append('winter') + season_list.append("winter") seasondict[month_list[i]] = season_list[i] - dates_table['season'] = dates_table['month'].apply(lambda x: seasondict[x]) + dates_table["season"] = dates_table["month"].apply(lambda x: seasondict[x]) return dates_table @@ -139,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] @@ -154,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. @@ -182,27 +228,29 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py Line Profiling: Loading in the table takes the most time (~2.3 s) """ rgi_regionsO1 = sorted(list(rgi_table.O1Region.unique())) - glac_no = [x.split('-')[1] for x in rgi_table.RGIId.values] + glac_no = [x.split("-")[1] for x in rgi_table.RGIId.values] glac_no_byregion = {} for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = int(i.split('.')[0]) - glac_no_only = i.split('.')[1] + region = int(i.split(".")[0]) + glac_no_only = i.split(".")[1] glac_no_byregion[int(region)].append(glac_no_only) # Load data for each region for count, region in enumerate(rgi_regionsO1): # Select regional data for indexing glac_no = sorted(glac_no_byregion[region]) - rgi_table_region = rgi_table.iloc[np.where(rgi_table.O1Region.values == region)[0]] + rgi_table_region = rgi_table.iloc[ + np.where(rgi_table.O1Region.values == region)[0] + ] # Load table ds = pd.read_csv(filepath + filedict[region]) # Select glaciers based on 01Index value from main_glac_rgi table # as long as Huss tables have all rows associated with rgi attribute table, then this shortcut works - glac_table = ds.iloc[rgi_table_region['O1Index'].values] + glac_table = ds.iloc[rgi_table_region["O1Index"].values] # Merge multiple regions if count == 0: glac_table_all = glac_table @@ -229,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 @@ -270,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. @@ -298,13 +353,13 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' """ if glac_no is not None: glac_no_byregion = {} - rgi_regionsO1 = [int(i.split('.')[0]) for i in glac_no] + rgi_regionsO1 = [int(i.split(".")[0]) for i in glac_no] rgi_regionsO1 = list(set(rgi_regionsO1)) for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = i.split('.')[0] - glac_no_only = i.split('.')[1] + region = i.split(".")[0] + glac_no_only = i.split(".")[1] glac_no_byregion[int(region)].append(glac_no_only) for region in rgi_regionsO1: @@ -314,88 +369,118 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' rgi_regionsO1 = sorted(rgi_regionsO1) glacier_table = pd.DataFrame() for region in rgi_regionsO1: - if glac_no is not None: rgi_glac_number = glac_no_byregion[region] -# if len(rgi_glac_number) < 50: + # if len(rgi_glac_number) < 50: for i in os.listdir(rgi_fp): - if i.startswith(str(region).zfill(2)) and i.endswith('.csv'): + if i.startswith(str(region).zfill(2)) and i.endswith(".csv"): rgi_fn = i try: csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn) except: - csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding='latin1') + csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding="latin1") # Populate glacer_table with the glaciers of interest - if rgi_regionsO2 == 'all' and rgi_glac_number == 'all': + if rgi_regionsO2 == "all" and rgi_glac_number == "all": if debug: - print("All glaciers within region(s) %s are included in this model run." % (region)) + print( + "All glaciers within region(s) %s are included in this model run." + % (region) + ) if glacier_table.empty: glacier_table = csv_regionO1 else: glacier_table = pd.concat([glacier_table, csv_regionO1], axis=0) - elif rgi_regionsO2 != 'all' and rgi_glac_number == 'all': + 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) + glacier_table.rename(columns={"index": "O1Index"}, inplace=True) # Record the reference date - glacier_table['RefDate'] = glacier_table['BgnDate'] + glacier_table["RefDate"] = glacier_table["BgnDate"] # if there is an end date, then roughly average the year - enddate_idx = glacier_table.loc[(glacier_table['EndDate'] > 0), 'EndDate'].index.values - glacier_table.loc[enddate_idx,'RefDate'] = ( - np.mean((glacier_table.loc[enddate_idx,['BgnDate', 'EndDate']].values / 10**4).astype(int), - axis=1).astype(int) * 10**4 + 9999) + enddate_idx = glacier_table.loc[ + (glacier_table["EndDate"] > 0), "EndDate" + ].index.values + glacier_table.loc[enddate_idx, "RefDate"] = ( + np.mean( + ( + glacier_table.loc[enddate_idx, ["BgnDate", "EndDate"]].values / 10**4 + ).astype(int), + axis=1, + ).astype(int) + * 10**4 + + 9999 + ) # drop columns of data that is not being used glacier_table.drop(rgi_cols_drop, axis=1, inplace=True) # add column with the O1 glacier numbers glacier_table[rgi_O1Id_colname] = ( - glacier_table['RGIId'].str.split('.').apply(pd.Series).loc[:,1].astype(int)) - glacier_table['rgino_str'] = [x.split('-')[1] for x in glacier_table.RGIId.values] -# glacier_table[rgi_glacno_float_colname] = (np.array([np.str.split(glacier_table['RGIId'][x],'-')[1] -# for x in range(glacier_table.shape[0])]).astype(float)) - glacier_table[rgi_glacno_float_colname] = (np.array([x.split('-')[1] for x in glacier_table['RGIId']] -# [np.str.split(glacier_table['RGIId'][x],'-')[1] -# for x in range(glacier_table.shape[0])] - ).astype(float)) + glacier_table["RGIId"].str.split(".").apply(pd.Series).loc[:, 1].astype(int) + ) + glacier_table["rgino_str"] = [x.split("-")[1] for x in glacier_table.RGIId.values] + # glacier_table[rgi_glacno_float_colname] = (np.array([np.str.split(glacier_table['RGIId'][x],'-')[1] + # for x in range(glacier_table.shape[0])]).astype(float)) + glacier_table[rgi_glacno_float_colname] = np.array( + [x.split("-")[1] for x in glacier_table["RGIId"]] + # [np.str.split(glacier_table['RGIId'][x],'-')[1] + # for x in range(glacier_table.shape[0])] + ).astype(float) # set index name glacier_table.index.name = indexname # Longitude between 0-360deg (no negative) - glacier_table['CenLon_360'] = glacier_table['CenLon'] - glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360'] = ( - 360 + glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360']) + glacier_table["CenLon_360"] = glacier_table["CenLon"] + glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] = ( + 360 + glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] + ) # Subset glaciers based on their terminus type termtype_values = [] if include_landterm: @@ -410,25 +495,30 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' termtype_values.append(5) if include_laketerm: termtype_values.append(2) - glacier_table = glacier_table.loc[glacier_table['TermType'].isin(termtype_values)] + glacier_table = glacier_table.loc[glacier_table["TermType"].isin(termtype_values)] glacier_table.reset_index(inplace=True, drop=True) # Glacier number with no trailing zeros - glacier_table['glacno'] = [str(int(x.split('-')[1].split('.')[0])) + '.' + x.split('-')[1].split('.')[1] - for x in glacier_table.RGIId] + glacier_table["glacno"] = [ + str(int(x.split("-")[1].split(".")[0])) + "." + x.split("-")[1].split(".")[1] + for x in glacier_table.RGIId + ] # Remove glaciers below threshold - glacier_table = glacier_table.loc[glacier_table['Area'] > min_glac_area_km2,:] + glacier_table = glacier_table.loc[glacier_table["Area"] > min_glac_area_km2, :] glacier_table.reset_index(inplace=True, drop=True) # Remove glaciers that are meant to be skipped if glac_no_skip is not None: - glac_no_all = list(glacier_table['glacno']) + glac_no_all = list(glacier_table["glacno"]) glac_no_unique = [x for x in glac_no_all if x not in glac_no_skip] unique_idx = [glac_no_all.index(x) for x in glac_no_unique] - glacier_table = glacier_table.loc[unique_idx,:] + glacier_table = glacier_table.loc[unique_idx, :] glacier_table.reset_index(inplace=True, drop=True) - print("This study is focusing on %s glaciers in region %s" % (glacier_table.shape[0], rgi_regionsO1)) + print( + "This study is focusing on %s glaciers in region %s" + % (glacier_table.shape[0], rgi_regionsO1) + ) return glacier_table @@ -468,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 = [] @@ -491,7 +581,7 @@ def split_list(lst, n=1, option_ordered=1, group_thousands=False): 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) @@ -508,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: diff --git a/pygem/scraps/dummy_task_module.py b/pygem/scraps/dummy_task_module.py index 6398ea43..24269d52 100755 --- a/pygem/scraps/dummy_task_module.py +++ b/pygem/scraps/dummy_task_module.py @@ -8,13 +8,16 @@ log = logging.getLogger(__name__) # Add the new name "my_netcdf_file" to the list of things that the GlacierDirectory understands -cfg.BASENAMES['my_netcdf_file'] = ('somefilename.nc', "This is just a documentation string") +cfg.BASENAMES["my_netcdf_file"] = ( + "somefilename.nc", + "This is just a documentation string", +) @entity_task(log, writes=[]) def dummy_task(gdir, some_param=None): """Very dummy""" - fpath = gdir.get_filepath('my_netcdf_file') + fpath = gdir.get_filepath("my_netcdf_file") da = xr.DataArray([1, 2, 3]) da.to_netcdf(fpath) diff --git a/pygem/scraps/run.py b/pygem/scraps/run.py index 5955c1cb..f6daddbe 100755 --- a/pygem/scraps/run.py +++ b/pygem/scraps/run.py @@ -6,24 +6,24 @@ cfg.initialize() # How many grid points around the glacier? -cfg.PARAMS['border'] = 10 +cfg.PARAMS["border"] = 10 # Make it robust -cfg.PARAMS['use_intersects'] = False -cfg.PARAMS['continue_on_error'] = True +cfg.PARAMS["use_intersects"] = False +cfg.PARAMS["continue_on_error"] = True # Local working directory (where OGGM will write its output) -cfg.PATHS['working_dir'] = utils.get_temp_dir('some_wd') +cfg.PATHS["working_dir"] = utils.get_temp_dir("some_wd") # RGI file -path = utils.get_rgi_region_file('11') +path = utils.get_rgi_region_file("11") rgidf = gpd.read_file(path) # Select only 2 glaciers rgidf = rgidf.iloc[:2] # Sort for more efficient parallel computing -rgidf = rgidf.sort_values('Area', ascending=False) +rgidf = rgidf.sort_values("Area", ascending=False) # Go - create the pre-processed glacier directories gdirs = workflow.init_glacier_directories(rgidf) @@ -36,5 +36,5 @@ # See that we can read the new dummy data: import xarray as xr -fpath = gdirs[0].get_filepath('my_netcdf_file') +fpath = gdirs[0].get_filepath("my_netcdf_file") print(xr.open_dataset(fpath)) diff --git a/pygem/setup/config.py b/pygem/setup/config.py index 9412f77f..db6485d1 100644 --- a/pygem/setup/config.py +++ b/pygem/setup/config.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + import os import shutil @@ -12,9 +13,11 @@ __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): + + def __init__(self, config_filename="config.yaml", base_dir=None, overwrite=False): """ Initialize the ConfigManager class. @@ -24,9 +27,11 @@ def __init__(self, config_filename='config.yaml', base_dir=None, overwrite=False overwrite (bool, optional): Whether to overwrite an existing configuration file. Defaults to False. """ self.config_filename = config_filename - self.base_dir = base_dir or os.path.join(os.path.expanduser('~'), 'PyGEM') + 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() @@ -48,7 +53,7 @@ def read_config(self, validate=True): validate (bool): Whether to validate the configuration file contents. Defaults to True. """ ryaml = ruamel.yaml.YAML() - with open(self.config_path, 'r') as f: + with open(self.config_path, "r") as f: user_config = ryaml.load(f) if validate: @@ -64,7 +69,7 @@ def _write_config(self, config): """ ryaml = ruamel.yaml.YAML() ryaml.preserve_quotes = True - with open(self.config_path, 'w') as file: + with open(self.config_path, "w") as file: ryaml.dump(config, file) def update_config(self, updates): @@ -78,7 +83,7 @@ def update_config(self, updates): for key, value in updates.items(): if key not in self.EXPECTED_TYPES: raise KeyError(f"Unrecognized configuration key: {key}") - keys = key.split('.') + keys = key.split(".") d = config for sub_key in keys[:-1]: d = d[sub_key] @@ -104,22 +109,25 @@ def _validate_config(self, config): 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)), + "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, diff --git a/pygem/shop/debris.py b/pygem/shop/debris.py index 3ba335a3..032714cc 100755 --- a/pygem/shop/debris.py +++ b/pygem/shop/debris.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + import logging import os @@ -33,13 +34,25 @@ log = logging.getLogger(__name__) # Add the new name "hd" to the list of things that the GlacierDirectory understands -if 'debris_hd' not in cfg.BASENAMES: - cfg.BASENAMES['debris_hd'] = ('debris_hd.tif', 'Raster of debris thickness data') -if 'debris_ed' not in cfg.BASENAMES: - cfg.BASENAMES['debris_ed'] = ('debris_ed.tif', 'Raster of debris enhancement factor data') - -@entity_task(log, writes=['debris_hd', 'debris_ed']) -def debris_to_gdir(gdir, debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['debris_relpath']}", add_to_gridded=True, hd_max=5, hd_min=0, ed_max=10, ed_min=0): +if "debris_hd" not in cfg.BASENAMES: + cfg.BASENAMES["debris_hd"] = ("debris_hd.tif", "Raster of debris thickness data") +if "debris_ed" not in cfg.BASENAMES: + cfg.BASENAMES["debris_ed"] = ( + "debris_ed.tif", + "Raster of debris enhancement factor data", + ) + + +@entity_task(log, writes=["debris_hd", "debris_ed"]) +def debris_to_gdir( + gdir, + debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['debris_relpath']}", + add_to_gridded=True, + hd_max=5, + hd_min=0, + ed_max=10, + ed_min=0, +): """Reproject the debris thickness and enhancement factor files to the given glacier directory Variables are exported as new files in the glacier directory. @@ -54,79 +67,96 @@ def debris_to_gdir(gdir, debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['de 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 + '/' + 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' + if os.path.exists(hd_dir + glac_str_nolead + "_hdts_m.tif"): + hd_fn = hd_dir + glac_str_nolead + "_hdts_m.tif" + elif os.path.exists(hd_dir + glac_str_nolead + "_hdts_m_extrap.tif"): + hd_fn = hd_dir + glac_str_nolead + "_hdts_m_extrap.tif" else: hd_fn = None if hd_fn is not None: - rasterio_to_gdir(gdir, hd_fn, 'debris_hd', resampling='bilinear') + rasterio_to_gdir(gdir, hd_fn, "debris_hd", resampling="bilinear") if add_to_gridded and hd_fn is not None: - output_fn = gdir.get_filepath('debris_hd') + output_fn = gdir.get_filepath("debris_hd") # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask - data[data>hd_max] = 0 - data[data hd_max] = 0 + data[data < hd_min] = 0 # Write data - vn = 'debris_hd' + vn = "debris_hd" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = 'm' - v.long_name = 'Debris thicknness' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "m" + v.long_name = "Debris thicknness" v[:] = data # If debris enhancement factor data exists, then write to glacier directory - if os.path.exists(ed_dir + glac_str_nolead + '_meltfactor.tif'): - ed_fn = ed_dir + glac_str_nolead + '_meltfactor.tif' - elif os.path.exists(ed_dir + glac_str_nolead + '_meltfactor_extrap.tif'): - ed_fn = ed_dir + glac_str_nolead + '_meltfactor_extrap.tif' + if os.path.exists(ed_dir + glac_str_nolead + "_meltfactor.tif"): + ed_fn = ed_dir + glac_str_nolead + "_meltfactor.tif" + elif os.path.exists(ed_dir + glac_str_nolead + "_meltfactor_extrap.tif"): + ed_fn = ed_dir + glac_str_nolead + "_meltfactor_extrap.tif" else: ed_fn = None if ed_fn is not None: - rasterio_to_gdir(gdir, ed_fn, 'debris_ed', resampling='bilinear') + rasterio_to_gdir(gdir, ed_fn, "debris_ed", resampling="bilinear") if add_to_gridded and ed_fn is not None: - output_fn = gdir.get_filepath('debris_ed') + output_fn = gdir.get_filepath("debris_ed") # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask - data[data>ed_max] = 1 - data[data ed_max] = 1 + data[data < ed_min] = 1 # Write data - vn = 'debris_ed' + vn = "debris_ed" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = '-' - v.long_name = 'Debris enhancement factor' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "-" + v.long_name = "Debris enhancement factor" v[:] = data - -@entity_task(log, writes=['inversion_flowlines']) -def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): +@entity_task(log, writes=["inversion_flowlines"]) +def debris_binned(gdir, ignore_debris=False, fl_str="inversion_flowlines"): """Bin debris thickness and enhancement factors. Updates the 'inversion_flowlines' save file. @@ -141,19 +171,21 @@ def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): 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 if flowlines is not None: # Add binned debris thickness and enhancement factors to flowlines - if os.path.exists(gdir.get_filepath('debris_hd')) and ignore_debris==False: - ds = xr.open_dataset(gdir.get_filepath('gridded_data')) - glacier_mask = ds['glacier_mask'].values - topo = ds['topo_smoothed'].values - hd = ds['debris_hd'].values - ed = ds['debris_ed'].values + if os.path.exists(gdir.get_filepath("debris_hd")) and ignore_debris == False: + ds = xr.open_dataset(gdir.get_filepath("gridded_data")) + glacier_mask = ds["glacier_mask"].values + topo = ds["topo_smoothed"].values + hd = ds["debris_hd"].values + ed = ds["debris_ed"].values # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) @@ -164,16 +196,22 @@ def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): # 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): + 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]) diff --git a/pygem/shop/icethickness.py b/pygem/shop/icethickness.py index 3756788f..b1a5be12 100755 --- a/pygem/shop/icethickness.py +++ b/pygem/shop/icethickness.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + import logging import os import pickle @@ -24,16 +25,28 @@ # read the config pygem_prms = config_manager.read_config() -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') +if "consensus_mass" not in cfg.BASENAMES: + cfg.BASENAMES["consensus_mass"] = ( + "consensus_mass.pkl", + "Glacier mass from consensus ice thickness data", + ) +if "consensus_h" not in cfg.BASENAMES: + cfg.BASENAMES["consensus_h"] = ( + "consensus_h.tif", + "Raster of consensus ice thickness data", + ) # Module logger log = logging.getLogger(__name__) -@entity_task(log, writes=['consensus_mass']) -def consensus_gridded(gdir, h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['calib']['data']['icethickness']['h_consensus_relpath']}", add_mass=True, add_to_gridded=True): + +@entity_task(log, writes=["consensus_mass"]) +def consensus_gridded( + gdir, + h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['calib']['data']['icethickness']['h_consensus_relpath']}", + add_mass=True, + add_to_gridded=True, +): """Bin consensus ice thickness and add total glacier mass to the given glacier directory Updates the 'inversion_flowlines' save file and creates new consensus_mass.pkl @@ -44,55 +57,75 @@ def consensus_gridded(gdir, h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['c where to write the data """ # If binned mb data exists, then write to glacier directory - h_fn = h_consensus_fp + 'RGI60-' + gdir.rgi_region + '/' + gdir.rgi_id + '_thickness.tif' - assert os.path.exists(h_fn), 'Error: h_consensus_fullfn for ' + gdir.rgi_id + ' does not exist.' + h_fn = ( + h_consensus_fp + + "RGI60-" + + gdir.rgi_region + + "/" + + gdir.rgi_id + + "_thickness.tif" + ) + assert os.path.exists(h_fn), ( + "Error: h_consensus_fullfn for " + gdir.rgi_id + " does not exist." + ) # open consensus ice thickness estimate - h_dr = rasterio.open(h_fn, 'r', driver='GTiff') + h_dr = rasterio.open(h_fn, "r", driver="GTiff") h = h_dr.read(1).astype(rasterio.float32) # Glacier mass [kg] - glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms['constants']['density_ice'] -# print(glacier_mass_raw) + glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms["constants"][ + "density_ice" + ] + # print(glacier_mass_raw) if add_mass: # Pickle data - consensus_fn = gdir.get_filepath('consensus_mass') - with open(consensus_fn, 'wb') as f: + consensus_fn = gdir.get_filepath("consensus_mass") + with open(consensus_fn, "wb") as f: pickle.dump(glacier_mass_raw, f) - if add_to_gridded: - rasterio_to_gdir(gdir, h_fn, 'consensus_h', resampling='bilinear') - output_fn = gdir.get_filepath('consensus_h') + rasterio_to_gdir(gdir, h_fn, "consensus_h", resampling="bilinear") + output_fn = gdir.get_filepath("consensus_h") # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask # Pixel area pixel_m2 = abs(gdir.grid.dx * gdir.grid.dy) # Glacier mass [kg] reprojoected (may lose or gain mass depending on resampling algorithm) - glacier_mass_reprojected = (data * pixel_m2).sum() * pygem_prms['constants']['density_ice'] + glacier_mass_reprojected = (data * pixel_m2).sum() * pygem_prms[ + "constants" + ]["density_ice"] # Scale data to ensure conservation of mass during reprojection data_scaled = data * glacier_mass_raw / glacier_mass_reprojected -# glacier_mass = (data_scaled * pixel_m2).sum() * pygem_prms['constants']['density_ice'] -# print(glacier_mass) + # glacier_mass = (data_scaled * pixel_m2).sum() * pygem_prms['constants']['density_ice'] + # print(glacier_mass) # Write data - vn = 'consensus_h' + vn = "consensus_h" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = 'm' - v.long_name = 'Consensus ice thicknness' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "m" + v.long_name = "Consensus ice thicknness" v[:] = data_scaled -@entity_task(log, writes=['inversion_flowlines']) +@entity_task(log, writes=["inversion_flowlines"]) def consensus_binned(gdir): """Bin consensus ice thickness ice estimates. @@ -103,16 +136,18 @@ def consensus_binned(gdir): gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ - flowlines = gdir.read_pickle('inversion_flowlines') + flowlines = gdir.read_pickle("inversion_flowlines") fl = flowlines[0] - assert len(flowlines) == 1, 'Error: binning debris data set up only for single flowlines at present' + assert len(flowlines) == 1, ( + "Error: binning debris data set up only for single flowlines at present" + ) # Add binned debris thickness and enhancement factors to flowlines - ds = xr.open_dataset(gdir.get_filepath('gridded_data')) - glacier_mask = ds['glacier_mask'].values - topo = ds['topo_smoothed'].values - h = ds['consensus_h'].values + ds = xr.open_dataset(gdir.get_filepath("gridded_data")) + glacier_mask = ds["glacier_mask"].values + topo = ds["topo_smoothed"].values + h = ds["consensus_h"].values # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) @@ -122,14 +157,18 @@ def consensus_binned(gdir): # 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): + 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() @@ -139,4 +178,4 @@ def consensus_binned(gdir): fl.consensus_h = h_binned # Overwrite pickle - gdir.write_pickle(flowlines, 'inversion_flowlines') + gdir.write_pickle(flowlines, "inversion_flowlines") diff --git a/pygem/shop/mbdata.py b/pygem/shop/mbdata.py index 559e5e94..7ca31ebf 100755 --- a/pygem/shop/mbdata.py +++ b/pygem/shop/mbdata.py @@ -5,6 +5,7 @@ Distrubted under the MIT lisence """ + # Built-in libaries import json import logging @@ -16,14 +17,14 @@ import numpy as np import pandas as pd -#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 @@ -37,12 +38,19 @@ log = logging.getLogger(__name__) # Add the new name "mb_calib_pygem" to the list of things that the GlacierDirectory understands -if 'mb_calib_pygem' not in cfg.BASENAMES: - cfg.BASENAMES['mb_calib_pygem'] = ('mb_calib_pygem.json', 'Mass balance observations for model calibration') +if "mb_calib_pygem" not in cfg.BASENAMES: + cfg.BASENAMES["mb_calib_pygem"] = ( + "mb_calib_pygem.json", + "Mass balance observations for model calibration", + ) -@entity_task(log, writes=['mb_calib_pygem']) -def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup']['include_frontalablation']): +@entity_task(log, writes=["mb_calib_pygem"]) +def mb_df_to_gdir( + gdir, + mb_dataset="Hugonnet2021", + facorrected=pygem_prms["setup"]["include_frontalablation"], +): """Select specific mass balance and add observations to the given glacier directory Parameters @@ -52,19 +60,28 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup """ # get dataset name (could potentially be swapped with others besides Hugonnet21) mbdata_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}" - mbdata_fp_fa = mbdata_fp + pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn'] + mbdata_fp_fa = ( + mbdata_fp + + pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_facorrected_fn"] + ) if facorrected and os.path.exists(mbdata_fp_fa): mbdata_fp = mbdata_fp_fa else: - mbdata_fp = mbdata_fp + pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn'] + mbdata_fp = ( + mbdata_fp + pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_fn"] + ) - assert os.path.exists(mbdata_fp), "Error, mass balance dataset does not exist: {mbdata_fp}" - assert 'hugonnet2021' in mbdata_fp.lower(), "Error, mass balance dataset not yet supported: {mbdata_fp}" - rgiid_cn = 'rgiid' - mb_cn = 'mb_mwea' - mberr_cn = 'mb_mwea_err' - mb_clim_cn = 'mb_clim_mwea' - mberr_clim_cn = 'mb_clim_mwea_err' + assert os.path.exists(mbdata_fp), ( + "Error, mass balance dataset does not exist: {mbdata_fp}" + ) + assert "hugonnet2021" in mbdata_fp.lower(), ( + "Error, mass balance dataset not yet supported: {mbdata_fp}" + ) + rgiid_cn = "rgiid" + mb_cn = "mb_mwea" + mberr_cn = "mb_mwea_err" + mb_clim_cn = "mb_clim_mwea" + mberr_clim_cn = "mb_clim_mwea_err" # read reference mass balance dataset and pull data of interest mb_df = pd.read_csv(mbdata_fp) @@ -85,7 +102,7 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup 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) @@ -98,23 +115,27 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup mbdata = { key: value for key, value in { - 'mb_mwea': float(mb_mwea), - 'mb_mwea_err': float(mb_mwea_err), - 'mb_clim_mwea': float(mb_clim_mwea) if mb_clim_mwea is not None else None, - 'mb_clim_mwea_err': float(mb_clim_mwea_err) if mb_clim_mwea_err is not None else None, - 't1_str': t1_str, - 't2_str': t2_str, - 'nyears': nyears, + "mb_mwea": float(mb_mwea), + "mb_mwea_err": float(mb_mwea_err), + "mb_clim_mwea": float(mb_clim_mwea) + if mb_clim_mwea is not None + else None, + "mb_clim_mwea_err": float(mb_clim_mwea_err) + if mb_clim_mwea_err is not None + else None, + "t1_str": t1_str, + "t2_str": t2_str, + "nyears": nyears, }.items() if value is not None } - mb_fn = gdir.get_filepath('mb_calib_pygem') - with open(mb_fn, 'w') as f: + mb_fn = gdir.get_filepath("mb_calib_pygem") + with open(mb_fn, "w") as f: json.dump(mbdata, f) -#@entity_task(log, writes=['mb_obs']) -#def mb_bins_to_glacierwide(gdir, mb_binned_fp=pygem_prms.mb_binned_fp): +# @entity_task(log, writes=['mb_obs']) +# def mb_bins_to_glacierwide(gdir, mb_binned_fp=pygem_prms.mb_binned_fp): # """Convert binned mass balance data to glacier-wide and add observations to the given glacier directory # # Parameters @@ -155,7 +176,7 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # # ##%% -#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'] @@ -268,5 +289,3 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # 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 f38a94dd..215e66ea 100644 --- a/pygem/shop/oib.py +++ b/pygem/shop/oib.py @@ -7,6 +7,7 @@ NASA Operation IceBridge data and processing class """ + import datetime import glob import json @@ -25,12 +26,17 @@ # 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']}" + def __init__(self, rgi6id="", rgi7id=""): + self.oib_datpath = ( + f"{pygem_prms['root']}/{pygem_prms['calib']['data']['oib']['oib_relpath']}" + ) self.rgi7_6_df = pd.read_csv(f"{self.oib_datpath}/../oibak_rgi6_rgi7_ids.csv") - self.rgi7_6_df['rgi7id'] = self.rgi7_6_df['rgi7id'].str.split('RGI2000-v7.0-G-').str[1] - self.rgi7_6_df['rgi6id'] = self.rgi7_6_df['rgi6id'].str.split('RGI60-').str[1] + self.rgi7_6_df["rgi7id"] = ( + self.rgi7_6_df["rgi7id"].str.split("RGI2000-v7.0-G-").str[1] + ) + self.rgi7_6_df["rgi6id"] = self.rgi7_6_df["rgi6id"].str.split("RGI60-").str[1] self.rgi6id = rgi6id self.rgi7id = rgi7id self.name = None @@ -43,14 +49,19 @@ def __init__(self, rgi6id='', rgi7id=''): 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 @@ -59,36 +70,42 @@ def _rgi6torgi7id(self, debug=False): return RGI version 7 glacier id for a given RGI version 6 id """ - self.rgi6id = self.rgi6id.split('.')[0].zfill(2) + '.' + self.rgi6id.split('.')[1] + self.rgi6id = ( + self.rgi6id.split(".")[0].zfill(2) + "." + self.rgi6id.split(".")[1] + ) # rgi7id = self.rgi7_6_df.loc[lambda self.rgi7_6_df: self.rgi7_6_df['rgi6id'] == rgi6id,'rgi7id'].tolist() - rgi7id = self.rgi7_6_df.loc[self.rgi7_6_df['rgi6id'] == self.rgi6id, 'rgi7id'].tolist() - if len(rgi7id)==1: - self.rgi7id = rgi7id[0] + rgi7id = self.rgi7_6_df.loc[ + self.rgi7_6_df["rgi6id"] == self.rgi6id, "rgi7id" + ].tolist() + if len(rgi7id) == 1: + self.rgi7id = rgi7id[0] if debug: - print(f'RGI6:{self.rgi6id} -> RGI7:{self.rgi7id}') - elif len(rgi7id)==0: - raise IndexError(f'No matching RGI7Id for {self.rgi6id}') - elif len(rgi7id)>1: - raise IndexError(f'More than one matching RGI7Id for {self.rgi6id}') - + print(f"RGI6:{self.rgi6id} -> RGI7:{self.rgi7id}") + elif len(rgi7id) == 0: + raise IndexError(f"No matching RGI7Id for {self.rgi6id}") + elif len(rgi7id) > 1: + raise IndexError(f"More than one matching RGI7Id for {self.rgi6id}") def _rgi7torgi6id(self, debug=False): """ return RGI version 6 glacier id for a given RGI version 7 id """ - self.rgi7id = self.rgi7id.split('-')[0].zfill(2) + '-' + self.rgi7id.split('-')[1] + self.rgi7id = ( + self.rgi7id.split("-")[0].zfill(2) + "-" + self.rgi7id.split("-")[1] + ) # rgi6id = self.rgi7_6_df.loc[lambda self.rgi7_6_df: self.rgi7_6_df['rgi7id'] == rgi7id,'rgi6id'].tolist() - rgi6id = self.rgi7_6_df.loc[self.rgi7_6_df['rgi7id'] == self.rgi7id, 'rgi6id'].tolist() - if len(rgi6id)==1: + rgi6id = self.rgi7_6_df.loc[ + self.rgi7_6_df["rgi7id"] == self.rgi7id, "rgi6id" + ].tolist() + if len(rgi6id) == 1: self.rgi6id = rgi6id[0] if debug: - print(f'RGI7:{self.rgi7id} -> RGI6:{self.rgi6id}') - elif len(rgi6id)==0: - raise IndexError(f'No matching RGI6Id for {self.rgi7id}') - elif len(rgi6id)>1: - raise IndexError(f'More than one matching RGI6Id for {self.rgi7id}') - + print(f"RGI7:{self.rgi7id} -> RGI6:{self.rgi6id}") + elif len(rgi6id) == 0: + raise IndexError(f"No matching RGI6Id for {self.rgi7id}") + elif len(rgi6id) > 1: + raise IndexError(f"More than one matching RGI6Id for {self.rgi7id}") def _date_check(self, dt_obj): """ @@ -98,24 +115,22 @@ def _date_check(self, dt_obj): if dt_obj.day < dim // 2: dt_obj_ = datetime.datetime(year=dt_obj.year, month=dt_obj.month, day=1) else: - dt_obj_ = datetime.datetime(year=dt_obj.year, month=dt_obj.month+1, day=1) + dt_obj_ = datetime.datetime(year=dt_obj.year, month=dt_obj.month + 1, day=1) return dt_obj_ - def _load(self): """ load Operation IceBridge data """ oib_fpath = glob.glob(f"{self.oib_datpath}/diffstats5_*{self.rgi7id}*.json") - if len(oib_fpath)==0: + if len(oib_fpath) == 0: return else: oib_fpath = oib_fpath[0] # load diffstats file - with open(oib_fpath, 'rb') as f: + with open(oib_fpath, "rb") as f: self.oib_dict = json.load(f) - self.name = split_by_uppercase(self.oib_dict['glacier_shortname']) - + self.name = split_by_uppercase(self.oib_dict["glacier_shortname"]) def _parsediffs(self, filter_count_pctl=10, debug=False): """ @@ -123,36 +138,55 @@ def _parsediffs(self, filter_count_pctl=10, debug=False): diffs_stacked: np.ndarray (#bins, #surveys) """ # get seasons stored in oib diffs - seasons = list(set(self.oib_dict.keys()).intersection(['march','may','august'])) + seasons = list( + set(self.oib_dict.keys()).intersection(["march", "may", "august"]) + ) for ssn in seasons: for yr in list(self.oib_dict[ssn].keys()): # get survey date - doy_int = int(np.ceil(self.oib_dict[ssn][yr]['mean_doy'])) - dt_obj = datetime.datetime.strptime(f'{int(yr)}-{doy_int}', '%Y-%j') + doy_int = int(np.ceil(self.oib_dict[ssn][yr]["mean_doy"])) + dt_obj = datetime.datetime.strptime(f"{int(yr)}-{doy_int}", "%Y-%j") # get survey data and filter by pixel count - diffs = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_median_diffs_vec']) - counts = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_count_vec']) + diffs = np.asarray( + self.oib_dict[ssn][yr]["bin_vals"]["bin_median_diffs_vec"] + ) + counts = np.asarray(self.oib_dict[ssn][yr]["bin_vals"]["bin_count_vec"]) mask = _filter_on_pixel_count(counts, filter_count_pctl) diffs[mask] = np.nan # uncertainty represented by IQR - sigmas = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_interquartile_range_diffs_vec']) + sigmas = np.asarray( + self.oib_dict[ssn][yr]["bin_vals"][ + "bin_interquartile_range_diffs_vec" + ] + ) sigmas[mask] = np.nan # add masked diffs to master dictionary - self.oib_diffs[self._date_check(dt_obj)] = (diffs,sigmas) + self.oib_diffs[self._date_check(dt_obj)] = (diffs, sigmas) # Sort the dictionary by date keys self.oib_diffs = dict(sorted(self.oib_diffs.items())) if debug: - print(f'OIB survey dates:\n{", ".join([str(dt.year)+"-"+str(dt.month)+"-"+str(dt.day) for dt in list(self.oib_diffs.keys())])}') + print( + f"OIB survey dates:\n{', '.join([str(dt.year) + '-' + str(dt.month) + '-' + str(dt.day) for dt in list(self.oib_diffs.keys())])}" + ) # get bin centers - self.bin_centers = (np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec']) + - np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'])) / 2 - self.bin_area = self.oib_dict['aad_dict']['hist_bin_areas_m2'] + self.bin_centers = ( + np.asarray( + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]["bin_vals"][ + "bin_start_vec" + ] + ) + + np.asarray( + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]["bin_vals"][ + "bin_stop_vec" + ] + ) + ) / 2 + self.bin_area = self.oib_dict["aad_dict"]["hist_bin_areas_m2"] # bin_edges = oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec'] # bin_edges.append(oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'][-1]) # bin_edges = np.asarray(bin_edges) - def _terminus_mask(self, debug=False): """ create mask of missing terminus ice using last oib survey @@ -166,31 +200,35 @@ def _terminus_mask(self, debug=False): mask = [] try: for i in inds: - tmp = diffs[i][lowest_bin:lowest_bin+50] + tmp = diffs[i][lowest_bin : lowest_bin + 50] if np.isnan(tmp).all(): continue else: # find peak we'll bake in the assumption that terminus thickness has decreased over time - we'll thus look for a trough if yr>=2013 (cop30 date) - if survey_dates[i].year>2013: + if survey_dates[i].year > 2013: idx = np.nanargmin(tmp) + lowest_bin else: - tmp = -1*tmp + tmp = -1 * tmp idx = np.nanargmax(tmp) + lowest_bin - mask = np.arange(0,idx+1,1) + mask = np.arange(0, idx + 1, 1) break if debug: plt.figure() - cmap=plt.cm.rainbow(np.linspace(0, 1, len(inds))) + cmap = plt.cm.rainbow(np.linspace(0, 1, len(inds))) for i in inds[::-1]: - plt.plot(diffs[i],label=f'{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}',c=cmap[i]) + plt.plot( + diffs[i], + label=f"{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}", + c=cmap[i], + ) if idx: - plt.axvline(idx,c='k',ls=':') - plt.legend(loc='upper right') + plt.axvline(idx, c="k", ls=":") + plt.legend(loc="upper right") plt.show() except Exception as err: if debug: - print(f'_filter_terminus_missing_ice error: {err}') + print(f"_filter_terminus_missing_ice error: {err}") mask = [] # apply mask @@ -198,37 +236,57 @@ 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) + warnings.filterwarnings("ignore") + for i, (k, tup) in enumerate(self.oib_diffs.items()): + if i == 0: + y, self.bin_edges, _ = stats.binned_statistic( + x=self.bin_centers, + values=tup[0], + statistic=np.nanmean, + bins=nbins, + ) else: - y = stats.binned_statistic(x=self.bin_centers, values=tup[0], statistic=np.nanmean, bins=self.bin_edges)[0] - s = stats.binned_statistic(x=self.bin_centers, values=tup[1], statistic=np.nanmean, bins=self.bin_edges)[0] - self.oib_diffs[k] = (y,s) - self.bin_area = stats.binned_statistic(x=self.bin_centers, values=self.bin_area, statistic=np.nanmean, bins=self.bin_edges)[0] - self.bin_centers = ((self.bin_edges[:-1] + self.bin_edges[1:]) / 2) - + y = stats.binned_statistic( + x=self.bin_centers, + values=tup[0], + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + s = stats.binned_statistic( + x=self.bin_centers, + values=tup[1], + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + self.oib_diffs[k] = (y, s) + self.bin_area = stats.binned_statistic( + x=self.bin_centers, + values=self.bin_area, + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + self.bin_centers = (self.bin_edges[:-1] + self.bin_edges[1:]) / 2 # double difference all oib diffs from the same season 1+ year apart - def _dbl_diff(self, months=range(1,13)): + def _dbl_diff(self, months=range(1, 13)): # prepopulate dbl_diffs dictionary object will structure with dates, dh, sigma # where dates is a tuple for each double differenced array in the format of (date1,date2), # where date1's cop30 differences were subtracted from date2's to get the dh values for that time span, # and the sigma was taken as the mean sigma from each date - self.dbl_diffs['dates'] = [] - self.dbl_diffs['dh'] = [] - self.dbl_diffs['sigma'] = [] + self.dbl_diffs["dates"] = [] + self.dbl_diffs["dh"] = [] + self.dbl_diffs["sigma"] = [] # loop through months for m in months: # filter and sort dates to include only those in the target month - filtered_dates = sorted([x for x in list(self.oib_diffs.keys()) if x.month == m]) + filtered_dates = sorted( + [x for x in list(self.oib_diffs.keys()) if x.month == m] + ) # Calculate differences for consecutive pairs that are >=1 full year apart for i in range(len(filtered_dates) - 1): date1 = filtered_dates[i] @@ -237,44 +295,56 @@ def _dbl_diff(self, months=range(1,13)): # Check if the pair is at least one full year apart if year_diff >= 1: - self.dbl_diffs['dates'].append((date1,date2)) - self.dbl_diffs['dh'].append(self.oib_diffs[date2][0] - self.oib_diffs[date1][0]) + self.dbl_diffs["dates"].append((date1, date2)) + self.dbl_diffs["dh"].append( + self.oib_diffs[date2][0] - self.oib_diffs[date1][0] + ) # self.dbl_diffs['sigma'].append((self.oib_diffs[date2][1] + self.oib_diffs[date1][1]) / 2) - self.dbl_diffs['sigma'].append(self.oib_diffs[date2][1] + self.oib_diffs[date1][1]) + self.dbl_diffs["sigma"].append( + self.oib_diffs[date2][1] + self.oib_diffs[date1][1] + ) # column stack dh and sigmas into single 2d array - if len(self.dbl_diffs['dh'])>0: - self.dbl_diffs['dh'] = np.column_stack(self.dbl_diffs['dh']) - self.dbl_diffs['sigma'] = np.column_stack(self.dbl_diffs['sigma']) + if len(self.dbl_diffs["dh"]) > 0: + self.dbl_diffs["dh"] = np.column_stack(self.dbl_diffs["dh"]) + self.dbl_diffs["sigma"] = np.column_stack(self.dbl_diffs["sigma"]) else: - self.dbl_diffs['dh'] = np.nan + self.dbl_diffs["dh"] = np.nan # check if deltah is all nan - if np.isnan(self.dbl_diffs['dh']).all(): - self.dbl_diffs['dh'] = None - self.dbl_diffs['sigma'] = None - - - def _elevchange_to_masschange(self, ela, density_ablation=pygem_prms['constants']['density_ice'], density_accumulation=700): + if np.isnan(self.dbl_diffs["dh"]).all(): + self.dbl_diffs["dh"] = None + self.dbl_diffs["sigma"] = None + + def _elevchange_to_masschange( + self, + ela, + density_ablation=pygem_prms["constants"]["density_ice"], + density_accumulation=700, + ): # convert elevation changes to mass change using piecewise density conversion - if self.dbl_diffs['dh'] is not None: + if self.dbl_diffs["dh"] is not None: # populate density conversion column corresponding to bin center elevation conversion_factor = np.ones(len(self.bin_centers)) - conversion_factor[np.where(self.bin_centers=ela)] = density_accumulation + conversion_factor[np.where(self.bin_centers < ela)] = density_ablation + conversion_factor[np.where(self.bin_centers >= ela)] = density_accumulation # get change in mass per unit area as (dz * rho) [dmass / dm2] - self.dbl_diffs['dmda'] = self.dbl_diffs['dh'] * conversion_factor[:,np.newaxis] - self.dbl_diffs['dmda_err'] = self.dbl_diffs['sigma'] * conversion_factor[:,np.newaxis] + self.dbl_diffs["dmda"] = ( + self.dbl_diffs["dh"] * conversion_factor[:, np.newaxis] + ) + self.dbl_diffs["dmda_err"] = ( + self.dbl_diffs["sigma"] * conversion_factor[:, np.newaxis] + ) else: - self.dbl_diffs['dmda'] = None - self._dbl_diff['dmda_err'] = None + self.dbl_diffs["dmda"] = None + self._dbl_diff["dmda_err"] = None -def _filter_on_pixel_count(arr, pctl = 15): +def _filter_on_pixel_count(arr, pctl=15): """ filter oib diffs by perntile pixel count """ - arr=arr.astype(float) - arr[arr==0] = np.nan - mask = arr < np.nanpercentile(arr,pctl) + arr = arr.astype(float) + arr[arr == 0] = np.nan + mask = arr < np.nanpercentile(arr, pctl) return mask diff --git a/pygem/tests/test_basics.py b/pygem/tests/test_basics.py index 3a2c4c29..292a77d4 100755 --- a/pygem/tests/test_basics.py +++ b/pygem/tests/test_basics.py @@ -3,5 +3,4 @@ def test_version_string(): # simple test to check that the verion number is available - assert type(pygem.__version__ ) == str - + assert type(pygem.__version__) == str diff --git a/pygem/tests/test_config.py b/pygem/tests/test_config.py index 748525f3..ce1f3fe4 100644 --- a/pygem/tests/test_config.py +++ b/pygem/tests/test_config.py @@ -13,13 +13,11 @@ class TestConfigManager: def setup(self, tmp_path): """Setup a ConfigManager instance for each test.""" self.config_manager = ConfigManager( - config_filename='config.yaml', - base_dir=tmp_path, - overwrite=True + config_filename="config.yaml", base_dir=tmp_path, overwrite=True ) def test_config_created(self, tmp_path): - config_path = pathlib.Path(tmp_path) / 'config.yaml' + config_path = pathlib.Path(tmp_path) / "config.yaml" assert config_path.is_file() def test_read_config(self): @@ -30,16 +28,23 @@ def test_read_config(self): def test_update_config_unrecognized_key_error(self): """Test that a KeyError is raised when updating a value with an unrecognized key.""" - with pytest.raises(KeyError, match="Unrecognized configuration key: invalid_key"): + with pytest.raises( + KeyError, match="Unrecognized configuration key: invalid_key" + ): self.config_manager.update_config({"invalid_key": None}) - @pytest.mark.parametrize("key, invalid_value, expected_type, invalid_type", [ - ("sim.nsims", [1, 2, 3], "int", "list"), - ("calib.HH2015_params.kp_init", "0.5", "float", "str"), - ("setup.include_landterm", -999, "bool", "int"), - ("rgi.rgi_cols_drop", "not-a-list", "list", "str"), - ]) - def test_update_config_type_error(self, key, invalid_value, expected_type, invalid_type): + @pytest.mark.parametrize( + "key, invalid_value, expected_type, invalid_type", + [ + ("sim.nsims", [1, 2, 3], "int", "list"), + ("calib.HH2015_params.kp_init", "0.5", "float", "str"), + ("setup.include_landterm", -999, "bool", "int"), + ("rgi.rgi_cols_drop", "not-a-list", "list", "str"), + ], + ) + def test_update_config_type_error( + self, key, invalid_value, expected_type, invalid_type + ): """ Test that a TypeError is raised when updating a value with a new value of a wrong type. @@ -47,7 +52,7 @@ def test_update_config_type_error(self, key, invalid_value, expected_type, inval with pytest.raises( TypeError, match=f"Invalid type for '{key.replace('.', '\\.')}':" - f" expected.*{expected_type}.*, not.*{invalid_type}.*" + f" expected.*{expected_type}.*, not.*{invalid_type}.*", ): self.config_manager.update_config({key: invalid_value}) @@ -63,30 +68,32 @@ def test_update_config_list_element_type_error(self): with pytest.raises( TypeError, match=f"Invalid type for elements in '{key.replace('.', '\\.')}':" - f" expected all elements to be .*{expected_type}.*, but got.*{invalid_value}.*" + f" expected all elements to be .*{expected_type}.*, but got.*{invalid_value}.*", ): self.config_manager.update_config({key: invalid_value}) def test_compare_with_source(self): """Test that compare_with_source detects missing keys.""" # Remove a key from the config file - with open(self.config_manager.config_path, 'r') as f: + with open(self.config_manager.config_path, "r") as f: config = yaml.safe_load(f) - del config['sim']['nsims'] - with open(self.config_manager.config_path, 'w') as f: + del config["sim"]["nsims"] + with open(self.config_manager.config_path, "w") as f: yaml.dump(config, f) - with pytest.raises(KeyError, match=r"Missing required key in configuration: sim\.nsims"): + with pytest.raises( + KeyError, match=r"Missing required key in configuration: sim\.nsims" + ): self.config_manager.read_config(validate=True) def test_update_config(self): """Test that update_config updates the config file for all data types.""" updates = { - "sim.nsims": 5, # int - "calib.HH2015_params.kp_init": 0.5, # float - "user.email": "updated@example.com", # str - "setup.include_landterm": False, # bool - "rgi.rgi_cols_drop": ['Item1', 'Item2'], # list + "sim.nsims": 5, # int + "calib.HH2015_params.kp_init": 0.5, # float + "user.email": "updated@example.com", # str + "setup.include_landterm": False, # bool + "rgi.rgi_cols_drop": ["Item1", "Item2"], # list } # Values before updating @@ -95,7 +102,14 @@ def test_update_config(self): assert config["calib"]["HH2015_params"]["kp_init"] == 1.5 assert config["user"]["email"] == "drounce@cmu.edu" assert config["setup"]["include_landterm"] == True - assert config["rgi"]["rgi_cols_drop"] == ["GLIMSId", "BgnDate", "EndDate", "Status", "Linkages", "Name"] + assert config["rgi"]["rgi_cols_drop"] == [ + "GLIMSId", + "BgnDate", + "EndDate", + "Status", + "Linkages", + "Name", + ] self.config_manager.update_config(updates) config = self.config_manager.read_config() @@ -113,7 +127,9 @@ def test_update_config_dict(self): config = self.config_manager.read_config() assert config["user"]["name"] == "David Rounce" assert config["user"]["email"] == "drounce@cmu.edu" - assert config["user"]["institution"] == "Carnegie Mellon University, Pittsburgh PA" + assert ( + config["user"]["institution"] == "Carnegie Mellon University, Pittsburgh PA" + ) updates = { "user": { diff --git a/pygem/tests/test_notebooks.py b/pygem/tests/test_notebooks.py index 2ec97366..e928f69b 100644 --- a/pygem/tests/test_notebooks.py +++ b/pygem/tests/test_notebooks.py @@ -14,8 +14,10 @@ def test_notebook(notebook): # TODO #54: Test all notebooks - if notebook not in ("simple_test.ipynb", "advanced_test.ipynb", "advanced_test_tw.ipynb"): + if notebook not in ( + "simple_test.ipynb", + "advanced_test.ipynb", + "advanced_test_tw.ipynb", + ): pytest.skip() - subprocess.check_call( - ["pytest", "--nbmake", os.path.join(nb_dir, notebook)] - ) + subprocess.check_call(["pytest", "--nbmake", os.path.join(nb_dir, notebook)]) diff --git a/pygem/utils/_funcs.py b/pygem/utils/_funcs.py index ae89bea8..255d3d8a 100755 --- a/pygem/utils/_funcs.py +++ b/pygem/utils/_funcs.py @@ -7,6 +7,7 @@ Functions that didn't fit into other modules """ + import json import numpy as np @@ -18,6 +19,7 @@ # read the config pygem_prms = config_manager.read_config() + def annualweightedmean_array(var, dates_table): """ Calculate annual mean of variable according to the timestep. @@ -35,15 +37,20 @@ def annualweightedmean_array(var, dates_table): var_annual : np.ndarray Annual weighted mean of variable """ - if pygem_prms['time']['timestep'] == 'monthly': - dayspermonth = dates_table['daysinmonth'].values.reshape(-1,12) + if pygem_prms["time"]["timestep"] == "monthly": + dayspermonth = dates_table["daysinmonth"].values.reshape(-1, 12) # creates matrix (rows-years, columns-months) of the number of days per month daysperyear = dayspermonth.sum(axis=1) # creates an array of the days per year (includes leap years) - weights = (dayspermonth / daysperyear[:,np.newaxis]).reshape(-1) + weights = (dayspermonth / daysperyear[:, np.newaxis]).reshape(-1) # computes weights for each element, then reshapes it from matrix (rows-years, columns-months) to an array, # where each column (each monthly timestep) is the weight given to that specific month - var_annual = (var*weights[np.newaxis,:]).reshape(-1,12).sum(axis=1).reshape(-1,daysperyear.shape[0]) + var_annual = ( + (var * weights[np.newaxis, :]) + .reshape(-1, 12) + .sum(axis=1) + .reshape(-1, daysperyear.shape[0]) + ) # computes matrix (rows - bins, columns - year) of weighted average for each year # explanation: var*weights[np.newaxis,:] multiplies each element by its corresponding weight; .reshape(-1,12) # reshapes the matrix to only have 12 columns (1 year), so the size is (rows*cols/12, 12); .sum(axis=1) @@ -52,15 +59,15 @@ def annualweightedmean_array(var, dates_table): # If averaging a single year, then reshape so it returns a 1d array if var_annual.shape[1] == 1: var_annual = var_annual.reshape(var_annual.shape[0]) - elif pygem_prms['time']['timestep'] == 'daily': - print('\nError: need to code the groupbyyearsum and groupbyyearmean for daily timestep.' - 'Exiting the model run.\n') + elif pygem_prms["time"]["timestep"] == "daily": + print( + "\nError: need to code the groupbyyearsum and groupbyyearmean for daily timestep." + "Exiting the model run.\n" + ) exit() return var_annual - - def append_json(file_path, new_key, new_value): """ Opens a JSON file, reads its content, adds a new key-value pair, diff --git a/pygem/utils/_funcs_selectglaciers.py b/pygem/utils/_funcs_selectglaciers.py index 53682182..68a32a6a 100644 --- a/pygem/utils/_funcs_selectglaciers.py +++ b/pygem/utils/_funcs_selectglaciers.py @@ -7,6 +7,7 @@ Functions of different ways to select glaciers """ + # Built-in libraries import os import pickle @@ -16,7 +17,7 @@ import pandas as pd -#%% ----- Functions to select specific glacier numbers ----- +# %% ----- Functions to select specific glacier numbers ----- def get_same_glaciers(glac_fp, ending): """ Get same glaciers for testing of priors @@ -58,12 +59,12 @@ def glac_num_fromrange(int_low, int_high): y : list list of rgi glacier numbers """ - x = (np.arange(int_low, int_high+1)).tolist() + x = (np.arange(int_low, int_high + 1)).tolist() y = [str(i).zfill(5) for i in x] return y -def glac_fromcsv(csv_fullfn, cn='RGIId'): +def glac_fromcsv(csv_fullfn, cn="RGIId"): """ Generate list of glaciers from csv file @@ -78,31 +79,31 @@ def glac_fromcsv(csv_fullfn, cn='RGIId'): list of glacier numbers, e.g., ['14.00001', 15.00001'] """ df = pd.read_csv(csv_fullfn) - return [x.split('-')[1] for x in df[cn].values] + return [x.split("-")[1] for x in df[cn].values] -def glac_wo_cal(regions, prms_fp_sub=None, cal_option='MCMC'): +def glac_wo_cal(regions, prms_fp_sub=None, cal_option="MCMC"): """ Glacier list of glaciers that still need to be calibrated """ - todo_list=[] + todo_list = [] for reg in regions: prms_fns = [] - prms_fp = prms_fp_sub + str(reg).zfill(2) + '/' + prms_fp = prms_fp_sub + str(reg).zfill(2) + "/" for i in os.listdir(prms_fp): - if i.endswith('-modelprms_dict.pkl'): + if i.endswith("-modelprms_dict.pkl"): prms_fns.append(i) prms_fns = sorted(prms_fns) for nfn, prms_fn in enumerate(prms_fns): - glac_str = prms_fn.split('-')[0] + glac_str = prms_fn.split("-")[0] - if nfn%500 == 0: + if nfn % 500 == 0: print(glac_str) # Load model parameters - with open(prms_fp + prms_fn, 'rb') as f: + with open(prms_fp + prms_fn, "rb") as f: modelprms_dict = pickle.load(f) # Check if 'MCMC' is in the modelprms_dict diff --git a/setup.py b/setup.py index 007b9d82..bb5e30c9 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ Distrubted under the MIT lisence """ + from setuptools import setup if __name__ == "__main__": - setup() From d7d01e243532aac6d8df426468b69b4118e42529 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:04:19 +0000 Subject: [PATCH 10/17] Manually fix F822: Undefined fn name in `__all__` --- pygem/output.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pygem/output.py b/pygem/output.py index 4f0ab67e..bdc9c7ec 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -37,12 +37,6 @@ "single_glacier", "glacierwide_stats", "binned_stats", - "set_fn", - "get_fn", - "set_modelprms", - "create_xr_ds", - "get_xr_ds", - "save_xr_ds", "calc_stats_array", ] From 0210aa18d2d308e21f789effff29f3c71005bbce Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:26:31 +0000 Subject: [PATCH 11/17] Drop .pre-commit-config.yaml [skip ci] --- .pre-commit-config.yaml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 143c24fe..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -repos: -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 - hooks: - - id: ruff # run the linter - args: [ --fix ] - - id: ruff-format # run the formatter \ No newline at end of file From b0637f7b1bdbef1be5fdce1afdb54758b8e92ea6 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:52:07 +0000 Subject: [PATCH 12/17] Add linting and formatting contribution guidelines [skip ci] --- docs/contributing.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/contributing.md b/docs/contributing.md index 054c356a..6b6ea0a4 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -56,3 +56,17 @@ 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. + +## Code linting and formatting +PyGEM uses [ruff](https://docs.astral.sh/ruff/formatter) for linting and formatting, which ensures that the code adheres to a consistent coding style ([Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and prevents potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file. + +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. + +To automatically format the codebase using Ruff, run the following command: +``` +ruff format /path/to/code +``` From 75fc63ef84b9e63e3df60058c637389b9f5a75e5 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:19:46 +0000 Subject: [PATCH 13/17] Describe linting and formatting CI integration in guidelines [skip ci] --- docs/contributing.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 6b6ea0a4..9a4458e2 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). @@ -57,8 +58,9 @@ Installing a package in editable mode creates a symbolic link to your source cod - 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 uses [ruff](https://docs.astral.sh/ruff/formatter) for linting and formatting, which ensures that the code adheres to a consistent coding style ([Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and prevents potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file. +PyGEM uses [ruff](https://docs.astral.sh/ruff/formatter) for linting and formatting, which ensures that the code adheres to a consistent coding style ([Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and prevents potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file. Linting and formatting checks are integrated into the CI pipeline, and any detected issues will cause it to fail. To lint the codebase using Ruff, run the following command: ``` From d043d4780fb50489829d6201368a6766cf921426 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Thu, 3 Apr 2025 06:05:05 +0000 Subject: [PATCH 14/17] Use single quotes --- docs/conf.py | 58 +- pygem/bin/op/duplicate_gdirs.py | 26 +- pygem/bin/op/initialize.py | 36 +- pygem/bin/op/list_failed_simulations.py | 104 +- .../postproc/postproc_binned_monthly_mass.py | 92 +- .../postproc/postproc_compile_simulations.py | 562 +++--- pygem/bin/postproc/postproc_distribute_ice.py | 86 +- pygem/bin/postproc/postproc_monthly_mass.py | 56 +- pygem/bin/preproc/preproc_fetch_mbdata.py | 42 +- pygem/bin/preproc/preproc_wgms_estimate_kp.py | 342 ++-- pygem/bin/run/run_calibration.py | 1780 ++++++++--------- .../run/run_calibration_frontalablation.py | 1580 +++++++-------- pygem/bin/run/run_calibration_reg_glena.py | 258 +-- pygem/bin/run/run_mcmc_priors.py | 340 ++-- pygem/bin/run/run_simulation.py | 976 ++++----- pygem/class_climate.py | 352 ++-- pygem/gcmbiasadj.py | 50 +- pygem/glacierdynamics.py | 286 +-- pygem/massbalance.py | 288 +-- pygem/mcmc.py | 124 +- pygem/oggm_compat.py | 132 +- pygem/output.py | 990 ++++----- pygem/pygem_modelsetup.py | 192 +- pygem/scraps/dummy_task_module.py | 8 +- pygem/scraps/run.py | 14 +- pygem/setup/config.py | 502 ++--- pygem/shop/debris.py | 102 +- pygem/shop/icethickness.py | 80 +- pygem/shop/mbdata.py | 56 +- pygem/shop/oib.py | 118 +- pygem/tests/test_config.py | 102 +- pygem/tests/test_notebooks.py | 16 +- pygem/utils/_funcs.py | 20 +- pygem/utils/_funcs_selectglaciers.py | 14 +- pyproject.toml | 3 + setup.py | 2 +- 36 files changed, 4896 insertions(+), 4893 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index afa84cb2..db2f2529 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,56 +11,56 @@ import tomllib -sys.path.insert(0, os.path.abspath("../pygem/")) +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"] +project = 'PyGEM' +copyright = '2023, David Rounce' +author = 'David Rounce' +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", + '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'] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "sphinx_book_theme" -html_static_path = ["_static"] +html_theme = 'sphinx_book_theme' +html_static_path = ['_static'] html_theme_options = { - "repository_url": "https://github.com/PyGEM-Community/PyGEM", - "use_repository_button": True, - "show_nav_level": 2, - "navigation_depth": 3, + 'repository_url': 'https://github.com/PyGEM-Community/PyGEM', + 'use_repository_button': True, + 'show_nav_level': 2, + 'navigation_depth': 3, } diff --git a/pygem/bin/op/duplicate_gdirs.py b/pygem/bin/op/duplicate_gdirs.py index ea3d3131..af236931 100644 --- a/pygem/bin/op/duplicate_gdirs.py +++ b/pygem/bin/op/duplicate_gdirs.py @@ -23,46 +23,46 @@ def main(): parser = argparse.ArgumentParser( - description="Script to make duplicate oggm glacier directories - primarily to avoid corruption if parellelizing runs on a single glacier" + 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", + '-rgi_glac_number', type=str, default=None, - help="Randoph Glacier Inventory region", + help='Randoph Glacier Inventory region', ) parser.add_argument( - "-num_copies", + '-num_copies', type=int, default=1, - help="Number of copies to create of the glacier directory data", + 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(".") + reg, id = glac_num.split('.') reg = reg.zfill(2) thous = id[:2] - root = pygem_prms["root"] + "/" + pygem_prms["oggm"]["oggm_gdir_relpath"] - sfix = "/per_glacier/" + f"RGI60-{reg}/" + f"RGI60-{reg}.{thous}/" + root = pygem_prms['root'] + '/' + pygem_prms['oggm']['oggm_gdir_relpath'] + sfix = '/per_glacier/' + f'RGI60-{reg}/' + f'RGI60-{reg}.{thous}/' for n in range(num_copies): - nroot = os.path.abspath(root.replace("gdirs", f"gdirs_{n + 1}")) + nroot = os.path.abspath(root.replace('gdirs', f'gdirs_{n + 1}')) # duplicate structure - os.makedirs(nroot + sfix + f"RGI60-{reg}.{id}", exist_ok=True) + os.makedirs(nroot + sfix + f'RGI60-{reg}.{id}', exist_ok=True) # copy directory data shutil.copytree( - root + sfix + f"RGI60-{reg}.{id}", - nroot + sfix + f"RGI60-{reg}.{id}", + root + sfix + f'RGI60-{reg}.{id}', + nroot + sfix + f'RGI60-{reg}.{id}', dirs_exist_ok=True, ) return -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pygem/bin/op/initialize.py b/pygem/bin/op/initialize.py index a95cf1aa..c6f052e1 100644 --- a/pygem/bin/op/initialize.py +++ b/pygem/bin/op/initialize.py @@ -22,23 +22,23 @@ 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 @@ -46,7 +46,7 @@ def get_confirm_token(response): 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) @@ -57,7 +57,7 @@ def get_unique_folder_name(dir): 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 @@ -74,28 +74,28 @@ def download_and_unzip_from_google_drive(file_id, output_dir): 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 + base_url, params={'id': file_id, 'confirm': token}, stream=True ) save_response_content(response, zip_path) # Unzip the file - tmppath = os.path.join(output_dir, "tmp") - with zipfile.ZipFile(zip_path, "r") as zip_ref: + tmppath = os.path.join(output_dir, 'tmp') + with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(tmppath) # get root dir name of zipped files @@ -119,14 +119,14 @@ def download_and_unzip_from_google_drive(file_id, output_dir): def main(): # Define the base directory - basedir = os.path.join(os.path.expanduser("~"), "PyGEM") + basedir = os.path.join(os.path.expanduser('~'), 'PyGEM') # Google Drive file id for sample dataset - file_id = "1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ" + file_id = '1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ' # download and unzip out = download_and_unzip_from_google_drive(file_id, basedir) if out: - print("Downloaded PyGEM sample dataset:") + print('Downloaded PyGEM sample dataset:') print(os.path.abspath(out)) try: print_file_tree(out) @@ -134,14 +134,14 @@ def main(): pass else: - print("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__": +if __name__ == '__main__': main() diff --git a/pygem/bin/op/list_failed_simulations.py b/pygem/bin/op/list_failed_simulations.py index 65785bdf..7ed08a24 100644 --- a/pygem/bin/op/list_failed_simulations.py +++ b/pygem/bin/op/list_failed_simulations.py @@ -29,56 +29,56 @@ 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", + rgi_regionsO2='all', + rgi_glac_number='all', glac_no=None, debug=True, ) - glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) + glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) # get list of glacier simulation files 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" + sim_dir + f'*_{calib_opt}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc' ) # Glaciers with successful runs to process - glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] - glacno_ran = [x.split(".")[0].zfill(2) + "." + x[-5:] for x in glacno_ran] + glacno_ran = [x.split('/')[-1].split('_')[0] for x in fps] + glacno_ran = [x.split('.')[0].zfill(2) + '.' + x[-5:] for x in glacno_ran] # print stats of successfully simualated glaciers main_glac_rgi = main_glac_rgi_all.loc[ main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1) ] print( - f"{gcm} {str(scenario).replace('None', '')} glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0] / main_glac_rgi_all.shape[0] * 100, 3)}%)" + 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)}%)" + 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] + 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) @@ -91,73 +91,73 @@ def main(): 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 = parser.add_argument_group('required named arguments') requiredNamed.add_argument( - "-rgi_region01", + '-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="+", + 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", + '-gcm_name', type=str, default=None, - help="GCM name to compile results from (ex. ERA5 or CESM2)", + help='GCM name to compile results from (ex. ERA5 or CESM2)', ) parser.add_argument( - "-scenario", - action="store", + '-scenario', + action='store', type=str, default=None, - help="rcp or ssp scenario used for model run (ex. rcp26 or ssp585)", + help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)', ) parser.add_argument( - "-gcm_startyear", - action="store", + '-gcm_startyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_startyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_startyear'], + help='start year for the model run', ) parser.add_argument( - "-gcm_endyear", - action="store", + '-gcm_endyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_endyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_endyear'], + help='start year for the model run', ) parser.add_argument( - "-option_calibration", - action="store", + '-option_calibration', + action='store', type=str, - default=pygem_prms["calib"]["option_calibration"], + default=pygem_prms['calib']['option_calibration'], help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', ) parser.add_argument( - "-option_bias_adjustment", - action="store", + '-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, \ + 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)", + 2: HH2015 methods, 3: quantile delta mapping)', ) parser.add_argument( - "-outdir", + '-outdir', type=str, default=None, - help="directory to output json file containing list of failed glaciers in each RGI region", + 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") + parser.add_argument('-v', '--verbose', action='store_true', help='verbose flag') args = parser.parse_args() region = args.rgi_region01 scenario = args.scenario gcm_name = args.gcm_name bias_adj = args.option_bias_adjustment - simpath = pygem_prms["root"] + "/Output/simulations/" + simpath = pygem_prms['root'] + '/Output/simulations/' - if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: + if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: scenario = None bias_adj = 0 @@ -165,7 +165,7 @@ def main(): region = [region] if args.outdir and not os.path.isdir(args.outdir): - print(f"Specified output path does not exist: {args.outdir}") + print(f'Specified output path does not exist: {args.outdir}') sys.exit(1) for reg in region: @@ -183,20 +183,20 @@ def main(): if args.outdir: fout = os.path.join( args.outdir, - f"R{str(reg).zfill(2)}_{args.gcm_name}_{scenario}_{args.gcm_startyear}_{args.gcm_endyear}_failed_rgiids.json", - ).replace("None_", "") - with open(fout, "w") as f: + 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}" + 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}:" + 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', '')}" + 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 0ee4bde6..cf57db07 100644 --- a/pygem/bin/postproc/postproc_binned_monthly_mass.py +++ b/pygem/bin/postproc/postproc_binned_monthly_mass.py @@ -35,30 +35,30 @@ def getparser(): Use argparse to add arguments from the command line """ parser = argparse.ArgumentParser( - description="process monthly ice thickness for PyGEM simulation" + description='process monthly ice thickness for PyGEM simulation' ) # add arguments parser.add_argument( - "-simpath", - action="store", + '-simpath', + action='store', type=str, - nargs="+", + nargs='+', default=None, - help="path to PyGEM binned simulation (can take multiple)", + help='path to PyGEM binned simulation (can take multiple)', ) parser.add_argument( - "-binned_simdir", - action="store", + '-binned_simdir', + action='store', type=str, default=None, - help="directory with binned simulations for which to process monthly thickness", + help='directory with binned simulations for which to process monthly thickness', ) parser.add_argument( - "-ncores", - action="store", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use", + help='number of simultaneous processes (cores) to use', ) return parser @@ -107,10 +107,10 @@ 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"] + 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!" + 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( @@ -136,7 +136,7 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): h_monthly = running_delta_h_monthly + h_annual[:, :, 0][:, :, np.newaxis] # convert to mass per unit area - m_spec_monthly = h_monthly * pygem_prms["constants"]["density_ice"] + m_spec_monthly = h_monthly * pygem_prms['constants']['density_ice'] ### get monthly mass ### # note, binned monthly thickness and mass is currently per unit area @@ -145,7 +145,7 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): # so we'll resort to using the annual binned glacier mass and thickness in order to get to binned glacier area ######################## # first convert m_annual to bin_voluma_annual - v_annual = m_annual / pygem_prms["constants"]["density_ice"] + v_annual = m_annual / pygem_prms['constants']['density_ice'] # now get area: use numpy divide where denominator is greater than 0 to avoid divide error # note, indexing of [:,:,1:] so that annual area array has same shape as flux_div_annual a_annual = np.divide( @@ -188,35 +188,35 @@ 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_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_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_mass_monthly'] = collections.OrderedDict( + [('glac', glac_values), ('bin', bin_values), ('time', time_values)] ) # Attributes dictionary output_attrs_dict = {} - output_attrs_dict["bin_thick_monthly"] = { - "long_name": "binned monthly ice thickness", - "units": "m", - "temporal_resolution": "monthly", - "comment": "monthly ice thickness binned by surface elevation (assuming constant flux divergence throughout a given year)", + output_attrs_dict['bin_thick_monthly'] = { + 'long_name': 'binned monthly ice thickness', + 'units': 'm', + 'temporal_resolution': 'monthly', + 'comment': 'monthly ice thickness binned by surface elevation (assuming constant flux divergence throughout a given year)', } - output_attrs_dict["bin_mass_spec_monthly"] = { - "long_name": "binned monthly specific ice mass", - "units": "kg m^-2", - "temporal_resolution": "monthly", - "comment": "monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)", + output_attrs_dict['bin_mass_spec_monthly'] = { + 'long_name': 'binned monthly specific ice mass', + 'units': 'kg m^-2', + 'temporal_resolution': 'monthly', + 'comment': 'monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)', } - output_attrs_dict["bin_mass_monthly"] = { - "long_name": "binned monthly ice mass", - "units": "kg", - "temporal_resolution": "monthly", - "comment": "monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)", + output_attrs_dict['bin_mass_monthly'] = { + 'long_name': 'binned monthly ice mass', + 'units': 'kg', + 'temporal_resolution': 'monthly', + 'comment': 'monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)', } # Add variables to empty dataset and merge together @@ -246,11 +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} + 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 + 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 @@ -289,7 +289,7 @@ def run(simpath): # append to existing binned netcdf output_ds_binned.to_netcdf( - simpath, mode="a", encoding=encoding_binned, engine="netcdf4" + simpath, mode='a', encoding=encoding_binned, engine='netcdf4' ) # close datasets @@ -308,7 +308,7 @@ def main(): 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: @@ -317,12 +317,12 @@ def main(): ncores = 1 # Parallel processing - print("Processing with " + str(ncores) + " cores...") + print('Processing with ' + str(ncores) + ' cores...') with multiprocessing.Pool(ncores) as p: p.map(run, simpath) - print("Total processing time:", time.time() - time_start, "s") + print('Total processing time:', time.time() - time_start, 's') -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pygem/bin/postproc/postproc_compile_simulations.py b/pygem/bin/postproc/postproc_compile_simulations.py index fd8ae3b7..cbaa4c96 100644 --- a/pygem/bin/postproc/postproc_compile_simulations.py +++ b/pygem/bin/postproc/postproc_compile_simulations.py @@ -31,28 +31,28 @@ 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", + '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', } @@ -70,27 +70,27 @@ def run(args): gcm_endyear, vars, ) = args - print(f"RGI region {reg}") + print(f'RGI region {reg}') # #%% ----- PROCESS DATASETS FOR INDIVIDUAL GLACIERS AND ELEVATION BINS ----- - comppath = simpath + "/compile/" + comppath = simpath + '/compile/' # define base directory - base_dir = simpath + "/" + str(reg).zfill(2) + "/" + 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", + rgi_regionsO2='all', + rgi_glac_number='all', glac_no=None, debug=True, ) - glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) + glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) ### CREATE BATCHES ### # get last glacier number to define number of batches - lastn = int(sorted(glacno_list_all)[-1].split(".")[1]) + lastn = int(sorted(glacno_list_all)[-1].split('.')[1]) # round up to thosand batch_interval = 1000 last_thous = np.ceil(lastn / batch_interval) * batch_interval @@ -114,28 +114,28 @@ def run(args): if scenario: # ensure scenario has been run for each gcm for gcm in gcms: - if scenario not in os.listdir(base_dir + "/" + gcm): + if scenario not in os.listdir(base_dir + '/' + gcm): # remove the gcm from our gcm list if the desired scenario is not contained gcms.remove(gcm) - print(f"scenario {scenario} not found for {gcm}, skipping") + 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( - "__", "_" + + '/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" + + '/stats/' + + f'*{gcm}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc' )[0] - nsets = fn.split("/")[-1].split("_")[-4] + nsets = fn.split('/')[-1].split('_')[-4] ds_glac = xr.open_dataset(fn) year_values = ds_glac.year.values @@ -145,24 +145,24 @@ def run(args): missing_vars = list(set(vars) - set(ds_vars)) if len(missing_vars) > 0: vars = list(set(vars).intersection(ds_vars)) - print(f"Warning: Requested variables are missing: {missing_vars}") + print(f'Warning: Requested variables are missing: {missing_vars}') ############################################################ - print(f"Compiling GCMS: {gcms}") - print(f"Realizations: {realizations}") - print(f"Variables: {vars}") + print(f'Compiling GCMS: {gcms}') + print(f'Realizations: {realizations}') + print(f'Variables: {vars}') ### LEVEL I ### # loop through glacier batches of 1000 for nbatch, glacno_list in enumerate(glacno_list_batches): - print(f"Batch {nbatch}:") + print(f'Batch {nbatch}:') # batch start timer loop_start = time.time() # get batch start and end numbers - batch_start = glacno_list[0].split(".")[1] - batch_end = glacno_list[-1].split(".")[1] + batch_start = glacno_list[0].split('.')[1] + batch_end = glacno_list[-1].split('.')[1] print(nbatch, batch_start, batch_end) @@ -191,24 +191,24 @@ def run(args): # 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/" + sim_dir = base_dir + gcm + '/' + scenario + '/stats/' ### LEVEL III ### for realization in realizations: - print(f"GCM: {gcm} {realization}") + 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( - "__", "_" + + 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: # Glaciers with successful runs to process - glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] + 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 + 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( @@ -216,10 +216,10 @@ def run(args): ) ] 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)}%)" + 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)}%)" + 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 @@ -242,9 +242,9 @@ def run(args): # loop through each glacier in batch list for i, glacno in enumerate(glacno_list): # get glacier string and file name - glacier_str = "{0:0.5f}".format(float(glacno)) - glacno_fn = f"{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( - "__", "_" + glacier_str = '{0:0.5f}'.format(float(glacno)) + glacno_fn = f'{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace( + '__', '_' ) # try to load all glaciers in region try: @@ -490,47 +490,47 @@ def run(args): # ===== CREATE NETCDF FILES===== # get common attributes - rgiid_list = ["RGI60-" + x for x in glacno_list] + rgiid_list = ['RGI60-' + x for x in glacno_list] cenlon_list = list(main_glac_rgi_batch.CenLon.values) cenlat_list = list(main_glac_rgi_batch.CenLat.values) attrs_dict = { - "Region": str(reg) + " - " + rgi_reg_dict[reg], - "source": f"PyGEMv{pygem.__version__}", - "institution": pygem_prms["user"]["institution"], - "history": f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " - + datetime.today().strftime("%Y-%m-%d"), - "references": "doi:10.1126/science.abo1324", - "Conventions": "CF-1.9", - "featureType": "timeSeries", + 'Region': str(reg) + ' - ' + rgi_reg_dict[reg], + 'source': f'PyGEMv{pygem.__version__}', + 'institution': pygem_prms['user']['institution'], + 'history': f'Created by {pygem_prms["user"]["name"]} ({pygem_prms["user"]["email"]}) on ' + + datetime.today().strftime('%Y-%m-%d'), + 'references': 'doi:10.1126/science.abo1324', + 'Conventions': 'CF-1.9', + 'featureType': 'timeSeries', } # loop through variables for var in vars: # get common coords - if "annual" in var: + if 'annual' in var: tvals = year_values else: tvals = time_values if realizations[0]: coords_dict = dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model=(["realization"], realizations), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), + RGIId=(['glacier'], rgiid_list), + Climate_Model=(['realization'], realizations), + lon=(['glacier'], cenlon_list), + lat=(['glacier'], cenlat_list), time=tvals, ) - coord_order = ["realization", "glacier", "time"] + 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), + RGIId=(['glacier'], rgiid_list), + Climate_Model=(['model'], gcms), + lon=(['glacier'], cenlon_list), + lat=(['glacier'], cenlat_list), time=tvals, ) - coord_order = ["model", "glacier", "time"] + coord_order = ['model', 'glacier', 'time'] # glac_runoff_monthly - if var == "glac_runoff_monthly": + if var == 'glac_runoff_monthly': ds = xr.Dataset( data_vars=dict( glac_runoff_monthly=( @@ -542,16 +542,16 @@ def run(args): 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['long_name'] = 'glacier-wide runoff' + ds.glac_runoff_monthly.attrs['units'] = 'm3' + ds.glac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' + ds.glac_runoff_monthly.attrs['comment'] = ( + 'runoff from the glacier terminus, which moves over time' ) - ds.glac_runoff_monthly.attrs["grid_mapping"] = "crs" + ds.glac_runoff_monthly.attrs['grid_mapping'] = 'crs' # offglac_runoff_monthly - elif var == "offglac_runoff_monthly": + elif var == 'offglac_runoff_monthly': ds = xr.Dataset( data_vars=dict( offglac_runoff_monthly=( @@ -563,16 +563,16 @@ def run(args): 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['long_name'] = 'off-glacier-wide runoff' + ds.offglac_runoff_monthly.attrs['units'] = 'm3' + ds.offglac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' + ds.offglac_runoff_monthly.attrs['comment'] = ( + 'off-glacier runoff from area where glacier no longer exists' ) - ds.offglac_runoff_monthly.attrs["grid_mapping"] = "crs" + ds.offglac_runoff_monthly.attrs['grid_mapping'] = 'crs' # glac_acc_monthly - elif var == "glac_acc_monthly": + elif var == 'glac_acc_monthly': ds = xr.Dataset( data_vars=dict( glac_acc_monthly=(coord_order, reg_glac_allgcms_acc_monthly), @@ -581,16 +581,16 @@ def run(args): coords=coords_dict, attrs=attrs_dict, ) - ds.glac_acc_monthly.attrs["long_name"] = ( - "glacier-wide accumulation, in water equivalent" + 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" + 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": + elif var == 'glac_melt_monthly': ds = xr.Dataset( data_vars=dict( glac_melt_monthly=(coord_order, reg_glac_allgcms_melt_monthly), @@ -599,15 +599,15 @@ def run(args): coords=coords_dict, attrs=attrs_dict, ) - ds.glac_melt_monthly.attrs["long_name"] = ( - "glacier-wide melt, in water equivalent" + 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" + 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": + elif var == 'glac_refreeze_monthly': ds = xr.Dataset( data_vars=dict( glac_refreeze_monthly=( @@ -619,15 +619,15 @@ def run(args): coords=coords_dict, attrs=attrs_dict, ) - ds.glac_refreeze_monthly.attrs["long_name"] = ( - "glacier-wide refreeze, in water equivalent" + 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" + 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": + elif var == 'glac_frontalablation_monthly': ds = xr.Dataset( data_vars=dict( glac_frontalablation_monthly=( @@ -637,28 +637,28 @@ def run(args): crs=np.nan, ), coords=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model=(["model"], gcms), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), + 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['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['units'] = 'm3' + ds.glac_frontalablation_monthly.attrs['temporal_resolution'] = 'monthly' + ds.glac_frontalablation_monthly.attrs['comment'] = ( + 'mass losses from calving, subaerial frontal melting, \ sublimation above the waterline and subaqueous frontal melting below the waterline; \ - positive values indicate mass lost like melt" + positive values indicate mass lost like melt' ) - ds.glac_frontalablation_monthly.attrs["grid_mapping"] = "crs" + ds.glac_frontalablation_monthly.attrs['grid_mapping'] = 'crs' # glac_massbaltotal_monthly - elif var == "glac_massbaltotal_monthly": + elif var == 'glac_massbaltotal_monthly': ds = xr.Dataset( data_vars=dict( glac_massbaltotal_monthly=( @@ -670,18 +670,18 @@ def run(args): 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['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['units'] = 'm3' + ds.glac_massbaltotal_monthly.attrs['temporal_resolution'] = 'monthly' + ds.glac_massbaltotal_monthly.attrs['comment'] = ( + 'total mass balance is the sum of the climatic mass balance and frontal ablation' ) - ds.glac_massbaltotal_monthly.attrs["grid_mapping"] = "crs" + ds.glac_massbaltotal_monthly.attrs['grid_mapping'] = 'crs' # glac_prec_monthly - elif var == "glac_prec_monthly": + elif var == 'glac_prec_monthly': ds = xr.Dataset( data_vars=dict( glac_prec_monthly=(coord_order, reg_glac_allgcms_prec_monthly), @@ -690,18 +690,18 @@ def run(args): coords=coords_dict, attrs=attrs_dict, ) - ds.glac_prec_monthly.attrs["long_name"] = ( - "glacier-wide precipitation (liquid)" + 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['units'] = 'm3' + ds.glac_prec_monthly.attrs['temporal_resolution'] = 'monthly' + ds.glac_prec_monthly.attrs['comment'] = ( + 'only the liquid precipitation, solid precipitation excluded' ) - ds.glac_prec_monthly.attrs["grid_mapping"] = "crs" + ds.glac_prec_monthly.attrs['grid_mapping'] = 'crs' # glac_mass_monthly - elif var == "glac_mass_monthly": + elif var == 'glac_mass_monthly': ds = xr.Dataset( data_vars=dict( glac_mass_monthly=(coord_order, reg_glac_allgcms_mass_monthly), @@ -710,16 +710,16 @@ def run(args): 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['long_name'] = 'glacier mass' + ds.glac_mass_monthly.attrs['units'] = 'kg' + ds.glac_mass_monthly.attrs['temporal_resolution'] = 'monthly' + ds.glac_mass_monthly.attrs['comment'] = ( + 'mass of ice based on area and ice thickness at start of the year and the monthly total mass balance' ) - ds.glac_mass_monthly.attrs["grid_mapping"] = "crs" + ds.glac_mass_monthly.attrs['grid_mapping'] = 'crs' # glac_area_annual - elif var == "glac_area_annual": + elif var == 'glac_area_annual': ds = xr.Dataset( data_vars=dict( glac_area_annual=(coord_order, reg_glac_allgcms_area_annual), @@ -728,14 +728,14 @@ def run(args): 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" + 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": + elif var == 'glac_mass_annual': ds = xr.Dataset( data_vars=dict( glac_mass_annual=(coord_order, reg_glac_allgcms_mass_annual), @@ -744,97 +744,97 @@ def run(args): 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['long_name'] = 'glacier mass' + ds.glac_mass_annual.attrs['units'] = 'kg' + ds.glac_mass_annual.attrs['temporal_resolution'] = 'annual' + ds.glac_mass_annual.attrs['comment'] = ( + 'mass of ice based on area and ice thickness at start of the year' ) - ds.glac_mass_annual.attrs["grid_mapping"] = "crs" + 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"] = ( + ds.crs.attrs['grid_mapping_name'] = 'latitude_longitude' + ds.crs.attrs['longitude_of_prime_meridian'] = 0.0 + ds.crs.attrs['semi_major_axis'] = 6378137.0 + ds.crs.attrs['inverse_flattening'] = 298.257223563 + ds.crs.attrs['proj4text'] = '+proj=longlat +datum=WGS84 +no_defs' + ds.crs.attrs['crs_wkt'] = ( 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' ) # time attributes - different for monthly v annual - ds.time.attrs["long_name"] = "time" - if "annual" in var: - ds.time.attrs["range"] = ( - str(year_values[0]) + " - " + str(year_values[-1]) + ds.time.attrs['long_name'] = 'time' + if 'annual' in var: + ds.time.attrs['range'] = ( + str(year_values[0]) + ' - ' + str(year_values[-1]) ) - ds.time.attrs["comment"] = "years referring to the start of each year" - elif "monthly" in var: - ds.time.attrs["range"] = ( - str(time_values[0]) + " - " + str(time_values[-1]) + ds.time.attrs['comment'] = 'years referring to the start of each year' + elif 'monthly' in var: + ds.time.attrs['range'] = ( + str(time_values[0]) + ' - ' + str(time_values[-1]) ) - ds.time.attrs["comment"] = "start of the month" + ds.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['long_name'] = 'Randolph Glacier Inventory Id' + ds.RGIId.attrs['comment'] = ( + 'RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)' ) - ds.RGIId.attrs["cf_role"] = "timeseries_id" + ds.RGIId.attrs['cf_role'] = 'timeseries_id' if realizations[0]: - ds.Climate_Model.attrs["long_name"] = f"{gcms[0]} realization" + ds.Climate_Model.attrs['long_name'] = f'{gcms[0]} realization' else: - ds.Climate_Model.attrs["long_name"] = "General Circulation Model" + ds.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.lon.attrs['standard_name'] = 'longitude' + ds.lon.attrs['long_name'] = 'longitude of glacier center' + ds.lon.attrs['units'] = 'degrees_east' - ds.lat.attrs["standard_name"] = "latitude" - ds.lat.attrs["long_name"] = "latitude of glacier center" - ds.lat.attrs["units"] = "degrees_north" + ds.lat.attrs['standard_name'] = 'latitude' + ds.lat.attrs['long_name'] = 'latitude of glacier center' + ds.lat.attrs['units'] = 'degrees_north' # save batch - vn_fp = f"{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/" + vn_fp = f'{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/' if not os.path.exists(vn_fp): os.makedirs(vn_fp, exist_ok=True) if realizations[0]: - ds_fn = f"R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( - "__", "_" + ds_fn = f'R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace( + '__', '_' ) else: - ds_fn = f"R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( - "__", "_" + ds_fn = f'R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace( + '__', '_' ) ds.to_netcdf(vn_fp + ds_fn) loop_end = time.time() - print(f"Batch {nbatch} runtime:\t{np.round(loop_end - loop_start, 2)} seconds") + print(f'Batch {nbatch} runtime:\t{np.round(loop_end - loop_start, 2)} seconds') ### MERGE BATCHES FOR ANNUAL VARS ### - vns = ["glac_mass_annual", "glac_area_annual"] + vns = ['glac_mass_annual', 'glac_area_annual'] for vn in vns: if vn in vars: - vn_fp = f"{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/" + vn_fp = f'{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/' fn_merge_list_start = [] if realizations[0]: fn_merge_list = glob.glob( - f"{vn_fp}/R{str(reg).zfill(2)}_{vn}_{gcms[0]}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( - "__", "_" + 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( - "__", "_" + f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace( + '__', '_' ) ) - fn_merge_list_start = [int(f.split("-")[-2]) for f in fn_merge_list] + fn_merge_list_start = [int(f.split('-')[-2]) for f in fn_merge_list] if len(fn_merge_list) > 0: fn_merge_list = [ @@ -848,11 +848,11 @@ def run(args): 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" + fn.split('Batch')[0][:-1] + + f'_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc' ) ds.to_netcdf(ds_fn) @@ -870,98 +870,98 @@ def main(): 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 = parser.add_argument_group('required named arguments') requiredNamed.add_argument( - "-rgi_region01", + '-rgi_region01', type=int, default=None, required=True, - nargs="+", + nargs='+', help='Randoph Glacier Inventory region (can take multiple, e.g. "1 2 3")', ) requiredNamed.add_argument( - "-gcm_name", + '-gcm_name', type=str, default=None, required=True, - nargs="+", + nargs='+', help='GCM name for which to compile simulations (can take multiple, ex. "ERA5" or "CESM2")', ) parser.add_argument( - "-scenario", - action="store", + '-scenario', + action='store', type=str, default=None, - nargs="+", + nargs='+', help='rcp or ssp scenario used for model run (can take multiple, ex. "ssp245 ssp585")', ) parser.add_argument( - "-realization", - action="store", + '-realization', + action='store', type=str, default=None, - nargs="+", + nargs='+', help='realization from large ensemble used for model run (cant take multiple, ex. "r1i1p1f1 r2i1p1f1 r3i1p1f1")', ) parser.add_argument( - "-gcm_startyear", - action="store", + '-gcm_startyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_startyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_startyear'], + help='start year for the model run', ) parser.add_argument( - "-gcm_endyear", - action="store", + '-gcm_endyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_endyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_endyear'], + help='start year for the model run', ) parser.add_argument( - "-sim_path", + '-sim_path', type=str, - default=pygem_prms["root"] + "/Output/simulations/", - help="PyGEM simulations filepath", + default=pygem_prms['root'] + '/Output/simulations/', + help='PyGEM simulations filepath', ) parser.add_argument( - "-option_calibration", - action="store", + '-option_calibration', + action='store', type=str, - default=pygem_prms["calib"]["option_calibration"], + default=pygem_prms['calib']['option_calibration'], help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', ) parser.add_argument( - "-option_bias_adjustment", - action="store", + '-option_bias_adjustment', + action='store', type=int, - default=pygem_prms["sim"]["option_bias_adjustment"], + 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", + '-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", + '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="+", + nargs='+', ) parser.add_argument( - "-ncores", - action="store", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + help='number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization', ) args = parser.parse_args() @@ -977,10 +977,10 @@ def main(): vars = args.vars if not simpath: - simpath = pygem_prms["root"] + "/Output/simulations/" + simpath = pygem_prms['root'] + '/Output/simulations/' - if not os.path.exists(simpath + "compile/"): - os.makedirs(simpath + "compile/") + if not os.path.exists(simpath + 'compile/'): + os.makedirs(simpath + 'compile/') if not isinstance(region, list): region = [region] @@ -991,40 +991,40 @@ def main(): if scenarios: if not isinstance(scenarios, list): scenarios = [scenarios] - if set(["ERA5", "ERA-Interim", "COAWST"]) & set(gcms): + 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}" + 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"]): + scenarios = [''] + if set(gcms) - set(['ERA5', 'ERA-Interim', 'COAWST']): raise ValueError( - f"Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}" + f'Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}' ) if realizations is None: - realizations = [""] + realizations = [''] else: if not isinstance(realizations, list): realizations = [realizations] if len(gcms) > 1: raise ValueError( - f"Script not set up to aggregate multiple GCMs and realizations simultaneously - if aggregating multiple realizations, specify a single GCM at a time\nGCMs: {gcms}\nrealizations: {realizations}" + 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", + '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 @@ -1038,16 +1038,16 @@ def main(): # pack variables for multiprocessing list_packed_vars = [] kwargs = [ - "region", - "simpath", - "gcms", - "realizations", - "scenario", - "calib", - "bias_adj", - "gcm_startyear", - "gcm_endyear", - "vars", + '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 @@ -1068,19 +1068,19 @@ def main(): ] ) print( - f"job {i}:", - [f"{name}={val}" for name, val in zip(kwargs, list_packed_vars[-1])], + f'job {i}:', + [f'{name}={val}' for name, val in zip(kwargs, list_packed_vars[-1])], ) i += 1 # parallel processing - print("Processing with " + str(num_cores) + " cores...") + print('Processing with ' + str(num_cores) + ' cores...') with multiprocessing.Pool(num_cores) as p: p.map(run, list_packed_vars) 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__": +if __name__ == '__main__': main() diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index c16ca91d..adbf3efb 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -42,24 +42,24 @@ def getparser(): Use argparse to add arguments from the command line """ parser = argparse.ArgumentParser( - description="distrube PyGEM simulated ice thickness to a 2D grid" + description='distrube PyGEM simulated ice thickness to a 2D grid' ) # add arguments parser.add_argument( - "-simpath", - action="store", + '-simpath', + action='store', type=str, - nargs="+", - help="path to PyGEM binned simulation (can take multiple)", + nargs='+', + help='path to PyGEM binned simulation (can take multiple)', ) parser.add_argument( - "-ncores", - action="store", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use", + help='number of simultaneous processes (cores) to use', ) - parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") + parser.add_argument('-v', '--debug', action='store_true', help='Flag for debugging') return parser @@ -76,30 +76,30 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): area_m2(time, dis_along_flowline): float64 thickness_m (time, dis_along_flowline): float64 """ - yr0, yr1 = pygem_simpath.split("_")[-3:-1] + yr0, yr1 = pygem_simpath.split('_')[-3:-1] pygem_ds = xr.open_dataset(pygem_simpath).sel(year=slice(yr0, yr1)) - time = pygem_ds.coords["year"].values.flatten().astype(float) - distance_along_flowline = pygem_ds["bin_distance"].values.flatten().astype(float) - area = pygem_ds["bin_area_annual"].values[0].astype(float).T - thick = pygem_ds["bin_thick_annual"].values[0].astype(float).T + time = pygem_ds.coords['year'].values.flatten().astype(float) + distance_along_flowline = pygem_ds['bin_distance'].values.flatten().astype(float) + area = pygem_ds['bin_area_annual'].values[0].astype(float).T + thick = pygem_ds['bin_thick_annual'].values[0].astype(float).T vol = area * thick diag_ds = xr.Dataset() - diag_ds.coords["time"] = time - diag_ds.coords["dis_along_flowline"] = distance_along_flowline - diag_ds["area_m2"] = (("time", "dis_along_flowline"), area) - diag_ds["area_m2"].attrs["description"] = "Section area" - diag_ds["area_m2"].attrs["unit"] = "m 2" - diag_ds["thickness_m"] = (("time", "dis_along_flowline"), thick * np.nan) - diag_ds["thickness_m"].attrs["description"] = "Section thickness" - diag_ds["thickness_m"].attrs["unit"] = "m" - diag_ds["volume_m3"] = (("time", "dis_along_flowline"), vol) - diag_ds["volume_m3"].attrs["description"] = "Section volume" - diag_ds["volume_m3"].attrs["unit"] = "m 3" + diag_ds.coords['time'] = time + diag_ds.coords['dis_along_flowline'] = distance_along_flowline + diag_ds['area_m2'] = (('time', 'dis_along_flowline'), area) + diag_ds['area_m2'].attrs['description'] = 'Section area' + diag_ds['area_m2'].attrs['unit'] = 'm 2' + diag_ds['thickness_m'] = (('time', 'dis_along_flowline'), thick * np.nan) + diag_ds['thickness_m'].attrs['description'] = 'Section thickness' + diag_ds['thickness_m'].attrs['unit'] = 'm' + diag_ds['volume_m3'] = (('time', 'dis_along_flowline'), vol) + diag_ds['volume_m3'].attrs['description'] = 'Section volume' + diag_ds['volume_m3'].attrs['unit'] = 'm 3' # diag_ds.to_netcdf(oggm_diag, 'w', group='fl_0') if debug: # plot volume - vol = diag_ds.sum(dim=["dis_along_flowline"])["volume_m3"] + vol = diag_ds.sum(dim=['dis_along_flowline'])['volume_m3'] f, ax = plt.subplots(1, figsize=(5, 5)) (vol / vol[0]).plot(ax=ax) plt.show() @@ -111,18 +111,18 @@ 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 + 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( + 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( + ds.simulated_thickness.sel(time=ds.coords['time'].values[-1]).plot( ax=ax2, vmin=0, vmax=vmax ) - ax1.axis("equal") - ax2.axis("equal") + ax1.axis('equal') + ax2.axis('equal') plt.tight_layout() plt.show() @@ -130,18 +130,18 @@ def plot_distributed_thickness(ds): def run(simpath, debug=False): if os.path.isfile(simpath): pygem_path, pygem_fn = os.path.split(simpath) - pygem_fn_split = pygem_fn.split("_") - f_suffix = "_".join(pygem_fn_split[1:])[:-3] + pygem_fn_split = pygem_fn.split('_') + f_suffix = '_'.join(pygem_fn_split[1:])[:-3] glac_no = pygem_fn_split[0] glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[ 0, : ] - glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_calving"] + glacier_rgi_table['TermType'] not in [1, 5] + or not pygem_prms['setup']['include_calving'] ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False @@ -170,12 +170,12 @@ def run(simpath, debug=False): 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 + 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}"), + '2D simulated ice thickness created: ', + gdir.get_filepath('gridded_simulation', filesuffix=f'_pygem_{f_suffix}'), ) if debug: plot_distributed_thickness(ds) @@ -196,12 +196,12 @@ def main(): # set up partial function with debug argument run_with_debug = partial(run, debug=args.debug) # parallel processing - print("Processing with " + str(ncores) + " cores...") + print('Processing with ' + str(ncores) + ' cores...') with multiprocessing.Pool(ncores) as p: p.map(run_with_debug, args.simpath) - print("Total processing time:", time.time() - time_start, "s") + print('Total processing time:', time.time() - time_start, 's') -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pygem/bin/postproc/postproc_monthly_mass.py b/pygem/bin/postproc/postproc_monthly_mass.py index 18c66e53..b7367f32 100644 --- a/pygem/bin/postproc/postproc_monthly_mass.py +++ b/pygem/bin/postproc/postproc_monthly_mass.py @@ -36,29 +36,29 @@ 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" + description='process monthly glacierwide mass from annual mass and total monthly mass balance' ) # add arguments parser.add_argument( - "-simpath", - action="store", + '-simpath', + action='store', type=str, - nargs="+", - help="path to PyGEM simulation (can take multiple)", + nargs='+', + help='path to PyGEM simulation (can take multiple)', ) parser.add_argument( - "-simdir", - action="store", + '-simdir', + action='store', type=str, default=None, - help="directory with glacierwide simulation outputs for which to process monthly mass", + help='directory with glacierwide simulation outputs for which to process monthly mass', ) parser.add_argument( - "-ncores", - action="store", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use", + help='number of simultaneous processes (cores) to use', ) return parser @@ -91,7 +91,7 @@ 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") + np.reshape(glac_massbaltotal_monthly, (-1, 12), order='C') .cumsum(axis=-1) .reshape(oshape) ) @@ -128,17 +128,17 @@ def update_xrdataset(input_ds, glac_mass_monthly): time_values = input_ds.time.values output_coords_dict = collections.OrderedDict() - output_coords_dict["glac_mass_monthly"] = collections.OrderedDict( - [("glac", glac_values), ("time", time_values)] + output_coords_dict['glac_mass_monthly'] = collections.OrderedDict( + [('glac', glac_values), ('time', time_values)] ) # Attributes dictionary output_attrs_dict = {} - output_attrs_dict["glac_mass_monthly"] = { - "long_name": "glacier mass", - "units": "kg", - "temporal_resolution": "monthly", - "comment": "monthly glacier mass", + output_attrs_dict['glac_mass_monthly'] = { + 'long_name': 'glacier mass', + 'units': 'kg', + 'temporal_resolution': 'monthly', + 'comment': 'monthly glacier mass', } # Add variables to empty dataset and merge together @@ -168,9 +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 @@ -192,7 +192,7 @@ def run(simpath): glac_mass_monthly = get_monthly_mass( statsds.glac_mass_annual.values, statsds.glac_massbaltotal_monthly.values - * pygem_prms["constants"]["density_ice"], + * pygem_prms['constants']['density_ice'], ) statsds.close() @@ -204,7 +204,7 @@ def run(simpath): # append to existing stats netcdf output_ds_stats.to_netcdf( - simpath, mode="a", encoding=encoding, engine="netcdf4" + simpath, mode='a', encoding=encoding, engine='netcdf4' ) # close datasets @@ -213,7 +213,7 @@ def run(simpath): except: pass else: - print("Simulation not found: ", simpath) + print('Simulation not found: ', simpath) return @@ -225,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 @@ -238,12 +238,12 @@ def main(): ncores = 1 # Parallel processing - print("Processing with " + str(args.ncores) + " cores...") + print('Processing with ' + str(args.ncores) + ' cores...') with multiprocessing.Pool(args.ncores) as p: p.map(run, simpath) - print("Total processing time:", time.time() - time_start, "s") + print('Total processing time:', time.time() - time_start, 's') -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pygem/bin/preproc/preproc_fetch_mbdata.py b/pygem/bin/preproc/preproc_fetch_mbdata.py index 063ab2d5..70d1f2b5 100644 --- a/pygem/bin/preproc/preproc_fetch_mbdata.py +++ b/pygem/bin/preproc/preproc_fetch_mbdata.py @@ -25,7 +25,7 @@ pygem_prms = config_manager.read_config() -def run(fp="", debug=False, overwrite=False): +def run(fp='', debug=False, overwrite=False): """ pull geodetic mass balance data from OGGM The original 'raw' were acquired and combined from https://doi.org/10.6096/13 (time series/dh__rgi60_pergla_rates) @@ -40,63 +40,63 @@ def run(fp="", debug=False, overwrite=False): """ mbdf = utils.get_geodetic_mb_dataframe() if debug: - print("MB data loaded from OGGM:") + print('MB data loaded from OGGM:') print(mbdf.head()) # pull only 2000-2020 period - mbdf_subset = mbdf[mbdf.period == "2000-01-01_2020-01-01"] + mbdf_subset = mbdf[mbdf.period == '2000-01-01_2020-01-01'] # reset the index mbdf_subset = mbdf_subset.reset_index() # sort by the rgiid column - mbdf_subset = mbdf_subset.sort_values(by="rgiid") + mbdf_subset = mbdf_subset.sort_values(by='rgiid') # rename some keys to work with what other scripts/functions expect mbdf_subset = mbdf_subset.rename( - columns={"dmdtda": "mb_mwea", "err_dmdtda": "mb_mwea_err"} + columns={'dmdtda': 'mb_mwea', 'err_dmdtda': 'mb_mwea_err'} ) - if fp[-4:] != ".csv": - fp += ".csv" + if fp[-4:] != '.csv': + fp += '.csv' if os.path.isfile(fp) and not overwrite: raise FileExistsError( - f"The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}" + f'The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}' ) mbdf_subset.to_csv(fp, index=False) if debug: - print(f"Filled global geodetic mass balance data saved to: {fp}") + print(f'Filled global geodetic mass balance data saved to: {fp}') print(mbdf_subset.head()) def main(): parser = argparse.ArgumentParser( - description="grab filled Hugonnet et al. 2021 geodetic mass balance data from OGGM and converts to a format PyGEM utilizes" + 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", + '-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)", + 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", + '-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('-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__": +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 2245109f..de07d8d0 100644 --- a/pygem/bin/preproc/preproc_wgms_estimate_kp.py +++ b/pygem/bin/preproc/preproc_wgms_estimate_kp.py @@ -34,62 +34,62 @@ def subset_winter( - wgms_eee_fp="", - wgms_ee_fp="", - wgms_e_fp="", - wgms_id_fp="", - wgms_ee_winter_fp="", - wgms_ee_winter_fp_subset="", + wgms_eee_fp='', + wgms_ee_fp='', + wgms_e_fp='', + wgms_id_fp='', + wgms_ee_winter_fp='', + wgms_ee_winter_fp_subset='', subset_time_value=20000000, ): """ subset winter mass balance data from WGMS """ # Load data - wgms_e_df = pd.read_csv(wgms_e_fp, encoding="unicode_escape") - wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding="unicode_escape") - wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding="unicode_escape") - wgms_id_df = pd.read_csv(wgms_id_fp, encoding="unicode_escape") + wgms_e_df = pd.read_csv(wgms_e_fp, encoding='unicode_escape') + wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding='unicode_escape') + wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding='unicode_escape') + wgms_id_df = pd.read_csv(wgms_id_fp, encoding='unicode_escape') # Map dictionary wgms_id_dict = dict(zip(wgms_id_df.WGMS_ID, wgms_id_df.RGI_ID)) - wgms_ee_df_raw["rgiid_raw"] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=["rgiid_raw"]) - wgms_eee_df_raw["rgiid_raw"] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=["rgiid_raw"]) + wgms_ee_df_raw['rgiid_raw'] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=['rgiid_raw']) + wgms_eee_df_raw['rgiid_raw'] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=['rgiid_raw']) # Link RGIv5.0 with RGIv6.0 - rgi60_fp = pygem_prms["root"] + "/RGI/rgi60/00_rgi60_attribs/" - rgi50_fp = pygem_prms["root"] + "/RGI/00_rgi50_attribs/" + rgi60_fp = pygem_prms['root'] + '/RGI/rgi60/00_rgi60_attribs/' + rgi50_fp = pygem_prms['root'] + '/RGI/00_rgi50_attribs/' # Process each region regions_str = [ - "01", - "02", - "03", - "04", - "05", - "06", - "07", - "08", - "09", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', ] rgi60_df = None rgi50_df = None for reg_str in regions_str: # RGI60 data for i in os.listdir(rgi60_fp): - if i.startswith(reg_str) and i.endswith(".csv"): - rgi60_df_reg = pd.read_csv(rgi60_fp + i, encoding="unicode_escape") + if i.startswith(reg_str) and i.endswith('.csv'): + rgi60_df_reg = pd.read_csv(rgi60_fp + i, encoding='unicode_escape') # append datasets if rgi60_df is None: rgi60_df = rgi60_df_reg @@ -98,8 +98,8 @@ def subset_winter( # RGI50 data for i in os.listdir(rgi50_fp): - if i.startswith(reg_str) and i.endswith(".csv"): - rgi50_df_reg = pd.read_csv(rgi50_fp + i, encoding="unicode_escape") + if i.startswith(reg_str) and i.endswith('.csv'): + rgi50_df_reg = pd.read_csv(rgi50_fp + i, encoding='unicode_escape') # append datasets if rgi50_df is None: rgi50_df = rgi50_df_reg @@ -108,23 +108,23 @@ def subset_winter( # Merge based on GLIMSID glims_rgi50_dict = dict(zip(rgi50_df.GLIMSId, rgi50_df.RGIId)) - rgi60_df["RGIId_50"] = rgi60_df.GLIMSId.map(glims_rgi50_dict) - rgi60_df_4dict = rgi60_df.dropna(subset=["RGIId_50"]) + rgi60_df['RGIId_50'] = rgi60_df.GLIMSId.map(glims_rgi50_dict) + rgi60_df_4dict = rgi60_df.dropna(subset=['RGIId_50']) rgi50_rgi60_dict = dict(zip(rgi60_df_4dict.RGIId_50, rgi60_df_4dict.RGIId)) rgi60_self_dict = dict(zip(rgi60_df.RGIId, rgi60_df.RGIId)) rgi50_rgi60_dict.update(rgi60_self_dict) # Add RGIId for version 6 to WGMS - wgms_ee_df_raw["rgiid"] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) - wgms_eee_df_raw["rgiid"] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + wgms_ee_df_raw['rgiid'] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + wgms_eee_df_raw['rgiid'] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) # Drop points without data - wgms_ee_df = wgms_ee_df_raw.dropna(subset=["rgiid"]) - wgms_eee_df = wgms_eee_df_raw.dropna(subset=["rgiid"]) + wgms_ee_df = wgms_ee_df_raw.dropna(subset=['rgiid']) + wgms_eee_df = wgms_eee_df_raw.dropna(subset=['rgiid']) # Winter balances only - wgms_ee_df_winter = wgms_ee_df.dropna(subset=["WINTER_BALANCE"]) - wgms_ee_df_winter = wgms_ee_df_winter.sort_values("rgiid") + wgms_ee_df_winter = wgms_ee_df.dropna(subset=['WINTER_BALANCE']) + wgms_ee_df_winter = wgms_ee_df_winter.sort_values('rgiid') wgms_ee_df_winter.reset_index(inplace=True, drop=True) # Add the winter time period using the E-MASS-BALANCE-OVERVIEW file @@ -136,16 +136,16 @@ def subset_winter( for nrow in np.arange(wgms_ee_df_winter.shape[0]): if nrow % 500 == 0: - print(nrow, "of", wgms_ee_df_winter.shape[0]) - name = wgms_ee_df_winter.loc[nrow, "NAME"] - wgmsid = wgms_ee_df_winter.loc[nrow, "WGMS_ID"] - year = wgms_ee_df_winter.loc[nrow, "YEAR"] + print(nrow, 'of', wgms_ee_df_winter.shape[0]) + name = wgms_ee_df_winter.loc[nrow, 'NAME'] + wgmsid = wgms_ee_df_winter.loc[nrow, 'WGMS_ID'] + year = wgms_ee_df_winter.loc[nrow, 'YEAR'] try: e_idx = np.where( - (wgms_e_df["NAME"] == name) - & (wgms_e_df["WGMS_ID"] == wgmsid) - & (wgms_e_df["Year"] == year) + (wgms_e_df['NAME'] == name) + & (wgms_e_df['WGMS_ID'] == wgmsid) + & (wgms_e_df['Year'] == year) )[0][0] except: e_idx = None @@ -159,61 +159,61 @@ def subset_winter( # 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['BEGIN_PERIOD'] > subset_time_value ] - wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna(subset=["END_WINTER"]) + wgms_ee_df_winter_subset = wgms_ee_df_winter_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="" + wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_stats_fp='' ): """ This is used to estimate the precipitation factor for the bounds of HH2015_mod """ # Load data assert os.path.exists(wgms_ee_winter_fp_subset), ( - "wgms_ee_winter_fn_subset does not exist!" + 'wgms_ee_winter_fn_subset does not exist!' ) - wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding="unicode_escape") + 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.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['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['END_YEAR'] = [int(x[0:4]) for x in wgms_df.loc[:, 'END_WINTER']] + wgms_df['END_MONTH'] = [int(x[4:6]) for x in list(wgms_df.loc[:, 'END_WINTER'])] + wgms_df['END_DAY'] = [int(x[6:]) for x in list(wgms_df.loc[:, 'END_WINTER'])] + wgms_df['END_YEARMONTH'] = [x[0:6] for x in list(wgms_df.loc[:, 'END_WINTER'])] # ===== PROCESS UNIQUE GLACIERS ===== - rgiids_unique = list(wgms_df["rgiid"].unique()) - glac_no = [x.split("-")[1] for x in rgiids_unique] + rgiids_unique = list(wgms_df['rgiid'].unique()) + glac_no = [x.split('-')[1] for x in rgiids_unique] main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=pygem_prms["climate"]["ref_startyear"], - endyear=pygem_prms["climate"]["ref_endyear"], + startyear=pygem_prms['climate']['ref_startyear'], + endyear=pygem_prms['climate']['ref_endyear'], spinupyears=0, - option_wateryear=pygem_prms["climate"]["ref_wateryear"], + option_wateryear=pygem_prms['climate']['ref_wateryear'], ) dates_table_yearmo = [ - str(dates_table.loc[x, "year"]) + str(dates_table.loc[x, "month"]).zfill(2) + str(dates_table.loc[x, 'year']) + str(dates_table.loc[x, 'month']).zfill(2) for x in range(dates_table.shape[0]) ] # ===== LOAD CLIMATE DATA ===== # Climate class - gcm = class_climate.GCM(name=pygem_prms["climate"]["ref_gcm_name"]) + gcm = class_climate.GCM(name=pygem_prms['climate']['ref_gcm_name']) # Air temperature [degC] gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( @@ -233,18 +233,18 @@ def est_kp( ) # ===== PROCESS THE OBSERVATIONS ====== - prec_cn = pygem_prms["climate"]["ref_gcm_name"] + "_prec" + prec_cn = pygem_prms['climate']['ref_gcm_name'] + '_prec' wgms_df[prec_cn] = np.nan - wgms_df["kp"] = np.nan - wgms_df["ndays"] = np.nan + wgms_df['kp'] = np.nan + wgms_df['ndays'] = np.nan for glac in range(main_glac_rgi.shape[0]): - print(glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"]) + print(glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac], 'RGIId']) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) rgiid = glacier_rgi_table.RGIId - wgms_df_single = (wgms_df.loc[wgms_df["rgiid"] == rgiid]).copy() + wgms_df_single = (wgms_df.loc[wgms_df['rgiid'] == rgiid]).copy() glac_idx = wgms_df_single.index.values wgms_df_single.reset_index(inplace=True, drop=True) @@ -256,49 +256,49 @@ def est_kp( # - 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 + 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 + 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 + 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 + 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"] + 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"] + wgms_df_single.loc[nobs, 'BEGIN_YEARMONTH'] + != wgms_df_single.loc[nobs, 'END_YEARMONTH'] ) - and (wgms_df_single.loc[nobs, "WINTER_BALANCE"] > 0) + and (wgms_df_single.loc[nobs, 'WINTER_BALANCE'] > 0) ): # Begin index idx_begin = dates_table_yearmo.index( - wgms_df_single.loc[nobs, "BEGIN_YEARMONTH"] + wgms_df_single.loc[nobs, 'BEGIN_YEARMONTH'] ) idx_end = dates_table_yearmo.index( - wgms_df_single.loc[nobs, "END_YEARMONTH"] + 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"] + * 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"] + - wgms_df_single.loc[nobs, 'END_DAY'] + / dates_table.loc[idx_end, 'daysinmonth'] ) # Winter Precipitation @@ -311,129 +311,129 @@ def est_kp( # Number of days ndays = ( - dates_table.loc[idx_begin:idx_end, "daysinmonth"].sum() - - wgms_df_single.loc[nobs, "BEGIN_DAY"] + 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"] + dates_table.loc[idx_end, 'daysinmonth'] + - wgms_df_single.loc[nobs, 'END_DAY'] ) ) - wgms_df_single.loc[nobs, "ndays"] = ndays + 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, 'kp'] = wgms_df_single['kp'].values + wgms_df.loc[glac_idx, 'ndays'] = wgms_df_single['ndays'].values # Drop nan values - wgms_df_wkp = wgms_df.dropna(subset=["kp"]).copy() + wgms_df_wkp = wgms_df.dropna(subset=['kp']).copy() wgms_df_wkp.reset_index(inplace=True, drop=True) wgms_df_wkp.to_csv(wgms_ee_winter_fp_kp, index=False) # Calculate stats for all and each region - wgms_df_wkp["reg"] = [ - x.split("-")[1].split(".")[0] for x in wgms_df_wkp["rgiid"].values + 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()) + 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", + 'region', + 'count_obs', + 'count_glaciers', + 'kp_mean', + 'kp_std', + 'kp_med', + 'kp_nmad', + 'kp_min', + 'kp_max', ] reg_kp_df = pd.DataFrame( np.zeros((len(reg_unique) + 1, len(reg_kp_cns))), columns=reg_kp_cns ) # Only those with at least 1 month of data - wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp["ndays"] >= 30] + wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp['ndays'] >= 30] # All stats - reg_kp_df.loc[0, "region"] = "all" - reg_kp_df.loc[0, "count_obs"] = wgms_df_wkp.shape[0] - reg_kp_df.loc[0, "count_glaciers"] = len(wgms_df_wkp["rgiid"].unique()) - reg_kp_df.loc[0, "kp_mean"] = np.mean(wgms_df_wkp.kp.values) - reg_kp_df.loc[0, "kp_std"] = np.std(wgms_df_wkp.kp.values) - reg_kp_df.loc[0, "kp_med"] = np.median(wgms_df_wkp.kp.values) - reg_kp_df.loc[0, "kp_nmad"] = median_abs_deviation( - wgms_df_wkp.kp.values, scale="normal" + reg_kp_df.loc[0, '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, '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] + 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, '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_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)) + 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") + ' 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)) + 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" + description='estimate precipitation factors from WGMS winter mass balance data' ) parser.add_argument( - "-o", "--overwrite", action="store_true", help="Flag to overwrite existing data" + '-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_dsn = 'DOI-WGMS-FoG-2020-08/' + wgms_eee_fp = wgms_fp + wgms_dsn + 'WGMS-FoG-2020-08-EEE-MASS-BALANCE-POINT.csv' + wgms_ee_fp = wgms_fp + wgms_dsn + 'WGMS-FoG-2020-08-EE-MASS-BALANCE.csv' + wgms_e_fp = wgms_fp + wgms_dsn + 'WGMS-FoG-2020-08-E-MASS-BALANCE-OVERVIEW.csv' + wgms_id_fp = wgms_fp + wgms_dsn + 'WGMS-FoG-2020-08-AA-GLACIER_ID_LUT.csv' in_fps = [x for x in [wgms_eee_fp, wgms_ee_fp, wgms_e_fp, wgms_id_fp]] # outputs wgms_ee_winter_fp = ( - wgms_fp + "WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv" + wgms_fp + 'WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv' ) - wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace(".csv", "-subset.csv") - wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace(".csv", "-subset-kp.csv") - wgms_reg_kp_stats_fp = wgms_fp + "WGMS-FoG-2019-12-reg_kp_summary.csv" + wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace('.csv', '-subset.csv') + wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace('.csv', '-subset-kp.csv') + wgms_reg_kp_stats_fp = wgms_fp + 'WGMS-FoG-2019-12-reg_kp_summary.csv' out_subset_fps = [wgms_ee_winter_fp, wgms_ee_winter_fp_subset] output_kp_fps = [wgms_ee_winter_fp_kp, wgms_reg_kp_stats_fp] @@ -445,7 +445,7 @@ def main(): missing = False for fp in in_fps: if not os.path.isfile(fp): - print(f"Missing required WGMS datafile: {fp}") + print(f'Missing required WGMS datafile: {fp}') missing = True if missing: sys.exit(1) @@ -471,5 +471,5 @@ def main(): ) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pygem/bin/run/run_calibration.py b/pygem/bin/run/run_calibration.py index 181663dd..0d715d51 100755 --- a/pygem/bin/run/run_calibration.py +++ b/pygem/bin/run/run_calibration.py @@ -78,106 +78,106 @@ def getparser(): ------- Object containing arguments and their respective values. """ - parser = argparse.ArgumentParser(description="Run PyGEM calibration") + parser = argparse.ArgumentParser(description='Run PyGEM calibration') # add arguments parser.add_argument( - "-rgi_region01", + '-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="+", + 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", + '-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`)", + 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( - "-ref_gcm_name", - action="store", + '-ref_gcm_name', + action='store', type=str, - default=pygem_prms["climate"]["ref_gcm_name"], - help="reference gcm name", + default=pygem_prms['climate']['ref_gcm_name'], + help='reference gcm name', ) parser.add_argument( - "-ref_startyear", - action="store", + '-ref_startyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_startyear"], - help="reference period starting year for calibration (typically 2000)", + default=pygem_prms['climate']['ref_startyear'], + help='reference period starting year for calibration (typically 2000)', ) parser.add_argument( - "-ref_endyear", - action="store", + '-ref_endyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_endyear"], - help="reference period ending year for calibration (typically 2019)", + default=pygem_prms['climate']['ref_endyear'], + help='reference period ending year for calibration (typically 2019)', ) ( parser.add_argument( - "-rgi_glac_number_fn", - action="store", + '-rgi_glac_number_fn', + action='store', type=str, default=None, - help="filepath containing list of rgi_glac_number, helpful for running batches on spc", + help='filepath containing list of rgi_glac_number, helpful for running batches on spc', ), ) parser.add_argument( - "-rgi_glac_number", - action="store", + '-rgi_glac_number', + action='store', type=float, - default=pygem_prms["setup"]["glac_no"], - nargs="+", - help="Randoph Glacier Inventory glacier number (can take multiple)", + default=pygem_prms['setup']['glac_no'], + nargs='+', + help='Randoph Glacier Inventory glacier number (can take multiple)', ) parser.add_argument( - "-ncores", - action="store", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use (default is 1, ie. no parallelization)", + help='number of simultaneous processes (cores) to use (default is 1, ie. no parallelization)', ) parser.add_argument( - "-option_calibration", - action="store", + '-option_calibration', + action='store', type=str, - default=pygem_prms["calib"]["option_calibration"], + default=pygem_prms['calib']['option_calibration'], help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', ) parser.add_argument( - "-nchains", - action="store", + '-nchains', + action='store', type=int, - default=pygem_prms["calib"]["MCMC_params"]["n_chains"], - help="number of chains in MCMC calibration", + default=pygem_prms['calib']['MCMC_params']['n_chains'], + help='number of chains in MCMC calibration', ) parser.add_argument( - "-chain_length", - action="store", + '-chain_length', + action='store', type=int, - default=pygem_prms["calib"]["MCMC_params"]["mcmc_sample_no"], - help="number of samples in a chain for MCMC calibration", + default=pygem_prms['calib']['MCMC_params']['mcmc_sample_no'], + help='number of samples in a chain for MCMC calibration', ) parser.add_argument( - "-burn_pct", - action="store", + '-burn_pct', + action='store', type=int, - default=pygem_prms["calib"]["MCMC_params"]["mcmc_burn_pct"], - help="burn-in percentage for MCMC calibration", + default=pygem_prms['calib']['MCMC_params']['mcmc_burn_pct'], + help='burn-in percentage for MCMC calibration', ) # flags parser.add_argument( - "-option_ordered", - action="store_true", - help="Flag to keep glacier lists ordered (default is false)", + '-option_ordered', + action='store_true', + help='Flag to keep glacier lists ordered (default is false)', ) parser.add_argument( - "-p", "--progress_bar", action="store_true", help="Flag to show progress bar" + '-p', '--progress_bar', action='store_true', help='Flag to show progress bar' ) - parser.add_argument("-v", "--debug", action="store_true", help="Flag for debugging") + parser.add_argument('-v', '--debug', action='store_true', help='Flag for debugging') return parser @@ -226,9 +226,9 @@ def mb_mwea_calc( return nbinyears_negmbclim elif return_tbias_mustmelt_wmb: nbinyears_negmbclim = len(np.where(mbmod.glac_bin_massbalclim_annual < 0)[0]) - t1_idx = gdir.mbdata["t1_idx"] - t2_idx = gdir.mbdata["t2_idx"] - nyears = gdir.mbdata["nyears"] + t1_idx = gdir.mbdata['t1_idx'] + t2_idx = gdir.mbdata['t2_idx'] + nyears = gdir.mbdata['nyears'] mb_mwea = ( mbmod.glac_wide_massbaltotal[t1_idx : t2_idx + 1].sum() / mbmod.glac_wide_area_annual[0] @@ -238,9 +238,9 @@ def mb_mwea_calc( # Otherwise return specific mass balance else: # Specific mass balance [mwea] - t1_idx = gdir.mbdata["t1_idx"] - t2_idx = gdir.mbdata["t2_idx"] - nyears = gdir.mbdata["nyears"] + t1_idx = gdir.mbdata['t1_idx'] + t2_idx = gdir.mbdata['t2_idx'] + nyears = gdir.mbdata['nyears'] mb_mwea = ( mbmod.glac_wide_massbaltotal[t1_idx : t2_idx + 1].sum() / mbmod.glac_wide_area_annual[0] @@ -295,18 +295,18 @@ def load(cls, em_mod_path=None): torch.set_num_threads(1) state_dict = torch.load(em_mod_path, weights_only=False) - emulator_extra_fp = em_mod_path.replace(".pth", "_extra.json") - with open(emulator_extra_fp, "r") as f: + emulator_extra_fp = em_mod_path.replace('.pth', '_extra.json') + with open(emulator_extra_fp, 'r') as f: emulator_extra_dict = json.load(f) # convert lists to torch tensors X_train = torch.stack( - [torch.tensor(lst) for lst in emulator_extra_dict["X_train"]], dim=1 + [torch.tensor(lst) for lst in emulator_extra_dict['X_train']], dim=1 ) - X_mean = torch.tensor(emulator_extra_dict["X_mean"]) - X_std = torch.tensor(emulator_extra_dict["X_std"]) - y_train = torch.tensor(emulator_extra_dict["y_train"]) - y_mean = torch.tensor(emulator_extra_dict["y_mean"]) - y_std = torch.tensor(emulator_extra_dict["y_std"]) + X_mean = torch.tensor(emulator_extra_dict['X_mean']) + X_std = torch.tensor(emulator_extra_dict['X_std']) + y_train = torch.tensor(emulator_extra_dict['y_train']) + y_mean = torch.tensor(emulator_extra_dict['y_mean']) + y_std = torch.tensor(emulator_extra_dict['y_std']) # initialize likelihood and model likelihood = gpytorch.likelihoods.GaussianLikelihood() @@ -323,8 +323,8 @@ def create_emulator( glacier_str, sims_df, y_cn, - X_cns=["tbias", "kp", "ddfsnow"], - em_fp=pygem_prms["root"] + "/Output/emulator/", + X_cns=['tbias', 'kp', 'ddfsnow'], + em_fp=pygem_prms['root'] + '/Output/emulator/', debug=False, ): """ @@ -350,7 +350,7 @@ def create_emulator( # This is required for the supercomputer such that resources aren't stolen from other cpus torch.set_num_threads(1) - assert y_cn in sims_df.columns, "emulator error: y_cn not in sims_df" + assert y_cn in sims_df.columns, 'emulator error: y_cn not in sims_df' ################### ### get Xy data ### @@ -360,12 +360,12 @@ def create_emulator( y = sims_df.loc[:, y_cn] if debug: - print(f"Calibration x-parameters: {', '.join(X_cns)}") - print(f"Calibration y-parametes: {y_cn}") - print(f"X:\n{X}") - print(f"X-shape:\n{X.shape}\n") - print(f"y:\n{y}") - print(f"y-shape:\n{y.shape}") + print(f'Calibration x-parameters: {", ".join(X_cns)}') + print(f'Calibration y-parametes: {y_cn}') + print(f'X:\n{X}') + print(f'X-shape:\n{X.shape}\n') + print(f'y:\n{y}') + print(f'y-shape:\n{y.shape}') ################### # pull values (note order matters here. whenever emulator is evaluated, order should be same as order in X) @@ -406,7 +406,7 @@ def create_emulator( if debug: f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], "k*") + ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], 'k*') ax.fill_between( y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], alpha=0.5 ) @@ -451,7 +451,7 @@ def create_emulator( if debug: f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], "k*") + ax.plot(y_test.numpy()[idx], y_pred.mean.numpy()[idx], 'k*') ax.fill_between( y_test.numpy()[idx], lower.numpy()[idx], upper.numpy()[idx], alpha=0.5 ) @@ -474,14 +474,14 @@ def create_emulator( y_set = y_set_norm * y_std + y_mean f, ax = plt.subplots(1, 1, figsize=(4, 4)) - kp_1_idx = np.where(sims_df["kp"] == 1)[0] - ax.plot(sims_df.loc[kp_1_idx, "tbias"], sims_df.loc[kp_1_idx, y_cn]) - ax.plot(tbias_set, y_set, ".") - ax.set_xlabel("tbias (degC)") - if y_cn == "mb_mwea": - ax.set_ylabel("PyGEM MB (mwea)") - elif y_cn == "nbinyrs_negmbclim": - ax.set_ylabel("nbinyrs_negmbclim (-)") + kp_1_idx = np.where(sims_df['kp'] == 1)[0] + ax.plot(sims_df.loc[kp_1_idx, 'tbias'], sims_df.loc[kp_1_idx, y_cn]) + ax.plot(tbias_set, y_set, '.') + ax.set_xlabel('tbias (degC)') + if y_cn == 'mb_mwea': + ax.set_ylabel('PyGEM MB (mwea)') + elif y_cn == 'nbinyrs_negmbclim': + ax.set_ylabel('nbinyrs_negmbclim (-)') plt.show() # Compare the modeled and emulated mass balances @@ -489,36 +489,36 @@ def create_emulator( y_em = y_em_norm * y_std + y_mean f, ax = plt.subplots(1, 1, figsize=(4, 4)) - ax.plot(y, y_em, ".") + ax.plot(y, y_em, '.') ax.plot([y.min(), y.max()], [y.min(), y.max()]) - if y_cn == "mb_mwea": - ax.set_xlabel("emulator MB (mwea)") - ax.set_ylabel("PyGEM MB (mwea)") + if y_cn == 'mb_mwea': + ax.set_xlabel('emulator MB (mwea)') + ax.set_ylabel('PyGEM MB (mwea)') ax.set_xlim(-1, 1) ax.set_ylim(-1, 1) - elif y_cn == "nbinyrs_negmbclim": - ax.set_xlabel("emulator nbinyrs_negmbclim (-)") - ax.set_ylabel("PyGEM nbinyrs_negmbclim (-)") + elif y_cn == 'nbinyrs_negmbclim': + ax.set_xlabel('emulator nbinyrs_negmbclim (-)') + ax.set_ylabel('PyGEM nbinyrs_negmbclim (-)') plt.show() # ----- EXPORT EMULATOR ----- # Save emulator (model state, x_train, y_train, etc.) - em_mod_fn = glacier_str + "-emulator-" + y_cn + ".pth" - em_mod_fp = em_fp + "models/" + glacier_str.split(".")[0].zfill(2) + "/" + em_mod_fn = glacier_str + '-emulator-' + y_cn + '.pth' + em_mod_fp = em_fp + 'models/' + glacier_str.split('.')[0].zfill(2) + '/' if not os.path.exists(em_mod_fp): os.makedirs(em_mod_fp, exist_ok=True) torch.save(model.state_dict(), em_mod_fp + em_mod_fn) # Extra required datasets (convert to lists to avoid any serialization issues with torch tensors) em_extra_dict = { - "X_train": [X.tolist() for X in X_train.T], - "X_mean": [X.tolist() for X in X_mean.T], - "X_std": [X.tolist() for X in X_std.T], - "y_train": y_train.tolist(), - "y_mean": float(y_mean), - "y_std": float(y_std), + 'X_train': [X.tolist() for X in X_train.T], + 'X_mean': [X.tolist() for X in X_mean.T], + 'X_std': [X.tolist() for X in X_std.T], + 'y_train': y_train.tolist(), + 'y_mean': float(y_mean), + 'y_std': float(y_std), } - em_extra_fn = em_mod_fn.replace(".pth", "_extra.json") - with open(em_mod_fp + em_extra_fn, "w") as f: + em_extra_fn = em_mod_fn.replace('.pth', '_extra.json') + with open(em_mod_fp + em_extra_fn, 'w') as f: json.dump(em_extra_dict, f) return massbalEmulator(model, likelihood, X_mean, X_std, y_mean, y_std) @@ -554,21 +554,21 @@ def run(list_packed_vars): 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"], + spinupyears=pygem_prms['climate']['ref_spinupyears'], + option_wateryear=pygem_prms['climate']['ref_wateryear'], ) # ===== LOAD CLIMATE DATA ===== # Climate class - 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, dates_table ) - if pygem_prms["mb"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + if 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 ) @@ -592,19 +592,19 @@ def run(list_packed_vars): if debug or glac == 0 or glac == main_glac_rgi.shape[0]: print( gcm_name, - ":", - main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"], + ':', + main_glac_rgi.loc[main_glac_rgi.index.values[glac], 'RGIId'], ) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_frontalablation"] + glacier_rgi_table['TermType'] not in [1, 5] + or not pygem_prms['setup']['include_frontalablation'] ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False @@ -613,65 +613,65 @@ def run(list_packed_vars): gdir = single_flowline_glacier_directory_with_calving(glacier_str) gdir.is_tidewater = True - fls = gdir.read_pickle("inversion_flowlines") + fls = gdir.read_pickle('inversion_flowlines') glacier_area = fls[0].widths_m * fls[0].dx_meter # 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, :], + '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 # ----- Calibration data ----- try: - mbdata_fn = gdir.get_filepath("mb_calib_pygem") + mbdata_fn = gdir.get_filepath('mb_calib_pygem') - with open(mbdata_fn, "r") as f: + with open(mbdata_fn, 'r') as f: gdir.mbdata = json.load(f) # Tidewater glaciers - use climatic mass balance since calving_k already calibrated separately if gdir.is_tidewater: - assert "mb_clim_mwea" in gdir.mbdata.keys(), ( - "include_frontalablation is set as true, but fontal ablation has yet to be calibrated." + assert 'mb_clim_mwea' in gdir.mbdata.keys(), ( + 'include_frontalablation is set as true, but fontal ablation has yet to be calibrated.' ) - mb_obs_mwea = gdir.mbdata["mb_clim_mwea"] - mb_obs_mwea_err = gdir.mbdata["mb_clim_mwea_err"] + mb_obs_mwea = gdir.mbdata['mb_clim_mwea'] + mb_obs_mwea_err = gdir.mbdata['mb_clim_mwea_err'] # non-tidewater - use geodetic mass balance else: # Load data - mb_obs_mwea = gdir.mbdata["mb_mwea"] - mb_obs_mwea_err = gdir.mbdata["mb_mwea_err"] + mb_obs_mwea = gdir.mbdata['mb_mwea'] + mb_obs_mwea_err = gdir.mbdata['mb_mwea_err'] # Add time indices consistent with dates_table for mb calculations - gdir.mbdata["t1_datetime"] = pd.to_datetime(gdir.mbdata["t1_str"]) - gdir.mbdata["t2_datetime"] = pd.to_datetime( - gdir.mbdata["t2_str"] + gdir.mbdata['t1_datetime'] = pd.to_datetime(gdir.mbdata['t1_str']) + gdir.mbdata['t2_datetime'] = pd.to_datetime( + gdir.mbdata['t2_str'] ) - timedelta(days=1) - t1_year = gdir.mbdata["t1_datetime"].year - t1_month = gdir.mbdata["t1_datetime"].month - t2_year = gdir.mbdata["t2_datetime"].year - t2_month = gdir.mbdata["t2_datetime"].month + t1_year = gdir.mbdata['t1_datetime'].year + t1_month = gdir.mbdata['t1_datetime'].month + t2_year = gdir.mbdata['t2_datetime'].year + t2_month = gdir.mbdata['t2_datetime'].month t1_idx = dates_table[ - (t1_year == dates_table["year"]) - & (t1_month == dates_table["month"]) + (t1_year == dates_table['year']) + & (t1_month == dates_table['month']) ].index.values[0] t2_idx = dates_table[ - (t2_year == dates_table["year"]) - & (t2_month == dates_table["month"]) + (t2_year == dates_table['year']) + & (t2_month == dates_table['month']) ].index.values[0] # Record indices - gdir.mbdata["t1_idx"] = t1_idx - gdir.mbdata["t2_idx"] = t2_idx + gdir.mbdata['t1_idx'] = t1_idx + gdir.mbdata['t2_idx'] = t2_idx if debug: print( - " mb_data (mwea): " + ' mb_data (mwea): ' + str(np.round(mb_obs_mwea, 2)) - + " +/- " + + ' +/- ' + str(np.round(mb_obs_mwea_err, 2)) ) @@ -680,21 +680,21 @@ def run(list_packed_vars): # LOG FAILURE fail_fp = ( - pygem_prms["root"] - + "/Output/cal_fail/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/cal_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(f"Error with mass balance data: {err}") + txt_fn_fail = glacier_str + '-cal_fail.txt' + with open(fail_fp + txt_fn_fail, 'w') as text_file: + text_file.write(f'Error with mass balance data: {err}') print( - "\n" + '\n' + glacier_str - + " mass balance data missing. Check dataset and column names.\n" + + ' mass balance data missing. Check dataset and column names.\n' ) except: @@ -702,64 +702,64 @@ def run(list_packed_vars): if debug: assert os.path.exists(mbdata_fn), ( - "Mass balance data missing. Check dataset and column names" + 'Mass balance data missing. Check dataset and column names' ) # ----- CALIBRATION OPTIONS ------ if (fls is not None) and (gdir.mbdata is not None) and (glacier_area.sum() > 0): modelprms = { - "kp": pygem_prms["sim"]["params"]["kp"], - "tbias": pygem_prms["sim"]["params"]["tbias"], - "ddfsnow": pygem_prms["sim"]["params"]["ddfsnow"], - "ddfice": pygem_prms["sim"]["params"]["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"], - "tsnow_threshold": pygem_prms["sim"]["params"]["tsnow_threshold"], - "precgrad": pygem_prms["sim"]["params"]["precgrad"], + 'kp': pygem_prms['sim']['params']['kp'], + 'tbias': pygem_prms['sim']['params']['tbias'], + 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], + 'ddfice': pygem_prms['sim']['params']['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'], + 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], + 'precgrad': pygem_prms['sim']['params']['precgrad'], } # %% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== # - precipitation factor, temperature bias, degree-day factor of snow - if args.option_calibration == "emulator": - tbias_step = pygem_prms["calib"]["emulator_params"]["tbias_step"] - tbias_init = pygem_prms["calib"]["emulator_params"]["tbias_init"] - kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] - ddfsnow_init = pygem_prms["calib"]["emulator_params"]["ddfsnow_init"] + if args.option_calibration == 'emulator': + tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] + tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] + kp_init = pygem_prms['calib']['emulator_params']['kp_init'] + ddfsnow_init = pygem_prms['calib']['emulator_params']['ddfsnow_init'] # ----- Initialize model parameters ----- - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init - modelprms["ddfsnow"] = ddfsnow_init - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init + modelprms['ddfsnow'] = ddfsnow_init + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) - nsims = pygem_prms["calib"]["emulator_params"]["emulator_sims"] + 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) - + "/" + pygem_prms['root'] + + '/Output/emulator/sims/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) - sims_fn = glacier_str + "-" + str(nsims) + "_emulator_sims.csv" + 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"] + 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"]) + gdir.historical_climate['temp'] + + gdir.historical_climate['lr'] + * (fls[0].surface_h.min() - gdir.historical_climate['elev']) ).max() ) - modelprms["tbias"] = tbias_maxacc + modelprms['tbias'] = tbias_maxacc nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, modelprms, @@ -768,7 +768,7 @@ def run(list_packed_vars): return_tbias_mustmelt_wmb=True, ) while nbinyears_negmbclim < 10 or mb_mwea > mb_obs_mwea: - modelprms["tbias"] = modelprms["tbias"] + tbias_step + modelprms['tbias'] = modelprms['tbias'] + tbias_step nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, modelprms, @@ -778,20 +778,20 @@ def run(list_packed_vars): ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "ddfsnow:", - np.round(modelprms["ddfsnow"], 4), - "mb_mwea:", + '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:', nbinyears_negmbclim, ) tbias_stepsmall = 0.05 while nbinyears_negmbclim > 10: - modelprms["tbias"] = modelprms["tbias"] - tbias_stepsmall + modelprms['tbias'] = modelprms['tbias'] - tbias_stepsmall nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, modelprms, @@ -801,20 +801,20 @@ def run(list_packed_vars): ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "ddfsnow:", - np.round(modelprms["ddfsnow"], 4), - "mb_mwea:", + '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:', nbinyears_negmbclim, ) # Tbias lower bound - tbias_bndlow = modelprms["tbias"] + tbias_stepsmall - modelprms["tbias"] = tbias_bndlow + tbias_bndlow = modelprms['tbias'] + tbias_stepsmall + modelprms['tbias'] = tbias_bndlow nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, modelprms, @@ -824,20 +824,20 @@ def run(list_packed_vars): ) output_all = np.array( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], mb_mwea, nbinyears_negmbclim, ] ) # Tbias lower bound & high precipitation factor - modelprms["kp"] = stats.gamma.ppf( + modelprms['kp'] = stats.gamma.ppf( 0.99, - pygem_prms["calib"]["emulator_params"]["kp_gamma_alpha"], + pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1 - / pygem_prms["calib"]["emulator_params"]["kp_gamma_beta"], + / pygem_prms['calib']['emulator_params']['kp_gamma_beta'], ) nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, @@ -848,9 +848,9 @@ def run(list_packed_vars): ) output_single = np.array( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], mb_mwea, nbinyears_negmbclim, ] @@ -859,23 +859,23 @@ def run(list_packed_vars): if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "ddfsnow:", - np.round(modelprms["ddfsnow"], 4), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'ddfsnow:', + np.round(modelprms['ddfsnow'], 4), + 'mb_mwea:', np.round(mb_mwea, 3), ) # Tbias 'mid-point' - modelprms["kp"] = pygem_prms["calib"]["emulator_params"]["kp_init"] + modelprms['kp'] = pygem_prms['calib']['emulator_params']['kp_init'] ncount_tbias = 0 tbias_bndhigh = 10 tbias_middle = tbias_bndlow + tbias_step - while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 50: - modelprms["tbias"] = modelprms["tbias"] + tbias_step + 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, @@ -885,32 +885,32 @@ def run(list_packed_vars): ) output_single = np.array( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], mb_mwea, nbinyears_negmbclim, ] ) output_all = np.vstack((output_all, output_single)) - tbias_middle = modelprms["tbias"] - tbias_step / 2 + tbias_middle = modelprms['tbias'] - tbias_step / 2 ncount_tbias += 1 if debug: print( ncount_tbias, - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "ddfsnow:", - np.round(modelprms["ddfsnow"], 4), - "mb_mwea:", + '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 + modelprms['tbias'] = modelprms['tbias'] + tbias_step nbinyears_negmbclim, mb_mwea = mb_mwea_calc( gdir, modelprms, @@ -920,102 +920,102 @@ def run(list_packed_vars): ) output_single = np.array( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], mb_mwea, nbinyears_negmbclim, ] ) output_all = np.vstack((output_all, output_single)) - tbias_bndhigh = modelprms["tbias"] + tbias_bndhigh = modelprms['tbias'] ncount_tbias -= 1 if debug: print( ncount_tbias, - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "ddfsnow:", - np.round(modelprms["ddfsnow"], 4), - "mb_mwea:", + '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" + 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" + pygem_prms['calib']['emulator_params']['tbias_disttype'] + == 'truncnormal' ): tbias_zlow = (tbias_bndlow - tbias_middle) / pygem_prms[ - "calib" - ]["emulator_params"]["tbias_sigma"] + 'calib' + ]['emulator_params']['tbias_sigma'] tbias_zhigh = (tbias_bndhigh - tbias_middle) / pygem_prms[ - "calib" - ]["emulator_params"]["tbias_sigma"] + '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"], + scale=pygem_prms['calib']['emulator_params']['tbias_sigma'], size=nsims, ) if debug: print( - "\ntbias random:", tbias_random.mean(), tbias_random.std() + '\ntbias random:', tbias_random.mean(), tbias_random.std() ) # Precipitation factor kp_random = stats.gamma.rvs( - pygem_prms["calib"]["emulator_params"]["kp_gamma_alpha"], + pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1 - / pygem_prms["calib"]["emulator_params"]["kp_gamma_beta"], + / pygem_prms['calib']['emulator_params']['kp_gamma_beta'], size=nsims, ) if debug: - print("kp random:", kp_random.mean(), kp_random.std()) + print('kp random:', kp_random.mean(), kp_random.std()) # Degree-day factor of snow ddfsnow_zlow = ( - pygem_prms["calib"]["emulator_params"]["ddfsnow_bndlow"] - - pygem_prms["calib"]["emulator_params"]["ddfsnow_mu"] - ) / pygem_prms["calib"]["emulator_params"]["ddfsnow_sigma"] + 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"] + 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"], + 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:', ddfsnow_random.mean(), ddfsnow_random.std(), - "\n", + '\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"] + 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, @@ -1027,9 +1027,9 @@ def run(list_packed_vars): output_single = np.array( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], mb_mwea, nbinyears_negmbclim, ] @@ -1038,13 +1038,13 @@ def run(list_packed_vars): 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:", + '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), ) @@ -1052,11 +1052,11 @@ def run(list_packed_vars): sims_df = pd.DataFrame( output_all, columns=[ - "tbias", - "kp", - "ddfsnow", - "mb_mwea", - "nbinyrs_negmbclim", + 'tbias', + 'kp', + 'ddfsnow', + 'mb_mwea', + 'nbinyrs_negmbclim', ], ) if os.path.exists(sims_fp) == False: @@ -1068,32 +1068,32 @@ def run(list_packed_vars): sims_df = pd.read_csv(sims_fp + sims_fn) # ----- EMULATOR: Mass balance ----- - em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' em_mod_fp = ( - pygem_prms["root"] - + "/Output/emulator/models/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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"] + or pygem_prms['calib']['emulator_params']['overwrite_em_sims'] ): mbEmulator = create_emulator( - glacier_str, sims_df, y_cn="mb_mwea", debug=debug + glacier_str, sims_df, y_cn='mb_mwea', debug=debug ) else: mbEmulator = massbalEmulator.load(em_mod_path=em_mod_fp + em_mod_fn) # ===== HH2015 MODIFIED CALIBRATION USING EMULATOR ===== - if pygem_prms["calib"]["emulator_params"]["opt_hh2015_mod"]: - tbias_init = pygem_prms["calib"]["emulator_params"]["tbias_init"] - tbias_step = pygem_prms["calib"]["emulator_params"]["tbias_step"] - kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] - kp_bndlow = pygem_prms["calib"]["emulator_params"]["kp_bndlow"] - kp_bndhigh = pygem_prms["calib"]["emulator_params"]["kp_bndhigh"] - ddfsnow_init = pygem_prms["calib"]["emulator_params"][ - "ddfsnow_init" + if pygem_prms['calib']['emulator_params']['opt_hh2015_mod']: + tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] + tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] + kp_init = pygem_prms['calib']['emulator_params']['kp_init'] + kp_bndlow = pygem_prms['calib']['emulator_params']['kp_bndlow'] + kp_bndhigh = pygem_prms['calib']['emulator_params']['kp_bndhigh'] + ddfsnow_init = pygem_prms['calib']['emulator_params'][ + 'ddfsnow_init' ] # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- @@ -1109,7 +1109,7 @@ def update_bnds( ): """Update bounds for various parameters for the single_param_optimizer""" # If mass balance less than observation, reduce tbias - if prm2opt == "kp": + if prm2opt == 'kp': if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid prm_bndhigh_new, mb_mwea_high_new = ( @@ -1122,7 +1122,7 @@ def update_bnds( mb_mwea_low, ) prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid - elif prm2opt == "ddfsnow": + elif prm2opt == 'ddfsnow': if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = ( prm_bndlow, @@ -1135,7 +1135,7 @@ def update_bnds( prm_bndhigh, mb_mwea_high, ) - elif prm2opt == "tbias": + elif prm2opt == 'tbias': if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = ( prm_bndlow, @@ -1151,31 +1151,31 @@ def update_bnds( 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"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_mid_new = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) if debug: print( - prm2opt + "_bndlow:", + prm2opt + '_bndlow:', np.round(prm_bndlow_new, 2), - "mb_mwea_low:", + 'mb_mwea_low:', np.round(mb_mwea_low_new, 2), ) print( - prm2opt + "_bndhigh:", + prm2opt + '_bndhigh:', np.round(prm_bndhigh_new, 2), - "mb_mwea_high:", + 'mb_mwea_high:', np.round(mb_mwea_high_new, 2), ) print( - prm2opt + "_mid:", + prm2opt + '_mid:', np.round(prm_mid_new, 2), - "mb_mwea_mid:", + 'mb_mwea_mid:', np.round(mb_mwea_mid_new, 3), ) @@ -1203,71 +1203,71 @@ def single_param_optimizer( Computationally more robust and sometimes faster than scipy minimize """ assert prm2opt is not None, ( - "For single_param_optimizer you must specify parameter to optimize" + 'For single_param_optimizer you must specify parameter to optimize' ) - if prm2opt == "kp": + if prm2opt == 'kp': prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms["tbias"] = modelprms_subset["tbias"] - modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] - elif prm2opt == "ddfsnow": + modelprms['tbias'] = modelprms_subset['tbias'] + modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] + elif prm2opt == 'ddfsnow': prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms["kp"] = modelprms_subset["kp"] - modelprms["tbias"] = modelprms_subset["tbias"] - elif prm2opt == "tbias": + modelprms['kp'] = modelprms_subset['kp'] + modelprms['tbias'] = modelprms_subset['tbias'] + elif prm2opt == 'tbias': prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms["kp"] = modelprms_subset["kp"] - modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + modelprms['kp'] = modelprms_subset['kp'] + modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_low = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) # Upper bound modelprms[prm2opt] = prm_bndhigh - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_high = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [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"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_mid = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) if debug: print( - prm2opt + "_bndlow:", + prm2opt + '_bndlow:', np.round(prm_bndlow, 2), - "mb_mwea_low:", + 'mb_mwea_low:', np.round(mb_mwea_low, 2), ) print( - prm2opt + "_bndhigh:", + prm2opt + '_bndhigh:', np.round(prm_bndhigh, 2), - "mb_mwea_high:", + 'mb_mwea_high:', np.round(mb_mwea_high, 2), ) print( - prm2opt + "_mid:", + prm2opt + '_mid:', np.round(prm_mid, 2), - "mb_mwea_mid:", + 'mb_mwea_mid:', np.round(mb_mwea_mid, 3), ) @@ -1289,7 +1289,7 @@ def single_param_optimizer( > mb_mwea_threshold ): if debug: - print("\n ncount:", ncount) + print('\n ncount:', ncount) ( prm_bndlow, prm_bndhigh, @@ -1313,25 +1313,25 @@ def single_param_optimizer( # ===== SET THINGS UP ====== if debug: - sims_df["mb_em"] = np.nan + sims_df['mb_em'] = np.nan for nidx in sims_df.index.values: - modelprms["tbias"] = sims_df.loc[nidx, "tbias"] - modelprms["kp"] = sims_df.loc[nidx, "kp"] - modelprms["ddfsnow"] = sims_df.loc[nidx, "ddfsnow"] - sims_df.loc[nidx, "mb_em"] = mbEmulator.eval( + modelprms['tbias'] = 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"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], ] ) - sims_df["mb_em_dif"] = sims_df["mb_em"] - sims_df["mb_mwea"] + sims_df['mb_em_dif'] = sims_df['mb_em'] - sims_df['mb_mwea'] # ----- TEMPERATURE BIAS BOUNDS ----- # Selects from emulator sims dataframe - sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] - tbias_bndhigh = float(sims_df_subset["tbias"].max()) - tbias_bndlow = float(sims_df_subset["tbias"].min()) + sims_df_subset = sims_df.loc[sims_df['kp'] == 1, :] + tbias_bndhigh = float(sims_df_subset['tbias'].max()) + tbias_bndlow = float(sims_df_subset['tbias'].min()) # Adjust tbias_init based on bounds if tbias_init > tbias_bndhigh: @@ -1341,24 +1341,24 @@ def single_param_optimizer( # ----- Mass balance bounds ----- # Upper bound - modelprms["kp"] = kp_bndhigh - modelprms["tbias"] = tbias_bndlow - modelprms["ddfsnow"] = ddfsnow_init + modelprms['kp'] = kp_bndhigh + modelprms['tbias'] = tbias_bndlow + modelprms['ddfsnow'] = ddfsnow_init mb_mwea_bndhigh = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) # Lower bound - modelprms["kp"] = kp_bndlow - modelprms["tbias"] = tbias_bndhigh - modelprms["ddfsnow"] = ddfsnow_init + modelprms['kp'] = kp_bndlow + modelprms['tbias'] = tbias_bndhigh + modelprms['ddfsnow'] = ddfsnow_init mb_mwea_bndlow = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) if debug: print( - "mb_mwea_max:", + 'mb_mwea_max:', np.round(mb_mwea_bndhigh, 2), - "mb_mwea_min:", + 'mb_mwea_min:', np.round(mb_mwea_bndlow, 2), ) @@ -1367,25 +1367,25 @@ def single_param_optimizer( tbias_opt = tbias_bndlow kp_opt = kp_bndhigh troubleshoot_fp = ( - pygem_prms["root"] - + "/Output/errors/" + pygem_prms['root'] + + '/Output/errors/' + args.option_calibration - + "/" - + glacier_str.split(".")[0].zfill(2) - + "/" + + '/' + + 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" + txt_fn_extrapfail = glacier_str + '-mbs_obs_outside_bnds.txt' with open( - troubleshoot_fp + txt_fn_extrapfail, "w" + troubleshoot_fp + txt_fn_extrapfail, 'w' ) as text_file: text_file.write( glacier_str - + " observed mass balance exceeds max accumulation " - + "with value of " + + ' observed mass balance exceeds max accumulation ' + + 'with value of ' + str(np.round(mb_obs_mwea, 2)) - + " mwea" + + ' mwea' ) elif mb_obs_mwea < mb_mwea_bndlow: @@ -1393,175 +1393,175 @@ def single_param_optimizer( tbias_opt = tbias_bndhigh kp_opt = kp_bndlow troubleshoot_fp = ( - pygem_prms["root"] - + "/Output/errors/" + pygem_prms['root'] + + '/Output/errors/' + args.option_calibration - + "/" - + glacier_str.split(".")[0].zfill(2) - + "/" + + '/' + + 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" + txt_fn_extrapfail = glacier_str + '-mbs_obs_outside_bnds.txt' with open( - troubleshoot_fp + txt_fn_extrapfail, "w" + troubleshoot_fp + txt_fn_extrapfail, 'w' ) as text_file: text_file.write( glacier_str - + " observed mass balance below max loss " - + "with value of " + + ' observed mass balance below max loss ' + + 'with value of ' + str(np.round(mb_obs_mwea, 2)) - + " mwea" + + ' mwea' ) else: continue_param_search = True # ===== ADJUST LOWER AND UPPER BOUNDS TO SET UP OPTIMIZATION ====== # Initialize model parameters - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init - modelprms["ddfsnow"] = ddfsnow_init + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init + modelprms['ddfsnow'] = ddfsnow_init test_count = 0 test_count_acc = 0 mb_mwea = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) if mb_mwea > mb_obs_mwea: if debug: - print("increase tbias, decrease kp") + print('increase tbias, decrease kp') kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms["kp"] = kp_bndlow + modelprms['kp'] = kp_bndlow mb_mwea = mbEmulator.eval( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], ] ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) while mb_mwea > mb_obs_mwea and test_count < 100: # Update temperature bias - modelprms["tbias"] = modelprms["tbias"] + tbias_step + modelprms['tbias'] = modelprms['tbias'] + tbias_step # Update bounds - tbias_bndhigh_opt = modelprms["tbias"] - tbias_bndlow_opt = modelprms["tbias"] - tbias_step + tbias_bndhigh_opt = modelprms['tbias'] + tbias_bndlow_opt = modelprms['tbias'] - tbias_step # Compute mass balance mb_mwea = mbEmulator.eval( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], ] ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) test_count += 1 else: if debug: - print("decrease tbias, increase kp") + print('decrease tbias, increase kp') kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh mb_mwea = mbEmulator.eval( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], ] ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) while mb_obs_mwea > mb_mwea and test_count < 100: # Update temperature bias - modelprms["tbias"] = modelprms["tbias"] - tbias_step + modelprms['tbias'] = modelprms['tbias'] - tbias_step # If temperature bias is at lower limit, then increase precipitation factor - if modelprms["tbias"] <= tbias_bndlow: - modelprms["tbias"] = tbias_bndlow + if modelprms['tbias'] <= tbias_bndlow: + modelprms['tbias'] = tbias_bndlow if test_count_acc > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh test_count_acc += 1 # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms["tbias"] - tbias_bndhigh_opt = modelprms["tbias"] + tbias_step + tbias_bndlow_opt = modelprms['tbias'] + tbias_bndhigh_opt = modelprms['tbias'] + tbias_step # Compute mass balance mb_mwea = mbEmulator.eval( [ - modelprms["tbias"], - modelprms["kp"], - modelprms["ddfsnow"], + modelprms['tbias'], + modelprms['kp'], + modelprms['ddfsnow'], ] ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) test_count += 1 # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print("Round 1:") + 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)) + + ' kp: ' + + str(np.round(modelprms['kp'], 2)) + + ' ddfsnow: ' + + str(np.round(modelprms['ddfsnow'], 4)) + + ' tbias: ' + + str(np.round(modelprms['tbias'], 2)) ) # Reset parameters - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init - modelprms["ddfsnow"] = ddfsnow_init + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init + modelprms['ddfsnow'] = ddfsnow_init # Lower bound - modelprms["kp"] = kp_bndlow + modelprms['kp'] = kp_bndlow mb_mwea_kp_low = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) # Upper bound - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh mb_mwea_kp_high = mbEmulator.eval( - [modelprms["tbias"], modelprms["kp"], modelprms["ddfsnow"]] + [modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']] ) # Optimal precipitation factor @@ -1574,117 +1574,117 @@ def single_param_optimizer( else: # Single parameter optimizer (computationally more efficient and less prone to fail) modelprms_subset = { - "kp": kp_init, - "ddfsnow": ddfsnow_init, - "tbias": tbias_init, + '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", + prm2opt='kp', kp_bnds=kp_bnds, debug=debug, ) - kp_opt = modelprms_opt["kp"] + kp_opt = modelprms_opt['kp'] continue_param_search = False # Update parameter values - modelprms["kp"] = kp_opt + modelprms['kp'] = kp_opt if debug: print( - " kp:", + ' kp:', np.round(kp_opt, 2), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 3), - "obs_mwea:", + '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]), + '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", + prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug, ) # Update parameter values - tbias_opt = modelprms_opt["tbias"] - modelprms["tbias"] = tbias_opt + tbias_opt = modelprms_opt['tbias'] + modelprms['tbias'] = tbias_opt if debug: print( - " tbias:", + ' tbias:', np.round(tbias_opt, 3), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 3), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 3), ) else: - tbias_opt = modelprms["tbias"] + tbias_opt = modelprms['tbias'] if debug: print( - "\n\ntbias:", + '\n\ntbias:', np.round(tbias_opt, 2), - "kp:", + 'kp:', np.round(kp_opt, 2), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 3), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 3), - "\n\n", + '\n\n', ) modelparams_opt = modelprms - modelparams_opt["kp"] = kp_opt - modelparams_opt["tbias"] = tbias_opt + modelparams_opt['kp'] = kp_opt + modelparams_opt['tbias'] = tbias_opt # Export model parameters modelprms = modelparams_opt for vn in [ - "ddfice", - "ddfsnow", - "kp", - "precgrad", - "tbias", - "tsnow_threshold", + '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['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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, "r") as f: + with open(modelprms_fullfn, 'r') as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, "w") as f: + with open(modelprms_fullfn, 'w') as f: json.dump(modelprms_dict, f) # %% ===== MCMC CALIBRATION ====== @@ -1692,24 +1692,24 @@ def single_param_optimizer( # ddfsnow and kp. Then create an ensemble of parameter sets evenly sampled from these # distributions, and output these sets of parameters and their corresponding mass balances to be # used in the simulations. - elif args.option_calibration == "MCMC": - if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: + elif args.option_calibration == 'MCMC': + if pygem_prms['calib']['MCMC_params']['option_use_emulator']: # load emulator - em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' em_mod_fp = ( - pygem_prms["root"] - + "/Output/emulator/models/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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}" + 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 + outpath_sfix = '' # output file path suffix if using emulator else: outpath_sfix = ( - "-fullsim" # output file path suffix if not using emulator + '-fullsim' # output file path suffix if not using emulator ) # --------------------------------- @@ -1720,10 +1720,10 @@ def calc_mb_total_minelev(modelprms): """Approximate estimate of the mass balance at minimum elevation""" fl = fls[0] min_elev = fl.surface_h.min() - glacier_gcm_temp = gdir.historical_climate["temp"] - glacier_gcm_prec = gdir.historical_climate["prec"] - glacier_gcm_lr = gdir.historical_climate["lr"] - glacier_gcm_elev = gdir.historical_climate["elev"] + glacier_gcm_temp = gdir.historical_climate['temp'] + glacier_gcm_prec = gdir.historical_climate['prec'] + glacier_gcm_lr = gdir.historical_climate['lr'] + glacier_gcm_elev = gdir.historical_climate['elev'] # Temperature using gcm and glacier lapse rates # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange T_minelev = ( @@ -1731,7 +1731,7 @@ def calc_mb_total_minelev(modelprms): + glacier_gcm_lr * ( glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + pygem_prms['mb']['option_elev_ref_downscale'] ] - glacier_gcm_elev ) @@ -1739,41 +1739,41 @@ def calc_mb_total_minelev(modelprms): * ( min_elev - glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + pygem_prms['mb']['option_elev_ref_downscale'] ] ) - + modelprms["tbias"] + + 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"] + * modelprms['kp'] * ( 1 - + modelprms["precgrad"] + + modelprms['precgrad'] * ( min_elev - glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + 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 + T_minelev * dates_table['daysinmonth'].values ) melt_energy_available[melt_energy_available < 0] = 0 # assume all snow melt because anything more would melt underlying ice in lowermost bin # SNOW MELT [m w.e.] - Melt_minelev = modelprms["ddfsnow"] * melt_energy_available + Melt_minelev = modelprms['ddfsnow'] * melt_energy_available # Total mass balance over entire period at minimum elvation mb_total_minelev = (Acc_minelev - Melt_minelev).sum() @@ -1782,28 +1782,28 @@ def calc_mb_total_minelev(modelprms): def get_priors(priors): # define distribution based on priors dists = [] - for param in ["tbias", "kp", "ddfsnow"]: - if priors[param]["type"] == "normal": + for param in ['tbias', 'kp', 'ddfsnow']: + if priors[param]['type'] == 'normal': dist = stats.norm( - loc=priors[param]["mu"], scale=priors[param]["sigma"] + loc=priors[param]['mu'], scale=priors[param]['sigma'] ) - elif priors[param]["type"] == "uniform": + elif priors[param]['type'] == 'uniform': dist = stats.uniform( - low=priors[param]["low"], high=priors[param]["high"] + low=priors[param]['low'], high=priors[param]['high'] ) - elif priors[param]["type"] == "gamma": + elif priors[param]['type'] == 'gamma': dist = stats.gamma( - a=priors[param]["alpha"], - scale=1 / priors[param]["beta"], + a=priors[param]['alpha'], + scale=1 / priors[param]['beta'], ) - elif priors[param]["type"] == "truncnormal": + 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"], + 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 @@ -1824,7 +1824,7 @@ def get_initials(dists, threshold=0.01): def mb_max(*args, **kwargs): """Model parameters cannot completely melt the glacier (psuedo-likelihood fxn)""" - if kwargs["massbal"] < mb_max_loss: + if kwargs['massbal'] < mb_max_loss: return -np.inf else: return 0 @@ -1832,12 +1832,12 @@ def mb_max(*args, **kwargs): def must_melt(kp, tbias, ddfsnow, **kwargs): """Likelihood function for mass balance [mwea] based on model parametersr (psuedo-likelihood fxn)""" modelprms_copy = modelprms.copy() - modelprms_copy["tbias"] = float(tbias) - modelprms_copy["kp"] = float(kp) - modelprms_copy["ddfsnow"] = float(ddfsnow) - modelprms_copy["ddfice"] = ( - modelprms_copy["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms_copy['tbias'] = float(tbias) + modelprms_copy['kp'] = float(kp) + modelprms_copy['ddfsnow'] = float(ddfsnow) + modelprms_copy['ddfice'] = ( + modelprms_copy['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_total_minelev = calc_mb_total_minelev(modelprms_copy) if mb_total_minelev < 0: @@ -1852,8 +1852,8 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # --------------------------------- # Maximum mass loss [mwea] (based on consensus ice thickness estimate) # consensus_mass has units of kg - if os.path.exists(gdir.get_filepath("consensus_mass")): - with open(gdir.get_filepath("consensus_mass"), "rb") as f: + if os.path.exists(gdir.get_filepath('consensus_mass')): + with open(gdir.get_filepath('consensus_mass'), 'rb') as f: consensus_mass = pickle.load(f) else: # Mean global ice thickness from Farinotti et al. (2019) used for missing consensus glaciers @@ -1862,13 +1862,13 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): glacier_rgi_table.Area * 1e6 * ice_thickness_constant - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] ) mb_max_loss = ( -1 * consensus_mass - / pygem_prms["constants"]["density_water"] + / pygem_prms['constants']['density_water'] / gdir.rgi_area_m2 / (gdir.dates_table.shape[0] / 12) ) @@ -1878,58 +1878,58 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # ----- PRIORS ----- # ------------------ # Prior distributions (specified or informed by regions) - if pygem_prms["calib"]["priors_reg_fn"] is not None: + if pygem_prms['calib']['priors_reg_fn'] is not None: # Load priors priors_df = pd.read_csv( - pygem_prms["root"] - + "/Output/calibration/" - + pygem_prms["calib"]["priors_reg_fn"] + 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"]) + (priors_df.O1Region == glacier_rgi_table['O1Region']) + & (priors_df.O2Region == glacier_rgi_table['O2Region']) )[0][0] # Precipitation factor priors - kp_gamma_alpha = float(priors_df.loc[priors_idx, "kp_alpha"]) - kp_gamma_beta = float(priors_df.loc[priors_idx, "kp_beta"]) + kp_gamma_alpha = float(priors_df.loc[priors_idx, 'kp_alpha']) + kp_gamma_beta = float(priors_df.loc[priors_idx, 'kp_beta']) # Temperature bias priors - tbias_mu = float(priors_df.loc[priors_idx, "tbias_mean"]) - tbias_sigma = float(priors_df.loc[priors_idx, "tbias_std"]) + tbias_mu = float(priors_df.loc[priors_idx, 'tbias_mean']) + tbias_sigma = float(priors_df.loc[priors_idx, 'tbias_std']) else: # Precipitation factor priors - kp_gamma_alpha = pygem_prms["calib"]["MCMC_params"][ - "kp_gamma_alpha" + kp_gamma_alpha = pygem_prms['calib']['MCMC_params'][ + 'kp_gamma_alpha' ] - kp_gamma_beta = pygem_prms["calib"]["MCMC_params"]["kp_gamma_beta"] + kp_gamma_beta = pygem_prms['calib']['MCMC_params']['kp_gamma_beta'] # Temperature bias priors - tbias_mu = pygem_prms["calib"]["MCMC_params"]["tbias_mu"] - tbias_sigma = pygem_prms["calib"]["MCMC_params"]["tbias_sigma"] + tbias_mu = pygem_prms['calib']['MCMC_params']['tbias_mu'] + tbias_sigma = pygem_prms['calib']['MCMC_params']['tbias_sigma'] # put all priors together into a dictionary priors = { - "tbias": { - "type": pygem_prms["calib"]["MCMC_params"]["tbias_disttype"], - "mu": float(tbias_mu), - "sigma": float(tbias_sigma), - "low": safe_float(getattr(pygem_prms, "tbias_bndlow", None)), - "high": safe_float(getattr(pygem_prms, "tbias_bndhigh", None)), + '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)), + '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"] + '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"] + 'high': float( + pygem_prms['calib']['MCMC_params']['ddfsnow_bndhigh'] ), }, } @@ -1941,24 +1941,24 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # ----- TEMPERATURE BIAS BOUNDS ----- # ----------------------------------- # note, temperature bias bounds will remain constant across chains if using emulator - if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: + if pygem_prms['calib']['MCMC_params']['option_use_emulator']: # Selects from emulator sims dataframe sims_fp = ( - pygem_prms["root"] - + "/Output/emulator/sims/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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" + + '-' + + str(pygem_prms['calib']['MCMC_params']['emulator_sims']) + + '_emulator_sims.csv' ) sims_df = pd.read_csv(sims_fp + sims_fn) - sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] - tbias_bndhigh = float(sims_df_subset["tbias"].max()) - tbias_bndlow = float(sims_df_subset["tbias"].min()) + sims_df_subset = sims_df.loc[sims_df['kp'] == 1, :] + tbias_bndhigh = float(sims_df_subset['tbias'].max()) + tbias_bndlow = float(sims_df_subset['tbias'].min()) # ----------------------------------- # ------------------- @@ -1968,7 +1968,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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"]: + if pygem_prms['calib']['MCMC_params']['option_use_emulator']: mbfxn = mbEmulator.eval # returns (mb_mwea) mbargs = None # no additional arguments for mbEmulator.eval() else: @@ -1984,7 +1984,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # 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"] + for key in ['tbias', 'kp', 'ddfsnow'] if key in priors } mb = mcmc.mbPosterior( @@ -1997,7 +1997,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # 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] = {} # ------------------- @@ -2016,7 +2016,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): ( tbias_mu, kp_gamma_alpha / kp_gamma_beta, - pygem_prms["calib"]["MCMC_params"]["ddfsnow_mu"], + pygem_prms['calib']['MCMC_params']['ddfsnow_mu'], ) ) # for all chains > 0, randomly sample from regional priors @@ -2024,7 +2024,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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}" + 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 @@ -2039,10 +2039,10 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): initial_guesses_z, mb.log_posterior, n_samples=args.chain_length, - h=pygem_prms["calib"]["MCMC_params"]["mcmc_step"], + 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" + thin_factor=pygem_prms['calib']['MCMC_params'][ + 'thin_interval' ], progress_bar=args.progress_bar, ) @@ -2072,21 +2072,21 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): if debug: # print('\nacceptance ratio:', model.step_method_dict[next(iter(model.stochastics))][0].ratio) print( - "mb_mwea_mean:", + 'mb_mwea_mean:', np.round(torch.mean(m_chain[:, -1]).item(), 3), - "mb_mwea_std:", + 'mb_mwea_std:', np.round(torch.std(m_chain[:, -1]).item(), 3), - "\nmb_obs_mean:", + '\nmb_obs_mean:', np.round(mb_obs_mwea, 3), - "mb_obs_std:", + 'mb_obs_std:', np.round(mb_obs_mwea_err, 3), ) # plot chain fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/fig/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/fig/' ) os.makedirs(fp, exist_ok=True) if args.ncores > 1: @@ -2100,7 +2100,7 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): ar, glacier_str, show=show, - fpath=f"{fp}/{glacier_str}-chain{n_chain}.png", + fpath=f'{fp}/{glacier_str}-chain{n_chain}.png', ) for i in pred_chain.keys(): mcmc.plot_resid_hist( @@ -2108,121 +2108,121 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): pred_chain[i], glacier_str, show=show, - fpath=f"{fp}/{glacier_str}-chain{n_chain}-residuals-{i}.png", + 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] = ( + 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"] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ).tolist() - modelprms_export["mb_mwea"][chain_str] = m_chain[:, 3].tolist() - modelprms_export["ar"][chain_str] = ar + 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['precgrad'] = [ + pygem_prms['sim']['params']['precgrad'] ] - modelprms_export["tsnow_threshold"] = [ - pygem_prms["sim"]["params"]["tsnow_threshold"] + 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_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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = [ ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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"]: + 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) - + "/" + pygem_prms['root'] + + f'/Output/calibration{outpath_sfix}/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) for fp in modelprms_fp: if not os.path.exists(fp): os.makedirs(fp, exist_ok=True) modelprms_fullfn = fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, "r") as f: + with open(modelprms_fullfn, 'r') as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms_export else: modelprms_dict = {args.option_calibration: modelprms_export} - with open(modelprms_fullfn, "w") as f: + 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) - + "/" + 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: + 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" + 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) - + "/" + 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: + 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}" + glacier_str + f' failed to complete MCMC: {err}' ) # -------------------- # %% ===== HUSS AND HOCK (2015) CALIBRATION ===== - elif args.option_calibration == "HH2015": - tbias_init = float(pygem_prms["calib"]["HH2015_params"]["tbias_init"]) - tbias_step = float(pygem_prms["calib"]["HH2015_params"]["tbias_step"]) - kp_init = float(pygem_prms["calib"]["HH2015_params"]["kp_init"]) - kp_bndlow = float(pygem_prms["calib"]["HH2015_params"]["kp_bndlow"]) - kp_bndhigh = float(pygem_prms["calib"]["HH2015_params"]["kp_bndhigh"]) + 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"] + pygem_prms['calib']['HH2015_params']['ddfsnow_init'] ) ddfsnow_bndlow = float( - pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndlow"] + pygem_prms['calib']['HH2015_params']['ddfsnow_bndlow'] ) ddfsnow_bndhigh = float( - pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndhigh"] + pygem_prms['calib']['HH2015_params']['ddfsnow_bndhigh'] ) # ----- Initialize model parameters ----- - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init - modelprms["ddfsnow"] = ddfsnow_init - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init + modelprms['ddfsnow'] = ddfsnow_init + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) continue_param_search = True @@ -2238,7 +2238,7 @@ def update_bnds( debug=False, ): # If mass balance less than observation, reduce tbias - if prm2opt == "kp": + if prm2opt == 'kp': if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid prm_bndhigh_new, mb_mwea_high_new = ( @@ -2248,7 +2248,7 @@ def update_bnds( else: prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid - elif prm2opt == "ddfsnow": + elif prm2opt == 'ddfsnow': if mb_mwea_mid < mb_obs_mwea: prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid @@ -2258,7 +2258,7 @@ def update_bnds( prm_bndhigh, mb_mwea_high, ) - elif prm2opt == "tbias": + 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 @@ -2271,9 +2271,9 @@ def update_bnds( 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"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_mid_new = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls @@ -2281,21 +2281,21 @@ def update_bnds( if debug: print( - prm2opt + "_bndlow:", + prm2opt + '_bndlow:', np.round(prm_bndlow_new, 2), - "mb_mwea_low:", + 'mb_mwea_low:', np.round(mb_mwea_low_new, 2), ) print( - prm2opt + "_bndhigh:", + prm2opt + '_bndhigh:', np.round(prm_bndhigh_new, 2), - "mb_mwea_high:", + 'mb_mwea_high:', np.round(mb_mwea_high_new, 2), ) print( - prm2opt + "_mid:", + prm2opt + '_mid:', np.round(prm_mid_new, 2), - "mb_mwea_mid:", + 'mb_mwea_mid:', np.round(mb_mwea_mid_new, 3), ) @@ -2319,39 +2319,39 @@ def single_param_optimizer( debug=False, ): assert prm2opt is not None, ( - "For single_param_optimizer you must specify parameter to optimize" + 'For single_param_optimizer you must specify parameter to optimize' ) - if prm2opt == "kp": + if prm2opt == 'kp': prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms["tbias"] = modelprms_subset["tbias"] - modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] - elif prm2opt == "ddfsnow": + modelprms['tbias'] = modelprms_subset['tbias'] + modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] + elif prm2opt == 'ddfsnow': prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms["kp"] = modelprms_subset["kp"] - modelprms["tbias"] = modelprms_subset["tbias"] - elif prm2opt == "tbias": + modelprms['kp'] = modelprms_subset['kp'] + modelprms['tbias'] = modelprms_subset['tbias'] + elif prm2opt == 'tbias': prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms["kp"] = modelprms_subset["kp"] - modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + modelprms['kp'] = modelprms_subset['kp'] + modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + 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"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_high = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls @@ -2359,9 +2359,9 @@ def single_param_optimizer( # Middle bound prm_mid = (prm_bndlow + prm_bndhigh) / 2 modelprms[prm2opt] = prm_mid - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_mid = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls @@ -2369,21 +2369,21 @@ def single_param_optimizer( if debug: print( - prm2opt + "_bndlow:", + prm2opt + '_bndlow:', np.round(prm_bndlow, 2), - "mb_mwea_low:", + 'mb_mwea_low:', np.round(mb_mwea_low, 2), ) print( - prm2opt + "_bndhigh:", + prm2opt + '_bndhigh:', np.round(prm_bndhigh, 2), - "mb_mwea_high:", + 'mb_mwea_high:', np.round(mb_mwea_high, 2), ) print( - prm2opt + "_mid:", + prm2opt + '_mid:', np.round(prm_mid, 2), - "mb_mwea_mid:", + 'mb_mwea_mid:', np.round(mb_mwea_mid, 3), ) @@ -2400,7 +2400,7 @@ def single_param_optimizer( np.absolute(mb_mwea_mid - mb_obs_mwea) > mb_mwea_threshold ): if debug: - print("\n ncount:", ncount) + print('\n ncount:', ncount) ( prm_bndlow, prm_bndhigh, @@ -2424,26 +2424,26 @@ def single_param_optimizer( # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print("Round 1:") + print('Round 1:') if debug: print( glacier_str - + " kp: " - + str(np.round(modelprms["kp"], 2)) - + " ddfsnow: " - + str(np.round(modelprms["ddfsnow"], 4)) - + " tbias: " - + str(np.round(modelprms["tbias"], 2)) + + ' 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 + modelprms['kp'] = kp_bndlow mb_mwea_kp_low = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) # Upper bound - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh mb_mwea_kp_high = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) @@ -2458,46 +2458,46 @@ def single_param_optimizer( else: # Single parameter optimizer (computationally more efficient and less prone to fail) modelprms_subset = { - "kp": kp_init, - "ddfsnow": ddfsnow_init, - "tbias": tbias_init, + '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", + prm2opt='kp', kp_bnds=kp_bnds, debug=debug, ) - kp_opt = modelprms_opt["kp"] + kp_opt = modelprms_opt['kp'] continue_param_search = False # Update parameter values - modelprms["kp"] = kp_opt + modelprms['kp'] = kp_opt if debug: print( - " kp:", np.round(kp_opt, 2), "mb_mwea:", np.round(mb_mwea, 2) + ' kp:', np.round(kp_opt, 2), 'mb_mwea:', np.round(mb_mwea, 2) ) # ===== ROUND 2: DEGREE-DAY FACTOR OF SNOW ====== if continue_param_search: if debug: - print("Round 2:") + print('Round 2:') # Lower bound - modelprms["ddfsnow"] = ddfsnow_bndlow - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['ddfsnow'] = ddfsnow_bndlow + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) mb_mwea_ddflow = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) # Upper bound - modelprms["ddfsnow"] = ddfsnow_bndhigh - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + 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 @@ -2512,40 +2512,40 @@ def single_param_optimizer( else: # Single parameter optimizer (computationally more efficient and less prone to fail) modelprms_subset = { - "kp": kp_opt, - "ddfsnow": ddfsnow_init, - "tbias": tbias_init, + '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", + prm2opt='ddfsnow', ddfsnow_bnds=ddfsnow_bnds, debug=debug, ) - ddfsnow_opt = modelprms_opt["ddfsnow"] + ddfsnow_opt = modelprms_opt['ddfsnow'] continue_param_search = False # Update parameter values - modelprms["ddfsnow"] = ddfsnow_opt - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['ddfsnow'] = ddfsnow_opt + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) if debug: print( - " ddfsnow:", + ' ddfsnow:', np.round(ddfsnow_opt, 4), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 2), ) else: - ddfsnow_opt = modelprms["ddfsnow"] + ddfsnow_opt = modelprms['ddfsnow'] # ===== ROUND 3: TEMPERATURE BIAS ====== if continue_param_search: if debug: - print("Round 3:") + print('Round 3:') # ----- TEMPBIAS: max accumulation ----- # Lower temperature bound based on no positive temperatures # Temperature at the lowest bin @@ -2553,120 +2553,120 @@ def single_param_optimizer( tbias_max_acc = ( -1 * ( - gdir.historical_climate["temp"] - + gdir.historical_climate["lr"] - * (fls[0].surface_h.min() - gdir.historical_climate["elev"]) + 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 + modelprms['tbias'] = tbias_bndlow mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) if debug: print( - " tbias_bndlow:", + ' tbias_bndlow:', np.round(tbias_bndlow, 2), - "mb_mwea:", + '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 + while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 20: + modelprms['tbias'] = modelprms['tbias'] + tbias_step mb_mwea = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) if debug: print( - " tc:", - np.round(modelprms["tbias"], 2), - "mb_mwea:", + ' tc:', + np.round(modelprms['tbias'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), ) - tbias_bndhigh = modelprms["tbias"] + 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, + '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", + prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug, ) # Update parameter values - tbias_opt = modelprms_opt["tbias"] - modelprms["tbias"] = tbias_opt + tbias_opt = modelprms_opt['tbias'] + modelprms['tbias'] = tbias_opt if debug: print( - " tbias:", + ' tbias:', np.round(tbias_opt, 3), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 3), ) else: - tbias_opt = modelprms["tbias"] + tbias_opt = modelprms['tbias'] # Export model parameters modelprms = modelprms_opt for vn in [ - "ddfice", - "ddfsnow", - "kp", - "precgrad", - "tbias", - "tsnow_threshold", + '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['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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, "r") as f: + with open(modelprms_fullfn, 'r') as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, "w") as f: + with open(modelprms_fullfn, 'w') as f: json.dump(modelprms_dict, f) # %% ===== MODIFIED HUSS AND HOCK (2015) CALIBRATION ===== # used in Rounce et al. (2020; MCMC paper) # - precipitation factor, then temperature bias (no ddfsnow) # - ranges different - elif args.option_calibration == "HH2015mod": - tbias_init = pygem_prms["calib"]["HH2015mod_params"]["tbias_init"] - tbias_step = pygem_prms["calib"]["HH2015mod_params"]["tbias_step"] - kp_init = pygem_prms["calib"]["HH2015mod_params"]["kp_init"] - kp_bndlow = pygem_prms["calib"]["HH2015mod_params"]["kp_bndlow"] - kp_bndhigh = pygem_prms["calib"]["HH2015mod_params"]["kp_bndhigh"] - ddfsnow_init = pygem_prms["calib"]["HH2015mod_params"]["ddfsnow_init"] + elif args.option_calibration == 'HH2015mod': + tbias_init = pygem_prms['calib']['HH2015mod_params']['tbias_init'] + tbias_step = pygem_prms['calib']['HH2015mod_params']['tbias_step'] + kp_init = pygem_prms['calib']['HH2015mod_params']['kp_init'] + kp_bndlow = pygem_prms['calib']['HH2015mod_params']['kp_bndlow'] + kp_bndhigh = pygem_prms['calib']['HH2015mod_params']['kp_bndhigh'] + ddfsnow_init = pygem_prms['calib']['HH2015mod_params']['ddfsnow_init'] # ----- Initialize model parameters ----- - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init - modelprms["ddfsnow"] = ddfsnow_init - modelprms["ddfice"] = ( - modelprms["ddfsnow"] - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init + modelprms['ddfsnow'] = ddfsnow_init + modelprms['ddfice'] = ( + modelprms['ddfsnow'] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) # ----- FUNCTIONS ----- @@ -2678,10 +2678,10 @@ def objective(modelprms_subset): modelprms_subset : list of model parameters [kp, ddfsnow, tbias] """ # Subset of model parameters used to reduce number of constraints required - modelprms["kp"] = modelprms_subset[0] - modelprms["tbias"] = tbias_init + modelprms['kp'] = modelprms_subset[0] + modelprms['tbias'] = tbias_init if len(modelprms_subset) > 1: - modelprms["tbias"] = modelprms_subset[1] + modelprms['tbias'] = modelprms_subset[1] # Mass balance mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) # Difference with observation (mwea) @@ -2693,8 +2693,8 @@ def run_objective( 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"], + 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. @@ -2713,19 +2713,19 @@ def run_objective( modelprms_opt = minimize( objective, modelprms_init, - method=pygem_prms["calib"]["HH2015mod_params"][ - "method_opt" + method=pygem_prms['calib']['HH2015mod_params'][ + 'method_opt' ], bounds=modelprms_bnds, - options={"ftol": ftol_opt, "eps": eps_opt}, + options={'ftol': ftol_opt, 'eps': eps_opt}, ) # Record the optimized parameters modelprms_subset = modelprms_opt.x else: modelprms_subset = modelprms.copy() - modelprms["kp"] = modelprms_subset[0] + modelprms['kp'] = modelprms_subset[0] if len(modelprms_subset) == 2: - modelprms["tbias"] = modelprms_subset[1] + modelprms['tbias'] = modelprms_subset[1] # Re-run the optimized parameters in order to see the mass balance mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) return modelprms, mb_mwea @@ -2736,34 +2736,34 @@ def run_objective( tbias_bndlow = ( -1 * ( - gdir.historical_climate["temp"] - + gdir.historical_climate["lr"] - * (fls[0].surface_h.min() - gdir.historical_climate["elev"]) + gdir.historical_climate['temp'] + + gdir.historical_climate['lr'] + * (fls[0].surface_h.min() - gdir.historical_climate['elev']) ).max() ) - modelprms["tbias"] = tbias_bndlow + modelprms['tbias'] = tbias_bndlow mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) if debug: print( - " tbias_bndlow:", + ' tbias_bndlow:', np.round(tbias_bndlow, 2), - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 2), ) # Tbias upper bound (based on kp_bndhigh) - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh - while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 20: - modelprms["tbias"] = modelprms["tbias"] + 1 + 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:", + ' tc:', + np.round(modelprms['tbias'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), ) - tbias_bndhigh = modelprms["tbias"] + tbias_bndhigh = modelprms['tbias'] # ===== ROUND 1: PRECIPITATION FACTOR ===== # Adjust bounds based on range of temperature bias @@ -2771,8 +2771,8 @@ def run_objective( tbias_init = tbias_bndhigh elif tbias_init < tbias_bndlow: tbias_init = tbias_bndlow - modelprms["tbias"] = tbias_init - modelprms["kp"] = kp_init + modelprms['tbias'] = tbias_init + modelprms['kp'] = kp_init tbias_bndlow_opt = tbias_init tbias_bndhigh_opt = tbias_init @@ -2789,13 +2789,13 @@ def run_objective( if debug: print( - "\ntbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + '\ntbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) @@ -2803,66 +2803,66 @@ def run_objective( test_count = 0 if mb_mwea > mb_obs_mwea: if debug: - print("increase tbias, decrease kp") + print('increase tbias, decrease kp') kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms["kp"] = kp_bndlow + modelprms['kp'] = kp_bndlow mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) while mb_mwea > mb_obs_mwea and test_count < 20: # Update temperature bias - modelprms["tbias"] = modelprms["tbias"] + tbias_step + modelprms['tbias'] = modelprms['tbias'] + tbias_step # Update bounds - tbias_bndhigh_opt = modelprms["tbias"] - tbias_bndlow_opt = modelprms["tbias"] - tbias_step + tbias_bndhigh_opt = modelprms['tbias'] + tbias_bndlow_opt = modelprms['tbias'] - tbias_step # Compute mass balance mb_mwea = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) test_count += 1 else: if debug: - print("decrease tbias, increase kp") + print('decrease tbias, increase kp') kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) while mb_obs_mwea > mb_mwea and test_count < 20: # Update temperature bias - modelprms["tbias"] = modelprms["tbias"] - tbias_step + modelprms['tbias'] = modelprms['tbias'] - tbias_step # If temperature bias is at lower limit, then increase precipitation factor - if modelprms["tbias"] <= tbias_bndlow: - modelprms["tbias"] = tbias_bndlow + if modelprms['tbias'] <= tbias_bndlow: + modelprms['tbias'] = tbias_bndlow if test_count > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms["kp"] = kp_bndhigh + modelprms['kp'] = kp_bndhigh # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms["tbias"] - tbias_bndhigh_opt = modelprms["tbias"] + tbias_step + tbias_bndlow_opt = modelprms['tbias'] + tbias_bndhigh_opt = modelprms['tbias'] + tbias_step # Compute mass balance mb_mwea = mb_mwea_calc( gdir, modelprms, glacier_rgi_table, fls=fls ) if debug: print( - "tbias:", - np.round(modelprms["tbias"], 2), - "kp:", - np.round(modelprms["kp"], 2), - "mb_mwea:", + 'tbias:', + np.round(modelprms['tbias'], 2), + 'kp:', + np.round(modelprms['kp'], 2), + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mwea:", + 'obs_mwea:', np.round(mb_obs_mwea, 2), ) test_count += 1 @@ -2875,8 +2875,8 @@ def run_objective( tbias_init = np.mean([tbias_bndlow_opt, tbias_bndhigh_opt]) if debug: - print("tbias bounds:", tbias_bnds) - print("kp bounds:", kp_bnds) + print('tbias bounds:', tbias_bnds) + print('kp bounds:', kp_bnds) # Set up optimization for only the precipitation factor if tbias_bndlow_opt == tbias_bndhigh_opt: @@ -2895,68 +2895,68 @@ def run_objective( ftol_opt=1e-3, ) - kp_opt = modelparams_opt["kp"] - tbias_opt = modelparams_opt["tbias"] + kp_opt = modelparams_opt['kp'] + tbias_opt = modelparams_opt['tbias'] if debug: print( - "mb_mwea:", + 'mb_mwea:', np.round(mb_mwea, 2), - "obs_mb:", + 'obs_mb:', np.round(mb_obs_mwea, 2), - "kp:", + 'kp:', np.round(kp_opt, 2), - "tbias:", + 'tbias:', np.round(tbias_opt, 2), - "\n\n", + '\n\n', ) # Export model parameters modelprms = modelparams_opt for vn in [ - "ddfice", - "ddfsnow", - "kp", - "precgrad", - "tbias", - "tsnow_threshold", + '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['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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, "r") as f: + with open(modelprms_fullfn, 'r') as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, "w") as f: + with open(modelprms_fullfn, 'w') as f: json.dump(modelprms_dict, f) else: # LOG FAILURE fail_fp = ( - pygem_prms["root"] - + "/Outputcal_fail/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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: @@ -2975,20 +2975,20 @@ 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: + 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"], + 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) + glac_no = list(main_glac_rgi_all['rgino_str'].values) # Number of cores for parallel processing if args.ncores > 1: @@ -3003,7 +3003,7 @@ def main(): # Read GCM names from argument parser gcm_name = args.ref_gcm_name - print("Processing:", gcm_name) + print('Processing:', gcm_name) # Pack variables for multiprocessing list_packed_vars = [] @@ -3011,7 +3011,7 @@ def main(): list_packed_vars.append([count, glac_no_lst, gcm_name]) # Parallel processing if num_cores > 1: - print("Processing in parallel with " + str(num_cores) + " cores...") + print('Processing in parallel with ' + str(num_cores) + ' cores...') with multiprocessing.Pool(num_cores) as p: p.map(run, list_packed_vars) # If not in parallel, then only should be one loop @@ -3020,8 +3020,8 @@ def main(): 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__": +if __name__ == '__main__': main() diff --git a/pygem/bin/run/run_calibration_frontalablation.py b/pygem/bin/run/run_calibration_frontalablation.py index 9fe0d7d4..7862f794 100644 --- a/pygem/bin/run/run_calibration_frontalablation.py +++ b/pygem/bin/run/run_calibration_frontalablation.py @@ -44,8 +44,8 @@ ############### ### globals ### ############### -frontal_ablation_Gta_cn = "fa_gta_obs" -frontal_ablation_Gta_unc_cn = "fa_gta_obs_unc" +frontal_ablation_Gta_cn = 'fa_gta_obs' +frontal_ablation_Gta_unc_cn = 'fa_gta_obs_unc' # Frontal ablation calibration parameter (yr-1) calving_k_init = 0.1 @@ -59,37 +59,37 @@ 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", + 'all': 'Global', + 'global': 'Global', + 1: 'Alaska', + 2: 'W Canada/USA', + 3: 'Arctic Canada North', + 4: 'Arctic Canada South', + 5: 'Greenland', + 6: 'Iceland', + 7: 'Svalbard', + 8: 'Scandinavia', + 9: 'Russian Arctic', + 10: 'North Asia', + 11: 'Central Europe', + 12: 'Caucasus/Middle East', + 13: 'Central Asia', + 14: 'South Asia West', + 15: 'South Asia East', + 16: 'Low Latitudes', + 17: 'Southern Andes', + 18: 'New Zealand', + 19: 'Antarctica/Subantarctic', } ############### def mwea_to_gta(mwea, area_m2): - return mwea * pygem_prms["constants"]["density_water"] * area_m2 / 1e12 + return mwea * pygem_prms['constants']['density_water'] * area_m2 / 1e12 def gta_to_mwea(gta, area_m2): - return gta * 1e12 / pygem_prms["constants"]["density_water"] / area_m2 + return gta * 1e12 / pygem_prms['constants']['density_water'] / area_m2 def reg_calving_flux( @@ -126,21 +126,21 @@ def reg_calving_flux( 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"], + spinupyears=pygem_prms['climate']['ref_spinupyears'], + option_wateryear=pygem_prms['climate']['ref_wateryear'], ) # ===== LOAD CLIMATE DATA ===== # Climate class - assert args.ref_gcm_name in ["ERA5", "ERA-Interim"], ( - "Error: Calibration not set up for " + args.ref_gcm_name + assert args.ref_gcm_name in ['ERA5', 'ERA-Interim'], ( + 'Error: Calibration not set up for ' + args.ref_gcm_name ) gcm = class_climate.GCM(name=args.ref_gcm_name) # Air temperature [degC] gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table ) - if pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in ["ERA5"]: + 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 ) @@ -161,52 +161,52 @@ def reg_calving_flux( # ===== CALIBRATE ALL THE GLACIERS AT ONCE ===== output_cns = [ - "RGIId", - "calving_k", - "calving_thick", - "calving_flux_Gta_inv", - "calving_flux_Gta", - "no_errors", - "oggm_dynamics", + 'RGIId', + 'calving_k', + 'calving_thick', + 'calving_flux_Gta_inv', + 'calving_flux_Gta', + 'no_errors', + 'oggm_dynamics', ] output_df = pd.DataFrame( np.zeros((main_glac_rgi.shape[0], len(output_cns))), columns=output_cns ) - output_df["RGIId"] = main_glac_rgi.RGIId - output_df["calving_k"] = calving_k - output_df["calving_thick"] = np.nan - output_df["calving_flux_Gta"] = np.nan - output_df["oggm_dynamics"] = 0 - output_df["mb_mwea_fa_asl_lost"] = 0.0 + output_df['RGIId'] = main_glac_rgi.RGIId + output_df['calving_k'] = calving_k + output_df['calving_thick'] = np.nan + output_df['calving_flux_Gta'] = np.nan + output_df['oggm_dynamics'] = 0 + output_df['mb_mwea_fa_asl_lost'] = 0.0 for nglac in np.arange(main_glac_rgi.shape[0]): if args.verbose: - print("\n", main_glac_rgi.loc[main_glac_rgi.index.values[nglac], "RGIId"]) + 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"]) + glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) gdir = single_flowline_glacier_directory_with_calving( - glacier_str, logging_level="CRITICAL", reset=reset_gdir, facorrected=False + glacier_str, logging_level='CRITICAL', reset=reset_gdir, facorrected=False ) gdir.is_tidewater = True - cfg.PARAMS["use_kcalving_for_inversion"] = True - cfg.PARAMS["use_kcalving_for_run"] = True + cfg.PARAMS['use_kcalving_for_inversion'] = True + cfg.PARAMS['use_kcalving_for_run'] = True try: - fls = gdir.read_pickle("inversion_flowlines") + fls = gdir.read_pickle('inversion_flowlines') glacier_area = fls[0].widths_m * fls[0].dx_meter - debris.debris_binned(gdir, fl_str="inversion_flowlines", ignore_debris=True) + debris.debris_binned(gdir, fl_str='inversion_flowlines', ignore_debris=True) except: fls = None # Add climate data to glacier directory gdir.historical_climate = { - "elev": gcm_elev[nglac], - "temp": gcm_temp[nglac, :], - "tempstd": gcm_tempstd[nglac, :], - "prec": gcm_prec[nglac, :], - "lr": gcm_lr[nglac, :], + '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 @@ -215,80 +215,80 @@ def reg_calving_flux( # ----- 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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) if not os.path.exists(modelprms_fp + modelprms_fn): # try using regional priors args.prms_from_reg_priors = True break - with open(modelprms_fp + modelprms_fn, "r") as f: + with open(modelprms_fp + modelprms_fn, 'r') as f: modelprms_dict = json.load(f) - modelprms_em = modelprms_dict["emulator"] - kp_value = modelprms_em["kp"][0] - tbias_value = modelprms_em["tbias"][0] + modelprms_em = modelprms_dict['emulator'] + kp_value = modelprms_em['kp'][0] + tbias_value = modelprms_em['tbias'][0] # Use most likely parameters from initial calibration to force the mass balance gradient for the inversion elif args.prms_from_reg_priors: - if pygem_prms["calib"]["priors_reg_fn"] is not None: + if pygem_prms['calib']['priors_reg_fn'] is not None: # Load priors priors_df = pd.read_csv( - pygem_prms["root"] - + "/Output/calibration/" - + pygem_prms["calib"]["priors_reg_fn"] + 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"]) + (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"] + 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"], + '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"] + cfg.PARAMS['calving_k'] = calving_k + cfg.PARAMS['inversion_calving_k'] = cfg.PARAMS['calving_k'] - if pygem_prms["sim"]["oggm_dynamics"]["use_reg_glena"]: + 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"] + 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"] + glen_a_multiplier = glena_df.loc[glena_idx, 'glens_a_multiplier'] + fs = glena_df.loc[glena_idx, 'fs'] else: - fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] - glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ - "glen_a_multiplier" + fs = pygem_prms['sim']['oggm_dynamics']['fs'] + glen_a_multiplier = pygem_prms['sim']['oggm_dynamics'][ + 'glen_a_multiplier' ] # CFL number (may use different values for calving to prevent errors) if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_frontalablation"] + 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" + 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 ----- @@ -303,10 +303,10 @@ def reg_calving_flux( # ----- CALVING ----- # Number of years (for OGGM's run_until_and_store) - if pygem_prms["time"]["timestep"] == "monthly": + if pygem_prms['time']['timestep'] == 'monthly': nyears = int(dates_table.shape[0] / 12) else: - assert True == False, "Adjust nyears for non-monthly timestep" + assert True == False, 'Adjust nyears for non-monthly timestep' mb_years = np.arange(nyears) # Perform inversion @@ -318,39 +318,39 @@ def reg_calving_flux( ) tasks.prepare_for_inversion(gdir) tasks.mass_conservation_inversion( - gdir, glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, fs=fs + 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, + 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" + gdir, fl_str='model_flowlines' ) # add debris enhancement factors to flowlines - nfls = gdir.read_pickle("model_flowlines") + nfls = gdir.read_pickle('model_flowlines') # Mass balance model mbmod = PyGEMMassBalance( gdir, modelprms, glacier_rgi_table, fls=nfls, option_areaconstant=True ) # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle("inversion_input")[-1] - th = cls["hgt"][-1] - vmin, vmax = cfg.PARAMS["free_board_marine_terminating"] + cls = gdir.read_pickle('inversion_input')[-1] + th = cls['hgt'][-1] + vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] water_level = utils.clip_scalar(0, th - vmax, th - vmin) ev_model = FluxBasedModel( nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + glen_a=cfg.PARAMS['glen_a'] * glen_a_multiplier, fs=fs, is_tidewater=gdir.is_tidewater, water_level=water_level, @@ -369,8 +369,8 @@ def reg_calving_flux( # 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"] + * 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] = ( @@ -393,55 +393,55 @@ def reg_calving_flux( # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward["calving_flux"] = ( + 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"] + out_calving_forward['calving_flux'] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward["calving_front_thick"] = thick[last_idx] + out_calving_forward['calving_front_thick'] = thick[last_idx] # Record in dataframe - output_df.loc[nglac, "calving_flux_Gta"] = calving_flux_Gta - output_df.loc[nglac, "calving_thick"] = out_calving_forward[ - "calving_front_thick" + output_df.loc[nglac, '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 + output_df.loc[nglac, 'no_errors'] = 1 + output_df.loc[nglac, 'oggm_dynamics'] = 1 if args.verbose or debug: print( - "OGGM dynamics, calving_k:", + 'OGGM dynamics, calving_k:', np.round(calving_k, 4), - "glen_a:", + 'glen_a:', np.round(glen_a_multiplier, 2), ) print( - " calving front thickness [m]:", - np.round(out_calving_forward["calving_front_thick"], 1), + ' calving front thickness [m]:', + np.round(out_calving_forward['calving_front_thick'], 1), ) print( - " calving flux model [Gt/yr]:", + ' 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") + 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, + glen_a=cfg.PARAMS['glen_a'] * glen_a_multiplier, fs=fs, is_tidewater=gdir.is_tidewater, water_level=water_level, @@ -463,8 +463,8 @@ def reg_calving_flux( calving_flux_km3a = ( ev_model.mb_model.glac_wide_frontalablation.sum() - * pygem_prms["constants"]["density_water"] - / pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_water'] + / pygem_prms['constants']['density_ice'] / nyears / 1e9 ) @@ -478,38 +478,38 @@ def reg_calving_flux( # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward["calving_flux"] = calving_flux_km3a + out_calving_forward['calving_flux'] = calving_flux_km3a # calving flux (Gt/yr) calving_flux_Gta = ( - out_calving_forward["calving_flux"] - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + out_calving_forward['calving_flux'] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward["calving_front_thick"] = thick[last_idx] + out_calving_forward['calving_front_thick'] = thick[last_idx] # Record in dataframe - output_df.loc[nglac, "calving_flux_Gta"] = calving_flux_Gta - output_df.loc[nglac, "calving_thick"] = out_calving_forward[ - "calving_front_thick" + output_df.loc[nglac, '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, 'no_errors'] = 1 if args.verbose or debug: print( - "Mass Redistribution curve, calving_k:", + 'Mass Redistribution curve, calving_k:', np.round(calving_k, 1), - "glen_a:", + 'glen_a:', np.round(glen_a_multiplier, 2), ) print( - " calving front thickness [m]:", - np.round(out_calving_forward["calving_front_thick"], 0), + ' calving front thickness [m]:', + np.round(out_calving_forward['calving_front_thick'], 0), ) print( - " calving flux model [Gt/yr]:", + ' calving flux model [Gt/yr]:', np.round(calving_flux_Gta, 5), ) @@ -531,12 +531,12 @@ def reg_calving_flux( 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"] + * 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 + 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 @@ -554,29 +554,29 @@ def reg_calving_flux( # 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:", + ' 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"] = ( + 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"], + ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors'], ] = (np.nan, np.nan, np.nan, 0) # Remove glaciers that failed to run if fa_glac_data_reg is None: reg_calving_gta_obs_good = None - output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) + output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() elif ignore_nan: - output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) + output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() rgiids_data = list(fa_glac_data_reg.RGIId.values) rgiids_mod = list(output_df_good.RGIId.values) @@ -618,7 +618,7 @@ def run_opt_fa( 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 @@ -626,7 +626,7 @@ def run_opt_fa( if reg_calving_gta_mod < reg_calving_gta_obs: if args.verbose: - print("\nincrease calving_k") + print('\nincrease calving_k') # print('reg_calving_gta_mod:', reg_calving_gta_mod) # print('reg_calving_gta_obs:', reg_calving_gta_obs) @@ -649,7 +649,7 @@ def run_opt_fa( reg_calving_gta_mod_last = reg_calving_gta_mod.copy() if args.verbose: - print(" increase calving_k_step:", calving_k_step) + print(' increase calving_k_step:', calving_k_step) # Increase calving k calving_k += calving_k_step @@ -667,8 +667,8 @@ def run_opt_fa( 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 @@ -679,21 +679,21 @@ def run_opt_fa( else: if args.verbose: - print("\ndecrease calving_k") - print("-----") - print("reg_calving_gta_mod:", reg_calving_gta_mod) - print("reg_calving_gta_obs:", reg_calving_gta_obs) - print("calving_k:", calving_k) - print("calving_k_bndlow:", calving_k_bndlow) + print('\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:", + '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"]) + 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 @@ -703,7 +703,7 @@ def run_opt_fa( > 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"]): + ) 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() @@ -724,8 +724,8 @@ def run_opt_fa( 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 @@ -735,14 +735,14 @@ def run_opt_fa( reg_calving_gta_mod_bndhigh = reg_calving_gta_mod_last if args.verbose: - print("bnds:", calving_k_bndlow, calving_k_bndhigh) + print('bnds:', calving_k_bndlow, calving_k_bndhigh) print( - "bnds gt/yr:", reg_calving_gta_mod_bndlow, reg_calving_gta_mod_bndhigh + '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) @@ -763,10 +763,10 @@ def run_opt_fa( calc_mb_geo_correction=calc_mb_geo_correction, ) if args.verbose: - print("upper bound:") - print(" calving_k:", np.round(calving_k, 4)) - print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) - print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + print('upper bound:') + print(' calving_k:', np.round(calving_k, 4)) + print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs, 4)) + print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod, 4)) # Check if lower bound causes good fit elif ( @@ -786,10 +786,10 @@ def run_opt_fa( calc_mb_geo_correction=calc_mb_geo_correction, ) if args.verbose: - print("lower bound:") - print(" calving_k:", np.round(calving_k, 4)) - print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) - print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + print('lower bound:') + print(' calving_k:', np.round(calving_k, 4)) + print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs, 4)) + print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod, 4)) else: # Calibrate between limited range @@ -815,7 +815,7 @@ def run_opt_fa( ): nround += 1 if args.verbose: - print("\nRound", nround) + print('\nRound', nround) # Update calving_k using midpoint calving_k = (calving_k_bndlow + calving_k_bndhigh) / 2 output_df, reg_calving_gta_mod, reg_calving_gta_obs = reg_calving_flux( @@ -827,9 +827,9 @@ def run_opt_fa( 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: @@ -854,30 +854,30 @@ def run_opt_fa( return output_df, calving_k -def merge_data(frontalablation_fp="", overwrite=False, verbose=False): - frontalablation_fn1 = "Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv" - frontalablation_fn2 = "frontalablation_glacier_data_minowa2021.csv" - frontalablation_fn3 = "frontalablation_glacier_data_osmanoglu.csv" - out_fn = frontalablation_fn1.replace(".csv", "-w17_19.csv") +def merge_data(frontalablation_fp='', overwrite=False, verbose=False): + frontalablation_fn1 = 'Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv' + frontalablation_fn2 = 'frontalablation_glacier_data_minowa2021.csv' + frontalablation_fn3 = 'frontalablation_glacier_data_osmanoglu.csv' + out_fn = frontalablation_fn1.replace('.csv', '-w17_19.csv') if os.path.isfile(frontalablation_fp + out_fn) and not overwrite: if verbose: print( - f"Combined frontal ablation dataset already exists, pass `-o` to overwrite: {frontalablation_fp + out_fn}" + 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", + '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 @@ -890,47 +890,47 @@ def merge_data(frontalablation_fp="", overwrite=False, verbose=False): 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['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_unc'] = fa_glac_data1[ + 'Frontal_ablation_2000_to_2020_gt_per_yr_mean_err' ] - fa_data_df1["Romain_gta_mbtot"] = fa_glac_data1["Romain_gta_mbtot"] - fa_data_df1["Romain_gta_mbclim"] = fa_glac_data1["Romain_gta_mbclim"] - fa_data_df1["Romain_mwea_mbtot"] = fa_glac_data1["Romain_mwea_mbtot"] - fa_data_df1["Romain_mwea_mbclim"] = fa_glac_data1["Romain_mwea_mbclim"] - fa_data_df1["thick_measured_yn"] = fa_glac_data1["thick_measured_yn"] - fa_data_df1["start_date"] = "20009999" - fa_data_df1["end_date"] = "20199999" - fa_data_df1["source"] = "Kochtitzky et al." + fa_data_df1['Romain_gta_mbtot'] = fa_glac_data1['Romain_gta_mbtot'] + fa_data_df1['Romain_gta_mbclim'] = fa_glac_data1['Romain_gta_mbclim'] + fa_data_df1['Romain_mwea_mbtot'] = fa_glac_data1['Romain_mwea_mbtot'] + fa_data_df1['Romain_mwea_mbclim'] = fa_glac_data1['Romain_mwea_mbclim'] + fa_data_df1['thick_measured_yn'] = fa_glac_data1['thick_measured_yn'] + fa_data_df1['start_date'] = '20009999' + fa_data_df1['end_date'] = '20199999' + fa_data_df1['source'] = 'Kochtitzky et al.' # Minowa data fa_data_df2 = pd.DataFrame( np.zeros((fa_glac_data2.shape[0], len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset, ) - fa_data_df2["RGIId"] = fa_glac_data2["RGIId"] - fa_data_df2["fa_gta_obs"] = fa_glac_data2["frontal_ablation_Gta"] - fa_data_df2["fa_gta_obs_unc"] = fa_glac_data2["frontal_ablation_unc_Gta"] - fa_data_df2["start_date"] = fa_glac_data2["start_date"] - fa_data_df2["end_date"] = fa_glac_data2["end_date"] - fa_data_df2["source"] = fa_glac_data2["Source"] - fa_data_df2.sort_values("RGIId", inplace=True) + fa_data_df2['RGIId'] = fa_glac_data2['RGIId'] + fa_data_df2['fa_gta_obs'] = fa_glac_data2['frontal_ablation_Gta'] + fa_data_df2['fa_gta_obs_unc'] = fa_glac_data2['frontal_ablation_unc_Gta'] + fa_data_df2['start_date'] = fa_glac_data2['start_date'] + fa_data_df2['end_date'] = fa_glac_data2['end_date'] + fa_data_df2['source'] = fa_glac_data2['Source'] + fa_data_df2.sort_values('RGIId', inplace=True) # Osmanoglu data fa_data_df3 = pd.DataFrame( np.zeros((fa_glac_data3.shape[0], len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset, ) - fa_data_df3["RGIId"] = fa_glac_data3["RGIId"] - fa_data_df3["fa_gta_obs"] = fa_glac_data3["frontal_ablation_Gta"] - fa_data_df3["fa_gta_obs_unc"] = fa_glac_data3["frontal_ablation_unc_Gta"] - fa_data_df3["start_date"] = fa_glac_data3["start_date"] - fa_data_df3["end_date"] = fa_glac_data3["end_date"] - fa_data_df3["source"] = fa_glac_data3["Source"] - fa_data_df3.sort_values("RGIId", inplace=True) + fa_data_df3['RGIId'] = fa_glac_data3['RGIId'] + fa_data_df3['fa_gta_obs'] = fa_glac_data3['frontal_ablation_Gta'] + fa_data_df3['fa_gta_obs_unc'] = fa_glac_data3['frontal_ablation_unc_Gta'] + fa_data_df3['start_date'] = fa_glac_data3['start_date'] + fa_data_df3['end_date'] = fa_glac_data3['end_date'] + fa_data_df3['source'] = fa_glac_data3['Source'] + fa_data_df3.sort_values('RGIId', inplace=True) # Concatenate datasets dfs = [fa_data_df1, fa_data_df2, fa_data_df3] @@ -940,7 +940,7 @@ def merge_data(frontalablation_fp="", overwrite=False, verbose=False): fa_data_df.to_csv(frontalablation_fp + out_fn, index=False) if verbose: print( - f"Combined frontal ablation dataset exported: {frontalablation_fp + out_fn}" + f'Combined frontal ablation dataset exported: {frontalablation_fp + out_fn}' ) return out_fn @@ -948,18 +948,18 @@ def merge_data(frontalablation_fp="", overwrite=False, verbose=False): def calib_ind_calving_k( regions, args=None, - frontalablation_fp="", - frontalablation_fn="", - output_fp="", - hugonnet2021_fp="", + 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) @@ -968,29 +968,29 @@ def calib_ind_calving_k( for reg in [regions]: # skip over any regions we don't have data for - if reg not in fa_glac_data["O1Region"].values.tolist(): + if reg not in fa_glac_data['O1Region'].values.tolist(): continue - output_fn = str(reg) + "-frontalablation_cal_ind.csv" + output_fn = str(reg) + '-frontalablation_cal_ind.csv' # Regional data - fa_glac_data_reg = fa_glac_data.loc[fa_glac_data["O1Region"] == reg, :].copy() + fa_glac_data_reg = fa_glac_data.loc[fa_glac_data['O1Region'] == reg, :].copy() fa_glac_data_reg.reset_index(inplace=True, drop=True) - fa_glac_data_reg["glacno"] = "" + fa_glac_data_reg['glacno'] = '' for nglac, rgiid in enumerate(fa_glac_data_reg.RGIId): # Avoid regional data and observations from multiple RGIIds (len==14) if ( - not fa_glac_data_reg.loc[nglac, "RGIId"] == "all" - and len(fa_glac_data_reg.loc[nglac, "RGIId"]) == 14 + 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[ + fa_glac_data_reg.loc[nglac, 'glacno'] = rgiid[ -8: ] # (str(int(rgiid.split('-')[1].split('.')[0])) + '.' + # rgiid.split('-')[1].split('.')[1]) # Drop observations that aren't of individual glaciers - fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=["glacno"]) + fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=['glacno']) fa_glac_data_reg.reset_index(inplace=True, drop=True) reg_calving_gta_obs = fa_glac_data_reg[frontal_ablation_Gta_cn].sum() @@ -1001,15 +1001,15 @@ def calib_ind_calving_k( # Tidewater glaciers termtype_list = [1, 5] main_glac_rgi = main_glac_rgi_all.loc[ - main_glac_rgi_all["TermType"].isin(termtype_list) + 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 = 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) @@ -1019,41 +1019,41 @@ def calib_ind_calving_k( mb_clim_reg_3std_min = mb_clim_reg_avg - 3 * mb_clim_reg_std if verbose: print( - "mb_clim_reg_avg:", + '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)) + 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: 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", + 'RGIId', + 'calving_k', + 'calving_k_nmad', + 'calving_thick', + 'calving_flux_Gta', + 'fa_gta_obs', + 'fa_gta_obs_unc', + 'fa_gta_max', + 'calving_flux_Gta_bndlow', + 'calving_flux_Gta_bndhigh', + 'no_errors', + 'oggm_dynamics', + 'mb_clim_gta', + 'mb_total_gta', + 'mb_clim_mwea', + 'mb_total_mwea', ] output_df_all = pd.DataFrame( np.zeros((main_glac_rgi.shape[0], len(output_cns))), columns=output_cns ) - output_df_all["RGIId"] = main_glac_rgi.RGIId - output_df_all["calving_k_nmad"] = 0.0 + output_df_all['RGIId'] = main_glac_rgi.RGIId + output_df_all['calving_k_nmad'] = 0.0 # Load observations fa_obs_dict = dict( @@ -1068,43 +1068,43 @@ def calib_ind_calving_k( # 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( + output_df_all['fa_gta_obs'] = output_df_all['RGIId'].map(fa_obs_dict) + output_df_all['fa_gta_obs_unc'] = output_df_all['RGIId'].map( fa_obs_unc_dict ) # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) - output_df_all["area_km2"] = output_df_all["RGIId"].map(rgi_area_dict) + output_df_all['area_km2'] = output_df_all['RGIId'].map(rgi_area_dict) # ----- LOAD DATA ON MB_CLIM CORRECTED FOR FRONTAL ABLATION ----- # use this to assess reasonableness of results and see if calving_k values affected fa_rgiids_list = list(fa_glac_data_reg.RGIId) - output_df_all["mb_total_gta_obs"] = np.nan - output_df_all["mb_clim_gta_obs"] = np.nan - output_df_all["mb_total_mwea_obs"] = np.nan - output_df_all["mb_clim_mwea_obs"] = np.nan + output_df_all['mb_total_gta_obs'] = np.nan + output_df_all['mb_clim_gta_obs'] = np.nan + output_df_all['mb_total_mwea_obs'] = np.nan + output_df_all['mb_clim_mwea_obs'] = np.nan # output_df_all['thick_measured_yn'] = np.nan for nglac, rgiid in enumerate(list(output_df_all.RGIId)): fa_idx = fa_rgiids_list.index(rgiid) - output_df_all.loc[nglac, "mb_total_gta_obs"] = fa_glac_data_reg.loc[ - fa_idx, "Romain_gta_mbtot" + output_df_all.loc[nglac, 'mb_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_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_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, 'mb_clim_mwea_obs'] = fa_glac_data_reg.loc[ + fa_idx, 'Romain_mwea_mbclim' ] # output_df_all.loc[nglac, 'thick_measured_yn'] = fa_glac_data_reg.loc[fa_idx, 'thick_measured_yn'] # ----- CORRECT TOO POSITIVE CLIMATIC MASS BALANCES ----- - output_df_all["mb_clim_gta"] = output_df_all["mb_clim_gta_obs"] - output_df_all["mb_total_gta"] = output_df_all["mb_total_gta_obs"] - output_df_all["mb_clim_mwea"] = output_df_all["mb_clim_mwea_obs"] - output_df_all["mb_total_mwea"] = output_df_all["mb_total_mwea_obs"] - output_df_all["fa_gta_max"] = output_df_all["fa_gta_obs"] + output_df_all['mb_clim_gta'] = output_df_all['mb_clim_gta_obs'] + output_df_all['mb_total_gta'] = output_df_all['mb_total_gta_obs'] + output_df_all['mb_clim_mwea'] = output_df_all['mb_clim_mwea_obs'] + output_df_all['mb_total_mwea'] = output_df_all['mb_total_mwea_obs'] + output_df_all['fa_gta_max'] = output_df_all['fa_gta_obs'] output_df_badmbclim = output_df_all.loc[ output_df_all.mb_clim_mwea_obs > mb_clim_reg_3std @@ -1117,21 +1117,21 @@ def calib_ind_calving_k( 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 @@ -1142,7 +1142,7 @@ def calib_ind_calving_k( # Select individual glacier 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, : @@ -1150,7 +1150,7 @@ def calib_ind_calving_k( fa_glac_data_ind.reset_index(inplace=True, drop=True) # Update the data - fa_gta_max = output_df_all.loc[nglac, "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 @@ -1190,53 +1190,53 @@ def calib_ind_calving_k( reg_calving_gta_mod_bndlow = None # Record bounds - output_df_all.loc[nglac, "calving_flux_Gta_bndlow"] = ( + output_df_all.loc[nglac, 'calving_flux_Gta_bndlow'] = ( reg_calving_gta_mod_bndlow ) - output_df_all.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + output_df_all.loc[nglac, 'calving_flux_Gta_bndhigh'] = ( reg_calving_gta_mod_bndhigh ) if verbose: - print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) - print(" fa_model_bndlow [Gt/yr] :", reg_calving_gta_mod_bndlow) + print(' fa_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 + ' 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_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_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, '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, '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, '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_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_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, '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, '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, 'oggm_dynamics'] = ( + output_df_bndhigh.loc[0, 'oggm_dynamics'] ) else: run_opt = True @@ -1254,32 +1254,32 @@ def calib_ind_calving_k( 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_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_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, '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, '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, 'oggm_dynamics'] = output_df.loc[ + 0, 'oggm_dynamics' ] # ----- ADD UNCERTAINTY ----- # Upper uncertainty if verbose: - print("\n\n----- upper uncertainty:") + 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.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_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( @@ -1295,22 +1295,22 @@ def calib_ind_calving_k( if verbose: print( - "calving_k:", + '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), + '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:") + 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_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( @@ -1318,13 +1318,13 @@ def calib_ind_calving_k( ) if verbose: print( - "calving_k:", + 'calving_k:', np.round(calving_k_nmadlow, 2), - "fa_data low:", + '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( @@ -1340,14 +1340,14 @@ def calib_ind_calving_k( calving_k_nmadlow = np.copy(calving_k) if verbose: print( - "calving_k:", + 'calving_k:', np.round(calving_k, 2), - "fa_data low:", + 'fa_data low:', np.round( - fa_glac_data_ind_low.loc[0, "fa_gta_obs"], 4 + fa_glac_data_ind_low.loc[0, 'fa_gta_obs'], 4 ), - "fa_mod low:", - np.round(output_df.loc[0, "calving_flux_Gta"], 4), + 'fa_mod low:', + np.round(output_df.loc[0, 'calving_flux_Gta'], 4), ) calving_k_nmad = np.mean( @@ -1359,17 +1359,17 @@ def calib_ind_calving_k( # Final if verbose: - print("----- final -----") + print('----- final -----') print( rgiid, - "calving_k (med/high/low/nmad):", + '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 + output_df_all.loc[nglac, 'calving_k_nmad'] = calving_k_nmad except: failed_glacs.append(glacier_str) pass @@ -1384,9 +1384,9 @@ def calib_ind_calving_k( # Write list of failed glaciers if len(failed_glacs) > 0: - with open(output_fp + output_fn[:-4] + "-failed.txt", "w") as f: + 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) @@ -1395,12 +1395,12 @@ def calib_ind_calving_k( # 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['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['fa_gta_obs'] == output_df_all['fa_gta_max']) + & (output_df_all['calving_k'] < calving_k_bndhigh_set), :, ] @@ -1409,19 +1409,19 @@ def calib_ind_calving_k( calving_k_reg_mean = output_df_all_good.calving_k.mean() if verbose: print( - " calving_k mean/med:", + ' 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" + 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() + output_df_all['calving_k_rnd1'] = output_df_all['calving_k'].copy() # ----- PLOT RESULTS FOR EACH GLACIER ----- if len(rgiids_good) > 0: - with np.errstate(all="ignore"): + with np.errstate(all='ignore'): plot_max_raw = np.max( [ output_df_all_good.calving_flux_Gta.max(), @@ -1443,12 +1443,12 @@ def calib_ind_calving_k( x_min, x_max = plot_min, plot_max fig, ax = plt.subplots( - 2, 2, squeeze=False, gridspec_kw={"wspace": 0.4, "hspace": 0.4} + 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 + glac_area_all = output_df_all_good['area_km2'].values s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] @@ -1456,37 +1456,37 @@ def calib_ind_calving_k( 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", + output_df_all_good['fa_gta_obs'], + output_df_all_good['calving_flux_Gta'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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_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 + [x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1 ) # Log scale - ax[0, 0].set_xscale("log") - ax[0, 0].set_yscale("log") + ax[0, 0].set_xscale('log') + ax[0, 0].set_yscale('log') # Legend - obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] + obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] for nlabel, obs_label in enumerate(obs_labels): ax[0, 0].scatter( [-10], [-10], - color="grey", - marker="o", + color='grey', + marker='o', linewidth=1, - facecolor="none", + facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label, @@ -1494,15 +1494,15 @@ def calib_ind_calving_k( ax[0, 0].text( 0.06, 0.98, - "Area (km$^{2}$)", + 'Area (km$^{2}$)', size=12, - horizontalalignment="left", - verticalalignment="top", + horizontalalignment='left', + verticalalignment='top', transform=ax[0, 0].transAxes, - color="grey", + color='grey', ) leg = ax[0, 0].legend( - loc="upper left", + loc='upper left', ncol=1, fontsize=10, frameon=False, @@ -1510,7 +1510,7 @@ def calib_ind_calving_k( borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor="grey", + labelcolor='grey', ) # ----- Histogram ----- @@ -1521,7 +1521,7 @@ def calib_ind_calving_k( ) hist, bins = np.histogram( output_df_all_good.loc[ - output_df_all_good["no_errors"] == 1, "calving_k" + output_df_all_good['no_errors'] == 1, 'calving_k' ], bins=vn_bins, ) @@ -1529,9 +1529,9 @@ def calib_ind_calving_k( x=vn_bins[:-1] + 0.1 / 2, height=hist, width=(bins[1] - bins[0]), - align="center", - edgecolor="black", - color="grey", + 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) @@ -1550,51 +1550,51 @@ def calib_ind_calving_k( ) # 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", + output_df_all_good['calving_k'], + output_df_all_good['mb_clim_mwea'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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].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", + output_df_all_good['area_km2'], + output_df_all_good['calving_k'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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].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"], + output_df_all_good['area_km2'], + output_df_all_good['calving_k'], ) if verbose: print( - " r_value =", + ' r_value =', np.round(r_value, 2), - "slope = ", + 'slope = ', np.round(slope, 5), - "intercept = ", + 'intercept = ', np.round(intercept, 5), - "p_value = ", + 'p_value = ', np.round(p_value, 6), ) area_min = 0 @@ -1602,20 +1602,20 @@ def calib_ind_calving_k( ax[1, 1].plot( [area_min, area_max], [intercept + slope * area_min, intercept + slope * area_max], - color="k", + color='k', ) # Save figure fig.set_size_inches(6, 6) fig_fullfn = ( - output_fp + str(reg) + "-frontalablation_glac_compare-cal_ind-good.png" + output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind-good.png' ) - fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) + 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" + output_df_all['calving_k'] == calving_k_bndhigh_set, 'RGIId' ].values ) for nglac, rgiid in enumerate(output_df_all.RGIId): @@ -1641,11 +1641,11 @@ def calib_ind_calving_k( ignore_nan=False, ) - output_df_all.loc[nglac, "calving_flux_Gta"] = output_df.loc[ - 0, "calving_flux_Gta" + 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.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 ) @@ -1654,12 +1654,12 @@ def calib_ind_calving_k( # ----- 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") + 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", + rgi_regionsO2='all', + rgi_glac_number='all', include_landterm=False, include_laketerm=False, include_tidewater=True, @@ -1669,7 +1669,7 @@ def calib_ind_calving_k( 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] + glac_no_missing = [x.split('-')[1] for x in rgiids_missing] main_glac_rgi_missing = modelsetup.selectglaciersrgitable( glac_no=glac_no_missing ) @@ -1684,8 +1684,8 @@ def calib_ind_calving_k( 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 @@ -1693,42 +1693,42 @@ def calib_ind_calving_k( 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 + 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( + 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 = 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"] = [ + 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, + 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_mwea_obs'] = output_df_missing[ + 'mb_clim_mwea_obs' ] - output_df_missing["mb_total_gta_obs"] = output_df_missing[ - "mb_total_gta_obs" + output_df_missing['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"] + 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] + glacier_str = rgiid.split('-')[1] try: main_glac_rgi_ind = modelsetup.selectglaciersrgitable( - glac_no=[rgiid.split("-")[1]] + glac_no=[rgiid.split('-')[1]] ) # Estimate frontal ablation for missing glaciers output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( @@ -1744,25 +1744,25 @@ def calib_ind_calving_k( # 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"] + 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):", + 'mb_clim (raw):', np.round( - output_df_missing.loc[nglac, "mb_clim_mwea_obs"], 2 + output_df_missing.loc[nglac, 'mb_clim_mwea_obs'], 2 ), ) print( - "mb_clim (fa_corrected):", + '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_clim (reg 95%):', np.round(mb_clim_reg_95, 2)) print( - "mb_total (95% min):", np.round(mb_clim_reg_3std_min, 2) + 'mb_total (95% min):', np.round(mb_clim_reg_3std_min, 2) ) # Set nmad to median value - correct if value reduced @@ -1770,33 +1770,33 @@ def calib_ind_calving_k( calving_k_nmad_missing = np.median( output_df_all_good.calving_k_nmad ) - output_df_missing.loc[nglac, "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", + '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"] = ( + 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_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_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, + 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: @@ -1804,7 +1804,7 @@ def calib_ind_calving_k( # 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"] + - output_df_missing.loc[nglac, 'mb_clim_mwea_obs'] ) # Reset bounds @@ -1814,13 +1814,13 @@ def calib_ind_calving_k( calving_k_step = np.copy(calving_k_step_set) # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] + 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.loc[0, 'RGIId'] = rgiid_ind # Check bounds bndlow_good = True @@ -1861,29 +1861,29 @@ def calib_ind_calving_k( if verbose: print( - "mb_mwea_fa_asl_lost_bndhigh:", - output_df_bndhigh.loc[0, "mb_mwea_fa_asl_lost"], + '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"], + '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"] = ( + output_df_missing.loc[nglac, 'calving_flux_Gta_bndlow'] = ( reg_calving_gta_mod_bndlow ) - output_df_missing.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + output_df_missing.loc[nglac, 'calving_flux_Gta_bndhigh'] = ( reg_calving_gta_mod_bndhigh ) if verbose: print( - " fa_model_bndlow [Gt/yr] :", + ' fa_model_bndlow [Gt/yr] :', reg_calving_gta_mod_bndlow, ) print( - " fa_model_bndhigh [Gt/yr] :", + ' fa_model_bndhigh [Gt/yr] :', reg_calving_gta_mod_bndhigh, ) @@ -1892,107 +1892,107 @@ def calib_ind_calving_k( if bndhigh_good and bndlow_good: if ( fa_mwea_max - < output_df_bndlow.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" + nglac, 'mb_clim_mwea_obs' ] + output_df_bndlow.loc[ - 0, "mb_mwea_fa_asl_lost" + 0, 'mb_mwea_fa_asl_lost' ] ) # Record output for cn in [ - "calving_k", - "calving_thick", - "calving_flux_Gta", - "no_errors", - "oggm_dynamics", + '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"] = ( + output_df_missing.loc[nglac, 'mb_clim_mwea'] = ( mb_clim_fa_corrected ) - output_df_missing.loc[nglac, "mb_clim_gta"] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] = ( mwea_to_gta( output_df_missing.loc[ - nglac, "mb_clim_mwea" + nglac, 'mb_clim_mwea' ], - output_df_missing.loc[nglac, "area_km2"] + 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, 'mb_total_gta'] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] - output_df_missing.loc[ - nglac, "calving_flux_Gta" + nglac, 'calving_flux_Gta' ] ) output_df_missing.loc[ - nglac, "mb_total_mwea" + nglac, 'mb_total_mwea' ] = gta_to_mwea( output_df_missing.loc[ - nglac, "mb_total_gta" + nglac, 'mb_total_gta' ], - output_df_missing.loc[nglac, "area_km2"] + output_df_missing.loc[nglac, 'area_km2'] * 1e6, ) run_opt = False elif ( - output_df_bndhigh.loc[0, "mb_mwea_fa_asl_lost"] + output_df_bndhigh.loc[0, 'mb_mwea_fa_asl_lost'] == output_df_bndlow.loc[ - 0, "mb_mwea_fa_asl_lost" + 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" + nglac, 'mb_clim_mwea_obs' ] - + output_df.loc[0, "mb_mwea_fa_asl_lost"] + + 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", + '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"] = ( + output_df_missing.loc[nglac, 'mb_clim_mwea'] = ( mb_clim_fa_corrected ) - output_df_missing.loc[nglac, "mb_clim_gta"] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] = ( mwea_to_gta( output_df_missing.loc[ - nglac, "mb_clim_mwea" + nglac, 'mb_clim_mwea' ], - output_df_missing.loc[nglac, "area_km2"] + 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, 'mb_total_gta'] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] - output_df_missing.loc[ - nglac, "calving_flux_Gta" + nglac, 'calving_flux_Gta' ] ) output_df_missing.loc[ - nglac, "mb_total_mwea" + nglac, 'mb_total_mwea' ] = gta_to_mwea( output_df_missing.loc[ - nglac, "mb_total_gta" + nglac, 'mb_total_gta' ], - output_df_missing.loc[nglac, "area_km2"] + output_df_missing.loc[nglac, 'area_km2'] * 1e6, ) run_opt = False @@ -2001,18 +2001,18 @@ def calib_ind_calving_k( # 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('\n\n\n-------') print( - "mb_clim_obs:", + 'mb_clim_obs:', np.round( output_df_missing.loc[ - nglac, "mb_clim_mwea_obs" + nglac, 'mb_clim_mwea_obs' ], 2, ), ) print( - "mb_clim_fa_corrected:", + 'mb_clim_fa_corrected:', np.round(mb_clim_fa_corrected, 2), ) @@ -2021,7 +2021,7 @@ def calib_ind_calving_k( ) / 20 calving_k_next = calving_k - calving_k_step_missing while ( - output_df.loc[0, "mb_mwea_fa_asl_lost"] + output_df.loc[0, 'mb_mwea_fa_asl_lost'] > fa_mwea_max and calving_k_next > 0 ): @@ -2046,51 +2046,51 @@ def calib_ind_calving_k( # 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"] + 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", + '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"] = ( + output_df_missing.loc[nglac, 'mb_clim_mwea'] = ( mb_clim_fa_corrected ) - output_df_missing.loc[nglac, "mb_clim_gta"] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] = ( mwea_to_gta( output_df_missing.loc[ - nglac, "mb_clim_mwea" + nglac, 'mb_clim_mwea' ], - output_df_missing.loc[nglac, "area_km2"] + 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, 'mb_total_gta'] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] - output_df_missing.loc[ - nglac, "calving_flux_Gta" + nglac, 'calving_flux_Gta' ] ) - output_df_missing.loc[nglac, "mb_total_mwea"] = ( + output_df_missing.loc[nglac, 'mb_total_mwea'] = ( gta_to_mwea( output_df_missing.loc[ - nglac, "mb_total_gta" + nglac, 'mb_total_gta' ], - output_df_missing.loc[nglac, "area_km2"] + output_df_missing.loc[nglac, 'area_km2'] * 1e6, ) ) if verbose: print( - "mb_clim_fa_corrected (updated):", + 'mb_clim_fa_corrected (updated):', np.round(mb_clim_fa_corrected, 2), ) @@ -2102,9 +2102,9 @@ def calib_ind_calving_k( ) / 20 calving_k_next = calving_k - calving_k_step_missing while ( - output_df.loc[0, "mb_mwea_fa_asl_lost"] + output_df.loc[0, 'mb_mwea_fa_asl_lost'] > 0.1 - * output_df_missing.loc[nglac, "mb_clim_mwea_obs"] + * output_df_missing.loc[nglac, 'mb_clim_mwea_obs'] and calving_k_next > 0 ): calving_k -= calving_k_step_missing @@ -2126,53 +2126,53 @@ def calib_ind_calving_k( # 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"] + 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", + '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"] = ( + output_df_missing.loc[nglac, 'mb_clim_mwea'] = ( mb_clim_fa_corrected ) - output_df_missing.loc[nglac, "mb_clim_gta"] = ( + 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_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_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"] = ( + 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, + output_df_missing.loc[nglac, 'mb_total_gta'], + output_df_missing.loc[nglac, 'area_km2'] * 1e6, ) ) if verbose: print( - "mb_clim_fa_corrected (updated):", + '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"] + 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"] + output_df_missing.loc[nglac, 'calving_k_nmad'] = ( + output_df_missing.loc[nglac, 'calving_k'] - calving_k_bndlow_set ) except: @@ -2184,10 +2184,10 @@ def calib_ind_calving_k( # Write list of failed glaciers if len(failed_glacs) > 0: with open( - output_fp + output_fn_missing[:-4] + "-failed.txt", "w" + 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) @@ -2209,12 +2209,12 @@ def calib_ind_calving_k( ## print('clim threshold:', np.round(mb{})) # #%% - output_fn_missing = output_fn.replace(".csv", "-missing.csv") + 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", + rgi_regionsO2='all', + rgi_glac_number='all', include_landterm=False, include_laketerm=False, include_tidewater=True, @@ -2223,7 +2223,7 @@ def calib_ind_calving_k( 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] + glac_no_missing = [x.split('-')[1] for x in rgiids_missing] main_glac_rgi_missing = modelsetup.selectglaciersrgitable( glac_no=glac_no_missing ) @@ -2238,8 +2238,8 @@ def calib_ind_calving_k( 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 @@ -2247,50 +2247,50 @@ def calib_ind_calving_k( 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 + 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( + 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 = 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"] = [ + 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, + 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_mwea_obs'] = output_df_missing[ + 'mb_clim_mwea_obs' ] - output_df_missing["mb_total_gta_obs"] = output_df_missing[ - "mb_clim_gta_obs" + output_df_missing['mb_total_gta_obs'] = output_df_missing[ + 'mb_clim_gta_obs' ] # Uncertainty with calving_k based on regional calibration # calving_k_nmad_missing = 1.4826 * median_abs_deviation(output_df_all.calving_k) calving_k_nmad_missing = np.median(output_df_all_good.calving_k_nmad) - output_df_missing["calving_k_nmad"] = calving_k_nmad_missing + 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 # Start with median value calving_k_med = np.median( - output_df_all.loc[output_df_all["O1Region"] == reg, "calving_k"] + 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]] + glac_no=[rgiid.split('-')[1]] ) - area_km2 = main_glac_rgi_ind.loc[0, "Area"] + 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( @@ -2304,39 +2304,39 @@ def calib_ind_calving_k( # 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" + nglac, 'mb_total_mwea_obs' ] mb_fa_mwea = gta_to_mwea( - output_df.loc[0, "calving_flux_Gta"], area_km2 * 1e6 + 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_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", + '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"], + 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"] = ( + 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"], + output_df_missing.loc[nglac, 'mb_total_gta'] = mwea_to_gta( + output_df_missing.loc[nglac, 'mb_total_gta'], area_km2 * 1e6, ) else: @@ -2349,7 +2349,7 @@ def calib_ind_calving_k( if fa_mwea_max < 0: if verbose: print( - "\n too positive, limiting fa_mwea_max to 10% mb_total_mwea" + '\n too positive, limiting fa_mwea_max to 10% mb_total_mwea' ) fa_mwea_max = 0.1 * mb_total_mwea @@ -2360,13 +2360,13 @@ def calib_ind_calving_k( calving_k_step = np.copy(calving_k_step_set) # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] + 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.loc[0, 'RGIId'] = rgiid_ind # Check bounds bndlow_good = True @@ -2406,16 +2406,16 @@ def calib_ind_calving_k( reg_calving_gta_mod_bndlow = None # Record bounds - output_df_missing.loc[nglac, "calving_flux_Gta_bndlow"] = ( + output_df_missing.loc[nglac, 'calving_flux_Gta_bndlow'] = ( reg_calving_gta_mod_bndlow ) - output_df_missing.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + output_df_missing.loc[nglac, 'calving_flux_Gta_bndhigh'] = ( reg_calving_gta_mod_bndhigh ) if verbose: print( - " fa_model_bndlow [mwea] :", + ' fa_model_bndlow [mwea] :', np.round( gta_to_mwea( reg_calving_gta_mod_bndlow, area_km2 * 1e6 @@ -2424,7 +2424,7 @@ def calib_ind_calving_k( ), ) print( - " fa_model_bndhigh [mwea] :", + ' fa_model_bndhigh [mwea] :', np.round( gta_to_mwea( reg_calving_gta_mod_bndhigh, area_km2 * 1e6 @@ -2432,12 +2432,12 @@ def calib_ind_calving_k( 2, ), ) - print(" fa_mwea_cal [mwea]:", np.round(fa_mwea_max, 2)) + print(' fa_mwea_cal [mwea]:', np.round(fa_mwea_max, 2)) if bndhigh_good and bndlow_good: if verbose: - print("\n-------") - print("mb_clim_mwea:", np.round(mb_clim_mwea, 2)) + print('\n-------') + print('mb_clim_mwea:', np.round(mb_clim_mwea, 2)) calving_k_step_missing = ( calving_k_med - calving_k_bndlow @@ -2466,7 +2466,7 @@ def calib_ind_calving_k( ) mb_fa_mwea = gta_to_mwea( - output_df.loc[0, "calving_flux_Gta"], + output_df.loc[0, 'calving_flux_Gta'], area_km2 * 1e6, ) @@ -2475,19 +2475,19 @@ def calib_ind_calving_k( if verbose: print( calving_k, - "mb_fa_mwea:", + 'mb_fa_mwea:', np.round(mb_fa_mwea, 2), - "mb_fa_mwea_max:", + '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", + 'calving_k', + 'calving_thick', + 'calving_flux_Gta', + 'no_errors', + 'oggm_dynamics', ]: output_df_missing.loc[nglac, cn] = output_df.loc[ 0, cn @@ -2495,50 +2495,50 @@ def calib_ind_calving_k( 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_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%):", + '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", + '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"] = ( + output_df_missing.loc[nglac, 'mb_clim_mwea'] = ( mb_clim_mwea ) - output_df_missing.loc[nglac, "mb_clim_gta"] = ( + output_df_missing.loc[nglac, 'mb_clim_gta'] = ( mwea_to_gta( - output_df_missing.loc[nglac, "mb_clim_mwea"], + output_df_missing.loc[nglac, 'mb_clim_mwea'], area_km2 * 1e6, ) ) - output_df_missing.loc[nglac, "mb_total_mwea"] = ( + output_df_missing.loc[nglac, 'mb_total_mwea'] = ( mb_total_mwea ) - output_df_missing.loc[nglac, "mb_total_gta"] = ( + output_df_missing.loc[nglac, 'mb_total_gta'] = ( mwea_to_gta( - output_df_missing.loc[nglac, "mb_total_mwea"], + 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"] + 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"] + output_df_missing.loc[nglac, 'calving_k_nmad'] = ( + output_df_missing.loc[nglac, 'calving_k'] - calving_k_bndlow_set ) @@ -2577,7 +2577,7 @@ def calib_ind_calving_k( output_df_missing = pd.read_csv(output_fp + output_fn_missing) # ----- PLOT RESULTS FOR EACH GLACIER ----- - with np.errstate(all="ignore"): + with np.errstate(all='ignore'): plot_max_raw = np.max( [output_df_all.calving_flux_Gta.max(), output_df_all.fa_gta_obs.max()] ) @@ -2593,12 +2593,12 @@ def calib_ind_calving_k( x_min, x_max = plot_min, plot_max fig, ax = plt.subplots( - 2, 2, squeeze=False, gridspec_kw={"wspace": 0.3, "hspace": 0.3} + 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 + glac_area_all = output_df_all['area_km2'].values s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] @@ -2606,37 +2606,37 @@ def calib_ind_calving_k( 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", + output_df_all['fa_gta_obs'], + output_df_all['calving_flux_Gta'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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_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 + [x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1 ) # Log scale - ax[0, 0].set_xscale("log") - ax[0, 0].set_yscale("log") + ax[0, 0].set_xscale('log') + ax[0, 0].set_yscale('log') # Legend - obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] + obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] for nlabel, obs_label in enumerate(obs_labels): ax[0, 0].scatter( [-10], [-10], - color="grey", - marker="o", + color='grey', + marker='o', linewidth=1, - facecolor="none", + facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label, @@ -2644,15 +2644,15 @@ def calib_ind_calving_k( ax[0, 0].text( 0.06, 0.98, - "Area (km$^{2}$)", + 'Area (km$^{2}$)', size=12, - horizontalalignment="left", - verticalalignment="top", + horizontalalignment='left', + verticalalignment='top', transform=ax[0, 0].transAxes, - color="grey", + color='grey', ) leg = ax[0, 0].legend( - loc="upper left", + loc='upper left', ncol=1, fontsize=10, frameon=False, @@ -2660,7 +2660,7 @@ def calib_ind_calving_k( borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor="grey", + labelcolor='grey', ) # ax[0,0].text(1.08, 0.97, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', # transform=ax[0,0].transAxes) @@ -2672,16 +2672,16 @@ def calib_ind_calving_k( # 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"], + 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", + 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) @@ -2700,69 +2700,69 @@ def calib_ind_calving_k( ) # 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", + output_df_all['calving_k'], + output_df_all['mb_clim_mwea'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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].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", + output_df_all['area_km2'], + output_df_all['calving_k'], + color='k', + marker='o', linewidth=1, - facecolor="none", + 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].set_xlabel('Area (km2)', size=12) + ax[1, 1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) # Save figure fig.set_size_inches(6, 6) - fig_fullfn = output_fp + str(reg) + "-frontalablation_glac_compare-cal_ind.png" - fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) + fig_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 + regions=list(range(1, 20)), output_fp='', merged_calving_k_fn='', verbose=False ): # get list of all regional frontal ablation calibration file names - output_reg_fns = sorted(glob.glob(f"{output_fp}/*-frontalablation_cal_ind.csv")) - output_reg_fns = [x.split("/")[-1] for x in output_reg_fns] + output_reg_fns = sorted(glob.glob(f'{output_fp}/*-frontalablation_cal_ind.csv')) + output_reg_fns = [x.split('/')[-1] for x in output_reg_fns] # loop through and merge for nreg, output_fn_reg in enumerate(output_reg_fns): # Load quality controlled frontal ablation data output_df_reg = pd.read_csv(output_fp + output_fn_reg) - if "calving_k_nmad" not in list(output_df_reg.columns): - output_df_reg["calving_k_nmad"] = 0 + if 'calving_k_nmad' not in list(output_df_reg.columns): + output_df_reg['calving_k_nmad'] = 0 if nreg == 0: output_df_all = output_df_reg else: output_df_all = pd.concat([output_df_all, output_df_reg], axis=0) - output_fn_reg_missing = output_fn_reg.replace(".csv", "-missing.csv") + output_fn_reg_missing = output_fn_reg.replace('.csv', '-missing.csv') if os.path.exists(output_fp + output_fn_reg_missing): # Check if second correction exists output_fn_reg_missing_v2 = output_fn_reg_missing.replace( - ".csv", "_wmbtotal_correction.csv" + '.csv', '_wmbtotal_correction.csv' ) if os.path.exists(output_fp + output_fn_reg_missing_v2): output_df_reg_missing = pd.read_csv( @@ -2771,14 +2771,14 @@ def merge_ind_calving_k( else: output_df_reg_missing = pd.read_csv(output_fp + output_fn_reg_missing) - if "calving_k_nmad" not in list(output_df_reg_missing.columns): - output_df_reg_missing["calving_k_nmad"] = 0 + 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 @@ -2786,23 +2786,23 @@ def merge_ind_calving_k( # ----- 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="", + 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" + '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`" + 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: @@ -2810,31 +2810,31 @@ def update_mbdata( mb_rgiids = list(mb_data.rgiid) # Record prior data - mb_data["mb_romain_mwea"] = mb_data["mb_mwea"] - mb_data["mb_romain_mwea_err"] = mb_data["mb_mwea_err"] - mb_data["mb_clim_mwea"] = mb_data["mb_mwea"] - mb_data["mb_clim_mwea_err"] = mb_data["mb_mwea_err"] + mb_data['mb_romain_mwea'] = mb_data['mb_mwea'] + mb_data['mb_romain_mwea_err'] = mb_data['mb_mwea_err'] + mb_data['mb_clim_mwea'] = mb_data['mb_mwea'] + mb_data['mb_clim_mwea_err'] = mb_data['mb_mwea_err'] # Update mass balance data for nglac, rgiid in enumerate(fa_glac_data.RGIId): - O1region = int(rgiid.split("-")[1].split(".")[0]) + O1region = int(rgiid.split('-')[1].split('.')[0]) if O1region in regions: # Update the mass balance data in Romain's file mb_idx = mb_rgiids.index(rgiid) - mb_data.loc[mb_idx, "mb_mwea"] = fa_glac_data.loc[nglac, "mb_total_mwea"] - mb_data.loc[mb_idx, "mb_clim_mwea"] = fa_glac_data.loc[ - nglac, "mb_clim_mwea" + mb_data.loc[mb_idx, 'mb_mwea'] = fa_glac_data.loc[nglac, 'mb_total_mwea'] + mb_data.loc[mb_idx, 'mb_clim_mwea'] = fa_glac_data.loc[ + nglac, 'mb_clim_mwea' ] if verbose: print( rgiid, - "mb_mwea:", - np.round(mb_data.loc[mb_idx, "mb_mwea"], 2), - "mb_clim:", - np.round(mb_data.loc[mb_idx, "mb_clim_mwea"], 2), - "mb_romain:", - np.round(mb_data.loc[mb_idx, "mb_romain_mwea"], 2), + '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 @@ -2843,10 +2843,10 @@ def update_mbdata( # Update gdirs glac_strs = [] for nglac, rgiid in enumerate(fa_glac_data.RGIId): - O1region = int(rgiid.split("-")[1].split(".")[0]) + O1region = int(rgiid.split('-')[1].split('.')[0]) if O1region in regions: # Select subsets of data - glacier_str = rgiid.split("-")[1] + glacier_str = rgiid.split('-')[1] glac_strs.append(glacier_str) # paralllelize func_ = partial( @@ -2859,7 +2859,7 @@ def update_mbdata( # plot calving_k by region -def plot_calving_k_allregions(output_fp=""): +def plot_calving_k_allregions(output_fp=''): fig = plt.figure() gs = fig.add_gridspec(nrows=3, ncols=3, wspace=0.4, hspace=0.4) ax1 = fig.add_subplot(gs[0, 0]) @@ -2877,7 +2877,7 @@ def plot_calving_k_allregions(output_fp=""): if ax not in [ax9]: reg = regions_ordered[nax] - calving_k_fn = str(reg) + "-frontalablation_cal_ind.csv" + calving_k_fn = str(reg) + '-frontalablation_cal_ind.csv' if not os.path.isfile(output_fp + calving_k_fn): continue output_df_all_good = pd.read_csv(output_fp + calving_k_fn) @@ -2896,7 +2896,7 @@ def plot_calving_k_allregions(output_fp=""): # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all_good["area_km2"].values + glac_area_all = output_df_all_good['area_km2'].values s_sizes = [10, 40, 120, 240] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] @@ -2904,25 +2904,25 @@ def plot_calving_k_allregions(output_fp=""): 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", + output_df_all_good['fa_gta_obs'], + output_df_all_good['calving_flux_Gta'], + color='k', + marker='o', linewidth=0.5, - facecolor="none", + 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.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", + horizontalalignment='right', + verticalalignment='bottom', transform=ax.transAxes, ) @@ -2930,11 +2930,11 @@ def plot_calving_k_allregions(output_fp=""): 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.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) + 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 @@ -2976,15 +2976,15 @@ def plot_calving_k_allregions(output_fp=""): # Legend if ax in [ax9]: - obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] + obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] for nlabel, obs_label in enumerate(obs_labels): ax.scatter( [-10], [-10], - color="grey", - marker="o", + color='grey', + marker='o', linewidth=1, - facecolor="none", + facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label, @@ -2992,15 +2992,15 @@ def plot_calving_k_allregions(output_fp=""): ax.text( 0.1, 1.06, - "Area (km$^{2}$)", + 'Area (km$^{2}$)', size=12, - horizontalalignment="left", - verticalalignment="top", + horizontalalignment='left', + verticalalignment='top', transform=ax.transAxes, - color="grey", + color='grey', ) leg = ax.legend( - loc="upper left", + loc='upper left', ncol=1, fontsize=10, frameon=False, @@ -3008,13 +3008,13 @@ def plot_calving_k_allregions(output_fp=""): borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor="grey", + 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.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([]) @@ -3022,25 +3022,25 @@ def plot_calving_k_allregions(output_fp=""): fig.text( 0.5, 0.04, - "Observed frontal ablation (Gt yr$^{-1}$)", + 'Observed frontal ablation (Gt yr$^{-1}$)', fontsize=12, - horizontalalignment="center", - verticalalignment="bottom", + horizontalalignment='center', + verticalalignment='bottom', ) fig.text( 0.04, 0.5, - "Modeled frontal ablation (Gt yr$^{-1}$)", + 'Modeled frontal ablation (Gt yr$^{-1}$)', size=12, - horizontalalignment="center", - verticalalignment="center", + horizontalalignment='center', + verticalalignment='center', rotation=90, ) # Save figure - fig_fn = "allregions_calving_ObsMod.png" + fig_fn = 'allregions_calving_ObsMod.png' fig.set_size_inches(6.5, 5.5) - fig.savefig(output_fp + fig_fn, bbox_inches="tight", dpi=300) + fig.savefig(output_fp + fig_fn, bbox_inches='tight', dpi=300) plt.close(fig) return @@ -3048,70 +3048,70 @@ 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" + description='Calibrate frontal ablation against reference calving datasets and update the reference mass balance data accordingly' ) # add arguments parser.add_argument( - "-rgi_region01", + '-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="+", + help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', + nargs='+', ) parser.add_argument( - "-ref_gcm_name", - action="store", + '-ref_gcm_name', + action='store', type=str, - default=pygem_prms["climate"]["ref_gcm_name"], - help="reference gcm name", + default=pygem_prms['climate']['ref_gcm_name'], + help='reference gcm name', ) parser.add_argument( - "-ref_startyear", - action="store", + '-ref_startyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_startyear"], - help="reference period starting year for calibration (typically 2000)", + default=pygem_prms['climate']['ref_startyear'], + help='reference period starting year for calibration (typically 2000)', ) parser.add_argument( - "-ref_endyear", - action="store", + '-ref_endyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_endyear"], - help="reference period ending year for calibration (typically 2019)", + default=pygem_prms['climate']['ref_endyear'], + help='reference period ending year for calibration (typically 2019)', ) parser.add_argument( - "-hugonnet2021_fn", - action="store", + '-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)", + 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", + '-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)", + 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", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + 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)", + '-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", + '-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('-v', '--verbose', action='store_true', help='Flag for verbose') args = parser.parse_args() args.prms_from_glac_cal = not args.prms_from_reg_priors @@ -3122,13 +3122,13 @@ 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}" + 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) # marge input calving datasets @@ -3174,5 +3174,5 @@ def main(): plot_calving_k_allregions(output_fp=output_fp) -if __name__ == "__main__": +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 7e13dae4..c674562b 100644 --- a/pygem/bin/run/run_calibration_reg_glena.py +++ b/pygem/bin/run/run_calibration_reg_glena.py @@ -64,87 +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", + '-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="+", + 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", + '-ref_gcm_name', + action='store', type=str, - default=pygem_prms["climate"]["ref_gcm_name"], - help="reference gcm name", + default=pygem_prms['climate']['ref_gcm_name'], + help='reference gcm name', ) parser.add_argument( - "-ref_startyear", - action="store", + '-ref_startyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_startyear"], - help="reference period starting year for calibration (typically 2000)", + default=pygem_prms['climate']['ref_startyear'], + help='reference period starting year for calibration (typically 2000)', ) parser.add_argument( - "-ref_endyear", - action="store", + '-ref_endyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_endyear"], - help="reference period ending year for calibration (typically 2019)", + default=pygem_prms['climate']['ref_endyear'], + help='reference period ending year for calibration (typically 2019)', ) parser.add_argument( - "-rgi_glac_number_fn", - action="store", + '-rgi_glac_number_fn', + action='store', type=str, default=None, - help="Filename containing list of rgi_glac_number, helpful for running batches on spc", + help='Filename containing list of rgi_glac_number, helpful for running batches on spc', ) parser.add_argument( - "-rgi_glac_number", - action="store", + '-rgi_glac_number', + action='store', type=float, - default=pygem_prms["setup"]["glac_no"], - nargs="+", - help="Randoph Glacier Inventory glacier number (can take multiple)", + default=pygem_prms['setup']['glac_no'], + nargs='+', + help='Randoph Glacier Inventory glacier number (can take multiple)', ) parser.add_argument( - "-fs", - action="store", + '-fs', + action='store', type=float, - default=pygem_prms["out"]["fs"], - help="Sliding parameter", + default=pygem_prms['out']['fs'], + help='Sliding parameter', ) parser.add_argument( - "-a_multiplier", - action="store", + '-a_multiplier', + action='store', type=float, - default=pygem_prms["out"]["glen_a_multiplier"], - help="Glen’s creep parameter A multiplier", + default=pygem_prms['out']['glen_a_multiplier'], + help='Glen’s creep parameter A multiplier', ) parser.add_argument( - "-a_multiplier_bndlow", - action="store", + '-a_multiplier_bndlow', + action='store', type=float, default=0.1, - help="Glen’s creep parameter A multiplier, low bound (default 0.1)", + help='Glen’s creep parameter A multiplier, low bound (default 0.1)', ) parser.add_argument( - "-a_multiplier_bndhigh", - action="store", + '-a_multiplier_bndhigh', + action='store', type=float, default=10, - help="Glen’s creep parameter A multiplier, upper bound (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)", + '-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('-v', '--debug', action='store_true', help='Flag for debugging') return parser @@ -178,33 +178,33 @@ def plot_nfls_section(nfls): axh = fig.add_axes(posax, frameon=False) axh.hist( - height, orientation="horizontal", range=ylim, bins=20, alpha=0.3, weights=area + height, orientation='horizontal', range=ylim, bins=20, alpha=0.3, weights=area ) axh.invert_xaxis() axh.xaxis.tick_top() - axh.set_xlabel("Area incl. tributaries (km$^2$)") - axh.xaxis.set_label_position("top") + axh.set_xlabel('Area incl. tributaries (km$^2$)') + axh.xaxis.set_label_position('top') axh.set_ylim(ylim) - axh.yaxis.set_ticks_position("right") + axh.yaxis.set_ticks_position('right') axh.set_yticks([]) - axh.axhline(y=ylim[1], color="black", alpha=1) # qick n dirty trick + axh.axhline(y=ylim[1], color='black', alpha=1) # qick n dirty trick # plot Centerlines cls = nfls[-1] x = np.arange(cls.nx) * cls.dx * cls.map_dx # Plot the bed - ax.plot(x, cls.bed_h, color="k", linewidth=2.5, label="Bed (Parab.)") + ax.plot(x, cls.bed_h, color='k', linewidth=2.5, label='Bed (Parab.)') # Where trapezoid change color - if hasattr(cls, "_do_trapeze") and cls._do_trapeze: + if hasattr(cls, '_do_trapeze') and cls._do_trapeze: bed_t = cls.bed_h * np.nan pt = cls.is_trapezoid & (~cls.is_rectangular) bed_t[pt] = cls.bed_h[pt] - ax.plot(x, bed_t, color="rebeccapurple", linewidth=2.5, label="Bed (Trap.)") + ax.plot(x, bed_t, color='rebeccapurple', linewidth=2.5, label='Bed (Trap.)') bed_t = cls.bed_h * np.nan bed_t[cls.is_rectangular] = cls.bed_h[cls.is_rectangular] - ax.plot(x, bed_t, color="crimson", linewidth=2.5, label="Bed (Rect.)") + ax.plot(x, bed_t, color='crimson', linewidth=2.5, label='Bed (Rect.)') # Plot glacier def surf_to_nan(surf_h, thick): @@ -216,14 +216,14 @@ def surf_to_nan(surf_h, thick): return surf_h surfh = surf_to_nan(cls.surface_h, cls.thick) - ax.plot(x, surfh, color="#003399", linewidth=2, label="Glacier") + ax.plot(x, surfh, color='#003399', linewidth=2, label='Glacier') ax.set_ylim(ylim) - ax.spines["top"].set_color("none") - ax.xaxis.set_ticks_position("bottom") - ax.set_xlabel("Distance along flowline (m)") - ax.set_ylabel("Altitude (m)") + ax.spines['top'].set_color('none') + ax.xaxis.set_ticks_position('bottom') + ax.set_xlabel('Distance along flowline (m)') + ax.set_ylabel('Altitude (m)') # Legend handles, labels = ax.get_legend_handles_labels() @@ -252,17 +252,17 @@ def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False) tasks.prepare_for_inversion(gdir) tasks.mass_conservation_inversion( - gdir, glen_a=cfg.PARAMS["glen_a"] * a_multiplier, fs=fs + 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") + 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: + 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 + pickle.load(f) / pygem_prms['constants']['density_ice'] / 1e9 ) reg_vol_km3_consensus += consensus_km3 @@ -270,8 +270,8 @@ def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False) if debug: plot_nfls_section(nfls) - print("\n\n Modeled vol [km3]: ", nfls[0].volume_km3) - print(" Consensus vol [km3]:", consensus_km3, "\n\n") + print('\n\n Modeled vol [km3]: ', nfls[0].volume_km3) + print(' Consensus vol [km3]:', consensus_km3, '\n\n') return reg_vol_km3_modeled, reg_vol_km3_consensus @@ -289,61 +289,61 @@ def main(): # Calibrate each region for reg in args.rgi_region01: - print("Region:", reg) + print('Region:', reg) # ===== LOAD GLACIERS ===== main_glac_rgi_all = modelsetup.selectglaciersrgitable( rgi_regionsO1=[reg], - rgi_regionsO2="all", - rgi_glac_number="all", + 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 = 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() + 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"] + > 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 + '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", + 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.", + '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"], + spinupyears=pygem_prms['climate']['ref_spinupyears'], + option_wateryear=pygem_prms['climate']['ref_wateryear'], ) # ===== LOAD CLIMATE DATA ===== # Climate class gcm_name = args.ref_gcm_name - assert gcm_name in ["ERA5", "ERA-Interim"], ( - "Error: Calibration not set up for " + gcm_name + assert gcm_name in ['ERA5', 'ERA-Interim'], ( + 'Error: Calibration not set up for ' + gcm_name ) gcm = class_climate.GCM(name=gcm_name) # Air temperature [degC] gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( gcm.temp_fn, gcm.temp_vn, main_glac_rgi_subset, dates_table ) - if pygem_prms["mbmod"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + 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 ) @@ -364,10 +364,10 @@ def main(): # ===== RUN MASS BALANCE ===== # Number of years (for OGGM's run_until_and_store) - if pygem_prms["time"]["timestep"] == "monthly": + if pygem_prms['time']['timestep'] == 'monthly': nyears = int(dates_table.shape[0] / 12) else: - assert True == False, "Adjust nyears for non-monthly timestep" + assert True == False, 'Adjust nyears for non-monthly timestep' reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 @@ -378,7 +378,7 @@ def main(): 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"]) + glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) if glac % 1000 == 0: print(glacier_str) @@ -388,52 +388,52 @@ def main(): gdir = single_flowline_glacier_directory(glacier_str) # Flowlines - fls = gdir.read_pickle("inversion_flowlines") + fls = gdir.read_pickle('inversion_flowlines') # Add climate data to glacier directory gdir.historical_climate = { - "elev": gcm_elev[glac], - "temp": gcm_temp[glac, :], - "tempstd": gcm_tempstd[glac, :], - "prec": gcm_prec[glac, :], - "lr": gcm_lr[glac, :], + '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_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + 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." + glacier_str + ' calibrated parameters do not exist.' ) - with open(modelprms_fullfn, "r") as f: + 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"] + 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], + 'kp': modelprms_all['kp'][0], + 'tbias': modelprms_all['tbias'][0], + 'ddfsnow': modelprms_all['ddfsnow'][0], + 'ddfice': modelprms_all['ddfice'][0], + 'tsnow_threshold': modelprms_all['tsnow_threshold'][0], + 'precgrad': modelprms_all['precgrad'][0], } # ----- ICE THICKNESS INVERSION using OGGM ----- # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms["mbmod"]["include_debris"]: + if pygem_prms['mbmod']['include_debris']: inversion_filter = True else: inversion_filter = False @@ -460,9 +460,9 @@ def main(): mbmods.append(mbmod_inv) gdirs.append(gdir) except: - print(glacier_str + " failed - likely no gdir") + 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( @@ -492,11 +492,11 @@ def main(): 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) + 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 @@ -538,19 +538,19 @@ def to_minimize(a_multiplier): ) print( - "\n\nOptimized:\n glens_a_multiplier:", np.round(a_multiplier_opt, 3) + '\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) + 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", + '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, :] = [ @@ -564,7 +564,7 @@ def to_minimize(a_multiplier): try: glena_df = pd.read_csv( - f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}" + f'{pygem_prms["root"]}/{pygem_prms["out"]["glena_reg_relpath"]}' ) # Add or overwrite existing file @@ -578,17 +578,17 @@ def to_minimize(a_multiplier): glena_df = glena_df_single except Exception as err: - print(f"Error saving results: {err}") + print(f'Error saving results: {err}') - glena_df = glena_df.sort_values("O1Region", ascending=True) + 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']}", + f'{pygem_prms["root"]}/{pygem_prms["out"]["glena_reg_relpath"]}', index=False, ) - 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__": +if __name__ == '__main__': main() diff --git a/pygem/bin/run/run_mcmc_priors.py b/pygem/bin/run/run_mcmc_priors.py index 69a0cee8..6333fcb3 100644 --- a/pygem/bin/run/run_mcmc_priors.py +++ b/pygem/bin/run/run_mcmc_priors.py @@ -23,43 +23,43 @@ # 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", + 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", + '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', ] @@ -68,47 +68,47 @@ 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", + '-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="+", + 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", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use", + help='number of simultaneous processes (cores) to use', ) parser.add_argument( - "-option_calibration", - action="store", + '-option_calibration', + action='store', type=str, - default="emulator", + default='emulator', help='calibration option (defaultss to "emulator")', ) parser.add_argument( - "-priors_reg_outpath", - action="store", + '-priors_reg_outpath', + action='store', type=str, - default=pygem_prms["root"] - + "/Output/calibration/" - + pygem_prms["calib"]["priors_reg_fn"], - help="output path", + 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('-v', '--debug', action='store_true', help='Flag for debugging') parser.add_argument( - "-p", "--plot", action="store_true", help="Flag for plotting regional priors" + '-p', '--plot', action='store_true', help='Flag for plotting regional priors' ) return parser -def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=""): +def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): # EXPORT PRIORS if os.path.exists(priors_reg_outpath): priors_df = pd.read_csv(priors_reg_outpath) @@ -124,38 +124,38 @@ def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=""): else: priors_df = priors_df_single - priors_df = priors_df.sort_values(["O1Region", "O2Region"], ascending=[True, True]) + priors_df = priors_df.sort_values(['O1Region', 'O2Region'], ascending=[True, True]) priors_df.reset_index(inplace=True, drop=True) priors_df.to_csv(priors_reg_outpath, index=False) return priors_df -def plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2=""): +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} + 1, 2, figsize=(6, 4), gridspec_kw={'wspace': 0.3, 'hspace': 0.3} ) labelsize = 1 fig.text( 0.5, 0.9, - "Region " + 'Region ' + str(reg) - + " (subregion: " + + ' (subregion: ' + str(regO2) - + ")".replace(" (subregion: )", "(all subregions)"), - ha="center", + + ')'.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)") + 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_fn = str(reg) + '-' + str(regO2) + '_hist_mcmc_priors.png'.replace('-_', '_') fig.savefig(fig_fp + fig_fn, pad_inches=0, dpi=150) @@ -164,23 +164,23 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): nbins = 50 ncols = 3 nrows = int(np.ceil(len(rgi_regionsO2) / ncols)) - priors_df_regO1 = priors_df.loc[priors_df["O1Region"] == reg] + priors_df_regO1 = priors_df.loc[priors_df['O1Region'] == reg] fig, ax = plt.subplots( - nrows, ncols, squeeze=False, gridspec_kw={"wspace": 0.5, "hspace": 0.5} + nrows, ncols, squeeze=False, gridspec_kw={'wspace': 0.5, 'hspace': 0.5} ) nrow = 0 ncol = 0 for nreg, regO2 in enumerate(rgi_regionsO2): - priors_df_regO2 = priors_df_regO1.loc[priors_df["O2Region"] == regO2] - kp_values = main_glac_rgi.loc[main_glac_rgi["O2Region"] == regO2, "kp"].values + priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] + kp_values = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, 'kp'].values nglaciers = kp_values.shape[0] # Plot histogram counts, bins, patches = ax[nrow, ncol].hist( kp_values, - facecolor="grey", - edgecolor="grey", + facecolor='grey', + edgecolor='grey', linewidth=0.1, bins=nbins, density=True, @@ -190,15 +190,15 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 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") + ax[nrow, ncol].plot(bins, rv.pdf(bins), color='k') # add alpha and beta as text gammatext = ( - r"$\alpha$=" + r'$\alpha$=' + str(np.round(alpha, 2)) - + "\n" - + r"$\beta$=" + + '\n' + + r'$\beta$=' + str(np.round(beta, 2)) - + "\n$n$=" + + '\n$n$=' + str(nglaciers) ) ax[nrow, ncol].text( @@ -206,20 +206,20 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 0.95, gammatext, size=10, - horizontalalignment="right", - verticalalignment="top", + horizontalalignment='right', + verticalalignment='top', transform=ax[nrow, ncol].transAxes, ) # Subplot title - title_str = reg_dict[reg] + " (" + str(regO2) + ")" + title_str = reg_dict[reg] + ' (' + str(regO2) + ')' ax[nrow, ncol].text( 0.5, 1.01, title_str, size=10, - horizontalalignment="center", - verticalalignment="bottom", + horizontalalignment='center', + verticalalignment='bottom', transform=ax[nrow, ncol].transAxes, ) @@ -234,45 +234,45 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: for nextra in np.arange(0, n_extras): - ax[nrow, ncol].axis("off") + ax[nrow, ncol].axis('off') ncol += 1 # Labels fig.text( 0.04, 0.5, - "Probability Density", - va="center", - ha="center", - rotation="vertical", + '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.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", + 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} + 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] + priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] tbias_values = main_glac_rgi.loc[ - main_glac_rgi["O2Region"] == regO2, "tbias" + 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", + facecolor='grey', + edgecolor='grey', linewidth=0.1, bins=nbins, density=True, @@ -282,15 +282,15 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 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$=" + r'$\mu$=' + str(np.round(mu, 2)) - + "\n" - + r"$\sigma$=" + + '\n' + + r'$\sigma$=' + str(np.round(sigma, 2)) - + "\n$n$=" + + '\n$n$=' + str(nglaciers) ) ax[nrow, ncol].text( @@ -298,20 +298,20 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 0.95, normtext, size=10, - horizontalalignment="right", - verticalalignment="top", + horizontalalignment='right', + verticalalignment='top', transform=ax[nrow, ncol].transAxes, ) # Title - title_str = reg_dict[reg] + " (" + str(regO2) + ")" + title_str = reg_dict[reg] + ' (' + str(regO2) + ')' ax[nrow, ncol].text( 0.5, 1.01, title_str, size=10, - horizontalalignment="center", - verticalalignment="bottom", + horizontalalignment='center', + verticalalignment='bottom', transform=ax[nrow, ncol].transAxes, ) @@ -326,78 +326,78 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: for nextra in np.arange(0, n_extras): - ax[nrow, ncol].axis("off") + ax[nrow, ncol].axis('off') ncol += 1 # Labels fig.text( 0.04, 0.5, - "Probability Density", - va="center", - ha="center", - rotation="vertical", + 'Probability Density', + va='center', + ha='center', + rotation='vertical', size=12, ) - fig.text(0.5, 0.04, r"$T_{bias}$ ($^\circ$C)", va="center", ha="center", size=12) + fig.text(0.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", + 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 + reg, option_calibration='emulator', priors_reg_outpath='', debug=False, plot=False ): # Calibration filepath - modelprms_fp = pygem_prms["root"] + "/Output/calibration/" + str(reg).zfill(2) + "/" + modelprms_fp = pygem_prms['root'] + '/Output/calibration/' + str(reg).zfill(2) + '/' # Load glaciers glac_list = [ - x.split("-")[0] + x.split('-')[0] for x in os.listdir(modelprms_fp) - if x.endswith("-modelprms_dict.json") + if x.endswith('-modelprms_dict.json') ] glac_list = sorted(glac_list) main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_list) # Add model parameters to main dataframe - main_glac_rgi["kp"] = np.nan - main_glac_rgi["tbias"] = np.nan - main_glac_rgi["ddfsnow"] = np.nan - main_glac_rgi["mb_mwea"] = np.nan - main_glac_rgi["kp_em"] = np.nan - main_glac_rgi["tbias_em"] = np.nan - main_glac_rgi["ddfsnow_em"] = np.nan - main_glac_rgi["mb_mwea_em"] = np.nan + main_glac_rgi['kp'] = np.nan + main_glac_rgi['tbias'] = np.nan + main_glac_rgi['ddfsnow'] = np.nan + main_glac_rgi['mb_mwea'] = np.nan + main_glac_rgi['kp_em'] = np.nan + main_glac_rgi['tbias_em'] = np.nan + main_glac_rgi['ddfsnow_em'] = np.nan + main_glac_rgi['mb_mwea_em'] = np.nan for nglac, rgino_str in enumerate(list(main_glac_rgi.rgino_str.values)): - glac_str = str(int(rgino_str.split(".")[0])) + "." + rgino_str.split(".")[1] + glac_str = str(int(rgino_str.split('.')[0])) + '.' + rgino_str.split('.')[1] # Load model parameters - modelprms_fn = glac_str + "-modelprms_dict.json" - with open(modelprms_fp + modelprms_fn, "r") as f: + modelprms_fn = glac_str + '-modelprms_dict.json' + with open(modelprms_fp + modelprms_fn, 'r') as f: modelprms_dict = json.load(f) assert option_calibration in list(modelprms_dict.keys()), ( - f"{glac_str}: {option_calibration} not in calibration data." + 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] + main_glac_rgi.loc[nglac, 'kp'] = modelprms['kp'][0] + main_glac_rgi.loc[nglac, 'tbias'] = modelprms['tbias'][0] + main_glac_rgi.loc[nglac, 'ddfsnow'] = modelprms['ddfsnow'][0] + main_glac_rgi.loc[nglac, 'mb_mwea'] = modelprms['mb_mwea'][0] + main_glac_rgi.loc[nglac, 'mb_obs_mwea'] = modelprms['mb_obs_mwea'][0] # get regional difference between calibrated mb_mwea and observed - main_glac_rgi["mb_dif_obs_cal"] = ( - main_glac_rgi["mb_obs_mwea"] - main_glac_rgi["mb_mwea"] + main_glac_rgi['mb_dif_obs_cal'] = ( + main_glac_rgi['mb_obs_mwea'] - main_glac_rgi['mb_mwea'] ) # define figure output path if plot: - fig_fp = os.path.split(priors_reg_outpath)[0] + "/figs/" + fig_fp = os.path.split(priors_reg_outpath)[0] + '/figs/' os.makedirs(fig_fp, exist_ok=True) # Priors for each subregion @@ -405,17 +405,17 @@ def run( 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['O2Region'] == regO2, : ] if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2) # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset["kp"]) - kp_std = np.std(main_glac_rgi_subset["kp"]) - kp_med = np.median(main_glac_rgi_subset["kp"]) - kp_min = np.min(main_glac_rgi_subset["kp"]) - kp_max = np.max(main_glac_rgi_subset["kp"]) + kp_mean = np.mean(main_glac_rgi_subset['kp']) + kp_std = np.std(main_glac_rgi_subset['kp']) + kp_med = np.median(main_glac_rgi_subset['kp']) + kp_min = np.min(main_glac_rgi_subset['kp']) + kp_max = np.max(main_glac_rgi_subset['kp']) # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: @@ -425,29 +425,29 @@ def run( kp_alpha = kp_mean * kp_beta # Temperature bias - tbias_mean = main_glac_rgi_subset["tbias"].mean() - tbias_std = main_glac_rgi_subset["tbias"].std() - tbias_med = np.median(main_glac_rgi_subset["tbias"]) - tbias_min = np.min(main_glac_rgi_subset["tbias"]) - tbias_max = np.max(main_glac_rgi_subset["tbias"]) + tbias_mean = main_glac_rgi_subset['tbias'].mean() + tbias_std = main_glac_rgi_subset['tbias'].std() + tbias_med = np.median(main_glac_rgi_subset['tbias']) + tbias_min = np.min(main_glac_rgi_subset['tbias']) + tbias_max = np.max(main_glac_rgi_subset['tbias']) # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 if debug: - print("\n", reg, "(" + str(regO2) + ")") + print('\n', reg, '(' + str(regO2) + ')') print( - "kp (mean/std/med/min/max):", + '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(' alpha/beta:', np.round(kp_alpha, 2), np.round(kp_beta, 2)) print( - "tbias (mean/std/med/min/max):", + 'tbias (mean/std/med/min/max):', np.round(tbias_mean, 2), np.round(tbias_std, 2), np.round(tbias_med, 2), @@ -485,11 +485,11 @@ def run( if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg) # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset["kp"]) - kp_std = np.std(main_glac_rgi_subset["kp"]) - kp_med = np.median(main_glac_rgi_subset["kp"]) - kp_min = np.min(main_glac_rgi_subset["kp"]) - kp_max = np.max(main_glac_rgi_subset["kp"]) + kp_mean = np.mean(main_glac_rgi_subset['kp']) + kp_std = np.std(main_glac_rgi_subset['kp']) + kp_med = np.median(main_glac_rgi_subset['kp']) + kp_min = np.min(main_glac_rgi_subset['kp']) + kp_max = np.max(main_glac_rgi_subset['kp']) # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: @@ -499,29 +499,29 @@ def run( kp_alpha = kp_mean * kp_beta # Temperature bias - tbias_mean = main_glac_rgi_subset["tbias"].mean() - tbias_std = main_glac_rgi_subset["tbias"].std() - tbias_med = np.median(main_glac_rgi_subset["tbias"]) - tbias_min = np.min(main_glac_rgi_subset["tbias"]) - tbias_max = np.max(main_glac_rgi_subset["tbias"]) + tbias_mean = main_glac_rgi_subset['tbias'].mean() + tbias_std = main_glac_rgi_subset['tbias'].std() + tbias_med = np.median(main_glac_rgi_subset['tbias']) + tbias_min = np.min(main_glac_rgi_subset['tbias']) + tbias_max = np.max(main_glac_rgi_subset['tbias']) # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 if debug: - print("\n", reg, "(all subregions)") + print('\n', reg, '(all subregions)') print( - "kp (mean/std/med/min/max):", + '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(' alpha/beta:', np.round(kp_alpha, 2), np.round(kp_beta, 2)) print( - "tbias (mean/std/med/min/max):", + 'tbias (mean/std/med/min/max):', np.round(tbias_mean, 2), np.round(tbias_std, 2), np.round(tbias_med, 2), @@ -569,7 +569,7 @@ def main(): ncores = 1 # Parallel processing - print("Processing with " + str(ncores) + " cores...") + print('Processing with ' + str(ncores) + ' cores...') partial_function = partial( run, option_calibration=args.option_calibration, @@ -580,8 +580,8 @@ def main(): 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__": +if __name__ == '__main__': main() diff --git a/pygem/bin/run/run_simulation.py b/pygem/bin/run/run_simulation.py index 1219bbd2..8f9d8fa8 100755 --- a/pygem/bin/run/run_simulation.py +++ b/pygem/bin/run/run_simulation.py @@ -56,15 +56,15 @@ from pygem.output import calc_stats_array from pygem.shop import debris -cfg.PARAMS["hydro_month_nh"] = 1 -cfg.PARAMS["hydro_month_sh"] = 1 -cfg.PARAMS["trapezoid_lambdas"] = 1 +cfg.PARAMS['hydro_month_nh'] = 1 +cfg.PARAMS['hydro_month_sh'] = 1 +cfg.PARAMS['trapezoid_lambdas'] = 1 # ----- FUNCTIONS ----- def none_or_value(value): """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 @@ -103,238 +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", + '-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="+", + 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", + '-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`)", + 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", + '-rgi_glac_number', + action='store', type=float, - default=pygem_prms["setup"]["glac_no"], - nargs="+", - help="Randoph Glacier Inventory glacier number (can take multiple)", + default=pygem_prms['setup']['glac_no'], + nargs='+', + help='Randoph Glacier Inventory glacier number (can take multiple)', ) parser.add_argument( - "-ref_gcm_name", - action="store", + '-ref_gcm_name', + action='store', type=str, - default=pygem_prms["climate"]["ref_gcm_name"], - help="reference gcm name", + default=pygem_prms['climate']['ref_gcm_name'], + help='reference gcm name', ) parser.add_argument( - "-ref_startyear", - action="store", + '-ref_startyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_startyear"], - help="reference period starting year for calibration (typically 2000)", + default=pygem_prms['climate']['ref_startyear'], + help='reference period starting year for calibration (typically 2000)', ) parser.add_argument( - "-ref_endyear", - action="store", + '-ref_endyear', + action='store', type=int, - default=pygem_prms["climate"]["ref_endyear"], - help="reference period ending year for calibration (typically 2019)", + default=pygem_prms['climate']['ref_endyear'], + help='reference period ending year for calibration (typically 2019)', ) parser.add_argument( - "-rgi_glac_number_fn", - action="store", + '-rgi_glac_number_fn', + action='store', type=str, default=None, - help="filepath containing list of rgi_glac_number, helpful for running batches on spc", + help='filepath containing list of rgi_glac_number, helpful for running batches on spc', ) parser.add_argument( - "-gcm_list_fn", - action="store", + '-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)", + 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", + '-gcm_name', + action='store', type=str, - default=pygem_prms["climate"]["gcm_name"], - help="GCM name used for model run", + default=pygem_prms['climate']['gcm_name'], + help='GCM name used for model run', ) parser.add_argument( - "-scenario", - action="store", + '-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)", + default=pygem_prms['climate']['scenario'], + help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)', ) parser.add_argument( - "-realization", - action="store", + '-realization', + action='store', type=str, default=None, - help="realization from large ensemble used for model run (ex. 1011.001 or 1301.020)", + help='realization from large ensemble used for model run (ex. 1011.001 or 1301.020)', ) parser.add_argument( - "-realization_list", - action="store", + '-realization_list', + action='store', type=str, default=None, - help="text file full of realizations to run", + help='text file full of realizations to run', ) parser.add_argument( - "-gcm_startyear", - action="store", + '-gcm_startyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_startyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_startyear'], + help='start year for the model run', ) parser.add_argument( - "-gcm_endyear", - action="store", + '-gcm_endyear', + action='store', type=int, - default=pygem_prms["climate"]["gcm_endyear"], - help="start year for the model run", + default=pygem_prms['climate']['gcm_endyear'], + help='start year for the model run', ) parser.add_argument( - "-mcmc_burn_pct", - action="store", + '-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)", + 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", + '-ncores', + action='store', type=int, default=1, - help="number of simultaneous processes (cores) to use", + help='number of simultaneous processes (cores) to use', ) parser.add_argument( - "-batch_number", - action="store", + '-batch_number', + action='store', type=int, default=None, - help="Batch number used to differentiate output on supercomputer", + help='Batch number used to differentiate output on supercomputer', ) parser.add_argument( - "-kp", - action="store", + '-kp', + action='store', type=float, - default=pygem_prms["sim"]["params"]["kp"], - help="Precipitation bias", + default=pygem_prms['sim']['params']['kp'], + help='Precipitation bias', ) parser.add_argument( - "-tbias", - action="store", + '-tbias', + action='store', type=float, - default=pygem_prms["sim"]["params"]["tbias"], - help="Temperature bias", + default=pygem_prms['sim']['params']['tbias'], + help='Temperature bias', ) parser.add_argument( - "-ddfsnow", - action="store", + '-ddfsnow', + action='store', type=float, - default=pygem_prms["sim"]["params"]["ddfsnow"], - help="Degree-day factor of snow", + default=pygem_prms['sim']['params']['ddfsnow'], + help='Degree-day factor of snow', ) parser.add_argument( - "-oggm_working_dir", - action="store", + '-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", + 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", + '-option_calibration', + action='store', type=none_or_value, - default=pygem_prms["calib"]["option_calibration"], + default=pygem_prms['calib']['option_calibration'], help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', ) parser.add_argument( - "-option_dynamics", - action="store", + '-option_dynamics', + action='store', type=none_or_value, - default=pygem_prms["sim"]["option_dynamics"], - help="glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)", + default=pygem_prms['sim']['option_dynamics'], + help='glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)', ) parser.add_argument( - "-use_reg_glena", - action="store", + '-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`)", + 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", + '-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, \ + 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)", + 2: HH2015 methods, 3: quantile delta mapping)', ) parser.add_argument( - "-nsims", - action="store", + '-nsims', + action='store', type=int, - default=pygem_prms["sim"]["nsims"], - help="number of simulations (note, defaults to 1 if `option_calibration` != `MCMC`)", + default=pygem_prms['sim']['nsims'], + help='number of simulations (note, defaults to 1 if `option_calibration` != `MCMC`)', ) parser.add_argument( - "-modelprms_fp", - action="store", + '-modelprms_fp', + action='store', type=str, default=None, - help="model parameters filepath", + help='model parameters filepath', ) parser.add_argument( - "-outputfn_sfix", - action="store", + '-outputfn_sfix', + action='store', type=str, - default="", - help="append custom filename suffix to simulation output", + 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"], + '-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"], + '-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"], + '-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"], + '-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)", + '-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('-v', '--debug', action='store_true', help='Flag for debugging') return parser @@ -359,12 +359,12 @@ def run(list_packed_vars): gcm_name = list_packed_vars[2] realization = list_packed_vars[3] if (gcm_name != args.ref_gcm_name) and (args.scenario is None): - scenario = os.path.basename(args.gcm_list_fn).split("_")[1] + scenario = os.path.basename(args.gcm_list_fn).split('_')[1] else: scenario = args.scenario debug = args.debug if debug: - print(f"scenario:{scenario}") + print(f'scenario:{scenario}') # ===== LOAD GLACIERS ===== main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) @@ -376,33 +376,33 @@ def run(list_packed_vars): 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"], + 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"], + 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"], + spinupyears=pygem_prms['climate']['gcm_spinupyears'], + option_wateryear=pygem_prms['climate']['gcm_wateryear'], ) if debug: - print("ref years:", args.ref_startyear, ref_endyear) - print("sim years:", args.gcm_startyear, args.gcm_endyear) + print('ref years:', args.ref_startyear, ref_endyear) + print('sim years:', args.gcm_startyear, args.gcm_endyear) # ===== LOAD CLIMATE DATA ===== # Climate class - if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: + if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: gcm = class_climate.GCM(name=gcm_name) ref_gcm = gcm dates_table_ref = dates_table_full @@ -446,10 +446,10 @@ def run(list_packed_vars): # ----- Temperature and Precipitation Bias Adjustments ----- # No adjustments if args.option_bias_adjustment == 0 or gcm_name == args.ref_gcm_name: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + 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:] @@ -467,8 +467,8 @@ def run(list_packed_vars): dates_table_full, args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + 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( @@ -479,8 +479,8 @@ def run(list_packed_vars): dates_table_full, args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + 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: @@ -493,8 +493,8 @@ def run(list_packed_vars): dates_table_full, args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + 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( @@ -503,8 +503,8 @@ def run(list_packed_vars): gcm_prec, dates_table_ref, dates_table_full, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + 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: @@ -517,8 +517,8 @@ def run(list_packed_vars): dates_table_full, args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], + gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears'], ) # Precipitation bias correction @@ -530,24 +530,24 @@ def run(list_packed_vars): dates_table_full, args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], - gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], + gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears'], ) # assert that the gcm_elev_adj is not None - assert gcm_elev_adj is not None, "No GCM elevation data" + assert gcm_elev_adj is not None, 'No GCM elevation data' # ----- Other Climate Datasets (Air temperature variability [degC] and Lapse rate [K m-1]) # Air temperature variability [degC] - if pygem_prms["mb"]["option_ablation"] != 2: + if pygem_prms['mb']['option_ablation'] != 2: gcm_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) ref_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table_ref.shape[0])) - elif pygem_prms["mb"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + elif pygem_prms['mb']['option_ablation'] == 2 and gcm_name in ['ERA5']: gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table ) ref_tempstd = gcm_tempstd - elif pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in ["ERA5"]: + elif pygem_prms['mb']['option_ablation'] == 2 and args.ref_gcm_name in ['ERA5']: # Compute temp std based on reference climate data ref_tempstd, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( ref_gcm.tempstd_fn, ref_gcm.tempstd_vn, main_glac_rgi, dates_table_ref @@ -561,7 +561,7 @@ def run(list_packed_vars): ref_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table_ref.shape[0])) # Lapse rate - if gcm_name in ["ERA-Interim", "ERA5"]: + 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 ) @@ -582,38 +582,38 @@ def run(list_packed_vars): # ===== RUN MASS BALANCE ===== # Number of simulations - if args.option_calibration == "MCMC": + if args.option_calibration == 'MCMC': nsims = args.nsims else: nsims = 1 # Number of years (for OGGM's run_until_and_store) - if pygem_prms["time"]["timestep"] == "monthly": + if pygem_prms['time']['timestep'] == 'monthly': nyears = int(dates_table.shape[0] / 12) nyears_ref = int(dates_table_ref.shape[0] / 12) else: - assert True == False, "Adjust nyears for non-monthly timestep" + assert True == False, 'Adjust nyears for non-monthly timestep' for glac in range(main_glac_rgi.shape[0]): if glac == 0: print( gcm_name, - ":", - main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"], + ':', + main_glac_rgi.loc[main_glac_rgi.index.values[glac], 'RGIId'], ) # Select subsets of data glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + glacier_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]: # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_frontalablation"] + 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 @@ -625,36 +625,36 @@ def run(list_packed_vars): glacier_str, working_dir=args.oggm_working_dir ) gdir.is_tidewater = True - cfg.PARAMS["use_kcalving_for_inversion"] = True - cfg.PARAMS["use_kcalving_for_run"] = True + cfg.PARAMS['use_kcalving_for_inversion'] = True + cfg.PARAMS['use_kcalving_for_run'] = True # Flowlines - fls = gdir.read_pickle("inversion_flowlines") + fls = gdir.read_pickle('inversion_flowlines') # Reference gdir for ice thickness inversion gdir_ref = copy.deepcopy(gdir) gdir_ref.historical_climate = { - "elev": ref_elev[glac], - "temp": ref_temp[glac, :], - "tempstd": ref_tempstd[glac, :], - "prec": ref_prec[glac, :], - "lr": ref_lr[glac, :], + 'elev': ref_elev[glac], + 'temp': ref_temp[glac, :], + 'tempstd': ref_tempstd[glac, :], + 'prec': ref_prec[glac, :], + 'lr': ref_lr[glac, :], } gdir_ref.dates_table = dates_table_ref # Add climate data to glacier directory - if pygem_prms["climate"]["hindcast"] == True: + if pygem_prms['climate']['hindcast'] == True: gcm_temp_adj = gcm_temp_adj[::-1] gcm_tempstd = gcm_tempstd[::-1] gcm_prec_adj = gcm_prec_adj[::-1] gcm_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, :], + '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 @@ -664,42 +664,42 @@ def run(list_packed_vars): if args.option_calibration: modelprms_fp = args.modelprms_fp if not modelprms_fp: - modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fn = glacier_str + '-modelprms_dict.json' modelprms_fp = ( - pygem_prms["root"] - + "/Output/calibration/" - + glacier_str.split(".")[0].zfill(2) - + "/" + pygem_prms['root'] + + '/Output/calibration/' + + glacier_str.split('.')[0].zfill(2) + + '/' ) + modelprms_fn assert os.path.exists(modelprms_fp), ( - "Calibrated parameters do not exist." + 'Calibrated parameters do not exist.' ) - with open(modelprms_fp, "r") as f: + 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" + 'Error: ' + args.option_calibration + ' not in modelprms_dict' ) modelprms_all = modelprms_dict[args.option_calibration] # MCMC needs model parameters to be selected - if args.option_calibration == "MCMC": + if args.option_calibration == 'MCMC': if nsims == 1: modelprms_all = { - "kp": [np.median(modelprms_all["kp"]["chain_0"])], - "tbias": [np.median(modelprms_all["tbias"]["chain_0"])], - "ddfsnow": [ - np.median(modelprms_all["ddfsnow"]["chain_0"]) + '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"]) + 'ddfice': [ + np.median(modelprms_all['ddfice']['chain_0']) ], - "tsnow_threshold": modelprms_all["tsnow_threshold"], - "precgrad": modelprms_all["precgrad"], + '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"]) + mcmc_sample_no = len(modelprms_all['kp']['chain_0']) sims_burn = int(args.mcmc_burn_pct / 100 * mcmc_sample_no) mp_spacing = int((mcmc_sample_no - sims_burn) / nsims) mp_idx_start = np.arange(sims_burn, sims_burn + mp_spacing) @@ -709,40 +709,40 @@ def run(list_packed_vars): mp_idx_start, mcmc_sample_no, mp_spacing ) modelprms_all = { - "kp": [ - modelprms_all["kp"]["chain_0"][mp_idx] + 'kp': [ + modelprms_all['kp']['chain_0'][mp_idx] for mp_idx in mp_idx_all ], - "tbias": [ - modelprms_all["tbias"]["chain_0"][mp_idx] + 'tbias': [ + modelprms_all['tbias']['chain_0'][mp_idx] for mp_idx in mp_idx_all ], - "ddfsnow": [ - modelprms_all["ddfsnow"]["chain_0"][mp_idx] + 'ddfsnow': [ + modelprms_all['ddfsnow']['chain_0'][mp_idx] for mp_idx in mp_idx_all ], - "ddfice": [ - modelprms_all["ddfice"]["chain_0"][mp_idx] + 'ddfice': [ + modelprms_all['ddfice']['chain_0'][mp_idx] for mp_idx in mp_idx_all ], - "tsnow_threshold": modelprms_all["tsnow_threshold"] + 'tsnow_threshold': modelprms_all['tsnow_threshold'] * nsims, - "precgrad": modelprms_all["precgrad"] * nsims, + 'precgrad': modelprms_all['precgrad'] * nsims, } else: nsims = 1 # Calving parameter if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_frontalablation"] + 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']}" + 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" + 'Calibrated calving dataset does not exist' ) calving_df = pd.read_csv(fp) calving_rgiids = list(calving_df.RGIId) @@ -750,18 +750,18 @@ def run(list_packed_vars): # 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 = calving_df.loc[calving_idx, 'calving_k'] calving_k_nmad = calving_df.loc[ - calving_idx, "calving_k_nmad" + calving_idx, 'calving_k_nmad' ] # Otherwise, use region's median value else: - calving_df["O1Region"] = [ - int(x.split("-")[1].split(".")[0]) + 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(reg_str), : ] calving_k = np.median(calving_df_reg.calving_k) calving_k_nmad = 0 @@ -790,91 +790,91 @@ def run(list_packed_vars): assert ( abs(np.median(calving_k_values) - calving_k) < 0.001 - ), "calving_k distribution too far off" + ), 'calving_k distribution too far off' if debug: print( - "calving_k_values:", + 'calving_k_values:', np.mean(calving_k_values), np.std(calving_k_values), - "\n", + '\n', calving_k_values, ) else: modelprms_all = { - "kp": [args.kp], - "tbias": [args.tbias], - "ddfsnow": [args.ddfsnow], - "ddfice": [ + 'kp': [args.kp], + 'tbias': [args.tbias], + 'ddfsnow': [args.ddfsnow], + 'ddfice': [ args.ddfsnow - / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + / pygem_prms['sim']['params']['ddfsnow_iceratio'] ], - "tsnow_threshold": [ - pygem_prms["sim"]["params"]["tsnow_threshold"] + 'tsnow_threshold': [ + pygem_prms['sim']['params']['tsnow_threshold'] ], - "precgrad": [pygem_prms["sim"]["params"]["precgrad"]], + 'precgrad': [pygem_prms['sim']['params']['precgrad']], } calving_k = ( - np.zeros(nsims) + pygem_prms["sim"]["params"]["calving_k"] + np.zeros(nsims) + pygem_prms['sim']['params']['calving_k'] ) calving_k_values = calving_k if debug and gdir.is_tidewater: - print("calving_k:", calving_k) + print('calving_k:', calving_k) # Load OGGM glacier dynamics parameters (if necessary) - if args.option_dynamics in ["OGGM", "MassRedistributionCurves"]: + if args.option_dynamics in ['OGGM', 'MassRedistributionCurves']: # CFL number (may use different values for calving to prevent errors) if ( - glacier_rgi_table["TermType"] not in [1, 5] - or not pygem_prms["setup"]["include_frontalablation"] + 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" + cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics'][ + 'cfl_number' ] else: - cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ - "cfl_number_calving" + cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics'][ + 'cfl_number_calving' ] if debug: - print("cfl number:", cfg.PARAMS["cfl_number"]) + print('cfl number:', cfg.PARAMS['cfl_number']) if args.use_reg_glena: glena_df = pd.read_csv( - f"{pygem_prms['root']}/{pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']}" + 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" + 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" + glena_idx, 'glens_a_multiplier' ] - fs = glena_df.loc[glena_idx, "fs"] + fs = glena_df.loc[glena_idx, 'fs'] else: args.option_dynamics = None - fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] - glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ - "glen_a_multiplier" + fs = pygem_prms['sim']['oggm_dynamics']['fs'] + glen_a_multiplier = pygem_prms['sim']['oggm_dynamics'][ + 'glen_a_multiplier' ] # Time attributes and values - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - annual_columns = np.unique(dates_table["wateryear"].values)[ + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + annual_columns = np.unique(dates_table['wateryear'].values)[ 0 : int(dates_table.shape[0] / 12) ] else: - annual_columns = np.unique(dates_table["year"].values)[ + 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] + pygem_prms['climate']['gcm_spinupyears'] : annual_columns.shape[0] ] year_values = np.concatenate( (year_values, np.array([annual_columns[-1] + 1])) @@ -943,41 +943,41 @@ def run(list_packed_vars): mb_em_sims = [] for n_iter in range(nsims): if debug: - print("n_iter:", n_iter) + print('n_iter:', n_iter) if calving_k is not None: calving_k = calving_k_values[n_iter] - cfg.PARAMS["calving_k"] = calving_k - cfg.PARAMS["inversion_calving_k"] = calving_k + cfg.PARAMS['calving_k'] = calving_k + cfg.PARAMS['inversion_calving_k'] = calving_k # successful_run used to continue runs when catching specific errors successful_run = True modelprms = { - "kp": modelprms_all["kp"][n_iter], - "tbias": modelprms_all["tbias"][n_iter], - "ddfsnow": modelprms_all["ddfsnow"][n_iter], - "ddfice": modelprms_all["ddfice"][n_iter], - "tsnow_threshold": modelprms_all["tsnow_threshold"][n_iter], - "precgrad": modelprms_all["precgrad"][n_iter], + '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)) + + ' kp: ' + + str(np.round(modelprms['kp'], 2)) + + ' ddfsnow: ' + + str(np.round(modelprms['ddfsnow'], 4)) + + ' tbias: ' + + str(np.round(modelprms['tbias'], 2)) ) # %% # ----- ICE THICKNESS INVERSION using OGGM ----- if args.option_dynamics is not None: # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms["mb"]["include_debris"]: + if pygem_prms['mb']['include_debris']: inversion_filter = True else: inversion_filter = False @@ -1003,7 +1003,7 @@ def run(list_packed_vars): # Non-tidewater glaciers if ( not gdir.is_tidewater - or not pygem_prms["setup"]["include_frontalablation"] + 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( @@ -1012,45 +1012,45 @@ def run(list_packed_vars): tasks.prepare_for_inversion(gdir) tasks.mass_conservation_inversion( gdir, - glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + 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 + 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, + glen_a=cfg.PARAMS['glen_a'] * glen_a_multiplier, fs=fs, ) # ----- INDENTED TO BE JUST WITH DYNAMICS ----- tasks.init_present_time_glacier(gdir) # adds bins below - if pygem_prms["mb"]["include_debris"]: + if pygem_prms['mb']['include_debris']: debris.debris_binned( - gdir, fl_str="model_flowlines" + gdir, fl_str='model_flowlines' ) # add debris enhancement factors to flowlines try: - nfls = gdir.read_pickle("model_flowlines") + nfls = gdir.read_pickle('model_flowlines') except FileNotFoundError as e: - if "model_flowlines.pkl" in str(e): + if 'model_flowlines.pkl' in str(e): tasks.compute_downstream_line(gdir) tasks.compute_downstream_bedshape(gdir) tasks.init_present_time_glacier(gdir) # adds bins below - nfls = gdir.read_pickle("model_flowlines") + nfls = gdir.read_pickle('model_flowlines') else: raise # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle("inversion_input")[-1] - th = cls["hgt"][-1] - vmin, vmax = cfg.PARAMS["free_board_marine_terminating"] + cls = gdir.read_pickle('inversion_input')[-1] + th = cls['hgt'][-1] + vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] water_level = utils.clip_scalar(0, th - vmax, th - vmin) # No ice dynamics options @@ -1071,9 +1071,9 @@ def run(list_packed_vars): ) # Glacier dynamics model - if args.option_dynamics == "OGGM": + if args.option_dynamics == 'OGGM': if debug: - print("OGGM GLACIER DYNAMICS!") + print('OGGM GLACIER DYNAMICS!') # new numerical scheme is SemiImplicitModel() but doesn't have frontal ablation yet # FluxBasedModel is old numerical scheme but includes frontal ablation @@ -1081,7 +1081,7 @@ def run(list_packed_vars): nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + glen_a=cfg.PARAMS['glen_a'] * glen_a_multiplier, fs=fs, is_tidewater=gdir.is_tidewater, water_level=water_level, @@ -1106,10 +1106,10 @@ def run(list_packed_vars): # - note: diag.calving_m3 is cumulative calving if debug: print( - "\n\ndiag.calving_m3:", diag.calving_m3.values + '\n\ndiag.calving_m3:', diag.calving_m3.values ) print( - "calving_m3_since_y0:", + 'calving_m3_since_y0:', ev_model.calving_m3_since_y0, ) calving_m3_annual = ( @@ -1117,8 +1117,8 @@ def run(list_packed_vars): diag.calving_m3.values[1:] - diag.calving_m3.values[0:-1] ) - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * 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[ @@ -1133,11 +1133,11 @@ def run(list_packed_vars): if debug: print( - "avg calving_m3:", + 'avg calving_m3:', calving_m3_annual.sum() / nyears, ) print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 @@ -1146,10 +1146,10 @@ def run(list_packed_vars): ), ) print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.calving_m3_since_y0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] / 1e12 / nyears, 4, @@ -1157,49 +1157,49 @@ def run(list_packed_vars): ) except RuntimeError as e: - if "Glacier exceeds domain boundaries" in repr(e): + if 'Glacier exceeds domain boundaries' in repr(e): count_exceed_boundary_errors += 1 successful_run = False # LOG FAILURE fail_domain_fp = ( - pygem_prms["root"] - + "/Output/simulations/fail-exceed_domain/" + 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 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" + txt_fn_fail = glacier_str + '-sim_failed.txt' with open( - fail_domain_fp + txt_fn_fail, "w" + fail_domain_fp + txt_fn_fail, 'w' ) as text_file: text_file.write( glacier_str - + " failed to complete " + + ' failed to complete ' + str(count_exceed_boundary_errors) - + " simulations" + + ' simulations' ) elif gdir.is_tidewater: if debug: print( - "OGGM dynamics failed, using mass redistribution curves" + '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, + 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" + spinupyears=pygem_prms['climate'][ + 'ref_spinupyears' ], ) _, diag = ev_model.run_until_and_store(nyears) @@ -1223,7 +1223,7 @@ def run(list_packed_vars): if debug: print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 @@ -1232,10 +1232,10 @@ def run(list_packed_vars): ), ) print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.calving_m3_since_y0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] / 1e12 / nyears, 4, @@ -1246,14 +1246,14 @@ def run(list_packed_vars): if gdir.is_tidewater: if debug: print( - "OGGM dynamics failed, using mass redistribution curves" + '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, + glen_a=cfg.PARAMS['glen_a'] * glen_a_multiplier, fs=fs, is_tidewater=gdir.is_tidewater, water_level=water_level, @@ -1279,7 +1279,7 @@ def run(list_packed_vars): if debug: print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 @@ -1288,10 +1288,10 @@ def run(list_packed_vars): ), ) print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.calving_m3_since_y0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] / 1e12 / nyears, 4, @@ -1302,14 +1302,14 @@ def run(list_packed_vars): raise # Mass redistribution model - elif args.option_dynamics == "MassRedistributionCurves": + elif args.option_dynamics == 'MassRedistributionCurves': if debug: - print("MASS REDISTRIBUTION CURVES!") + print('MASS REDISTRIBUTION CURVES!') ev_model = MassRedistributionCurveModel( nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + 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) @@ -1317,7 +1317,7 @@ def run(list_packed_vars): ) if debug: - print("New glacier vol", ev_model.volume_m3) + print('New glacier vol', ev_model.volume_m3) graphics.plot_modeloutput_section(ev_model) plt.show() try: @@ -1344,7 +1344,7 @@ def run(list_packed_vars): if debug: print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 @@ -1353,10 +1353,10 @@ def run(list_packed_vars): ), ) print( - "avg frontal ablation [Gta]:", + 'avg frontal ablation [Gta]:', np.round( ev_model.calving_m3_since_y0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] / 1e12 / nyears, 4, @@ -1364,32 +1364,32 @@ def run(list_packed_vars): ) except RuntimeError as e: - if "Glacier exceeds domain boundaries" in repr(e): + if 'Glacier exceeds domain boundaries' in repr(e): count_exceed_boundary_errors += 1 successful_run = False # LOG FAILURE fail_domain_fp = ( - pygem_prms["root"] - + "/Output/simulations/fail-exceed_domain/" + 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 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" + txt_fn_fail = glacier_str + '-sim_failed.txt' with open( - fail_domain_fp + txt_fn_fail, "w" + fail_domain_fp + txt_fn_fail, 'w' ) as text_file: text_file.write( glacier_str - + " failed to complete " + + ' failed to complete ' + str(count_exceed_boundary_errors) - + " simulations" + + ' simulations' ) else: raise @@ -1421,8 +1421,8 @@ def run(list_packed_vars): * 365 * 24 * 3600 - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) glac_wide_mb_mwea = ( mb_mwea * mbmod.glacier_area_initial @@ -1432,18 +1432,18 @@ def run(list_packed_vars): 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 + 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:", + 'iter:', n_iter, - "massbal (mean, std):", + 'massbal (mean, std):', np.round(np.mean(mb_all), 3), np.round(np.std(mb_all), 3), - "massbal (med):", + 'massbal (med):', np.round(np.median(mb_all), 3), ) @@ -1468,8 +1468,8 @@ def run(list_packed_vars): (diag.volume_m3.values[-1] - diag.volume_m3.values[0]) / area_initial / nyears - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) mb_mwea_mbmod = ( mbmod.glac_wide_massbaltotal.sum() @@ -1482,26 +1482,26 @@ def run(list_packed_vars): diag.volume_m3.values[-1] - diag.volume_m3.values[0] ) print( - " vol init [Gt]:", + ' vol init [Gt]:', np.round(diag.volume_m3.values[0] * 0.9 / 1e9, 5), ) print( - " vol final [Gt]:", + ' vol final [Gt]:', np.round(diag.volume_m3.values[-1] * 0.9 / 1e9, 5), ) print( - " vol change[Gt]:", + ' 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)) + 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]:", + 'mass loss [Gt]:', mbmod.glac_wide_massbaltotal.sum() / 1e9, ) @@ -1526,15 +1526,15 @@ def run(list_packed_vars): 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"] + * pygem_prms['constants']['density_ice'] ) output_glac_mass_bsl_annual[:, n_iter] = ( diag.volume_bsl_m3.values - * pygem_prms["constants"]["density_ice"] + * 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"] + * 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 @@ -1557,20 +1557,20 @@ def run(list_packed_vars): output_glac_bin_mass_annual_sim = ( mbmod.glac_bin_area_annual * mbmod.glac_bin_icethickness_annual - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] )[:, :, np.newaxis] output_glac_bin_icethickness_annual_sim = ( mbmod.glac_bin_icethickness_annual )[:, :, np.newaxis] # Update the latest thickness and volume if ev_model is not None: - fl_dx_meter = getattr(ev_model.fls[0], "dx_meter", None) - fl_widths_m = getattr(ev_model.fls[0], "widths_m", None) - fl_section = getattr(ev_model.fls[0], "section", None) + fl_dx_meter = getattr(ev_model.fls[0], 'dx_meter', None) + fl_widths_m = getattr(ev_model.fls[0], 'widths_m', None) + fl_section = getattr(ev_model.fls[0], 'section', None) else: - fl_dx_meter = getattr(nfls[0], "dx_meter", None) - fl_widths_m = getattr(nfls[0], "widths_m", None) - fl_section = getattr(nfls[0], "section", None) + fl_dx_meter = getattr(nfls[0], 'dx_meter', None) + fl_widths_m = getattr(nfls[0], 'widths_m', None) + fl_section = getattr(nfls[0], 'section', None) if fl_section is not None and fl_widths_m is not None: # thickness icethickness_t0 = np.zeros(fl_section.shape) @@ -1587,7 +1587,7 @@ def run(list_packed_vars): ) output_glac_bin_mass_annual_sim[:, -1, 0] = ( glacier_vol_t0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] ) output_glac_bin_area_annual = ( output_glac_bin_area_annual_sim @@ -1653,19 +1653,19 @@ def run(list_packed_vars): output_glac_bin_mass_annual_sim = ( mbmod.glac_bin_area_annual * mbmod.glac_bin_icethickness_annual - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] )[:, :, np.newaxis] output_glac_bin_icethickness_annual_sim = ( mbmod.glac_bin_icethickness_annual )[:, :, np.newaxis] if ev_model is not None: - fl_dx_meter = getattr(ev_model.fls[0], "dx_meter", None) - fl_widths_m = getattr(ev_model.fls[0], "widths_m", None) - fl_section = getattr(ev_model.fls[0], "section", None) + fl_dx_meter = getattr(ev_model.fls[0], 'dx_meter', None) + fl_widths_m = getattr(ev_model.fls[0], 'widths_m', None) + fl_section = getattr(ev_model.fls[0], 'section', None) else: - fl_dx_meter = getattr(nfls[0], "dx_meter", None) - fl_widths_m = getattr(nfls[0], "widths_m", None) - fl_section = getattr(nfls[0], "section", None) + fl_dx_meter = getattr(nfls[0], 'dx_meter', None) + fl_widths_m = getattr(nfls[0], 'widths_m', None) + fl_section = getattr(nfls[0], 'section', None) if fl_section is not None and fl_widths_m is not None: # thickness icethickness_t0 = np.zeros(fl_section.shape) @@ -1682,7 +1682,7 @@ def run(list_packed_vars): ) output_glac_bin_mass_annual_sim[:, -1, 0] = ( glacier_vol_t0 - * pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_ice'] ) output_glac_bin_area_annual = np.append( output_glac_bin_area_annual, @@ -1791,74 +1791,74 @@ def run(list_packed_vars): 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_ds_all_stats['glac_runoff_monthly'].values[0, :] = ( output_glac_runoff_monthly[:, n_iter] ) - output_ds_all_stats["glac_area_annual"].values[0, :] = ( + 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_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_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_ds_all_stats['glac_ELA_annual'].values[0, :] = ( output_glac_ELA_annual[:, n_iter] ) - output_ds_all_stats["offglac_runoff_monthly"].values[ + 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[ + 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[ + 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_ds_all_stats['glac_acc_monthly'].values[0, :] = ( output_glac_acc_monthly[:, n_iter] ) - output_ds_all_stats["glac_refreeze_monthly"].values[ + output_ds_all_stats['glac_refreeze_monthly'].values[ 0, : ] = output_glac_refreeze_monthly[:, n_iter] - output_ds_all_stats["glac_melt_monthly"].values[ + output_ds_all_stats['glac_melt_monthly'].values[ 0, : ] = output_glac_melt_monthly[:, n_iter] output_ds_all_stats[ - "glac_frontalablation_monthly" + 'glac_frontalablation_monthly' ].values[0, :] = output_glac_frontalablation_monthly[ :, n_iter ] - output_ds_all_stats["glac_massbaltotal_monthly"].values[ + output_ds_all_stats['glac_massbaltotal_monthly'].values[ 0, : ] = output_glac_massbaltotal_monthly[:, n_iter] - output_ds_all_stats["glac_snowline_monthly"].values[ + output_ds_all_stats['glac_snowline_monthly'].values[ 0, : ] = output_glac_snowline_monthly[:, n_iter] output_ds_all_stats[ - "glac_mass_change_ignored_annual" + 'glac_mass_change_ignored_annual' ].values[0, :] = output_glac_mass_change_ignored_annual[ :, n_iter ] - output_ds_all_stats["offglac_prec_monthly"].values[ + output_ds_all_stats['offglac_prec_monthly'].values[ 0, : ] = output_offglac_prec_monthly[:, n_iter] - output_ds_all_stats["offglac_melt_monthly"].values[ + output_ds_all_stats['offglac_melt_monthly'].values[ 0, : ] = output_offglac_melt_monthly[:, n_iter] - output_ds_all_stats["offglac_refreeze_monthly"].values[ + output_ds_all_stats['offglac_refreeze_monthly'].values[ 0, : ] = output_offglac_refreeze_monthly[:, n_iter] - output_ds_all_stats["offglac_snowpack_monthly"].values[ + 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}") + output_stats.get_fn().replace('SETS', f'set{n_iter}') + args.outputfn_sfix - + "all.nc" + + 'all.nc' ) output_stats.save_xr_ds() @@ -1943,135 +1943,135 @@ def run(list_packed_vars): ) # output mean/median from all simulations - output_ds_all_stats["glac_runoff_monthly"].values[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_ds_all_stats['glac_area_annual'].values[0, :] = ( output_glac_area_annual_stats[:, 0] ) - output_ds_all_stats["glac_mass_annual"].values[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_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_ds_all_stats['glac_ELA_annual'].values[0, :] = ( output_glac_ELA_annual_stats[:, 0] ) - output_ds_all_stats["offglac_runoff_monthly"].values[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_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_ds_all_stats['glac_prec_monthly'].values[0, :] = ( output_glac_prec_monthly_stats[:, 0] ) - output_ds_all_stats["glac_acc_monthly"].values[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_ds_all_stats['glac_refreeze_monthly'].values[0, :] = ( output_glac_refreeze_monthly_stats[:, 0] ) - output_ds_all_stats["glac_melt_monthly"].values[0, :] = ( + output_ds_all_stats['glac_melt_monthly'].values[0, :] = ( output_glac_melt_monthly_stats[:, 0] ) - output_ds_all_stats["glac_frontalablation_monthly"].values[ + output_ds_all_stats['glac_frontalablation_monthly'].values[ 0, : ] = output_glac_frontalablation_monthly_stats[:, 0] - output_ds_all_stats["glac_massbaltotal_monthly"].values[ + 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_ds_all_stats['glac_snowline_monthly'].values[0, :] = ( output_glac_snowline_monthly_stats[:, 0] ) - output_ds_all_stats["glac_mass_change_ignored_annual"].values[ + 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_ds_all_stats['offglac_prec_monthly'].values[0, :] = ( output_offglac_prec_monthly_stats[:, 0] ) - output_ds_all_stats["offglac_melt_monthly"].values[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_ds_all_stats['offglac_refreeze_monthly'].values[0, :] = ( output_offglac_refreeze_monthly_stats[:, 0] ) - output_ds_all_stats["offglac_snowpack_monthly"].values[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_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_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_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_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_ds_all_stats['glac_ELA_annual_mad'].values[0, :] = ( output_glac_ELA_annual_stats[:, 1] ) - output_ds_all_stats["offglac_runoff_monthly_mad"].values[ + 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[ + 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[ + 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_ds_all_stats['glac_acc_monthly_mad'].values[0, :] = ( output_glac_acc_monthly_stats[:, 1] ) - output_ds_all_stats["glac_refreeze_monthly_mad"].values[ + 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[ + output_ds_all_stats['glac_melt_monthly_mad'].values[ 0, : ] = output_glac_melt_monthly_stats[:, 1] output_ds_all_stats[ - "glac_frontalablation_monthly_mad" + 'glac_frontalablation_monthly_mad' ].values[0, :] = output_glac_frontalablation_monthly_stats[ :, 1 ] - output_ds_all_stats["glac_massbaltotal_monthly_mad"].values[ + 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[ + 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" + '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[ + 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[ + 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[ + 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[ + 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") + output_stats.get_fn().replace('SETS', f'{nsims}sets') + args.outputfn_sfix - + "all.nc" + + 'all.nc' ) output_stats.save_xr_ds() @@ -2079,7 +2079,7 @@ def run(list_packed_vars): if ( args.export_binned_data and glacier_rgi_table.Area - > pygem_prms["sim"]["out"]["export_binned_area_threshold"] + > 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 @@ -2115,40 +2115,40 @@ def run(list_packed_vars): 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_ds_binned_stats['bin_distance'].values[0, :] = ( output_glac_bin_dist ) - output_ds_binned_stats["bin_surface_h_initial"].values[ + output_ds_binned_stats['bin_surface_h_initial'].values[ 0, : ] = surface_h_initial - output_ds_binned_stats["bin_area_annual"].values[ + output_ds_binned_stats['bin_area_annual'].values[ 0, :, : ] = output_glac_bin_area_annual[:, :, n_iter] - output_ds_binned_stats["bin_mass_annual"].values[ + output_ds_binned_stats['bin_mass_annual'].values[ 0, :, : ] = output_glac_bin_mass_annual[:, :, n_iter] - output_ds_binned_stats["bin_thick_annual"].values[ + output_ds_binned_stats['bin_thick_annual'].values[ 0, :, : ] = output_glac_bin_icethickness_annual[:, :, n_iter] - output_ds_binned_stats["bin_massbalclim_annual"].values[ + output_ds_binned_stats['bin_massbalclim_annual'].values[ 0, :, : ] = output_glac_bin_massbalclim_annual[:, :, n_iter] output_ds_binned_stats[ - "bin_massbalclim_monthly" + 'bin_massbalclim_monthly' ].values[0, :, :] = output_glac_bin_massbalclim_monthly[ :, :, n_iter ] if args.export_binned_components: output_ds_binned_stats[ - "bin_accumulation_monthly" + 'bin_accumulation_monthly' ].values[0, :, :] = output_glac_bin_acc_monthly[ :, :, n_iter ] - output_ds_binned_stats["bin_melt_monthly"].values[ + output_ds_binned_stats['bin_melt_monthly'].values[ 0, :, : ] = output_glac_bin_melt_monthly[:, :, n_iter] output_ds_binned_stats[ - "bin_refreeze_monthly" + 'bin_refreeze_monthly' ].values[ 0, :, : ] = output_glac_bin_refreeze_monthly[:, :, n_iter] @@ -2156,10 +2156,10 @@ def run(list_packed_vars): # export binned stats for iteration output_binned.set_fn( output_binned.get_fn().replace( - "SETS", f"set{n_iter}" + 'SETS', f'set{n_iter}' ) + args.outputfn_sfix - + "binned.nc" + + 'binned.nc' ) output_binned.save_xr_ds() @@ -2187,88 +2187,88 @@ def run(list_packed_vars): # populate dataset with stats from each variable of interest output_ds_binned_stats[ - "bin_distance" + 'bin_distance' ].values = output_glac_bin_dist[np.newaxis, :] output_ds_binned_stats[ - "bin_surface_h_initial" + 'bin_surface_h_initial' ].values = surface_h_initial[np.newaxis, :] - output_ds_binned_stats["bin_area_annual"].values = np.median( + 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_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_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" + 'bin_massbalclim_annual' ].values = np.median( output_glac_bin_massbalclim_annual, axis=2 )[np.newaxis, :, :] output_ds_binned_stats[ - "bin_massbalclim_monthly" + '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" + 'bin_accumulation_monthly' ].values = np.median(output_glac_bin_acc_monthly, axis=2)[ np.newaxis, :, : ] output_ds_binned_stats[ - "bin_melt_monthly" + 'bin_melt_monthly' ].values = np.median(output_glac_bin_melt_monthly, axis=2)[ np.newaxis, :, : ] output_ds_binned_stats[ - "bin_refreeze_monthly" + '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" + '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" + '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" + '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") + output_binned.get_fn().replace('SETS', f'{nsims}sets') + args.outputfn_sfix - + "binned.nc" + + 'binned.nc' ) output_binned.save_xr_ds() except Exception as err: # LOG FAILURE fail_fp = ( - pygem_prms["root"] - + "/Output/simulations/failed/" + pygem_prms['root'] + + '/Output/simulations/failed/' + reg_str - + "/" + + '/' + gcm_name - + "/" + + '/' ) - if gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: - fail_fp += scenario + "/" + if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: + fail_fp += scenario + '/' if not os.path.exists(fail_fp): os.makedirs(fail_fp, exist_ok=True) - txt_fn_fail = glacier_str + "-sim_failed.txt" - with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + f" failed to complete simulation: {err}") + 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 if args.ncores == 1: @@ -2284,33 +2284,33 @@ def main(): # 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}]" + 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}]" + f'gcm_startyear [{args.gcm_startyear}] must be less than gcm_endyear [{args.gcm_endyear}]' ) except AssertionError as err: - print("error: ", err) + print('error: ', err) sys.exit(1) # RGI glacier number if args.rgi_glac_number: 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: + 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"], + 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) + glac_no = list(main_glac_rgi_all['rgino_str'].values) # Number of cores for parallel processing if args.ncores > 1: @@ -2332,18 +2332,18 @@ def main(): gcm_list = [args.ref_gcm_name] scenario = args.scenario else: - with open(args.gcm_list_fn, "r") as gcm_fn: + with open(args.gcm_list_fn, 'r') as gcm_fn: gcm_list = gcm_fn.read().splitlines() - scenario = os.path.basename(args.gcm_list_fn).split("_")[1] - print("Found %d gcms to process" % (len(gcm_list))) + scenario = os.path.basename(args.gcm_list_fn).split('_')[1] + print('Found %d gcms to process' % (len(gcm_list))) # Read realizations from argument parser if args.realization is not None: realizations = [args.realization] elif args.realization_list is not None: - with open(args.realization_list, "r") as real_fn: + with open(args.realization_list, 'r') as real_fn: realizations = list(real_fn.read().splitlines()) - print("Found %d realizations to process" % (len(realizations))) + print('Found %d realizations to process' % (len(realizations))) else: realizations = None @@ -2354,9 +2354,9 @@ def main(): # Loop through all GCMs for gcm_name in gcm_list: if args.scenario is None: - print("Processing:", gcm_name) + print('Processing:', gcm_name) elif args.scenario is not None: - print("Processing:", gcm_name, scenario) + print('Processing:', gcm_name, scenario) # Pack variables for multiprocessing list_packed_vars = [] if realizations is not None: @@ -2367,7 +2367,7 @@ def main(): 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...") + print('Processing with ' + str(num_cores) + ' cores...') # Parallel processing if num_cores > 1: with multiprocessing.Pool(num_cores) as p: @@ -2378,8 +2378,8 @@ def main(): 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__": +if __name__ == '__main__': main() diff --git a/pygem/class_climate.py b/pygem/class_climate.py index 99b4a5eb..d0193e09 100755 --- a/pygem/class_climate.py +++ b/pygem/class_climate.py @@ -43,9 +43,9 @@ 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"]: + 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!" + 'Longitude does not use 360 degrees. Check how negative values are handled!' ) # Source of climate data @@ -59,259 +59,259 @@ def __init__(self, name=str(), scenario=str(), realization=None): self.realization = realization # Set parameters for CESM2 Large Ensemble - if self.name == "smbb.f09_g17.LE2": + if self.name == 'smbb.f09_g17.LE2': # Standardized CESM2 Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = "tas" - self.prec_vn = "pr" - self.elev_vn = "orog" - self.lat_vn = "lat" - self.lon_vn = "lon" - self.time_vn = "time" + self.temp_vn = 'tas' + self.prec_vn = 'pr' + self.elev_vn = 'orog' + self.lat_vn = 'lat' + self.lon_vn = 'lon' + self.time_vn = 'time' # Variable filenames self.temp_fn = ( self.temp_vn - + "_mon_" + + '_mon_' + scenario - + "_" + + '_' + name - + "-" + + '-' + realization - + ".cam.h0.1980-2100.nc" + + '.cam.h0.1980-2100.nc' ) self.prec_fn = ( self.prec_vn - + "_mon_" + + '_mon_' + scenario - + "_" + + '_' + name - + "-" + + '-' + realization - + ".cam.h0.1980-2100.nc" + + '.cam.h0.1980-2100.nc' ) self.elev_fn = ( - self.elev_vn + "_fx_" + scenario + "_" + name + ".cam.h0.nc" + self.elev_vn + '_fx_' + scenario + '_' + name + '.cam.h0.nc' ) # Variable filepaths self.var_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cesm2_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario - + pygem_prms["climate"]["paths"]["cesm2_fp_var_ending"] + + pygem_prms['climate']['paths']['cesm2_fp_var_ending'] ) self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cesm2_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario - + pygem_prms["climate"]["paths"]["cesm2_fp_fx_ending"] + + pygem_prms['climate']['paths']['cesm2_fp_fx_ending'] ) # Extra information - self.timestep = pygem_prms["time"]["timestep"] - self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] - self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + self.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] self.scenario = scenario # Set parameters for GFDL SPEAR Large Ensemble - elif self.name == "GFDL-SPEAR-MED": + elif self.name == 'GFDL-SPEAR-MED': # Standardized GFDL SPEAR Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = "tas" - self.prec_vn = "pr" - self.elev_vn = "zsurf" - self.lat_vn = "lat" - self.lon_vn = "lon" - self.time_vn = "time" + self.temp_vn = 'tas' + self.prec_vn = 'pr' + self.elev_vn = 'zsurf' + self.lat_vn = 'lat' + self.lon_vn = 'lon' + self.time_vn = 'time' # Variable filenames self.temp_fn = ( self.temp_vn - + "_mon_" + + '_mon_' + scenario - + "_" + + '_' + name - + "-" + + '-' + realization - + "i1p1f1_gr3_1980-2100.nc" + + 'i1p1f1_gr3_1980-2100.nc' ) self.prec_fn = ( self.prec_vn - + "_mon_" + + '_mon_' + scenario - + "_" + + '_' + name - + "-" + + '-' + realization - + "i1p1f1_gr3_1980-2100.nc" + + 'i1p1f1_gr3_1980-2100.nc' ) - self.elev_fn = self.elev_vn + "_fx_" + scenario + "_" + name + ".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"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario - + pygem_prms["climate"]["paths"]["gfdl_fp_var_ending"] + + pygem_prms['climate']['paths']['gfdl_fp_var_ending'] ) self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["gfdl_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario - + pygem_prms["climate"]["paths"]["gfdl_fp_fx_ending"] + + pygem_prms['climate']['paths']['gfdl_fp_fx_ending'] ) # Extra information - self.timestep = pygem_prms["time"]["timestep"] - self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] - self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + self.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] self.scenario = scenario else: self.realization = [] # Set parameters for ERA5, ERA-Interim, and CMIP5 netcdf files - if self.name == "ERA5": + if self.name == 'ERA5': # Variable names - self.temp_vn = "t2m" - self.tempstd_vn = "t2m_std" - self.prec_vn = "tp" - self.elev_vn = "z" - self.lat_vn = "latitude" - self.lon_vn = "longitude" - self.time_vn = "time" - self.lr_vn = "lapserate" + self.temp_vn = 't2m' + self.tempstd_vn = 't2m_std' + self.prec_vn = 'tp' + self.elev_vn = 'z' + self.lat_vn = 'latitude' + self.lon_vn = 'longitude' + self.time_vn = 'time' + self.lr_vn = 'lapserate' # Variable filenames - self.temp_fn = pygem_prms["climate"]["paths"]["era5_temp_fn"] - self.tempstd_fn = pygem_prms["climate"]["paths"]["era5_tempstd_fn"] - self.prec_fn = pygem_prms["climate"]["paths"]["era5_prec_fn"] - self.elev_fn = pygem_prms["climate"]["paths"]["era5_elev_fn"] - self.lr_fn = pygem_prms["climate"]["paths"]["era5_lr_fn"] + self.temp_fn = pygem_prms['climate']['paths']['era5_temp_fn'] + self.tempstd_fn = pygem_prms['climate']['paths']['era5_tempstd_fn'] + self.prec_fn = pygem_prms['climate']['paths']['era5_prec_fn'] + self.elev_fn = pygem_prms['climate']['paths']['era5_elev_fn'] + self.lr_fn = pygem_prms['climate']['paths']['era5_lr_fn'] # Variable filepaths self.var_fp = ( - pygem_prms["root"] + pygem_prms["climate"]["paths"]["era5_relpath"] + pygem_prms['root'] + pygem_prms['climate']['paths']['era5_relpath'] ) self.fx_fp = ( - pygem_prms["root"] + pygem_prms["climate"]["paths"]["era5_relpath"] + 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.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] - elif self.name == "ERA-Interim": + elif self.name == 'ERA-Interim': # Variable names - self.temp_vn = "t2m" - self.prec_vn = "tp" - self.elev_vn = "z" - self.lat_vn = "latitude" - self.lon_vn = "longitude" - self.time_vn = "time" - self.lr_vn = "lapserate" + self.temp_vn = 't2m' + self.prec_vn = 'tp' + self.elev_vn = 'z' + self.lat_vn = 'latitude' + self.lon_vn = 'longitude' + self.time_vn = 'time' + self.lr_vn = 'lapserate' # Variable filenames - self.temp_fn = pygem_prms["climate"]["paths"]["eraint_temp_fn"] - self.prec_fn = pygem_prms["climate"]["paths"]["eraint_prec_fn"] - self.elev_fn = pygem_prms["climate"]["paths"]["eraint_elev_fn"] - self.lr_fn = pygem_prms["climate"]["paths"]["eraint_lr_fn"] + self.temp_fn = pygem_prms['climate']['paths']['eraint_temp_fn'] + self.prec_fn = pygem_prms['climate']['paths']['eraint_prec_fn'] + self.elev_fn = pygem_prms['climate']['paths']['eraint_elev_fn'] + self.lr_fn = pygem_prms['climate']['paths']['eraint_lr_fn'] # Variable filepaths self.var_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["eraint_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['eraint_relpath'] ) self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["eraint_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['eraint_relpath'] ) # Extra information - self.timestep = pygem_prms["time"]["timestep"] - self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] - self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + self.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] # Standardized CMIP5 format (GCM/RCP) - elif "rcp" in scenario: + elif 'rcp' in scenario: # Variable names - self.temp_vn = "tas" - self.prec_vn = "pr" - self.elev_vn = "orog" - self.lat_vn = "lat" - self.lon_vn = "lon" - self.time_vn = "time" + self.temp_vn = 'tas' + self.prec_vn = 'pr' + self.elev_vn = 'orog' + self.lat_vn = 'lat' + self.lon_vn = 'lon' + self.time_vn = 'time' # Variable filenames self.temp_fn = ( - self.temp_vn + "_mon_" + name + "_" + scenario + "_r1i1p1_native.nc" + self.temp_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' ) self.prec_fn = ( - self.prec_vn + "_mon_" + name + "_" + scenario + "_r1i1p1_native.nc" + self.prec_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' ) self.elev_fn = ( - self.elev_vn + "_fx_" + name + "_" + scenario + "_r0i0p0.nc" + self.elev_vn + '_fx_' + name + '_' + scenario + '_r0i0p0.nc' ) # Variable filepaths self.var_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip5_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario - + pygem_prms["climate"]["paths"]["cmip5_fp_var_ending"] + + pygem_prms['climate']['paths']['cmip5_fp_var_ending'] ) self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip5_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario - + pygem_prms["climate"]["paths"]["cmip5_fp_fx_ending"] + + 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 + "/" + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' ): self.var_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip5_relpath"] + 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 + "/" + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' ): self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip5_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cmip5_relpath'] + name - + "/" + + '/' ) # Extra information - self.timestep = pygem_prms["time"]["timestep"] - self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] - self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + self.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] self.scenario = scenario # Standardized CMIP6 format (GCM/SSP) - elif "ssp" in scenario: + elif 'ssp' in scenario: # Variable names - self.temp_vn = "tas" - self.prec_vn = "pr" - self.elev_vn = "orog" - self.lat_vn = "lat" - self.lon_vn = "lon" - self.time_vn = "time" + self.temp_vn = 'tas' + self.prec_vn = 'pr' + self.elev_vn = 'orog' + self.lat_vn = 'lat' + self.lon_vn = 'lon' + self.time_vn = 'time' # Variable filenames self.temp_fn = ( - name + "_" + scenario + "_r1i1p1f1_" + self.temp_vn + ".nc" + name + '_' + scenario + '_r1i1p1f1_' + self.temp_vn + '.nc' ) self.prec_fn = ( - name + "_" + scenario + "_r1i1p1f1_" + self.prec_vn + ".nc" + name + '_' + scenario + '_r1i1p1f1_' + self.prec_vn + '.nc' ) - self.elev_fn = name + "_" + self.elev_vn + ".nc" + self.elev_fn = name + '_' + self.elev_vn + '.nc' # Variable filepaths self.var_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip6_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cmip6_relpath'] + name - + "/" + + '/' ) self.fx_fp = ( - pygem_prms["root"] - + pygem_prms["climate"]["paths"]["cmip6_relpath"] + pygem_prms['root'] + + pygem_prms['climate']['paths']['cmip6_relpath'] + name - + "/" + + '/' ) # Extra information - self.timestep = pygem_prms["time"]["timestep"] - self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] - self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + self.timestep = pygem_prms['time']['timestep'] + self.rgi_lat_colname = pygem_prms['rgi']['rgi_lat_colname'] + self.rgi_lon_colname = pygem_prms['rgi']['rgi_lon_colname'] self.scenario = scenario def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): @@ -338,11 +338,11 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): data = xr.open_dataset(self.fx_fp + filename) glac_variable = np.zeros(main_glac_rgi.shape[0]) # If time dimension included, then set the time index (required for ERA Interim, but not for CMIP5 or COAWST) - if "time" in data[vn].coords: + if 'time' in data[vn].coords: time_idx = 0 # ERA Interim has only 1 value of time, so index is 0 # Find Nearest Neighbor - if self.name == "COAWST": + if self.name == 'COAWST': for glac in range(main_glac_rgi.shape[0]): latlon_dist = ( ( @@ -392,15 +392,15 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): # Correct units if necessary (CMIP5 already in m a.s.l., ERA Interim is geopotential [m2 s-2]) if vn == self.elev_vn: # If the variable has units associated with geopotential, then convert to m.a.s.l (ERA Interim) - if "units" in data[vn].attrs and (data[vn].attrs["units"] == "m**2 s**-2"): + if 'units' in data[vn].attrs and (data[vn].attrs['units'] == 'm**2 s**-2'): # Convert m2 s-2 to m by dividing by gravity (ERA Interim states to use 9.80665) glac_variable = glac_variable / 9.80665 # Elseif units already in m.a.s.l., then continue - elif "units" in data[vn].attrs and data[vn].attrs["units"] == "m": + elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': pass # Otherwise, provide warning else: - print("Check units of elevation from GCM is m.") + print('Check units of elevation from GCM is m.') return glac_variable @@ -410,7 +410,7 @@ def importGCMvarnearestneighbor_xarray( vn, main_glac_rgi, dates_table, - realizations=["r1i1p1f1", "r4i1p1f1"], + realizations=['r1i1p1f1', 'r4i1p1f1'], ): """ Import time series of variables and extract nearest neighbor. @@ -441,35 +441,35 @@ def importGCMvarnearestneighbor_xarray( """ # 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])) # Check GCM provides required years of data - years_check = pd.Series(data["time"]).apply(lambda x: int(x.strftime("%Y"))) + 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()) + 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()) + self.name + ' does not provide data back to ' + str(dates_table.year.min()) ) # Determine the correct time indices - if self.timestep == "monthly": + 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] + 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"))[ + 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 ] ) @@ -487,21 +487,21 @@ def importGCMvarnearestneighbor_xarray( # 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 # information - elif self.timestep == "daily": + elif self.timestep == 'daily': start_idx = ( np.where( pd.Series(data[self.time_vn]).apply( - lambda x: x.strftime("%Y-%m-%d") + lambda x: x.strftime('%Y-%m-%d') ) - == dates_table["date"].apply(lambda x: x.strftime("%Y-%m-%d"))[0] + == 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") + lambda x: x.strftime('%Y-%m-%d') ) - == dates_table["date"].apply(lambda x: x.strftime("%Y-%m-%d"))[ + == dates_table['date'].apply(lambda x: x.strftime('%Y-%m-%d'))[ dates_table.shape[0] - 1 ] ) @@ -509,7 +509,7 @@ def importGCMvarnearestneighbor_xarray( # Extract the time series time_series = pd.Series(data[self.time_vn][start_idx : end_idx + 1]) # Find Nearest Neighbor - if self.name == "COAWST": + if self.name == 'COAWST': for glac in range(main_glac_rgi.shape[0]): latlon_dist = ( ( @@ -548,7 +548,7 @@ def importGCMvarnearestneighbor_xarray( # Create dictionary of time series for each unique latitude/longitude glac_variable_dict = {} for latlon in latlon_nearidx_unique: - if "expver" in data.keys(): + 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] @@ -565,41 +565,41 @@ def importGCMvarnearestneighbor_xarray( # Perform corrections to the data if necessary # Surface air temperature corrections - if vn in ["tas", "t2m", "T2"]: - if "units" in data[vn].attrs and data[vn].attrs["units"] == "K": + if vn in ['tas', 't2m', 'T2']: + if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'K': # Convert from K to deg C glac_variable_series = glac_variable_series - 273.15 else: - print("Check units of air temperature from GCM is degrees C.") - elif vn in ["t2m_std"]: - if "units" in data[vn].attrs and data[vn].attrs["units"] not in ["C", "K"]: + print('Check units of air temperature 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" + 'Check units of air temperature standard deviation from GCM is degrees C or K' ) # Precipitation corrections # If the variable is precipitation - elif vn in ["pr", "tp", "TOTPRECIP"]: + elif vn in ['pr', 'tp', 'TOTPRECIP']: # If the variable has units and those units are meters (ERA Interim) - if "units" in data[vn].attrs and data[vn].attrs["units"] == "m": + if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': pass # Elseif the variable has units and those units are kg m-2 s-1 (CMIP5/CMIP6) - elif "units" in data[vn].attrs and data[vn].attrs["units"] == "kg m-2 s-1": + elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'kg m-2 s-1': # Convert from kg m-2 s-1 to m day-1 glac_variable_series = glac_variable_series / 1000 * 3600 * 24 # (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": + elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'mm': glac_variable_series = glac_variable_series / 1000 # Else check the variables units else: - print("Check units of precipitation from GCM is meters per day.") - if self.timestep == "monthly" and self.name != "COAWST": + print('Check units of precipitation from GCM is meters per day.') + if self.timestep == 'monthly' and self.name != 'COAWST': # Convert from meters per day to meters per month (COAWST data already 'monthly accumulated precipitation') - if "daysinmonth" in dates_table.columns: + if 'daysinmonth' in dates_table.columns: glac_variable_series = ( glac_variable_series - * dates_table["daysinmonth"].values[np.newaxis, :] + * dates_table['daysinmonth'].values[np.newaxis, :] ) elif vn != self.lr_vn: - print("Check units of air temperature or precipitation") + print('Check units of air temperature or precipitation') return glac_variable_series, time_series diff --git a/pygem/gcmbiasadj.py b/pygem/gcmbiasadj.py index 029a8995..eb7e2987 100755 --- a/pygem/gcmbiasadj.py +++ b/pygem/gcmbiasadj.py @@ -149,10 +149,10 @@ def temp_biasadj_HH2015( if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) bc_temp = gcm_temp[:, sim_idx_start:] @@ -201,7 +201,7 @@ def temp_biasadj_HH2015( ) < 1 ), ( - "Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree" + 'Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree' ) else: if debug: @@ -279,10 +279,10 @@ def prec_biasadj_HH2015( if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) bc_prec = gcm_prec[:, sim_idx_start:] @@ -302,13 +302,13 @@ def prec_biasadj_HH2015( 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" + '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" + 'gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified' ) assert gcm_prec_biasadj.min() >= 0, ( - "gcm_prec_adj is producing a negative precipitation value" + 'gcm_prec_adj is producing a negative precipitation value' ) return gcm_prec_biasadj, gcm_elev_biasadj @@ -378,10 +378,10 @@ def prec_biasadj_opt1( if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) bc_prec = gcm_prec[:, sim_idx_start:] @@ -458,13 +458,13 @@ def prec_biasadj_opt1( 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" + '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" + 'gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified' ) assert gcm_prec_biasadj.min() >= 0, ( - "gcm_prec_adj is producing a negative precipitation value" + 'gcm_prec_adj is producing a negative precipitation value' ) return gcm_prec_biasadj, gcm_elev_biasadj @@ -534,10 +534,10 @@ def temp_biasadj_QDM( if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) bc_temp = gcm_temp[:, sim_idx_start:] @@ -659,10 +659,10 @@ def prec_biasadj_QDM( if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) bc_prec = gcm_prec[:, sim_idx_start:] @@ -744,10 +744,10 @@ def monthly_avg_array_rolled( # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1981-2000) if gcm_startyear != ref_startyear: - if pygem_prms["climate"]["gcm_wateryear"] == "hydro": - dates_cn = "wateryear" + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + dates_cn = 'wateryear' else: - dates_cn = "year" + dates_cn = 'year' sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) gcm_array = gcm_array[:, sim_idx_start:] diff --git a/pygem/glacierdynamics.py b/pygem/glacierdynamics.py index 16daad2a..a66a5edf 100755 --- a/pygem/glacierdynamics.py +++ b/pygem/glacierdynamics.py @@ -82,7 +82,7 @@ def __init__( mb_model=mb_model, y0=y0, inplace=inplace, - mb_elev_feedback="annual", + mb_elev_feedback='annual', **kwargs, ) self.option_areaconstant = option_areaconstant @@ -102,11 +102,11 @@ def __init__( # HERE IS THE STUFF TO RECORD FOR EACH FLOWLINE! if self.is_tidewater: - self.calving_k = cfg.PARAMS["calving_k"] + self.calving_k = cfg.PARAMS['calving_k'] self.calving_m3_since_y0 = 0.0 # total calving since time y0 assert len(flowlines) == 1, ( - "MassRedistributionCurveModel is not set up for multiple flowlines" + 'MassRedistributionCurveModel is not set up for multiple flowlines' ) def run_until(self, y1, run_single_year=False): @@ -134,12 +134,12 @@ def run_until(self, y1, run_single_year=False): if self.check_for_boundaries: if self.fls[-1].thick[-1] > 10: raise RuntimeError( - "Glacier exceeds domain boundaries, at year: {}".format(self.yr) + 'Glacier exceeds domain boundaries, at year: {}'.format(self.yr) ) # Check for NaNs for fl in self.fls: if np.any(~np.isfinite(fl.thick)): - raise FloatingPointError("NaN in numerical solution.") + raise FloatingPointError('NaN in numerical solution.') def run_until_and_store( self, y1, run_path=None, diag_path=None, store_monthly_step=None @@ -177,27 +177,27 @@ def run_until_and_store( if int(y1) != y1: raise InvalidParamsError( - "run_until_and_store only accepts integer year dates." + '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." + '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) if store_monthly_step is None: - store_monthly_step = self.mb_step == "monthly" + store_monthly_step = self.mb_step == 'monthly' if store_monthly_step: monthly_time = utils.monthly_timeseries(self.yr, y1) else: monthly_time = np.arange(np.floor(self.yr), np.floor(y1) + 1) - sm = cfg.PARAMS["hydro_month_" + self.mb_model.hemisphere] + sm = cfg.PARAMS['hydro_month_' + self.mb_model.hemisphere] yrs, months = utils.floatyear_to_date(monthly_time) cyrs, cmonths = utils.hydrodate_to_calendardate(yrs, months, start_month=sm) @@ -218,70 +218,70 @@ def run_until_and_store( diag_ds = xr.Dataset() # Global attributes - diag_ds.attrs["description"] = "OGGM model output" - diag_ds.attrs["oggm_version"] = __version__ - diag_ds.attrs["calendar"] = "365-day no leap" - diag_ds.attrs["creation_date"] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) - diag_ds.attrs["hemisphere"] = self.mb_model.hemisphere - diag_ds.attrs["water_level"] = self.water_level + diag_ds.attrs['description'] = 'OGGM model output' + diag_ds.attrs['oggm_version'] = __version__ + diag_ds.attrs['calendar'] = '365-day no leap' + diag_ds.attrs['creation_date'] = strftime('%Y-%m-%d %H:%M:%S', gmtime()) + diag_ds.attrs['hemisphere'] = self.mb_model.hemisphere + diag_ds.attrs['water_level'] = self.water_level # Coordinates - diag_ds.coords["time"] = ("time", monthly_time) - diag_ds.coords["hydro_year"] = ("time", yrs) - diag_ds.coords["hydro_month"] = ("time", months) - diag_ds.coords["calendar_year"] = ("time", cyrs) - diag_ds.coords["calendar_month"] = ("time", cmonths) - - diag_ds["time"].attrs["description"] = "Floating hydrological year" - diag_ds["hydro_year"].attrs["description"] = "Hydrological year" - diag_ds["hydro_month"].attrs["description"] = "Hydrological month" - diag_ds["calendar_year"].attrs["description"] = "Calendar year" - diag_ds["calendar_month"].attrs["description"] = "Calendar month" + diag_ds.coords['time'] = ('time', monthly_time) + diag_ds.coords['hydro_year'] = ('time', yrs) + diag_ds.coords['hydro_month'] = ('time', months) + diag_ds.coords['calendar_year'] = ('time', cyrs) + diag_ds.coords['calendar_month'] = ('time', cmonths) + + diag_ds['time'].attrs['description'] = 'Floating hydrological year' + diag_ds['hydro_year'].attrs['description'] = 'Hydrological year' + diag_ds['hydro_month'].attrs['description'] = 'Hydrological month' + diag_ds['calendar_year'].attrs['description'] = 'Calendar year' + diag_ds['calendar_month'].attrs['description'] = 'Calendar month' # Variables and attributes - diag_ds["volume_m3"] = ("time", np.zeros(nm) * np.nan) - diag_ds["volume_m3"].attrs["description"] = "Total glacier volume" - diag_ds["volume_m3"].attrs["unit"] = "m 3" - diag_ds["volume_bsl_m3"] = ("time", np.zeros(nm) * np.nan) - diag_ds["volume_bsl_m3"].attrs["description"] = "Glacier volume below sea-level" - diag_ds["volume_bsl_m3"].attrs["unit"] = "m 3" - diag_ds["volume_bwl_m3"] = ("time", np.zeros(nm) * np.nan) - diag_ds["volume_bwl_m3"].attrs["description"] = "Glacier volume below " - diag_ds["volume_bwl_m3"].attrs["unit"] = "m 3" - - diag_ds["area_m2"] = ("time", np.zeros(nm) * np.nan) - diag_ds["area_m2"].attrs["description"] = "Total glacier area" - diag_ds["area_m2"].attrs["unit"] = "m 2" - diag_ds["length_m"] = ("time", np.zeros(nm) * np.nan) - diag_ds["length_m"].attrs["description"] = "Glacier length" - diag_ds["length_m"].attrs["unit"] = "m 3" - diag_ds["ela_m"] = ("time", np.zeros(nm) * np.nan) - diag_ds["ela_m"].attrs["description"] = ( - "Annual Equilibrium Line Altitude (ELA)" + diag_ds['volume_m3'] = ('time', np.zeros(nm) * np.nan) + diag_ds['volume_m3'].attrs['description'] = 'Total glacier volume' + diag_ds['volume_m3'].attrs['unit'] = 'm 3' + diag_ds['volume_bsl_m3'] = ('time', np.zeros(nm) * np.nan) + diag_ds['volume_bsl_m3'].attrs['description'] = 'Glacier volume below sea-level' + diag_ds['volume_bsl_m3'].attrs['unit'] = 'm 3' + diag_ds['volume_bwl_m3'] = ('time', np.zeros(nm) * np.nan) + diag_ds['volume_bwl_m3'].attrs['description'] = 'Glacier volume below ' + diag_ds['volume_bwl_m3'].attrs['unit'] = 'm 3' + + diag_ds['area_m2'] = ('time', np.zeros(nm) * np.nan) + diag_ds['area_m2'].attrs['description'] = 'Total glacier area' + diag_ds['area_m2'].attrs['unit'] = 'm 2' + diag_ds['length_m'] = ('time', np.zeros(nm) * np.nan) + diag_ds['length_m'].attrs['description'] = 'Glacier length' + diag_ds['length_m'].attrs['unit'] = 'm 3' + diag_ds['ela_m'] = ('time', np.zeros(nm) * np.nan) + diag_ds['ela_m'].attrs['description'] = ( + 'Annual Equilibrium Line Altitude (ELA)' ) - diag_ds["ela_m"].attrs["unit"] = "m a.s.l" + diag_ds['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'] = ('time', np.zeros(nm) * np.nan) + diag_ds['calving_m3'].attrs['description'] = ( + 'Total accumulated calving flux' ) - diag_ds["calving_m3"].attrs["unit"] = "m 3" - diag_ds["calving_rate_myr"] = ("time", np.zeros(nm) * np.nan) - diag_ds["calving_rate_myr"].attrs["description"] = "Calving rate" - diag_ds["calving_rate_myr"].attrs["unit"] = "m yr-1" + diag_ds['calving_m3'].attrs['unit'] = 'm 3' + diag_ds['calving_rate_myr'] = ('time', np.zeros(nm) * np.nan) + diag_ds['calving_rate_myr'].attrs['description'] = 'Calving rate' + diag_ds['calving_rate_myr'].attrs['unit'] = 'm yr-1' # Run j = 0 for i, (yr, mo) in enumerate(zip(yearly_time[:-1], months[:-1])): # Record initial parameters if i == 0: - diag_ds["volume_m3"].data[i] = self.volume_m3 - diag_ds["area_m2"].data[i] = self.area_m2 - diag_ds["length_m"].data[i] = self.length_m + diag_ds['volume_m3'].data[i] = self.volume_m3 + diag_ds['area_m2'].data[i] = self.area_m2 + diag_ds['length_m'].data[i] = self.length_m if self.is_tidewater: - diag_ds["volume_bsl_m3"].data[i] = self.volume_bsl_m3 - diag_ds["volume_bwl_m3"].data[i] = self.volume_bwl_m3 + diag_ds['volume_bsl_m3'].data[i] = self.volume_bsl_m3 + diag_ds['volume_bwl_m3'].data[i] = self.volume_bwl_m3 self.run_until(yr, run_single_year=True) # Model run @@ -296,47 +296,47 @@ def run_until_and_store( 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): ds = xr.Dataset() - ds.attrs["description"] = "OGGM model output" - ds.attrs["oggm_version"] = __version__ - ds.attrs["calendar"] = "365-day no leap" - ds.attrs["creation_date"] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) - ds.coords["time"] = yearly_time - ds["time"].attrs["description"] = "Floating hydrological year" + ds.attrs['description'] = 'OGGM model output' + ds.attrs['oggm_version'] = __version__ + ds.attrs['calendar'] = '365-day no leap' + ds.attrs['creation_date'] = strftime('%Y-%m-%d %H:%M:%S', gmtime()) + ds.coords['time'] = yearly_time + ds['time'].attrs['description'] = 'Floating hydrological year' varcoords = OrderedDict( - time=("time", yearly_time), year=("time", yearly_time) + time=('time', yearly_time), year=('time', yearly_time) ) - ds["ts_section"] = xr.DataArray(s, dims=("time", "x"), coords=varcoords) - ds["ts_width_m"] = xr.DataArray(w, dims=("time", "x"), coords=varcoords) + ds['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}, + 'ts_section': {'zlib': True, 'complevel': 5}, + 'ts_width_m': {'zlib': True, 'complevel': 5}, } for i, ds in enumerate(run_ds): - ds.to_netcdf(run_path, "a", group="fl_{}".format(i), encoding=encode) + ds.to_netcdf(run_path, 'a', group='fl_{}'.format(i), encoding=encode) # Add other diagnostics - diag_ds.to_netcdf(run_path, "a") + diag_ds.to_netcdf(run_path, 'a') if diag_path is not None: diag_ds.to_netcdf(diag_path) @@ -347,7 +347,7 @@ def updategeometry(self, year, debug=False): """Update geometry for a given year""" if debug: - print("year:", year) + print('year:', year) # Loop over flowlines for fl_id, fl in enumerate(self.fls): @@ -377,10 +377,10 @@ def updategeometry(self, year, debug=False): heights, fls=self.fls, fl_id=fl_id, year=year, debug=False ) if debug: - print("fa_m3_init:", fa_m3) + print('fa_m3_init:', fa_m3) vol_init = (self.fls[fl_id].section * fl.dx_meter).sum() - print(" volume init:", np.round(vol_init)) - print(" volume final:", np.round(vol_init - fa_m3)) + print(' volume init:', np.round(vol_init)) + print(' volume final:', np.round(vol_init - fa_m3)) # First, remove volume lost to frontal ablation # changes to _t0 not _t1, since t1 will be done in the mass redistribution @@ -389,7 +389,7 @@ def updategeometry(self, year, debug=False): )[0] while fa_m3 > 0 and len(glac_idx_bsl) > 0: if debug: - print("fa_m3_remaining:", fa_m3) + print('fa_m3_remaining:', fa_m3) # OGGM code # glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] @@ -397,7 +397,7 @@ def updategeometry(self, year, debug=False): if debug: print( - "before:", + '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), @@ -412,8 +412,8 @@ def updategeometry(self, year, debug=False): last_idx, int(12 * (year + 1) - 1) ] = ( vol_last - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) # Update ice thickness and section area section_t0[last_idx] = 0 @@ -433,8 +433,8 @@ def updategeometry(self, year, debug=False): last_idx, int(12 * (year + 1) - 1) ] = ( fa_m3 - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) # Frontal ablation bucket now empty fa_m3 = 0 @@ -447,13 +447,13 @@ def updategeometry(self, year, debug=False): if debug: print( - "after:", + '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:", + ' vol final:', (self.fls[fl_id].section * fl.dx_meter).sum(), ) @@ -470,7 +470,7 @@ def updategeometry(self, year, debug=False): ) sec_in_year = ( self.mb_model.dates_table.loc[ - 12 * year : 12 * (year + 1) - 1, "daysinmonth" + 12 * year : 12 * (year + 1) - 1, 'daysinmonth' ].values.sum() * 24 * 3600 @@ -539,8 +539,8 @@ def _get_annual_frontalablation( fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter - fl_widths_m = getattr(fl, "widths_m", None) - fl_section = getattr(fl, "section", None) + fl_widths_m = getattr(fl, 'widths_m', None) + fl_section = getattr(fl, 'section', None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) @@ -575,15 +575,15 @@ def _get_annual_frontalablation( # Volume [m3] and bed elevation [masl] of each bin if debug: print( - "\nyear:", + '\nyear:', year, - "\n sea level:", + '\n sea level:', self.water_level, - "bed elev:", + 'bed elev:', np.round(fl.bed_h[last_above_wl], 2), ) - print(" estimate frontal ablation") - print(" min elevation:", fl.surface_h[last_above_wl]) + print(' estimate frontal ablation') + print(' min elevation:', fl.surface_h[last_above_wl]) # --- The rest is for calving only --- self.calving_rate_myr = 0.0 @@ -603,7 +603,7 @@ def _get_annual_frontalablation( )[0] q_calving_max = np.sum(section[glac_idx_bsl]) * fl.dx_meter - if q_calving > q_calving_max + pygem_prms["constants"]["tolerance"]: + if q_calving > q_calving_max + pygem_prms['constants']['tolerance']: q_calving = q_calving_max # Add to the bucket and the diagnostics @@ -670,8 +670,8 @@ def _massredistributionHuss( glacier_volumechange = -1 * glacier_volumechange if debug: - print("\nDebugging Mass Redistribution Huss function\n") - print("glacier volume change:", glacier_volumechange) + print('\nDebugging Mass Redistribution Huss function\n') + print('glacier volume change:', glacier_volumechange) # If volume loss is more than the glacier volume, melt everything and stop here glacier_volume_total = (self.fls[0].section * self.fls[0].dx_meter).sum() @@ -699,11 +699,11 @@ def _massredistributionHuss( ) if debug: print( - "\nmax icethickness change:", + '\nmax icethickness change:', np.round(icethickness_change.max(), 3), - "\nmin icethickness change:", + '\nmin icethickness change:', np.round(icethickness_change.min(), 3), - "\nvolume remaining:", + '\nvolume remaining:', glacier_volumechange_remaining, ) nloop = 0 @@ -712,7 +712,7 @@ def _massredistributionHuss( # if glacier retreats (ice thickness == 0), volume change needs to be redistributed over glacier again while glacier_volumechange_remaining < 0: if debug: - print("\n\nGlacier retreating (loop " + str(nloop) + "):") + print('\n\nGlacier retreating (loop ' + str(nloop) + '):') section_t0_retreated = self.fls[0].section.copy() thick_t0_retreated = self.fls[0].thick.copy() @@ -750,13 +750,13 @@ def _massredistributionHuss( glacier_volumechange_remaining = 0 if debug: - print("ice thickness change:", icethickness_change) + print('ice thickness change:', icethickness_change) print( - "\nmax icethickness change:", + '\nmax icethickness change:', np.round(icethickness_change.max(), 3), - "\nmin icethickness change:", + '\nmin icethickness change:', np.round(icethickness_change.min(), 3), - "\nvolume remaining:", + '\nvolume remaining:', glacier_volumechange_remaining, ) nloop += 1 @@ -769,10 +769,10 @@ def _massredistributionHuss( # 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"] + icethickness_change > pygem_prms['sim']['icethickness_advancethreshold'] ).any() == True: if debug: - print("advancing glacier") + print('advancing glacier') # Record glacier area and ice thickness before advance corrections applied section_t0_raw = self.fls[0].section.copy() @@ -781,14 +781,14 @@ def _massredistributionHuss( glacier_area_t0_raw = width_t0_raw * self.fls[0].dx_meter if debug: - print("\n\nthickness t0:", thick_t0_raw) - print("glacier area t0:", glacier_area_t0_raw) - print("width_t0_raw:", width_t0_raw, "\n\n") + print('\n\nthickness t0:', thick_t0_raw) + print('glacier area t0:', glacier_area_t0_raw) + print('width_t0_raw:', width_t0_raw, '\n\n') # Index bins that are advancing icethickness_change[ icethickness_change - <= pygem_prms["sim"]["icethickness_advancethreshold"] + <= pygem_prms['sim']['icethickness_advancethreshold'] ] = 0 glac_idx_advance = icethickness_change.nonzero()[0] @@ -797,7 +797,7 @@ def _massredistributionHuss( glac_idx_advance ] - ( icethickness_change[glac_idx_advance] - - pygem_prms["sim"]["icethickness_advancethreshold"] + - pygem_prms['sim']['icethickness_advancethreshold'] ) glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter @@ -809,7 +809,7 @@ def _massredistributionHuss( ).sum() if debug: - print("advance volume [m3]:", advance_volume) + print('advance volume [m3]:', advance_volume) # Set the cross sectional area of the next bin advance_section = advance_volume / self.fls[0].dx_meter @@ -834,9 +834,9 @@ def _massredistributionHuss( print(self.fls[0].surface_h[glac_idx_t0]) print( glac_idx_t0_term, - "height:", + 'height:', self.fls[0].surface_h[glac_idx_t0_term], - "thickness:", + 'thickness:', self.fls[0].thick[glac_idx_t0_term], ) print( @@ -845,7 +845,7 @@ def _massredistributionHuss( > self.fls[0].surface_h[glac_idx_t0_term] )[0] ) - print("advance section:", advance_section) + print('advance section:', advance_section) thick_prior = np.copy(self.fls[0].thick) section_updated = np.copy(self.fls[0].section) @@ -874,12 +874,12 @@ def _massredistributionHuss( ) print( - "surface_h:", + 'surface_h:', self.fls[0].surface_h[glac_idx_t0], - "\nmax term elev:", + '\nmax term elev:', elev_term, ) - print("icethickness_change:", icethickness_change) + 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 @@ -893,9 +893,9 @@ def _massredistributionHuss( ) if debug: - print("thick_reduction:", thick_reduction) + print('thick_reduction:', thick_reduction) print( - "----\nprior to correction:", + '----\nprior to correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term], ) @@ -916,13 +916,13 @@ def _massredistributionHuss( if debug: print( - "post correction:", + 'post correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term], ) - print("surface_h:", self.fls[0].surface_h[glac_idx_t0]) - print("advance_volume:", advance_volume) - print("icethickness_change:", icethickness_change) + print('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 @@ -944,7 +944,7 @@ def _massredistributionHuss( (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"] + < 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 @@ -952,7 +952,7 @@ def _massredistributionHuss( 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 @@ -973,7 +973,7 @@ def _massredistributionHuss( - heights[glac_idx_initial].min() ) * 100 - < pygem_prms["sim"]["terminus_percentage"] + < pygem_prms['sim']['terminus_percentage'] ] if glac_idx_terminus_initial.shape[0] <= 1: glac_idx_terminus_initial = glac_idx_initial.copy() @@ -1074,7 +1074,7 @@ def _massredistributioncurveHuss( """ if debug: - print("\nDebugging mass redistribution curve Huss\n") + print('\nDebugging mass redistribution curve Huss\n') # Apply Huss redistribution if there are at least 3 elevation bands; otherwise, use the mass balance # Glacier area used to select parameters @@ -1118,7 +1118,7 @@ def _massredistributioncurveHuss( glacier_volumechange / (glacier_area_t0 * icethicknesschange_norm).sum() ) if debug: - print("fs_huss:", fs_huss) + print('fs_huss:', fs_huss) # Volume change [m3 ice] bin_volumechange = icethicknesschange_norm * fs_huss * glacier_area_t0 @@ -1127,7 +1127,7 @@ def _massredistributioncurveHuss( bin_volumechange = massbalclim_annual * glacier_area_t0 if debug: - print("-----\n") + print('-----\n') vol_before = section_t0 * self.fls[0].dx_meter # Update cross sectional area (updating thickness does not conserve mass in OGGM!) @@ -1140,9 +1140,9 @@ def _massredistributioncurveHuss( vol_after = self.fls[0].section * self.fls[0].dx_meter if debug: - print("vol_chg_wanted:", bin_volumechange.sum()) - print("vol_chg:", (vol_after.sum() - vol_before.sum())) - print("\n-----") + print('vol_chg_wanted:', bin_volumechange.sum()) + print('vol_chg:', (vol_after.sum() - vol_before.sum())) + print('\n-----') # Compute the remaining volume change bin_volumechange_remaining = bin_volumechange - ( @@ -1151,7 +1151,7 @@ def _massredistributioncurveHuss( ) # remove values below tolerance to avoid rounding errors bin_volumechange_remaining[ - abs(bin_volumechange_remaining) < pygem_prms["constants"]["tolerance"] + 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() diff --git a/pygem/massbalance.py b/pygem/massbalance.py index c8dcbfc7..9d2c439c 100644 --- a/pygem/massbalance.py +++ b/pygem/massbalance.py @@ -36,10 +36,10 @@ def __init__( modelprms, glacier_rgi_table, option_areaconstant=False, - hindcast=pygem_prms["climate"]["hindcast"], + hindcast=pygem_prms['climate']['hindcast'], frontalablation_k=None, - debug=pygem_prms["debug"]["mb"], - debug_refreeze=pygem_prms["debug"]["refreeze"], + debug=pygem_prms['debug']['mb'], + debug_refreeze=pygem_prms['debug']['refreeze'], fls=None, fl_id=0, heights=None, @@ -67,7 +67,7 @@ def __init__( switch to run the model in reverse or not (may be irrelevant after converting to OGGM's setup) """ if debug: - print("\n\nDEBUGGING MASS BALANCE FUNCTION\n\n") + print('\n\nDEBUGGING MASS BALANCE FUNCTION\n\n') self.debug_refreeze = debug_refreeze self.inversion_filter = inversion_filter @@ -79,12 +79,12 @@ def __init__( self.modelprms = modelprms self.glacier_rgi_table = glacier_rgi_table self.is_tidewater = gdir.is_tidewater - self.icethickness_initial = getattr(fls[fl_id], "thick", None) + self.icethickness_initial = getattr(fls[fl_id], 'thick', None) self.width_initial = fls[fl_id].widths_m self.glacier_area_initial = fls[fl_id].widths_m * fls[fl_id].dx_meter self.heights = fls[fl_id].surface_h if ( - pygem_prms["mb"]["include_debris"] + pygem_prms['mb']['include_debris'] and not ignore_debris and not gdir.is_tidewater ): @@ -99,14 +99,14 @@ def __init__( # Climate data self.dates_table = gdir.dates_table - self.glacier_gcm_temp = gdir.historical_climate["temp"] - self.glacier_gcm_tempstd = gdir.historical_climate["tempstd"] - self.glacier_gcm_prec = gdir.historical_climate["prec"] - self.glacier_gcm_elev = gdir.historical_climate["elev"] - self.glacier_gcm_lrgcm = gdir.historical_climate["lr"] - self.glacier_gcm_lrglac = gdir.historical_climate["lr"] - - if pygem_prms["climate"]["hindcast"] == True: + self.glacier_gcm_temp = gdir.historical_climate['temp'] + self.glacier_gcm_tempstd = gdir.historical_climate['tempstd'] + self.glacier_gcm_prec = gdir.historical_climate['prec'] + self.glacier_gcm_elev = gdir.historical_climate['elev'] + self.glacier_gcm_lrgcm = gdir.historical_climate['lr'] + self.glacier_gcm_lrglac = gdir.historical_climate['lr'] + + if pygem_prms['climate']['hindcast'] == True: self.glacier_gcm_prec = self.glacier_gcm_prec[::-1] self.glacier_gcm_temp = self.glacier_gcm_temp[::-1] self.glacier_gcm_lrgcm = self.glacier_gcm_lrgcm[::-1] @@ -168,7 +168,7 @@ def __init__( self.offglac_wide_snowpack = np.zeros(self.nmonths) self.offglac_wide_runoff = np.zeros(self.nmonths) - self.dayspermonth = self.dates_table["daysinmonth"].values + self.dayspermonth = self.dates_table['daysinmonth'].values self.surfacetype_ddf = np.zeros((nbins)) # Surface type DDF dictionary (manipulate this function for calibration or for each glacier) @@ -176,43 +176,43 @@ def __init__( self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial(self.heights) # Refreezing specific layers - if pygem_prms["mb"]["option_refreezing"] == "HH2015": + if pygem_prms['mb']['option_refreezing'] == 'HH2015': # Refreezing layers density, volumetric heat capacity, and thermal conductivity self.rf_dens_expb = ( - pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_bot"] - / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_top"] - ) ** (1 / (pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] - 1)) + 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"] + 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"] + 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"] + '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"] + '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) + (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) + (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]) + rgi_region = int(glacier_rgi_table.RGIId.split('-')[1].split('.')[0]) def get_annual_mb( self, @@ -220,7 +220,7 @@ def get_annual_mb( year=None, fls=None, fl_id=None, - debug=pygem_prms["debug"]["mb"], + debug=pygem_prms['debug']['mb'], option_areaconstant=False, ): """FIXED FORMAT FOR THE FLOWLINE MODEL @@ -242,16 +242,16 @@ def get_annual_mb( year = int(year) if self.repeat_period: year = year % ( - pygem_prms["climate"]["gcm_endyear"] - - pygem_prms["climate"]["gcm_startyear"] + pygem_prms['climate']['gcm_endyear'] + - pygem_prms['climate']['gcm_startyear'] ) fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter glacier_area_initial = self.glacier_area_initial - fl_widths_m = getattr(fl, "widths_m", None) - fl_section = getattr(fl, "section", None) + fl_widths_m = getattr(fl, 'widths_m', None) + fl_section = getattr(fl, 'section', None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) @@ -278,12 +278,12 @@ def get_annual_mb( bin_precsnow = np.zeros((nbins, nmonths)) # Refreezing specific layers - if pygem_prms["mb"]["option_refreezing"] == "HH2015" and year == 0: + if pygem_prms['mb']['option_refreezing'] == 'HH2015' and year == 0: self.te_rf[:, :, 0] = 0 # layer temp of each elev bin for present time step self.tl_rf[:, :, 0] = ( 0 # layer temp of each elev bin for previous time step ) - elif pygem_prms["mb"]["option_refreezing"] == "Woodward": + elif pygem_prms['mb']['option_refreezing'] == 'Woodward': refreeze_potential = np.zeros(nbins) if self.glacier_area_initial.sum() > 0: @@ -305,11 +305,11 @@ def get_annual_mb( # 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': # if (pygem_prms['time']['timestep'] == 'monthly') and (glac_idx_t0.shape[0] != 0): # AIR TEMPERATURE: Downscale the gcm temperature [deg C] to each bin - if pygem_prms["mb"]["option_temp2bins"] == 1: + if pygem_prms['mb']['option_temp2bins'] == 1: # Downscale using gcm and glacier lapse rates # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange self.bin_temp[:, 12 * year : 12 * (year + 1)] = ( @@ -317,7 +317,7 @@ def get_annual_mb( + self.glacier_gcm_lrgcm[12 * year : 12 * (year + 1)] * ( self.glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + pygem_prms['mb']['option_elev_ref_downscale'] ] - self.glacier_gcm_elev ) @@ -325,32 +325,32 @@ def get_annual_mb( * ( heights - self.glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + pygem_prms['mb']['option_elev_ref_downscale'] ] )[:, np.newaxis] - + self.modelprms["tbias"] + + self.modelprms['tbias'] ) # PRECIPITATION/ACCUMULATION: Downscale the precipitation (liquid and solid) to each bin - if pygem_prms["mb"]["option_prec2bins"] == 1: + if pygem_prms['mb']['option_prec2bins'] == 1: # Precipitation using precipitation factor and precipitation gradient # P_bin = P_gcm * prec_factor * (1 + prec_grad * (z_bin - z_ref)) bin_precsnow[:, 12 * year : 12 * (year + 1)] = ( self.glacier_gcm_prec[12 * year : 12 * (year + 1)] - * self.modelprms["kp"] + * self.modelprms['kp'] * ( 1 - + self.modelprms["precgrad"] + + self.modelprms['precgrad'] * ( heights - self.glacier_rgi_table.loc[ - pygem_prms["mb"]["option_elev_ref_downscale"] + pygem_prms['mb']['option_elev_ref_downscale'] ] ) )[:, np.newaxis] ) # Option to adjust prec of uppermost 25% of glacier for wind erosion and reduced moisture content - if pygem_prms["mb"]["option_preclimit"] == 1: + if pygem_prms['mb']['option_preclimit'] == 1: # Elevation range based on all flowlines raw_min_elev = [] raw_max_elev = [] @@ -395,34 +395,34 @@ def get_annual_mb( ] = 0.875 * bin_precsnow[glac_idx_t0, month].max() # Separate total precipitation into liquid (bin_prec) and solid (bin_acc) - if pygem_prms["mb"]["option_accumulation"] == 1: + if pygem_prms['mb']['option_accumulation'] == 1: # if temperature above threshold, then rain ( self.bin_prec[:, 12 * year : 12 * (year + 1)][ self.bin_temp[:, 12 * year : 12 * (year + 1)] - > self.modelprms["tsnow_threshold"] + > self.modelprms['tsnow_threshold'] ] ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ self.bin_temp[:, 12 * year : 12 * (year + 1)] - > self.modelprms["tsnow_threshold"] + > 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"] + <= self.modelprms['tsnow_threshold'] ] ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ self.bin_temp[:, 12 * year : 12 * (year + 1)] - <= self.modelprms["tsnow_threshold"] + <= self.modelprms['tsnow_threshold'] ] - elif pygem_prms["mb"]["option_accumulation"] == 2: + 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"] + - self.modelprms['tsnow_threshold'] ) / 2 ) * bin_precsnow[:, 12 * year : 12 * (year + 1)] @@ -434,32 +434,32 @@ def get_annual_mb( ( self.bin_prec[:, 12 * year : 12 * (year + 1)][ self.bin_temp[:, 12 * year : 12 * (year + 1)] - > self.modelprms["tsnow_threshold"] + 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.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 + > 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 + <= 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.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 + <= self.modelprms['tsnow_threshold'] - 1 ] ) = 0 @@ -479,13 +479,13 @@ def get_annual_mb( # MELT [m w.e.] # energy available for melt [degC day] - if pygem_prms["mb"]["option_ablation"] == 1: + if pygem_prms['mb']['option_ablation'] == 1: # option 1: energy based on monthly temperature melt_energy_available = ( self.bin_temp[:, step] * self.dayspermonth[step] ) melt_energy_available[melt_energy_available < 0] = 0 - elif pygem_prms["mb"]["option_ablation"] == 2: + elif pygem_prms['mb']['option_ablation'] == 2: # Seed randomness for repeatability, but base it on step to ensure the daily variability is not # the same for every single time step np.random.seed(step) @@ -527,7 +527,7 @@ def get_annual_mb( # 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"] + < 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: @@ -535,7 +535,7 @@ def get_annual_mb( self.surfacetype_ddf_dict[surfacetype_idx] ) # Debris enhancement factors in ablation area (debris in accumulation area would submerge) - if surfacetype_idx == 1 and pygem_prms["mb"]["include_debris"]: + if surfacetype_idx == 1 and pygem_prms['mb']['include_debris']: self.surfacetype_ddf[self.surfacetype == 1] = ( self.surfacetype_ddf[self.surfacetype == 1] * self.debris_ed[self.surfacetype == 1] @@ -552,7 +552,7 @@ def get_annual_mb( ) # REFREEZING - if pygem_prms["mb"]["option_refreezing"] == "HH2015": + if pygem_prms['mb']['option_refreezing'] == 'HH2015': if step > 0: self.tl_rf[:, :, step] = self.tl_rf[:, :, step - 1] self.te_rf[:, :, step] = self.te_rf[:, :, step - 1] @@ -563,12 +563,12 @@ def get_annual_mb( 3600 * 24 * self.dayspermonth[step] - / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dsc"] + / pygem_prms['mb']['HH2015_rf_opts']['rf_dsc'] ) if ( - pygem_prms["mb"]["HH2015_rf_opts"][ - "option_rf_limit_meltsnow" + pygem_prms['mb']['HH2015_rf_opts'][ + 'option_rf_limit_meltsnow' ] == 1 ): @@ -588,7 +588,7 @@ def get_annual_mb( # 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"] + < pygem_prms['mb']['HH2015_rf_opts']['rf_meltcrit'] ): if ( self.debug_refreeze @@ -596,9 +596,9 @@ def get_annual_mb( and step < 12 ): print( - "\nMonth " - + str(self.dates_table.loc[step, "month"]), - "Computing heat conduction", + '\nMonth ' + + str(self.dates_table.loc[step, 'month']), + 'Computing heat conduction', ) # Set refreeze equal to 0 @@ -606,7 +606,7 @@ def get_annual_mb( # 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"] + 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" @@ -614,7 +614,7 @@ def get_annual_mb( # cold/polythermal glaciers for j in np.arange( 1, - pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1, ): # Assume temperature of first layer equals air temperature @@ -630,9 +630,9 @@ def get_annual_mb( 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.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] @@ -653,9 +653,9 @@ def get_annual_mb( and step < 12 ): print( - "tl_rf:", + 'tl_rf:', [ - "{:.2f}".format(x) + '{:.2f}'.format(x) for x in self.tl_rf[:, gidx, step] ], ) @@ -668,9 +668,9 @@ def get_annual_mb( and step < 12 ): print( - "\nMonth " - + str(self.dates_table.loc[step, "month"]), - "Computing refreeze", + '\nMonth ' + + str(self.dates_table.loc[step, 'month']), + 'Computing refreeze', ) # Refreezing over firn surface @@ -678,7 +678,7 @@ def get_annual_mb( self.surfacetype[gidx] == 3 ): nlayers = ( - pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1 ) # Refreezing over ice surface @@ -688,9 +688,9 @@ def get_annual_mb( ( self.bin_snowpack[gidx, step] / (self.rf_layers_dens[0] / 1000) - + pygem_prms["mb"]["HH2015_rf_opts"]["pp"] + + pygem_prms['mb']['HH2015_rf_opts']['pp'] ) - / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dz"], + / 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), @@ -703,14 +703,14 @@ def get_annual_mb( # 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" + > pygem_prms['mb']['HH2015_rf_opts'][ + 'rf_layers' ] - 1 ): smax = ( - pygem_prms["mb"]["HH2015_rf_opts"][ - "rf_layers" + pygem_prms['mb']['HH2015_rf_opts'][ + 'rf_layers' ] - 1 ) @@ -727,9 +727,9 @@ def get_annual_mb( and step < 12 ): print( - "calculating potential refreeze from " + 'calculating potential refreeze from ' + str(nlayers) - + " layers" + + ' layers' ) for j in np.arange(0, nlayers): @@ -738,11 +738,11 @@ def get_annual_mb( rf_cold_layer = ( self.tl_rf[j, gidx, step] * self.rf_layers_ch[j] - * pygem_prms["mb"]["HH2015_rf_opts"][ - "rf_dz" + * pygem_prms['mb']['HH2015_rf_opts'][ + 'rf_dz' ] - / pygem_prms["constants"]["Lh_rf"] - / pygem_prms["constants"]["density_water"] + / pygem_prms['constants']['Lh_rf'] + / pygem_prms['constants']['density_water'] ) self.rf_cold[gidx] -= rf_cold_layer @@ -752,15 +752,15 @@ def get_annual_mb( and step < 12 ): print( - "j:", + 'j:', j, - "tl_rf @ j:", + 'tl_rf @ j:', np.round(self.tl_rf[j, gidx, step], 2), - "ch @ j:", + 'ch @ j:', np.round(self.rf_layers_ch[j], 2), - "rf_cold_layer @ j:", + 'rf_cold_layer @ j:', np.round(rf_cold_layer, 2), - "rf_cold @ j:", + 'rf_cold @ j:', np.round(self.rf_cold[gidx], 2), ) @@ -770,7 +770,7 @@ def get_annual_mb( and step < 12 ): print( - "rf_cold:", np.round(self.rf_cold[gidx], 2) + 'rf_cold:', np.round(self.rf_cold[gidx], 2) ) # Compute refreezing @@ -804,24 +804,24 @@ def get_annual_mb( if self.debug_refreeze and step < 12 and gidx == gidx_debug: print( - "Month " + str(self.dates_table.loc[step, "month"]), - "Rf_cold remaining:", + 'Month ' + str(self.dates_table.loc[step, 'month']), + 'Rf_cold remaining:', np.round(self.rf_cold[gidx], 2), - "Snow depth:", + 'Snow depth:', np.round( self.bin_snowpack[glac_idx_t0[nbin], step], 2 ), - "Snow melt:", + 'Snow melt:', np.round( self.bin_meltsnow[glac_idx_t0[nbin], step], 2 ), - "Rain:", + 'Rain:', np.round(self.bin_prec[glac_idx_t0[nbin], step], 2), - "Rfrz:", + 'Rfrz:', np.round(self.bin_refreeze[gidx, step], 2), ) - elif pygem_prms["mb"]["option_refreezing"] == "Woodward": + 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 @@ -846,17 +846,17 @@ def get_annual_mb( if self.debug_refreeze: print( - "Year " + 'Year ' + str(year) - + " Month " - + str(self.dates_table.loc[step, "month"]), - "Refreeze potential:", + + ' Month ' + + str(self.dates_table.loc[step, 'month']), + 'Refreeze potential:', np.round(refreeze_potential[glac_idx_t0[0]], 3), - "Snow depth:", + 'Snow depth:', np.round(self.bin_snowpack[glac_idx_t0[0], step], 2), - "Snow melt:", + 'Snow melt:', np.round(self.bin_meltsnow[glac_idx_t0[0], step], 2), - "Rain:", + 'Rain:', np.round(self.bin_prec[glac_idx_t0[0], step], 2), ) @@ -881,14 +881,14 @@ def get_annual_mb( ] self.bin_refreeze[ abs(self.bin_refreeze[:, step]) - < pygem_prms["constants"]["tolerance"], + < pygem_prms['constants']['tolerance'], step, ] = 0 # update refreeze potential refreeze_potential -= self.bin_refreeze[:, step] refreeze_potential[ abs(refreeze_potential) - < pygem_prms["constants"]["tolerance"] + < pygem_prms['constants']['tolerance'] ] = 0 # SNOWPACK REMAINING [m w.e.] @@ -897,7 +897,7 @@ def get_annual_mb( ) self.snowpack_remaining[ abs(self.snowpack_remaining[:, step]) - < pygem_prms["constants"]["tolerance"], + < pygem_prms['constants']['tolerance'], step, ] = 0 @@ -986,8 +986,8 @@ def get_annual_mb( ) mb = ( self.glac_bin_massbalclim[:, 12 * year : 12 * (year + 1)].sum(1) - * pygem_prms["constants"]["density_water"] - / pygem_prms["constants"]["density_ice"] + * pygem_prms['constants']['density_water'] + / pygem_prms['constants']['density_ice'] / seconds_in_year ) @@ -1058,7 +1058,7 @@ def _convert_glacwide_results( # Check if need to adjust for complete removal of the glacier # - needed for accurate runoff calcs and accurate mass balance components - icethickness_t0 = getattr(fls[fl_id], "thick", None) + icethickness_t0 = getattr(fls[fl_id], 'thick', None) if icethickness_t0 is not None: # Mass loss cannot exceed glacier volume if glacier_area.sum() > 0: @@ -1066,8 +1066,8 @@ def _convert_glacwide_results( -1 * (glacier_area * icethickness_t0).sum() / glacier_area.sum() - * pygem_prms["constants"]["density_ice"] - / pygem_prms["constants"]["density_water"] + * pygem_prms['constants']['density_ice'] + / pygem_prms['constants']['density_water'] ) # Check annual climatic mass balance (mwea) mb_mwea = ( @@ -1080,7 +1080,7 @@ def _convert_glacwide_results( if len(glac_idx) > 0: # Quality control for thickness - if hasattr(fls[fl_id], "thick"): + if hasattr(fls[fl_id], 'thick'): thickness = fls[fl_id].thick glacier_area[thickness == 0] = 0 section = fls[fl_id].section @@ -1132,8 +1132,8 @@ def _convert_glacwide_results( 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"] + * 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() ) @@ -1243,8 +1243,8 @@ def ensure_mass_conservation(self, diag): # 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"] + * 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]] = ( @@ -1255,8 +1255,8 @@ def ensure_mass_conservation(self, diag): # 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"] + * 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] @@ -1324,29 +1324,29 @@ 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: + if pygem_prms['mb']['option_surfacetype_initial'] == 1: surfacetype[ - (elev_bins < self.glacier_rgi_table.loc["Zmed"]) + (elev_bins < self.glacier_rgi_table.loc['Zmed']) & (self.glacier_area_initial > 0) ] = 1 surfacetype[ - (elev_bins >= self.glacier_rgi_table.loc["Zmed"]) + (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: + elif pygem_prms['mb']['option_surfacetype_initial'] == 2: surfacetype[ - (elev_bins < self.glacier_rgi_table["Zmean"]) + (elev_bins < self.glacier_rgi_table['Zmean']) & (self.glacier_area_initial > 0) ] = 1 surfacetype[ - (elev_bins >= self.glacier_rgi_table["Zmean"]) + (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" + + 'Exiting model run.\n' ) exit() # Compute firnline index @@ -1357,7 +1357,7 @@ def _surfacetypebinsinitial(self, elev_bins): # avoid errors if there is no firn, i.e., the entire glacier is melting firnline_idx = np.where(surfacetype != 0)[0][-1] # If firn is included, then specify initial firn conditions - if pygem_prms["mb"]["include_firn"] == 1: + if pygem_prms['mb']['include_firn'] == 1: surfacetype[surfacetype == 2] = 3 # everything initially considered snow is considered firn, i.e., the model initially assumes there is no # snow on the surface anywhere. @@ -1429,15 +1429,15 @@ def _surfacetypebinsannual( firnline_idx = np.where(surfacetype != 0)[0][-1] # Apply surface type model options # If firn surface type option is included, then snow is changed to firn - if pygem_prms["mb"]["include_firn"] == 1: + if pygem_prms['mb']['include_firn'] == 1: surfacetype[surfacetype == 2] = 3 return surfacetype, firnline_idx def _surfacetypeDDFdict( self, modelprms, - include_firn=pygem_prms["mb"]["include_firn"], - option_ddf_firn=pygem_prms["mb"]["option_ddf_firn"], + 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. @@ -1464,15 +1464,15 @@ def _surfacetypeDDFdict( Dictionary relating the surface types with their respective degree day factors """ surfacetype_ddf_dict = { - 0: modelprms["ddfsnow"], - 1: modelprms["ddfice"], - 2: modelprms["ddfsnow"], + 0: modelprms['ddfsnow'], + 1: modelprms['ddfice'], + 2: modelprms['ddfsnow'], } if include_firn: if option_ddf_firn == 0: - surfacetype_ddf_dict[3] = modelprms["ddfsnow"] + surfacetype_ddf_dict[3] = modelprms['ddfsnow'] elif option_ddf_firn == 1: surfacetype_ddf_dict[3] = np.mean( - [modelprms["ddfsnow"], modelprms["ddfice"]] + [modelprms['ddfsnow'], modelprms['ddfice']] ) return surfacetype_ddf_dict diff --git a/pygem/mcmc.py b/pygem/mcmc.py index 267e6836..f628672f 100644 --- a/pygem/mcmc.py +++ b/pygem/mcmc.py @@ -23,9 +23,9 @@ pygem_prms = config_manager.read_config() torch.set_default_dtype(torch.float64) -plt.rcParams["font.family"] = "arial" -plt.rcParams["font.size"] = 8 -plt.rcParams["legend.fontsize"] = 6 +plt.rcParams['font.family'] = 'arial' +plt.rcParams['font.size'] = 8 +plt.rcParams['legend.fontsize'] = 6 # z-normalization functions @@ -50,7 +50,7 @@ def log_normal_density(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma = kwargs["mu"], kwargs["sigma"] + mu, sigma = kwargs['mu'], kwargs['sigma'] # flatten arrays and get dimensionality x = x.flatten() @@ -79,7 +79,7 @@ def log_gamma_density(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - alpha, beta = kwargs["alpha"], kwargs["beta"] # shape, scale + alpha, beta = kwargs['alpha'], kwargs['beta'] # shape, scale return ( alpha * torch.log(beta) + (alpha - 1) * torch.log(x) @@ -102,7 +102,7 @@ def log_truncated_normal(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma, lo, hi = kwargs["mu"], kwargs["sigma"], kwargs["low"], kwargs["high"] + mu, sigma, lo, hi = kwargs['mu'], kwargs['sigma'], kwargs['low'], kwargs['high'] # Standardize standard_x = (x - mu) / sigma standard_a = (lo - mu) / sigma @@ -122,9 +122,9 @@ def log_truncated_normal(x, **kwargs): # mapper dictionary - maps to appropriate log probability density function for given distribution `type` log_prob_fxn_map = { - "normal": log_normal_density, - "gamma": log_gamma_density, - "truncnormal": log_truncated_normal, + 'normal': log_normal_density, + 'gamma': log_gamma_density, + 'truncnormal': log_truncated_normal, } @@ -143,8 +143,8 @@ def __init__( self.check_priors() # get mean and std for each parameter type - self.means = torch.tensor([params["mu"] for params in self.priors.values()]) - self.stds = torch.tensor([params["sigma"] for params in self.priors.values()]) + self.means = torch.tensor([params['mu'] for params in self.priors.values()]) + self.stds = torch.tensor([params['sigma'] for params in self.priors.values()]) # check priors. remove any subkeys that have a `None` value, and ensure that we have a mean and standard deviation for and gamma distributions def check_priors(self): @@ -154,7 +154,7 @@ 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): self.priors[k][i] = torch.tensor([self.priors[k][i]]) @@ -163,18 +163,18 @@ def check_priors(self): del self.priors[k][i] for k in self.priors.keys(): - if self.priors[k]["type"] == "gamma" and "mu" not in self.priors[k].keys(): - self.priors[k]["mu"] = self.priors[k]["alpha"] / self.priors[k]["beta"] - self.priors[k]["sigma"] = float( - np.sqrt(self.priors[k]["alpha"]) / self.priors[k]["beta"] + if self.priors[k]['type'] == 'gamma' and 'mu' not in self.priors[k].keys(): + self.priors[k]['mu'] = self.priors[k]['alpha'] / self.priors[k]['beta'] + self.priors[k]['sigma'] = float( + np.sqrt(self.priors[k]['alpha']) / self.priors[k]['beta'] ) # update modelprms for evaluation def update_modelprms(self, m): - for i, k in enumerate(["tbias", "kp", "ddfsnow"]): + for i, k in enumerate(['tbias', 'kp', 'ddfsnow']): self.mb_args[1][k] = float(m[i]) - self.mb_args[1]["ddfice"] = ( - self.mb_args[1]["ddfsnow"] / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + self.mb_args[1]['ddfice'] = ( + self.mb_args[1]['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] ) # get mb_pred @@ -195,7 +195,7 @@ def log_prior(self, m): log_prior = [] for i, (key, params) in enumerate(self.priors.items()): params_copy = params.copy() - prior_type = params_copy.pop("type") + prior_type = params_copy.pop('type') function_to_call = log_prob_fxn_map[prior_type] log_prior.append(function_to_call(m[i], **params_copy)) log_prior = torch.stack(log_prior).sum() @@ -206,7 +206,7 @@ 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]} + self.obs[i][0], **{'mu': pred, 'sigma': self.obs[i][1]} ) return log_likehood @@ -214,7 +214,7 @@ def log_likelihood(self): 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) @@ -400,7 +400,7 @@ def effective_n(x): x = x - x.mean() # compute autocorrelation (note: only need second half since # they are symmetric) - rho = np.correlate(x, x, mode="full") + rho = np.correlate(x, x, mode='full') rho = rho[len(rho) // 2 :] # normalize the autocorrelation values # note: rho[0] is the variance * n_samples, so this is consistent @@ -436,94 +436,94 @@ def plot_chain( axes[0].plot( [], [], - label=f"mean={np.mean(m_chain[:, 0]):.3f}\nstd={np.std(m_chain[:, 0]):.3f}", + 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 + 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(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[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(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}", + 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 + loc='upper right', handlelength=0, borderaxespad=0, fontsize=fontsize ) - axes[1].set_ylabel(r"$K_p$", 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(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}", + 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 + loc='upper right', handlelength=0, borderaxespad=0, fontsize=fontsize ) - axes[2].set_ylabel(r"$fsnow$", 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", + 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", + 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(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}", + 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 + loc='upper right', handlelength=0, borderaxespad=0, fontsize=fontsize ) - axes[3].set_ylabel(r"$\dot{{b}}$", fontsize=fontsize) + axes[3].set_ylabel(r'$\dot{{b}}$', fontsize=fontsize) - axes[4].plot(ar, "tab:orange", lw=1) + axes[4].plot(ar, 'tab:orange', lw=1) axes[4].plot( - np.convolve(ar, np.ones(100) / 100, mode="valid"), - "k", - label="moving avg.", + 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 + loc='upper left', handlelength=0.5, borderaxespad=0, fontsize=fontsize ) - axes[4].set_ylabel(r"$AR$", 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") + ax.xaxis.set_ticks_position('both') + ax.yaxis.set_ticks_position('both') + ax.tick_params(axis='both', direction='inout') if i == 4: continue - ax.plot([], [], label=f"n_eff={neff[i]}") + ax.plot([], [], label=f'n_eff={neff[i]}') hands, ls = ax.get_legend_handles_labels() if i == 0: ax.legend( handles=[hands[1], hands[2], hands[3]], labels=[ls[1], ls[2], ls[3]], - loc="upper left", + loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize, @@ -532,7 +532,7 @@ def plot_chain( ax.legend( handles=[hands[-1]], labels=[ls[-1]], - loc="upper left", + loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize, @@ -572,12 +572,12 @@ def plot_resid_hist(obs, preds, title, fontsize=8, show=False, fpath=None): bin_edges[:-1], pct, width=bin_width, - edgecolor="black", - color="gray", - align="edge", + edgecolor='black', + color='gray', + align='edge', ) - axes.set_xlabel("residuals (pred - obs)", fontsize=fontsize) - axes.set_ylabel("count (%)", fontsize=fontsize) + axes.set_xlabel('residuals (pred - obs)', fontsize=fontsize) + axes.set_ylabel('count (%)', fontsize=fontsize) axes.set_title(title, fontsize=fontsize) plt.tight_layout() plt.subplots_adjust(hspace=0.1, wspace=0) diff --git a/pygem/oggm_compat.py b/pygem/oggm_compat.py index e556f85e..d4da1b4b 100755 --- a/pygem/oggm_compat.py +++ b/pygem/oggm_compat.py @@ -40,11 +40,11 @@ def __init__(self, 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']}", + 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) @@ -64,66 +64,66 @@ def single_flowline_glacier_directory( a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError("We expect rgi_id to be a string") - if rgi_id.startswith("RGI60-") == False: - rgi_id = "RGI60-" + rgi_id.split(".")[0].zfill(2) + "." + rgi_id.split(".")[1] + raise ValueError('We expect rgi_id to be a string') + if rgi_id.startswith('RGI60-') == False: + rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] else: - raise ValueError("Check RGIId is correct") + raise ValueError('Check RGIId is correct') # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS["use_multiprocessing"] = False + cfg.PARAMS['use_multiprocessing'] = False # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS["continue_on_error"] = True + cfg.PARAMS['continue_on_error'] = True # Has internet - cfg.PARAMS["has_internet"] = has_internet + cfg.PARAMS['has_internet'] = has_internet # Set border boundary - cfg.PARAMS["border"] = prepro_border + cfg.PARAMS['border'] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS["dl_verify"] = True - cfg.PARAMS["use_multiple_flowlines"] = False + cfg.PARAMS['dl_verify'] = True + cfg.PARAMS['use_multiple_flowlines'] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS["working_dir"] = working_dir + cfg.PATHS['working_dir'] = working_dir # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle("inversion_flowlines") + gdir.read_pickle('inversion_flowlines') except: reset = True if reset: # Start after the prepro task level - base_url = pygem_prms["oggm"]["base_url"] + base_url = pygem_prms['oggm']['base_url'] - cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + 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_border=cfg.PARAMS['border'], prepro_base_url=base_url, - prepro_rgi_version="62", + prepro_rgi_version='62', )[0] # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath("consensus_mass")): + if not os.path.isfile(gdir.get_filepath('consensus_mass')): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data - if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): + if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): workflow.execute_entity_task(mbdata.mb_df_to_gdir, gdir) # debris thickness and melt enhancement factors - if not os.path.isfile(gdir.get_filepath("debris_ed")) or 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) @@ -133,13 +133,13 @@ def single_flowline_glacier_directory( def single_flowline_glacier_directory_with_calving( rgi_id, - reset=pygem_prms["oggm"]["overwrite_gdirs"], - prepro_border=pygem_prms["oggm"]["border"], + 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"], + 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) @@ -160,68 +160,68 @@ def single_flowline_glacier_directory_with_calving( a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError("We expect rgi_id to be a string") - if rgi_id.startswith("RGI60-") == False: - rgi_id = "RGI60-" + rgi_id.split(".")[0].zfill(2) + "." + rgi_id.split(".")[1] + raise ValueError('We expect rgi_id to be a string') + if rgi_id.startswith('RGI60-') == False: + rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] else: - raise ValueError("Check RGIId is correct") + raise ValueError('Check RGIId is correct') # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS["use_multiprocessing"] = False + cfg.PARAMS['use_multiprocessing'] = False # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS["continue_on_error"] = True + cfg.PARAMS['continue_on_error'] = True # Has internet - cfg.PARAMS["has_internet"] = has_internet + cfg.PARAMS['has_internet'] = has_internet # Set border boundary - cfg.PARAMS["border"] = prepro_border + cfg.PARAMS['border'] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS["dl_verify"] = True - cfg.PARAMS["use_multiple_flowlines"] = False + cfg.PARAMS['dl_verify'] = True + cfg.PARAMS['use_multiple_flowlines'] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS["working_dir"] = working_dir + cfg.PATHS['working_dir'] = working_dir # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle("inversion_flowlines") + gdir.read_pickle('inversion_flowlines') except: reset = True if reset: # Start after the prepro task level - base_url = pygem_prms["oggm"]["base_url"] + base_url = pygem_prms['oggm']['base_url'] - cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + 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_border=cfg.PARAMS['border'], prepro_base_url=base_url, - prepro_rgi_version="62", + prepro_rgi_version='62', )[0] if not gdir.is_tidewater: - raise ValueError(f"{rgi_id} is not tidewater!") + raise ValueError(f'{rgi_id} is not tidewater!') # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath("consensus_mass")): + if not os.path.isfile(gdir.get_filepath('consensus_mass')): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data (note facorrected kwarg) - if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): + if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): workflow.execute_entity_task( - mbdata.mb_df_to_gdir, gdir, **{"facorrected": facorrected} + mbdata.mb_df_to_gdir, gdir, **{'facorrected': facorrected} ) return gdir @@ -263,13 +263,13 @@ def oggm_spinup(gdir): 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 + 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") + gdir.get_filepath('model_geometry', filesuffix='_dynamic_area') ) fmd_dynamic.run_until(2000) return fmd_dynamic.fls # flowlines after dynamic spinup at year 2000 @@ -289,8 +289,8 @@ def create_empty_glacier_directory(rgi_id): """ # RGIId check if type(rgi_id) != str: - raise ValueError("We expect rgi_id to be a string") - assert rgi_id.startswith("RGI60-"), "Check RGIId starts with RGI60-" + raise ValueError('We expect rgi_id to be a string') + assert rgi_id.startswith('RGI60-'), 'Check RGIId starts with RGI60-' # Create empty directory gdir = CompatGlacDir(rgi_id) @@ -311,7 +311,7 @@ def get_glacier_zwh(gdir): a dataframe with the requested data """ - fls = gdir.read_pickle("model_flowlines") + fls = gdir.read_pickle('model_flowlines') z = np.array([]) w = np.array([]) h = np.array([]) @@ -327,10 +327,10 @@ def get_glacier_zwh(gdir): # Output df = pd.DataFrame() - df["z"] = z - df["w"] = w - df["h"] = h - df["dx"] = dx + df['z'] = z + df['w'] = w + df['h'] = h + df['dx'] = dx return df @@ -369,14 +369,14 @@ def __init__(self, gdir, grad=3.0, h_perc=60, sigma_ela=100.0, seed=None): self.valid_bounds = [-1e4, 2e4] # in m self.grad = grad self.sigma_ela = sigma_ela - self.hemisphere = "nh" + self.hemisphere = 'nh' self.rng = np.random.RandomState(seed) # Decide on a reference ELA - grids_file = gdir.get_filepath("gridded_data") + grids_file = gdir.get_filepath('gridded_data') with netCDF4.Dataset(grids_file) as nc: - glacier_mask = nc.variables["glacier_mask"][:] - glacier_topo = nc.variables["topo_smoothed"][:] + glacier_mask = nc.variables['glacier_mask'][:] + glacier_topo = nc.variables['topo_smoothed'][:] self.orig_ela_h = np.percentile(glacier_topo[glacier_mask == 1], h_perc) self.ela_h_per_year = dict() # empty dictionary @@ -404,4 +404,4 @@ def get_annual_mb(self, heights, year=None, fl_id=None): mb = (np.asarray(heights) - ela_h) * self.grad # Convert to units of [m s-1] (meters of ice per second) - return mb / SEC_IN_YEAR / cfg.PARAMS["ice_density"] + return mb / SEC_IN_YEAR / cfg.PARAMS['ice_density'] diff --git a/pygem/output.py b/pygem/output.py index bdc9c7ec..0ebc658a 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -34,10 +34,10 @@ pygem_prms = config_manager.read_config() __all__ = [ - "single_glacier", - "glacierwide_stats", - "binned_stats", - "calc_stats_array", + 'single_glacier', + 'glacierwide_stats', + 'binned_stats', + 'calc_stats_array', ] @@ -105,9 +105,9 @@ 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.glacier_str = '{0:0.5f}'.format(self.glacier_rgi_table['RGIId_float']) self.reg_str = str(self.glacier_rgi_table.O1Region).zfill(2) - self.outdir = pygem_prms["root"] + "/Output/simulations/" + self.outdir = pygem_prms['root'] + '/Output/simulations/' self.set_fn() self._set_time_vals() self._model_params_record() @@ -123,23 +123,23 @@ def set_fn(self, outfn=None): if outfn: self.outfn = outfn else: - self.outfn = self.glacier_str + "_" + self.gcm_name + "_" + self.outfn = self.glacier_str + '_' + self.gcm_name + '_' if self.scenario: - self.outfn += f"{self.scenario}_" + self.outfn += f'{self.scenario}_' if self.realization: - self.outfn += f"{self.realization}_" + self.outfn += f'{self.realization}_' if self.option_calibration: - self.outfn += f"{self.option_calibration}_" + self.outfn += f'{self.option_calibration}_' else: - self.outfn += f"kp{self.modelprms['kp']}_ddfsnow{self.modelprms['ddfsnow']}_tbias{self.modelprms['tbias']}_" - if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: - self.outfn += f"ba{self.option_bias_adjustment}_" + self.outfn += f'kp{self.modelprms["kp"]}_ddfsnow{self.modelprms["ddfsnow"]}_tbias{self.modelprms["tbias"]}_' + if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: + self.outfn += f'ba{self.option_bias_adjustment}_' else: - self.outfn += "ba0_" + self.outfn += 'ba0_' if self.option_calibration: - self.outfn += "SETS_" - self.outfn += f"{self.gcm_startyear}_" - self.outfn += f"{self.gcm_endyear}_" + self.outfn += 'SETS_' + self.outfn += f'{self.gcm_startyear}_' + self.outfn += f'{self.gcm_endyear}_' def get_fn(self): """Return the output dataset filename.""" @@ -163,29 +163,29 @@ def set_modelprms(self, modelprms): 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)[ + if pygem_prms['climate']['gcm_wateryear'] == 'hydro': + self.year_type = 'water year' + self.annual_columns = np.unique(self.dates_table['wateryear'].values)[ 0 : int(self.dates_table.shape[0] / 12) ] - elif pygem_prms["climate"]["gcm_wateryear"] == "calendar": - self.year_type = "calendar year" - self.annual_columns = np.unique(self.dates_table["year"].values)[ + elif pygem_prms['climate']['gcm_wateryear'] == 'calendar': + self.year_type = 'calendar year' + self.annual_columns = np.unique(self.dates_table['year'].values)[ 0 : int(self.dates_table.shape[0] / 12) ] - elif pygem_prms["climate"]["gcm_wateryear"] == "custom": - self.year_type = "custom year" + 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] + pygem_prms['climate']['gcm_spinupyears'] * 12 : self.dates_table.shape[0] + 1, - "date", + '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] + 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])) @@ -196,15 +196,15 @@ def _model_params_record(self): # get all locally defined variables from the pygem_prms, excluding imports, functions, and classes self.mdl_params_dict = {} # overwrite variables that are possibly different from pygem_input - self.mdl_params_dict["ref_startyear"] = self.ref_startyear - self.mdl_params_dict["ref_endyear"] = self.ref_endyear - self.mdl_params_dict["gcm_startyear"] = self.gcm_startyear - self.mdl_params_dict["gcm_endyear"] = self.gcm_endyear - self.mdl_params_dict["gcm_name"] = self.gcm_name - self.mdl_params_dict["realization"] = self.realization - self.mdl_params_dict["scenario"] = self.scenario - self.mdl_params_dict["option_calibration"] = self.option_calibration - self.mdl_params_dict["option_bias_adjustment"] = self.option_bias_adjustment + self.mdl_params_dict['ref_startyear'] = self.ref_startyear + self.mdl_params_dict['ref_endyear'] = self.ref_endyear + self.mdl_params_dict['gcm_startyear'] = self.gcm_startyear + self.mdl_params_dict['gcm_endyear'] = self.gcm_endyear + self.mdl_params_dict['gcm_name'] = self.gcm_name + self.mdl_params_dict['realization'] = self.realization + self.mdl_params_dict['scenario'] = self.scenario + self.mdl_params_dict['option_calibration'] = self.option_calibration + self.mdl_params_dict['option_bias_adjustment'] = self.option_bias_adjustment # record manually defined modelprms if calibration option is None if not self.option_calibration: self._update_modelparams_record() @@ -217,65 +217,65 @@ 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['RGIId'] = collections.OrderedDict( + [('glac', self.glac_values)] ) - self.output_coords_dict["CenLon"] = 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['CenLat'] = collections.OrderedDict( + [('glac', self.glac_values)] ) - self.output_coords_dict["O1Region"] = 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['O2Region'] = collections.OrderedDict( + [('glac', self.glac_values)] ) - self.output_coords_dict["Area"] = 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", + '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", + '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", + '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", + 'RGIId': { + 'long_name': 'Randolph Glacier Inventory ID', + 'comment': 'RGIv6.0', }, - "CenLon": { - "long_name": "center longitude", - "units": "degrees E", - "comment": "value from 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", + '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", + 'O1Region': { + 'long_name': 'RGI order 1 region', + 'comment': 'value from RGIv6.0', }, - "O2Region": { - "long_name": "RGI order 2 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", + 'Area': { + 'long_name': 'glacier area', + 'units': 'm2', + 'comment': 'value from RGIv6.0', }, } @@ -301,7 +301,7 @@ def create_xr_ds(self): self.output_xr_ds = output_xr_ds_ else: self.output_xr_ds = xr.merge((self.output_xr_ds, output_xr_ds_)) - noencoding_vn = ["RGIId"] + noencoding_vn = ['RGIId'] # Add attributes for vn in self.output_xr_ds.variables: try: @@ -311,27 +311,27 @@ def create_xr_ds(self): # 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.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.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['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), + '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): @@ -370,415 +370,415 @@ def __post_init__(self): def _set_outdir(self): """Set the output directory path. Create if it does not already exist.""" - self.outdir += self.reg_str + "/" + self.gcm_name + "/" - if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: - self.outdir += self.scenario + "/" - self.outdir += "stats/" + self.outdir += self.reg_str + '/' + self.gcm_name + '/' + if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: + self.outdir += self.scenario + '/' + self.outdir += 'stats/' # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) 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_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_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_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_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_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_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_attrs_dict['glac_mass_bsl_annual'] = { + 'long_name': 'glacier mass below sea level', + 'units': 'kg', + 'temporal_resolution': 'annual', + 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year', } - self.output_coords_dict["glac_ELA_annual"] = collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + self.output_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_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_coords_dict['offglac_runoff_monthly'] = collections.OrderedDict( + [('glac', self.glac_values), ('time', self.time_values)] ) - self.output_attrs_dict["offglac_runoff_monthly"] = { - "long_name": "off-glacier-wide runoff", - "units": "m3", - "temporal_resolution": "monthly", - "comment": "off-glacier runoff from area where glacier no longer exists", + self.output_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"] = ( + self.output_coords_dict['glac_runoff_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_attrs_dict['glac_runoff_monthly_mad'] = { + 'long_name': 'glacier-wide runoff median absolute deviation', + 'units': 'm3', + 'temporal_resolution': 'monthly', + 'comment': 'runoff from the glacier terminus, which moves over time', } - self.output_coords_dict["glac_area_annual_mad"] = collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + self.output_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_attrs_dict['glac_area_annual_mad'] = { + 'long_name': 'glacier area median absolute deviation', + 'units': 'm2', + 'temporal_resolution': 'annual', + 'comment': 'area at start of the year', } - self.output_coords_dict["glac_mass_annual_mad"] = collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + self.output_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_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"] = ( + self.output_coords_dict['glac_mass_bsl_annual_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + [('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_attrs_dict['glac_mass_bsl_annual_mad'] = { + 'long_name': 'glacier mass below sea level median absolute deviation', + 'units': 'kg', + 'temporal_resolution': 'annual', + 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year', } - self.output_coords_dict["glac_ELA_annual_mad"] = collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + self.output_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_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"] = ( + self.output_coords_dict['offglac_runoff_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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", + 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', } # 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)] + if pygem_prms['sim']['out']['export_extra_vars']: + self.output_coords_dict['glac_prec_monthly'] = collections.OrderedDict( + [('glac', self.glac_values), ('time', self.time_values)] ) - self.output_attrs_dict["glac_prec_monthly"] = { - "long_name": "glacier-wide precipitation (liquid)", - "units": "m3", - "temporal_resolution": "monthly", - "comment": "only the liquid precipitation, solid precipitation excluded", + self.output_attrs_dict['glac_prec_monthly'] = { + 'long_name': 'glacier-wide precipitation (liquid)', + 'units': 'm3', + 'temporal_resolution': 'monthly', + 'comment': 'only the liquid precipitation, solid precipitation excluded', } - self.output_coords_dict["glac_temp_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_attrs_dict['glac_temp_monthly'] = { + 'standard_name': 'air_temperature', + 'long_name': 'glacier-wide mean air temperature', + 'units': 'K', + 'temporal_resolution': 'monthly', + 'comment': ( + 'each elevation bin is weighted equally to compute the mean temperature, and ' + 'bins where the glacier no longer exists due to retreat have been removed' ), } - self.output_coords_dict["glac_acc_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_attrs_dict['glac_acc_monthly'] = { + 'long_name': 'glacier-wide accumulation, in water equivalent', + 'units': 'm3', + 'temporal_resolution': 'monthly', + 'comment': 'only the solid precipitation', } - self.output_coords_dict["glac_refreeze_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_attrs_dict['glac_refreeze_monthly'] = { + 'long_name': 'glacier-wide refreeze, in water equivalent', + 'units': 'm3', + 'temporal_resolution': 'monthly', } - self.output_coords_dict["glac_melt_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_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"] = ( + self.output_coords_dict['glac_frontalablation_monthly'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_massbaltotal_monthly'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_attrs_dict['glac_massbaltotal_monthly'] = { + 'long_name': 'glacier-wide total mass balance, in water equivalent', + 'units': 'm3', + 'temporal_resolution': 'monthly', + 'comment': 'total mass balance is the sum of the climatic mass balance and frontal ablation', } - self.output_coords_dict["glac_snowline_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_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"] = ( + self.output_coords_dict['glac_mass_change_ignored_annual'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + [('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_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_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_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"] = ( + self.output_coords_dict['offglac_refreeze_monthly'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_attrs_dict['offglac_refreeze_monthly'] = { + 'long_name': 'off-glacier-wide refreeze, in water equivalent', + 'units': 'm3', + 'temporal_resolution': 'monthly', } - self.output_coords_dict["offglac_melt_monthly"] = collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + self.output_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_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"] = ( + self.output_coords_dict['offglac_snowpack_monthly'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('glac', self.glac_values), ('time', self.time_values)] ) ) - self.output_attrs_dict["offglac_snowpack_monthly"] = { - "long_name": "off-glacier-wide snowpack, in water equivalent", - "units": "m3", - "temporal_resolution": "monthly", - "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + self.output_attrs_dict['offglac_snowpack_monthly'] = { + 'long_name': 'off-glacier-wide snowpack, in water equivalent', + 'units': 'm3', + 'temporal_resolution': 'monthly', + 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze', } # if nsims > 1, store median-absolute deviation metrics if self.nsims > 1: - self.output_coords_dict["glac_prec_monthly_mad"] = ( + self.output_coords_dict['glac_prec_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_temp_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_acc_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_refreeze_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_melt_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_frontalablation_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_massbaltotal_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_snowline_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['glac_mass_change_ignored_annual_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("year", self.year_values)] + [('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_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"] = ( + self.output_coords_dict['offglac_prec_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['offglac_refreeze_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['offglac_melt_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('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_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"] = ( + self.output_coords_dict['offglac_snowpack_monthly_mad'] = ( collections.OrderedDict( - [("glac", self.glac_values), ("time", self.time_values)] + [('glac', self.glac_values), ('time', self.time_values)] ) ) - self.output_attrs_dict["offglac_snowpack_monthly_mad"] = { - "long_name": "off-glacier-wide snowpack, in water equivalent, median absolute deviation", - "units": "m3", - "temporal_resolution": "monthly", - "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + self.output_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', } @@ -818,188 +818,188 @@ def __post_init__(self): def _set_outdir(self): """Set the output directory path. Create if it does not already exist.""" - self.outdir += self.reg_str + "/" + self.gcm_name + "/" - if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: - self.outdir += self.scenario + "/" - self.outdir += "binned/" + self.outdir += self.reg_str + '/' + self.gcm_name + '/' + if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: + self.outdir += self.scenario + '/' + self.outdir += 'binned/' # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) 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_attrs_dict['bin_distance'] = { + 'long_name': 'distance downglacier', + 'units': 'm', + 'comment': 'horizontal distance calculated from top of glacier moving downglacier', } - self.output_coords_dict["bin_surface_h_initial"] = collections.OrderedDict( - [("glac", self.glac_values), ("bin", self.bin_values)] + self.output_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_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( + self.output_coords_dict['bin_area_annual'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('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_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( + self.output_coords_dict['bin_mass_annual'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('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_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( + self.output_coords_dict['bin_thick_annual'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('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_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( + self.output_coords_dict['bin_massbalclim_annual'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('glac', self.glac_values), + ('bin', self.bin_values), + ('year', self.year_values), ] ) - self.output_attrs_dict["bin_massbalclim_annual"] = ( + 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", + '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( + self.output_coords_dict['bin_massbalclim_monthly'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("time", self.time_values), + ('glac', self.glac_values), + ('bin', self.bin_values), + ('time', self.time_values), ] ) - self.output_attrs_dict["bin_massbalclim_monthly"] = { - "long_name": "binned monthly climatic mass balance, in water equivalent", - "units": "m", - "temporal_resolution": "monthly", - "comment": "monthly climatic mass balance from the PyGEM mass balance module", + self.output_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', } # optionally store binned mass balance components if self.binned_components: - self.output_coords_dict["bin_accumulation_monthly"] = ( + self.output_coords_dict['bin_accumulation_monthly'] = ( collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("time", self.time_values), + ('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_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( + self.output_coords_dict['bin_melt_monthly'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("time", self.time_values), + ('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_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( + self.output_coords_dict['bin_refreeze_monthly'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("time", self.time_values), + ('glac', self.glac_values), + ('bin', self.bin_values), + ('time', self.time_values), ] ) - self.output_attrs_dict["bin_refreeze_monthly"] = { - "long_name": "binned monthly refreeze, in water equivalent", - "units": "m", - "temporal_resolution": "monthly", - "comment": "monthly refreeze from the PyGEM mass balance module", + self.output_attrs_dict['bin_refreeze_monthly'] = { + 'long_name': 'binned monthly refreeze, in water equivalent', + 'units': 'm', + 'temporal_resolution': 'monthly', + 'comment': 'monthly refreeze from the PyGEM mass balance module', } # if nsims > 1, store median-absolute deviation metrics if self.nsims > 1: - self.output_coords_dict["bin_mass_annual_mad"] = collections.OrderedDict( + self.output_coords_dict['bin_mass_annual_mad'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('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_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( + self.output_coords_dict['bin_thick_annual_mad'] = collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('glac', self.glac_values), + ('bin', self.bin_values), + ('year', self.year_values), ] ) - self.output_attrs_dict["bin_thick_annual_mad"] = { - "long_name": "binned ice thickness median absolute deviation", - "units": "m", - "temporal_resolution": "annual", - "comment": "thickness of ice at start of the year", + self.output_attrs_dict['bin_thick_annual_mad'] = { + 'long_name': 'binned ice thickness median absolute deviation', + 'units': 'm', + 'temporal_resolution': 'annual', + 'comment': 'thickness of ice at start of the year', } - self.output_coords_dict["bin_massbalclim_annual_mad"] = ( + self.output_coords_dict['bin_massbalclim_annual_mad'] = ( collections.OrderedDict( [ - ("glac", self.glac_values), - ("bin", self.bin_values), - ("year", self.year_values), + ('glac', self.glac_values), + ('bin', self.bin_values), + ('year', self.year_values), ] ) ) - self.output_attrs_dict["bin_massbalclim_annual_mad"] = { - "long_name": "binned climatic mass balance, in water equivalent, median absolute deviation", - "units": "m", - "temporal_resolution": "annual", - "comment": "climatic mass balance is computed before dynamics so can theoretically exceed ice thickness", + self.output_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', } -def calc_stats_array(data, stats_cns=pygem_prms["sim"]["out"]["sim_stats"]): +def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): """ Calculate stats for a given variable. @@ -1018,20 +1018,20 @@ def calc_stats_array(data, stats_cns=pygem_prms["sim"]["out"]["sim_stats"]): # dictionary of functions to call for each stat in `stats_cns` stat_funcs = { - "mean": lambda x: np.nanmean(x, axis=1), - "std": lambda x: np.nanstd(x, axis=1), - "2.5%": lambda x: np.nanpercentile(x, 2.5, axis=1), - "25%": lambda x: np.nanpercentile(x, 25, axis=1), - "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"), + 'mean': lambda x: np.nanmean(x, axis=1), + 'std': lambda x: np.nanstd(x, axis=1), + '2.5%': lambda x: np.nanpercentile(x, 2.5, axis=1), + '25%': lambda x: np.nanpercentile(x, 25, axis=1), + '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'), } # calculate statustics for each stat in `stats_cns` with warnings.catch_warnings(): warnings.simplefilter( - "ignore", RuntimeWarning + 'ignore', RuntimeWarning ) # Suppress All-NaN Slice Warnings stats_list = [ stat_funcs[stat](data) for stat in stats_cns if stat in stat_funcs diff --git a/pygem/pygem_modelsetup.py b/pygem/pygem_modelsetup.py index 72b65f09..8de10b07 100755 --- a/pygem/pygem_modelsetup.py +++ b/pygem/pygem_modelsetup.py @@ -26,10 +26,10 @@ 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"], + 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. @@ -51,63 +51,63 @@ def datesmodelrun( # Include spinup time in start year startyear_wspinup = startyear - spinupyears # Convert start year into date depending on option_wateryear - if option_wateryear == "hydro": - startdate = str(startyear_wspinup - 1) + "-10-01" - enddate = str(endyear) + "-09-30" - elif option_wateryear == "calendar": - startdate = str(startyear_wspinup) + "-01-01" - enddate = str(endyear) + "-12-31" - elif option_wateryear == "custom": - startdate = str(startyear_wspinup) + "-" + pygem_prms["time"]["startmonthday"] - enddate = str(endyear) + "-" + pygem_prms["time"]["endmonthday"] + if option_wateryear == 'hydro': + startdate = str(startyear_wspinup - 1) + '-10-01' + enddate = str(endyear) + '-09-30' + elif option_wateryear == 'calendar': + startdate = str(startyear_wspinup) + '-01-01' + enddate = str(endyear) + '-12-31' + elif option_wateryear == 'custom': + startdate = str(startyear_wspinup) + '-' + pygem_prms['time']['startmonthday'] + enddate = str(endyear) + '-' + pygem_prms['time']['endmonthday'] else: - assert True == False, "\n\nError: Select an option_wateryear that exists.\n" + assert True == False, '\n\nError: Select an option_wateryear that exists.\n' # Convert input format into proper datetime format - startdate = datetime(*[int(item) for item in startdate.split("-")]) - enddate = datetime(*[int(item) for item in enddate.split("-")]) - if pygem_prms["time"]["timestep"] == "monthly": - startdate = startdate.strftime("%Y-%m") - enddate = enddate.strftime("%Y-%m") - elif pygem_prms["time"]["timestep"] == "daily": - startdate = startdate.strftime("%Y-%m-%d") - enddate = enddate.strftime("%Y-%m-%d") + startdate = datetime(*[int(item) for item in startdate.split('-')]) + enddate = datetime(*[int(item) for item in enddate.split('-')]) + if pygem_prms['time']['timestep'] == 'monthly': + startdate = startdate.strftime('%Y-%m') + enddate = enddate.strftime('%Y-%m') + elif pygem_prms['time']['timestep'] == 'daily': + startdate = startdate.strftime('%Y-%m-%d') + enddate = enddate.strftime('%Y-%m-%d') # Generate dates_table using date_range function - if pygem_prms["time"]["timestep"] == "monthly": + if pygem_prms['time']['timestep'] == 'monthly': # Automatically generate dates from start date to end data using a monthly frequency (MS), which generates # monthly data using the 1st of each month' dates_table = pd.DataFrame( - {"date": pd.date_range(startdate, enddate, freq="MS", unit="s")} + {'date': pd.date_range(startdate, enddate, freq='MS', unit='s')} ) # Select attributes of DateTimeIndex (dt.year, dt.month, and dt.daysinmonth) - dates_table["year"] = dates_table["date"].dt.year - dates_table["month"] = dates_table["date"].dt.month - dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth - dates_table["timestep"] = np.arange(len(dates_table["date"])) + dates_table['year'] = dates_table['date'].dt.year + dates_table['month'] = dates_table['date'].dt.month + dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth + dates_table['timestep'] = np.arange(len(dates_table['date'])) # Set date as index - dates_table.set_index("timestep", inplace=True) + dates_table.set_index('timestep', inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms["time"]["option_leapyear"] == 0: - mask1 = dates_table["daysinmonth"] == 29 - dates_table.loc[mask1, "daysinmonth"] = 28 - elif pygem_prms["time"]["timestep"] == "daily": + if pygem_prms['time']['option_leapyear'] == 0: + mask1 = dates_table['daysinmonth'] == 29 + dates_table.loc[mask1, 'daysinmonth'] = 28 + elif pygem_prms['time']['timestep'] == 'daily': # Automatically generate daily (freq = 'D') dates dates_table = pd.DataFrame( - {"date": pd.date_range(startdate, enddate, freq="D")} + {'date': pd.date_range(startdate, enddate, freq='D')} ) # Extract attributes for dates_table - dates_table["year"] = dates_table["date"].dt.year - dates_table["month"] = dates_table["date"].dt.month - dates_table["day"] = dates_table["date"].dt.day - dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth + dates_table['year'] = dates_table['date'].dt.year + dates_table['month'] = dates_table['date'].dt.month + dates_table['day'] = dates_table['date'].dt.day + dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth # Set date as index - dates_table.set_index("date", inplace=True) + dates_table.set_index('date', inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms["time"]["option_leapyear"] == 0: + if pygem_prms['time']['option_leapyear'] == 0: # First, change 'daysinmonth' number - mask1 = dates_table["daysinmonth"] == 29 - dates_table.loc[mask1, "daysinmonth"] = 28 + mask1 = dates_table['daysinmonth'] == 29 + dates_table.loc[mask1, 'daysinmonth'] = 28 # Next, remove the 29th days from the dates - mask2 = (dates_table["month"] == 2) & (dates_table["day"] == 29) + mask2 = (dates_table['month'] == 2) & (dates_table['day'] == 29) dates_table.drop(dates_table[mask2].index, inplace=True) else: print( @@ -117,10 +117,10 @@ def datesmodelrun( # Add column for water year # Water year for northern hemisphere using USGS definition (October 1 - September 30th), # e.g., water year for 2000 is from October 1, 1999 - September 30, 2000 - dates_table["wateryear"] = dates_table["year"] + dates_table['wateryear'] = dates_table['year'] for step in range(dates_table.shape[0]): - if dates_table.loc[step, "month"] >= 10: - dates_table.loc[step, "wateryear"] = dates_table.loc[step, "year"] + 1 + if dates_table.loc[step, 'month'] >= 10: + dates_table.loc[step, 'wateryear'] = dates_table.loc[step, 'year'] + 1 # Add column for seasons # create a season dictionary to assist groupby functions seasondict = {} @@ -128,15 +128,15 @@ def datesmodelrun( 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"] + month_list[i] >= pygem_prms['time']['summer_month_start'] + and month_list[i] < pygem_prms['time']['winter_month_start'] ): - season_list.append("summer") + season_list.append('summer') seasondict[month_list[i]] = season_list[i] else: - season_list.append("winter") + season_list.append('winter') seasondict[month_list[i]] = season_list[i] - dates_table["season"] = dates_table["month"].apply(lambda x: seasondict[x]) + dates_table['season'] = dates_table['month'].apply(lambda x: seasondict[x]) return dates_table @@ -216,7 +216,7 @@ def import_Husstable( filepath, filedict, drop_col_names, - indexname=pygem_prms["rgi"]["indexname"], + indexname=pygem_prms['rgi']['indexname'], option_shift_elevbins_20m=True, ): """Use the dictionary specified by the user to extract the desired variable. @@ -228,13 +228,13 @@ def import_Husstable( Line Profiling: Loading in the table takes the most time (~2.3 s) """ rgi_regionsO1 = sorted(list(rgi_table.O1Region.unique())) - glac_no = [x.split("-")[1] for x in rgi_table.RGIId.values] + glac_no = [x.split('-')[1] for x in rgi_table.RGIId.values] glac_no_byregion = {} for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = int(i.split(".")[0]) - glac_no_only = i.split(".")[1] + region = int(i.split('.')[0]) + glac_no_only = i.split('.')[1] glac_no_byregion[int(region)].append(glac_no_only) # Load data for each region @@ -250,7 +250,7 @@ def import_Husstable( # Select glaciers based on 01Index value from main_glac_rgi table # as long as Huss tables have all rows associated with rgi attribute table, then this shortcut works - glac_table = ds.iloc[rgi_table_region["O1Index"].values] + glac_table = ds.iloc[rgi_table_region['O1Index'].values] # Merge multiple regions if count == 0: glac_table_all = glac_table @@ -321,17 +321,17 @@ def import_Husstable( 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"], + 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"], + glac_no_skip=pygem_prms['setup']['glac_no_skip'], min_glac_area_km2=0, debug=False, ): @@ -353,13 +353,13 @@ def selectglaciersrgitable( """ if glac_no is not None: glac_no_byregion = {} - rgi_regionsO1 = [int(i.split(".")[0]) for i in glac_no] + rgi_regionsO1 = [int(i.split('.')[0]) for i in glac_no] rgi_regionsO1 = list(set(rgi_regionsO1)) for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = i.split(".")[0] - glac_no_only = i.split(".")[1] + region = i.split('.')[0] + glac_no_only = i.split('.')[1] glac_no_byregion[int(region)].append(glac_no_only) for region in rgi_regionsO1: @@ -375,57 +375,57 @@ def selectglaciersrgitable( # if len(rgi_glac_number) < 50: for i in os.listdir(rgi_fp): - if i.startswith(str(region).zfill(2)) and i.endswith(".csv"): + if i.startswith(str(region).zfill(2)) and i.endswith('.csv'): rgi_fn = i try: csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn) except: - csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding="latin1") + csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding='latin1') # Populate glacer_table with the glaciers of interest - if rgi_regionsO2 == "all" and rgi_glac_number == "all": + if rgi_regionsO2 == 'all' and rgi_glac_number == 'all': if debug: print( - "All glaciers within region(s) %s are included in this model run." + '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": + 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." + '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 + csv_regionO1['O2Region'] == regionO2 ] else: glacier_table = pd.concat( [ glacier_table, - csv_regionO1.loc[csv_regionO1["O2Region"] == regionO2], + 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" + '%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" + '%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 + '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] @@ -440,20 +440,20 @@ def selectglaciersrgitable( # reset the index so that it is in sequential order (0, 1, 2, etc.) glacier_table.reset_index(inplace=True) # drop connectivity 2 for Greenland and Antarctica - glacier_table = glacier_table.loc[glacier_table["Connect"].isin([0, 1])] + glacier_table = glacier_table.loc[glacier_table['Connect'].isin([0, 1])] glacier_table.reset_index(drop=True, inplace=True) # change old index to 'O1Index' to be easier to recall what it is - glacier_table.rename(columns={"index": "O1Index"}, inplace=True) + glacier_table.rename(columns={'index': 'O1Index'}, inplace=True) # Record the reference date - glacier_table["RefDate"] = glacier_table["BgnDate"] + glacier_table['RefDate'] = glacier_table['BgnDate'] # if there is an end date, then roughly average the year enddate_idx = glacier_table.loc[ - (glacier_table["EndDate"] > 0), "EndDate" + (glacier_table['EndDate'] > 0), 'EndDate' ].index.values - glacier_table.loc[enddate_idx, "RefDate"] = ( + glacier_table.loc[enddate_idx, 'RefDate'] = ( np.mean( ( - glacier_table.loc[enddate_idx, ["BgnDate", "EndDate"]].values / 10**4 + glacier_table.loc[enddate_idx, ['BgnDate', 'EndDate']].values / 10**4 ).astype(int), axis=1, ).astype(int) @@ -464,22 +464,22 @@ def selectglaciersrgitable( 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['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"]] + [x.split('-')[1] for x in glacier_table['RGIId']] # [np.str.split(glacier_table['RGIId'][x],'-')[1] # for x in range(glacier_table.shape[0])] ).astype(float) # set index name glacier_table.index.name = indexname # Longitude between 0-360deg (no negative) - glacier_table["CenLon_360"] = glacier_table["CenLon"] - glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] = ( - 360 + glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] + glacier_table['CenLon_360'] = glacier_table['CenLon'] + glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360'] = ( + 360 + glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360'] ) # Subset glaciers based on their terminus type termtype_values = [] @@ -495,28 +495,28 @@ def selectglaciersrgitable( termtype_values.append(5) if include_laketerm: termtype_values.append(2) - glacier_table = glacier_table.loc[glacier_table["TermType"].isin(termtype_values)] + glacier_table = glacier_table.loc[glacier_table['TermType'].isin(termtype_values)] glacier_table.reset_index(inplace=True, drop=True) # Glacier number with no trailing zeros - glacier_table["glacno"] = [ - str(int(x.split("-")[1].split(".")[0])) + "." + x.split("-")[1].split(".")[1] + glacier_table['glacno'] = [ + str(int(x.split('-')[1].split('.')[0])) + '.' + x.split('-')[1].split('.')[1] for x in glacier_table.RGIId ] # Remove glaciers below threshold - glacier_table = glacier_table.loc[glacier_table["Area"] > min_glac_area_km2, :] + glacier_table = glacier_table.loc[glacier_table['Area'] > min_glac_area_km2, :] glacier_table.reset_index(inplace=True, drop=True) # Remove glaciers that are meant to be skipped if glac_no_skip is not None: - glac_no_all = list(glacier_table["glacno"]) + glac_no_all = list(glacier_table['glacno']) glac_no_unique = [x for x in glac_no_all if x not in glac_no_skip] unique_idx = [glac_no_all.index(x) for x in glac_no_unique] glacier_table = glacier_table.loc[unique_idx, :] glacier_table.reset_index(inplace=True, drop=True) print( - "This study is focusing on %s glaciers in region %s" + 'This study is focusing on %s glaciers in region %s' % (glacier_table.shape[0], rgi_regionsO1) ) diff --git a/pygem/scraps/dummy_task_module.py b/pygem/scraps/dummy_task_module.py index 24269d52..60570ff8 100755 --- a/pygem/scraps/dummy_task_module.py +++ b/pygem/scraps/dummy_task_module.py @@ -8,9 +8,9 @@ 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', ) @@ -18,6 +18,6 @@ def dummy_task(gdir, some_param=None): """Very dummy""" - fpath = gdir.get_filepath("my_netcdf_file") + fpath = gdir.get_filepath('my_netcdf_file') da = xr.DataArray([1, 2, 3]) da.to_netcdf(fpath) diff --git a/pygem/scraps/run.py b/pygem/scraps/run.py index f6daddbe..5955c1cb 100755 --- a/pygem/scraps/run.py +++ b/pygem/scraps/run.py @@ -6,24 +6,24 @@ cfg.initialize() # How many grid points around the glacier? -cfg.PARAMS["border"] = 10 +cfg.PARAMS['border'] = 10 # Make it robust -cfg.PARAMS["use_intersects"] = False -cfg.PARAMS["continue_on_error"] = True +cfg.PARAMS['use_intersects'] = False +cfg.PARAMS['continue_on_error'] = True # Local working directory (where OGGM will write its output) -cfg.PATHS["working_dir"] = utils.get_temp_dir("some_wd") +cfg.PATHS['working_dir'] = utils.get_temp_dir('some_wd') # RGI file -path = utils.get_rgi_region_file("11") +path = utils.get_rgi_region_file('11') rgidf = gpd.read_file(path) # Select only 2 glaciers rgidf = rgidf.iloc[:2] # Sort for more efficient parallel computing -rgidf = rgidf.sort_values("Area", ascending=False) +rgidf = rgidf.sort_values('Area', ascending=False) # Go - create the pre-processed glacier directories gdirs = workflow.init_glacier_directories(rgidf) @@ -36,5 +36,5 @@ # See that we can read the new dummy data: import xarray as xr -fpath = gdirs[0].get_filepath("my_netcdf_file") +fpath = gdirs[0].get_filepath('my_netcdf_file') print(xr.open_dataset(fpath)) diff --git a/pygem/setup/config.py b/pygem/setup/config.py index db6485d1..11262932 100644 --- a/pygem/setup/config.py +++ b/pygem/setup/config.py @@ -11,13 +11,13 @@ 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): + def __init__(self, config_filename='config.yaml', base_dir=None, overwrite=False): """ Initialize the ConfigManager class. @@ -27,10 +27,10 @@ def __init__(self, config_filename="config.yaml", base_dir=None, overwrite=False overwrite (bool, optional): Whether to overwrite an existing configuration file. Defaults to False. """ self.config_filename = config_filename - self.base_dir = base_dir or os.path.join(os.path.expanduser("~"), "PyGEM") + 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" + os.path.dirname(os.path.abspath(__file__)), 'config.yaml' ) self.overwrite = overwrite self._ensure_config() @@ -45,7 +45,7 @@ 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. @@ -53,7 +53,7 @@ def read_config(self, validate=True): validate (bool): Whether to validate the configuration file contents. Defaults to True. """ ryaml = ruamel.yaml.YAML() - with open(self.config_path, "r") as f: + with open(self.config_path, 'r') as f: user_config = ryaml.load(f) if validate: @@ -69,7 +69,7 @@ def _write_config(self, config): """ ryaml = ruamel.yaml.YAML() ryaml.preserve_quotes = True - with open(self.config_path, "w") as file: + with open(self.config_path, 'w') as file: ryaml.dump(config, file) def update_config(self, updates): @@ -82,8 +82,8 @@ def update_config(self, updates): for key, value in updates.items(): if key not in self.EXPECTED_TYPES: - raise KeyError(f"Unrecognized configuration key: {key}") - keys = key.split(".") + raise KeyError(f'Unrecognized configuration key: {key}') + keys = key.split('.') d = config for sub_key in keys[:-1]: d = d[sub_key] @@ -100,13 +100,13 @@ def _validate_config(self, config): 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( @@ -123,248 +123,248 @@ def _validate_config(self, config): # 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 032714cc..5754c3fe 100755 --- a/pygem/shop/debris.py +++ b/pygem/shop/debris.py @@ -34,19 +34,19 @@ log = logging.getLogger(__name__) # Add the new name "hd" to the list of things that the GlacierDirectory understands -if "debris_hd" not in cfg.BASENAMES: - cfg.BASENAMES["debris_hd"] = ("debris_hd.tif", "Raster of debris thickness data") -if "debris_ed" not in cfg.BASENAMES: - cfg.BASENAMES["debris_ed"] = ( - "debris_ed.tif", - "Raster of debris enhancement factor data", +if 'debris_hd' not in cfg.BASENAMES: + cfg.BASENAMES['debris_hd'] = ('debris_hd.tif', 'Raster of debris thickness data') +if 'debris_ed' not in cfg.BASENAMES: + cfg.BASENAMES['debris_ed'] = ( + 'debris_ed.tif', + 'Raster of debris enhancement factor data', ) -@entity_task(log, writes=["debris_hd", "debris_ed"]) +@entity_task(log, writes=['debris_hd', 'debris_ed']) def debris_to_gdir( gdir, - debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['debris_relpath']}", + debris_dir=f'{pygem_prms["root"]}/{pygem_prms["mb"]["debris_relpath"]}', add_to_gridded=True, hd_max=5, hd_min=0, @@ -65,98 +65,98 @@ def debris_to_gdir( 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 + "/" + 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] + 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" + if os.path.exists(hd_dir + glac_str_nolead + '_hdts_m.tif'): + hd_fn = hd_dir + glac_str_nolead + '_hdts_m.tif' + elif os.path.exists(hd_dir + glac_str_nolead + '_hdts_m_extrap.tif'): + hd_fn = hd_dir + glac_str_nolead + '_hdts_m_extrap.tif' else: hd_fn = None if hd_fn is not None: - rasterio_to_gdir(gdir, hd_fn, "debris_hd", resampling="bilinear") + rasterio_to_gdir(gdir, hd_fn, 'debris_hd', resampling='bilinear') if add_to_gridded and hd_fn is not None: - output_fn = gdir.get_filepath("debris_hd") + output_fn = gdir.get_filepath('debris_hd') # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath("gridded_data") - with ncDataset(grids_file, "a") as nc: + grids_file = gdir.get_filepath('gridded_data') + with ncDataset(grids_file, 'a') as nc: # Mask values - glacier_mask = nc["glacier_mask"][:] + glacier_mask = nc['glacier_mask'][:] data = src.read(1) * glacier_mask data[data > hd_max] = 0 data[data < hd_min] = 0 # Write data - vn = "debris_hd" + vn = 'debris_hd' if vn in nc.variables: v = nc.variables[vn] else: v = nc.createVariable( vn, - "f8", + 'f8', ( - "y", - "x", + 'y', + 'x', ), zlib=True, ) - v.units = "m" - v.long_name = "Debris thicknness" + 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" + if os.path.exists(ed_dir + glac_str_nolead + '_meltfactor.tif'): + ed_fn = ed_dir + glac_str_nolead + '_meltfactor.tif' + elif os.path.exists(ed_dir + glac_str_nolead + '_meltfactor_extrap.tif'): + ed_fn = ed_dir + glac_str_nolead + '_meltfactor_extrap.tif' else: ed_fn = None if ed_fn is not None: - rasterio_to_gdir(gdir, ed_fn, "debris_ed", resampling="bilinear") + rasterio_to_gdir(gdir, ed_fn, 'debris_ed', resampling='bilinear') if add_to_gridded and ed_fn is not None: - output_fn = gdir.get_filepath("debris_ed") + output_fn = gdir.get_filepath('debris_ed') # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath("gridded_data") - with ncDataset(grids_file, "a") as nc: + grids_file = gdir.get_filepath('gridded_data') + with ncDataset(grids_file, 'a') as nc: # Mask values - glacier_mask = nc["glacier_mask"][:] + glacier_mask = nc['glacier_mask'][:] data = src.read(1) * glacier_mask data[data > ed_max] = 1 data[data < ed_min] = 1 # Write data - vn = "debris_ed" + vn = 'debris_ed' if vn in nc.variables: v = nc.variables[vn] else: v = nc.createVariable( vn, - "f8", + 'f8', ( - "y", - "x", + 'y', + 'x', ), zlib=True, ) - v.units = "-" - v.long_name = "Debris enhancement factor" + v.units = '-' + v.long_name = 'Debris enhancement factor' v[:] = data -@entity_task(log, writes=["inversion_flowlines"]) -def debris_binned(gdir, ignore_debris=False, fl_str="inversion_flowlines"): +@entity_task(log, writes=['inversion_flowlines']) +def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): """Bin debris thickness and enhancement factors. Updates the 'inversion_flowlines' save file. @@ -172,7 +172,7 @@ def debris_binned(gdir, ignore_debris=False, fl_str="inversion_flowlines"): fl = flowlines[0] assert len(flowlines) == 1, ( - "Error: binning debris only works for single flowlines at present" + 'Error: binning debris only works for single flowlines at present' ) except: @@ -180,12 +180,12 @@ def debris_binned(gdir, ignore_debris=False, fl_str="inversion_flowlines"): if flowlines is not None: # Add binned debris thickness and enhancement factors to flowlines - if os.path.exists(gdir.get_filepath("debris_hd")) and ignore_debris == False: - ds = xr.open_dataset(gdir.get_filepath("gridded_data")) - glacier_mask = ds["glacier_mask"].values - topo = ds["topo_smoothed"].values - hd = ds["debris_hd"].values - ed = ds["debris_ed"].values + if os.path.exists(gdir.get_filepath('debris_hd')) and ignore_debris == False: + ds = xr.open_dataset(gdir.get_filepath('gridded_data')) + glacier_mask = ds['glacier_mask'].values + topo = ds['topo_smoothed'].values + hd = ds['debris_hd'].values + ed = ds['debris_ed'].values # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) diff --git a/pygem/shop/icethickness.py b/pygem/shop/icethickness.py index b1a5be12..9e1b9b7e 100755 --- a/pygem/shop/icethickness.py +++ b/pygem/shop/icethickness.py @@ -25,25 +25,25 @@ # read the config pygem_prms = config_manager.read_config() -if "consensus_mass" not in cfg.BASENAMES: - cfg.BASENAMES["consensus_mass"] = ( - "consensus_mass.pkl", - "Glacier mass from 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", +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"]) +@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']}", + h_consensus_fp=f'{pygem_prms["root"]}/{pygem_prms["calib"]["data"]["icethickness"]["h_consensus_relpath"]}', add_mass=True, add_to_gridded=True, ): @@ -59,73 +59,73 @@ def consensus_gridded( # If binned mb data exists, then write to glacier directory h_fn = ( h_consensus_fp - + "RGI60-" + + 'RGI60-' + gdir.rgi_region - + "/" + + '/' + gdir.rgi_id - + "_thickness.tif" + + '_thickness.tif' ) assert os.path.exists(h_fn), ( - "Error: h_consensus_fullfn for " + gdir.rgi_id + " does not exist." + 'Error: h_consensus_fullfn for ' + gdir.rgi_id + ' does not exist.' ) # open consensus ice thickness estimate - h_dr = rasterio.open(h_fn, "r", driver="GTiff") + h_dr = rasterio.open(h_fn, 'r', driver='GTiff') h = h_dr.read(1).astype(rasterio.float32) # Glacier mass [kg] - glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms["constants"][ - "density_ice" + glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms['constants'][ + 'density_ice' ] # print(glacier_mass_raw) if add_mass: # Pickle data - consensus_fn = gdir.get_filepath("consensus_mass") - with open(consensus_fn, "wb") as f: + consensus_fn = gdir.get_filepath('consensus_mass') + with open(consensus_fn, 'wb') as f: pickle.dump(glacier_mass_raw, f) if add_to_gridded: - rasterio_to_gdir(gdir, h_fn, "consensus_h", resampling="bilinear") - output_fn = gdir.get_filepath("consensus_h") + rasterio_to_gdir(gdir, h_fn, 'consensus_h', resampling='bilinear') + output_fn = gdir.get_filepath('consensus_h') # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath("gridded_data") - with ncDataset(grids_file, "a") as nc: + grids_file = gdir.get_filepath('gridded_data') + with ncDataset(grids_file, 'a') as nc: # Mask values - glacier_mask = nc["glacier_mask"][:] + glacier_mask = nc['glacier_mask'][:] data = src.read(1) * glacier_mask # Pixel area pixel_m2 = abs(gdir.grid.dx * gdir.grid.dy) # Glacier mass [kg] reprojoected (may lose or gain mass depending on resampling algorithm) glacier_mass_reprojected = (data * pixel_m2).sum() * pygem_prms[ - "constants" - ]["density_ice"] + '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) # Write data - vn = "consensus_h" + vn = 'consensus_h' if vn in nc.variables: v = nc.variables[vn] else: v = nc.createVariable( vn, - "f8", + 'f8', ( - "y", - "x", + 'y', + 'x', ), zlib=True, ) - v.units = "m" - v.long_name = "Consensus ice thicknness" + v.units = 'm' + v.long_name = 'Consensus ice thicknness' v[:] = data_scaled -@entity_task(log, writes=["inversion_flowlines"]) +@entity_task(log, writes=['inversion_flowlines']) def consensus_binned(gdir): """Bin consensus ice thickness ice estimates. @@ -136,18 +136,18 @@ def consensus_binned(gdir): gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ - flowlines = gdir.read_pickle("inversion_flowlines") + flowlines = gdir.read_pickle('inversion_flowlines') fl = flowlines[0] assert len(flowlines) == 1, ( - "Error: binning debris data set up only for single flowlines at present" + 'Error: binning debris data set up only for single flowlines at present' ) # Add binned debris thickness and enhancement factors to flowlines - ds = xr.open_dataset(gdir.get_filepath("gridded_data")) - glacier_mask = ds["glacier_mask"].values - topo = ds["topo_smoothed"].values - h = ds["consensus_h"].values + ds = xr.open_dataset(gdir.get_filepath('gridded_data')) + glacier_mask = ds['glacier_mask'].values + topo = ds['topo_smoothed'].values + h = ds['consensus_h'].values # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) @@ -178,4 +178,4 @@ def consensus_binned(gdir): fl.consensus_h = h_binned # Overwrite pickle - gdir.write_pickle(flowlines, "inversion_flowlines") + gdir.write_pickle(flowlines, 'inversion_flowlines') diff --git a/pygem/shop/mbdata.py b/pygem/shop/mbdata.py index 7ca31ebf..b62dc856 100755 --- a/pygem/shop/mbdata.py +++ b/pygem/shop/mbdata.py @@ -38,18 +38,18 @@ log = logging.getLogger(__name__) # Add the new name "mb_calib_pygem" to the list of things that the GlacierDirectory understands -if "mb_calib_pygem" not 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"]) +@entity_task(log, writes=['mb_calib_pygem']) def mb_df_to_gdir( gdir, - mb_dataset="Hugonnet2021", - facorrected=pygem_prms["setup"]["include_frontalablation"], + mb_dataset='Hugonnet2021', + facorrected=pygem_prms['setup']['include_frontalablation'], ): """Select specific mass balance and add observations to the given glacier directory @@ -59,29 +59,29 @@ def mb_df_to_gdir( 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 = f'{pygem_prms["root"]}/{pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_relpath"]}' mbdata_fp_fa = ( mbdata_fp - + pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_facorrected_fn"] + + 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 + pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn'] ) assert os.path.exists(mbdata_fp), ( - "Error, mass balance dataset does not exist: {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 '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" + 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) @@ -102,7 +102,7 @@ def mb_df_to_gdir( 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) @@ -115,22 +115,22 @@ def mb_df_to_gdir( mbdata = { 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) + '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) + '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, + 't1_str': t1_str, + 't2_str': t2_str, + 'nyears': nyears, }.items() if value is not None } - mb_fn = gdir.get_filepath("mb_calib_pygem") - with open(mb_fn, "w") as f: + mb_fn = gdir.get_filepath('mb_calib_pygem') + with open(mb_fn, 'w') as f: json.dump(mbdata, f) diff --git a/pygem/shop/oib.py b/pygem/shop/oib.py index 215e66ea..b7be9611 100644 --- a/pygem/shop/oib.py +++ b/pygem/shop/oib.py @@ -28,15 +28,15 @@ class oib: - def __init__(self, rgi6id="", rgi7id=""): + def __init__(self, rgi6id='', rgi7id=''): self.oib_datpath = ( - f"{pygem_prms['root']}/{pygem_prms['calib']['data']['oib']['oib_relpath']}" + 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 = pd.read_csv(f'{self.oib_datpath}/../oibak_rgi6_rgi7_ids.csv') + self.rgi7_6_df['rgi7id'] = ( + self.rgi7_6_df['rgi7id'].str.split('RGI2000-v7.0-G-').str[1] ) - self.rgi7_6_df["rgi6id"] = self.rgi7_6_df["rgi6id"].str.split("RGI60-").str[1] + self.rgi7_6_df['rgi6id'] = self.rgi7_6_df['rgi6id'].str.split('RGI60-').str[1] self.rgi6id = rgi6id self.rgi7id = rgi7id self.name = None @@ -71,20 +71,20 @@ def _rgi6torgi7id(self, debug=False): """ self.rgi6id = ( - self.rgi6id.split(".")[0].zfill(2) + "." + self.rgi6id.split(".")[1] + 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" + 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}") + print(f'RGI6:{self.rgi6id} -> RGI7:{self.rgi7id}') elif len(rgi7id) == 0: - raise IndexError(f"No matching RGI7Id for {self.rgi6id}") + raise IndexError(f'No matching RGI7Id for {self.rgi6id}') elif len(rgi7id) > 1: - raise IndexError(f"More than one matching RGI7Id for {self.rgi6id}") + raise IndexError(f'More than one matching RGI7Id for {self.rgi6id}') def _rgi7torgi6id(self, debug=False): """ @@ -92,20 +92,20 @@ def _rgi7torgi6id(self, debug=False): """ self.rgi7id = ( - self.rgi7id.split("-")[0].zfill(2) + "-" + self.rgi7id.split("-")[1] + 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" + 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}") + print(f'RGI7:{self.rgi7id} -> RGI6:{self.rgi6id}') elif len(rgi6id) == 0: - raise IndexError(f"No matching RGI6Id for {self.rgi7id}") + raise IndexError(f'No matching RGI6Id for {self.rgi7id}') elif len(rgi6id) > 1: - raise IndexError(f"More than one matching RGI6Id for {self.rgi7id}") + raise IndexError(f'More than one matching RGI6Id for {self.rgi7id}') def _date_check(self, dt_obj): """ @@ -122,15 +122,15 @@ def _load(self): """ load Operation IceBridge data """ - oib_fpath = glob.glob(f"{self.oib_datpath}/diffstats5_*{self.rgi7id}*.json") + oib_fpath = glob.glob(f'{self.oib_datpath}/diffstats5_*{self.rgi7id}*.json') if len(oib_fpath) == 0: return else: oib_fpath = oib_fpath[0] # load diffstats file - with open(oib_fpath, "rb") as f: + with open(oib_fpath, 'rb') as f: self.oib_dict = json.load(f) - self.name = split_by_uppercase(self.oib_dict["glacier_shortname"]) + self.name = split_by_uppercase(self.oib_dict['glacier_shortname']) def _parsediffs(self, filter_count_pctl=10, debug=False): """ @@ -139,24 +139,24 @@ def _parsediffs(self, filter_count_pctl=10, debug=False): """ # get seasons stored in oib diffs seasons = list( - set(self.oib_dict.keys()).intersection(["march", "may", "august"]) + set(self.oib_dict.keys()).intersection(['march', 'may', 'august']) ) for ssn in seasons: for yr in list(self.oib_dict[ssn].keys()): # get survey date - doy_int = int(np.ceil(self.oib_dict[ssn][yr]["mean_doy"])) - dt_obj = datetime.datetime.strptime(f"{int(yr)}-{doy_int}", "%Y-%j") + doy_int = int(np.ceil(self.oib_dict[ssn][yr]['mean_doy'])) + dt_obj = datetime.datetime.strptime(f'{int(yr)}-{doy_int}', '%Y-%j') # get survey data and filter by pixel count diffs = np.asarray( - self.oib_dict[ssn][yr]["bin_vals"]["bin_median_diffs_vec"] + self.oib_dict[ssn][yr]['bin_vals']['bin_median_diffs_vec'] ) - counts = np.asarray(self.oib_dict[ssn][yr]["bin_vals"]["bin_count_vec"]) + counts = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_count_vec']) mask = _filter_on_pixel_count(counts, filter_count_pctl) diffs[mask] = np.nan # uncertainty represented by IQR sigmas = np.asarray( - self.oib_dict[ssn][yr]["bin_vals"][ - "bin_interquartile_range_diffs_vec" + self.oib_dict[ssn][yr]['bin_vals'][ + 'bin_interquartile_range_diffs_vec' ] ) sigmas[mask] = np.nan @@ -167,22 +167,22 @@ def _parsediffs(self, filter_count_pctl=10, debug=False): if debug: print( - f"OIB survey dates:\n{', '.join([str(dt.year) + '-' + str(dt.month) + '-' + str(dt.day) for dt in list(self.oib_diffs.keys())])}" + f'OIB survey dates:\n{", ".join([str(dt.year) + "-" + str(dt.month) + "-" + str(dt.day) for dt in list(self.oib_diffs.keys())])}' ) # get bin centers self.bin_centers = ( np.asarray( - self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]["bin_vals"][ - "bin_start_vec" + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals'][ + 'bin_start_vec' ] ) + np.asarray( - self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]["bin_vals"][ - "bin_stop_vec" + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals'][ + 'bin_stop_vec' ] ) ) / 2 - self.bin_area = self.oib_dict["aad_dict"]["hist_bin_areas_m2"] + self.bin_area = self.oib_dict['aad_dict']['hist_bin_areas_m2'] # bin_edges = oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec'] # bin_edges.append(oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'][-1]) # bin_edges = np.asarray(bin_edges) @@ -218,17 +218,17 @@ def _terminus_mask(self, debug=False): for i in inds[::-1]: plt.plot( diffs[i], - label=f"{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}", + label=f'{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}', c=cmap[i], ) if idx: - plt.axvline(idx, c="k", ls=":") - plt.legend(loc="upper right") + plt.axvline(idx, c='k', ls=':') + plt.legend(loc='upper right') plt.show() except Exception as err: if debug: - print(f"_filter_terminus_missing_ice error: {err}") + print(f'_filter_terminus_missing_ice error: {err}') mask = [] # apply mask @@ -241,7 +241,7 @@ def _rebin(self, agg=100): # 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") + warnings.filterwarnings('ignore') for i, (k, tup) in enumerate(self.oib_diffs.items()): if i == 0: y, self.bin_edges, _ = stats.binned_statistic( @@ -278,9 +278,9 @@ def _dbl_diff(self, months=range(1, 13)): # where dates is a tuple for each double differenced array in the format of (date1,date2), # where date1's cop30 differences were subtracted from date2's to get the dh values for that time span, # and the sigma was taken as the mean sigma from each date - self.dbl_diffs["dates"] = [] - self.dbl_diffs["dh"] = [] - self.dbl_diffs["sigma"] = [] + self.dbl_diffs['dates'] = [] + self.dbl_diffs['dh'] = [] + self.dbl_diffs['sigma'] = [] # loop through months for m in months: # filter and sort dates to include only those in the target month @@ -295,47 +295,47 @@ 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.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.dbl_diffs['sigma'].append( self.oib_diffs[date2][1] + self.oib_diffs[date1][1] ) # column stack dh and sigmas into single 2d array - if len(self.dbl_diffs["dh"]) > 0: - self.dbl_diffs["dh"] = np.column_stack(self.dbl_diffs["dh"]) - self.dbl_diffs["sigma"] = np.column_stack(self.dbl_diffs["sigma"]) + if len(self.dbl_diffs['dh']) > 0: + self.dbl_diffs['dh'] = np.column_stack(self.dbl_diffs['dh']) + self.dbl_diffs['sigma'] = np.column_stack(self.dbl_diffs['sigma']) else: - self.dbl_diffs["dh"] = np.nan + self.dbl_diffs['dh'] = np.nan # check if deltah is all nan - if np.isnan(self.dbl_diffs["dh"]).all(): - self.dbl_diffs["dh"] = None - self.dbl_diffs["sigma"] = None + if np.isnan(self.dbl_diffs['dh']).all(): + self.dbl_diffs['dh'] = None + self.dbl_diffs['sigma'] = None def _elevchange_to_masschange( self, ela, - density_ablation=pygem_prms["constants"]["density_ice"], + density_ablation=pygem_prms['constants']['density_ice'], density_accumulation=700, ): # convert elevation changes to mass change using piecewise density conversion - if self.dbl_diffs["dh"] is not None: + if self.dbl_diffs['dh'] is not None: # populate density conversion column corresponding to bin center elevation conversion_factor = np.ones(len(self.bin_centers)) conversion_factor[np.where(self.bin_centers < ela)] = density_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'] = ( + 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_err'] = ( + self.dbl_diffs['sigma'] * conversion_factor[:, np.newaxis] ) else: - self.dbl_diffs["dmda"] = None - self._dbl_diff["dmda_err"] = None + self.dbl_diffs['dmda'] = None + self._dbl_diff['dmda_err'] = None def _filter_on_pixel_count(arr, pctl=15): @@ -350,4 +350,4 @@ def _filter_on_pixel_count(arr, pctl=15): def split_by_uppercase(text): # Add space before each uppercase letter (except at the start of the string) - return re.sub(r"(? Date: Wed, 9 Apr 2025 14:48:29 -0400 Subject: [PATCH 15/17] missing import --- pygem/bin/postproc/postproc_distribute_ice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index 29d05c71..f4161e3b 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -8,6 +8,7 @@ # Built-in libraries import argparse +import glob import multiprocessing import os import time @@ -191,7 +192,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])) @@ -209,4 +210,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() From 34a9c3e5d7334584867b0bfca8568906e5bb7745 Mon Sep 17 00:00:00 2001 From: btobers Date: Wed, 9 Apr 2025 14:52:37 -0400 Subject: [PATCH 16/17] ruff format --- pygem/bin/postproc/postproc_distribute_ice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index f4161e3b..cfbab6fc 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -145,7 +145,6 @@ def run(simpath, debug=False): 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: @@ -208,6 +207,6 @@ def main(): print('Total processing time:', time.time() - time_start, 's') -if __name__ == '__main__': +if __name__ == '__main__': main() From ed54b64ce27b2cbd82d9e43ce732d55522f6c2d9 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:38:39 +0000 Subject: [PATCH 17/17] Apply suggestions from @btobers code review Co-authored-by: Brandon S. Tober --- docs/contributing.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 9a4458e2..06845764 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -60,14 +60,18 @@ Installing a package in editable mode creates a symbolic link to your source cod (ruff_target)= ## Code linting and formatting -PyGEM uses [ruff](https://docs.astral.sh/ruff/formatter) for linting and formatting, which ensures that the code adheres to a consistent coding style ([Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and prevents potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file. Linting and formatting checks are integrated into the CI pipeline, and any detected issues will cause it to fail. +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