From 06fc53cb21ea4c9ad75c96888cdc7bb6c11f09b3 Mon Sep 17 00:00:00 2001 From: hollyhan Date: Mon, 14 Nov 2022 13:18:23 -0700 Subject: [PATCH 01/10] Add a new testcase for processing ismip6 shelf-collapse mask data --- .../landice/tests/ismip6_forcing/__init__.py | 6 +- .../ismip6_forcing/shelf_collapse/__init__.py | 35 +++ .../shelf_collapse/process_shelf_collapse.py | 281 ++++++++++++++++++ 3 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py create mode 100644 compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py diff --git a/compass/landice/tests/ismip6_forcing/__init__.py b/compass/landice/tests/ismip6_forcing/__init__.py index 0d0fed1383..dc088306f0 100644 --- a/compass/landice/tests/ismip6_forcing/__init__.py +++ b/compass/landice/tests/ismip6_forcing/__init__.py @@ -2,11 +2,12 @@ from compass.landice.tests.ismip6_forcing.atmosphere import Atmosphere from compass.landice.tests.ismip6_forcing.ocean_thermal import OceanThermal from compass.landice.tests.ismip6_forcing.ocean_basal import OceanBasal - +from compass.landice.tests.ismip6_forcing.shelf_collapse import ShelfCollapse class Ismip6Forcing(TestGroup): """ - A test group for processing the ISMIP6 ocean and atmosphere forcing data + A test group for processing the ISMIP6 atmosphere, ocean and + shelf-collapse forcing data """ def __init__(self, mpas_core): @@ -24,3 +25,4 @@ def __init__(self, mpas_core): self.add_test_case(OceanBasal(test_group=self)) self.add_test_case(OceanThermal(test_group=self, process_obs=True)) self.add_test_case(OceanThermal(test_group=self, process_obs=False)) + self.add_test_case(ShelfCollapse(test_group=self)) diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py new file mode 100644 index 0000000000..dcd2fda0b9 --- /dev/null +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py @@ -0,0 +1,35 @@ +from compass.testcase import TestCase +from compass.landice.tests.ismip6_forcing.shelf_collapse.process_shelf_collapse \ + import ProcessShelfCollapse +from compass.landice.tests.ismip6_forcing.configure import configure as \ + configure_testgroup + + +class ShelfCollapse(TestCase): + """ + A test case for processing the ISMIP6 shelf-collapse mask data. + The test case builds a mapping file for interpolation between the + ISMIP6 polarstereo grid and MALI mesh, regrids the forcing data + and renames the ISMIP6 variables to corresponding MALI variables. + """ + + def __init__(self, test_group): + """ + Create the test case + + Parameters + ---------- + test_group : compass.landice.tests.ismip6_forcing.Ismip6Forcing + The test group that this test case belongs to + """ + name = "shelf_collapse" + super().__init__(test_group=test_group, name=name) + + step = ProcessShelfCollapse(test_case=self) + self.add_step(step) + + def configure(self): + """ + Configures test case + """ + configure_testgroup(config=self.config, check_model_options=False) diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py new file mode 100644 index 0000000000..8ce1b214e2 --- /dev/null +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py @@ -0,0 +1,281 @@ +import os +import shutil +import numpy as np +import xarray as xr +from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb \ + import build_mapping_file +from mpas_tools.io import write_netcdf +from mpas_tools.logging import check_call +from compass.step import Step + + +class ProcessShelfCollapse(Step): + """ + A step for processing (combine, remap and rename) the ISMIP6 shelf-collapse + mask data + """ + + def __init__(self, test_case): + """ + Create the step + + Parameters + ---------- + test_case : compass.landice.tests.ismip6_forcing.shelf_collapse. + ShelfCollapse + The test case this step belongs to + """ + super().__init__(test_case=test_case, name="process_shelf_collapse", + ntasks=4, min_tasks=1) + + def setup(self): + """ + Set up this step of the test case + """ + config = self.config + section = config["ismip6_ais"] + base_path_ismip6 = section.get("base_path_ismip6") + base_path_mali = section.get("base_path_mali") + period_endyear = section.get("period_endyear") + model = section.get("model") + scenario = section.get("scenario") + res_ismip6 = section.get("res_ismip6") + mali_mesh_name = section.get("mali_mesh_name") + mali_mesh_file = section.get("mali_mesh_file") + + model_list_2300 = ["CCSM4", "CESM2-WACCM","HadGEM2-ES","UKESM1-0-LL"] + if period_endyear != 2300 and model not in model_list_2300: + raise ValueError(f"ice shelf-collapse masks are provided by the " + f"ISMIP6 only for CCSM4-RCP85, " + f"CESM2-WACCM-SSP585,HadGEM2-ES-RCP85, " + f"UKESM1-0-LL-SSP585 model&scenarios. Please " + f"set up the correct config options in " + f"the config file.") + + input_file = self._files[period_endyear][model][scenario] \ + [res_ismip6][0] + self.add_input_file(filename=mali_mesh_file, + target=os.path.join(base_path_mali, + mali_mesh_file)) + self.add_input_file(filename=os.path.basename(input_file), + target=os.path.join(base_path_ismip6, + input_file)) + self.add_output_file(filename=f"{mali_mesh_name}_" + f"{os.path.basename(input_file)}") + + def run(self): + """ + Run this step of the test case + """ + logger = self.logger + config = self.config + section = config["ismip6_ais"] + period_endyear = section.get("period_endyear") + output_base_path = section.get("output_base_path") + model = section.get("model") + scenario = section.get("scenario") + res_ismip6 = section.get("res_ismip6") + mali_mesh_name = section.get("mali_mesh_name") + mali_mesh_file = section.get("mali_mesh_file") + + # we always want neareststod for the remapping method because we want + # a value between 0 and 1 per mesh point. + method_remap = "neareststod" + + # interpolate and rename the data + # ismip6 input files + input_file = self._files[period_endyear][model][scenario] \ + [res_ismip6][0] + + logger.info(f"!---Start processing the file---!") + logger.info(f"processing the input file " + f"'{os.path.basename(input_file)}'") + + # temporary file name. + remapped_file_temp = f"remapped.nc" + + # remap the input forcing file. + logger.info(f"Calling the remapping function...") + self.remap_ismip6_shelf_mask_to_mali_vars(os.path.basename(input_file), + remapped_file_temp, + mali_mesh_name, + mali_mesh_file, + method_remap) + + output_file = f"{mali_mesh_name}_{os.path.basename(input_file)}" + + # rename the ismip6 variables to MALI variables + logger.info(f"Renaming the ismip6 variables to " + f"mali variable names...") + self.rename_ismip6_shelf_mask_to_mali_vars(remapped_file_temp, + output_file) + + # round up/down the mask values to 1/0 + args = ["ncap2", "-O", "-s", + "where(calvingMask>=0.5) calvingMask=1", + output_file] + + check_call(args, logger=self.logger) + + args = ["ncap2", "-O", "-s", + "where(calvingMask<0.5) calvingMask=0", output_file] + check_call(args, logger=self.logger) + + logger.info(f"Remapping and renaming process done successfully. " + f"Removing the temporary files...") + + # remove the temporary combined file + os.remove(remapped_file_temp) + + # place the output file in appropriate directory + output_path = f"{output_base_path}/shelf_collapse/" \ + f"{model}_{scenario}/" + + if not os.path.exists(output_path): + logger.info("Creating a new directory for the output data...") + os.makedirs(output_path) + + src = os.path.join(os.getcwd(), output_file) + dst = os.path.join(output_path, output_file) + shutil.copy(src, dst) + + logger.info(f"!---Done processing the current file---!") + logger.info(f"") + logger.info(f"") + + def remap_ismip6_shelf_mask_to_mali_vars(self, input_file, output_file, + mali_mesh_name, mali_mesh_file, + method_remap): + """ + Remap the input ismip6 ice shelf-collapse mask data onto mali mesh + + Parameters + ---------- + input_file: str + data file on the ismip6 grid + + output_file : str + ismip6 data remapped onto mali mesh + + mali_mesh_name : str + name of the mali mesh used to name mapping files + + mali_mesh_file : str, optional + The MALI mesh file if mapping file does not exist + + method_remap : str, optional + Remapping method used in building a mapping file + """ + + # check if mapfile exists + mapping_file = f"map_ismip6_8km_to_{mali_mesh_name}_{method_remap}.nc" + + if not os.path.exists(mapping_file): + # build a mapping file if it doesn't already exist + build_mapping_file(self.config, self.ntasks, self.logger, + input_file, mapping_file, mali_mesh_file, + method_remap) + else: + self.logger.info(f"Mapping file exists. " + f"Remapping the input data...") + + # remap the input data + args = ["ncremap", + "-i", input_file, + "-o", output_file, + "-m", mapping_file] + + check_call(args, logger=self.logger) + + def rename_ismip6_shelf_mask_to_mali_vars(self, remapped_file_temp, + output_file): + """ + Rename variables in the remapped ismip6 input data + to the ones that MALI uses. + + Parameters + ---------- + remapped_file_temp : str + temporary ismip6 data remapped on mali mesh where data values are + rounded up/down + + output_file : str + remapped ismip6 data renamed on mali mesh + """ + + # open dataset in 20 years chunk + ds = xr.open_dataset(remapped_file_temp, chunks=dict(time=20)) + + # build dictionary and rename the ismip6 dimension and variables + ismip6_to_mali_dims = dict( + ncol="nCells", + time="Time") + ds = ds.rename(ismip6_to_mali_dims) + + ismip6_to_mali_vars = dict(mask="calvingMask") + ds = ds.rename(ismip6_to_mali_vars) + + # add xtime variable + xtime = [] + for t_index in range(ds.sizes["Time"]): + date = ds.Time[t_index] + # forcing files do not all match even years, so round up the years + # pandas round function does not work for years, so do it manually + yr = date.dt.year.values + mo = date.dt.month.values + dy = date.dt.day.values + dec_yr = np.around(yr + (30 * (mo - 1) + dy) / 365.0) + date = f"{dec_yr.astype(int)}-01-01_00:00:00".ljust(64) + xtime.append(date) + + ds["xtime"] = ("Time", xtime) + ds["xtime"] = ds.xtime.astype('S') + + ds["calvingMask"] = ds["calvingMask"].astype(int) + + # drop unnecessary variables on the regridded data on MALI mesh + ds = ds.drop_vars(["lat_vertices", "lon_vertices","lat", + "lon", "area"]) + + # write to a new netCDF file + write_netcdf(ds, output_file) + ds.close() + + # input files: input uniform melt rate coefficient (gamma0) + _files = { + #"2100": Will be added later + "2300": { + "CCSM4": { + "RCP85": { + "8km":[ + "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_08km.nc"], + "4km":[ + "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_04km.nc"] + }, + }, + "CESM2-WACCM": { + "SSP585": { + "8km":[ + "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_08km.nc"], + "4km":[ + "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_04km.nc"] + }, + }, + "HadGEM2-ES": { + "RCP85": { + "8km":[ + "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_08km.nc"], + "4km":[ + "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_04km.nc"] + }, + }, + "UKESM1-0-LL": { + "SSP585": { + "8km":[ + "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_08km.nc"], + "4km":[ + "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_04km.nc"] + } + } + } + } From 769061c7c20e0a0538e93f5b00c8cd2e25759dc4 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Mon, 11 Mar 2024 10:45:13 -0600 Subject: [PATCH 02/10] Apply changes from code review of Holly's `shelf_collapse` PR. - Rebased branch onto main - Fixed `ncap` bug caused by the `-O` flag. Switched to `-A` and combined the two `ncap` calls into one using `elsewhere` clause. - Added more information to the error message of when the model / scenario combinations are not valid for the shelf collpase exper. - Added a `data_res` config option to the `shelf_collapse` and `atmospheric` testcase configuration sections of the the config file. `data_res` was added at the testcase level (c.f. testgroup) b/c not all testcases provide data at multiple resoltion (e.g. `ocean_forcing`). --- .../ismip6_forcing/atmosphere/process_smb.py | 126 ++++++++--------- .../tests/ismip6_forcing/ismip6_forcing.cfg | 11 +- .../shelf_collapse/process_shelf_collapse.py | 127 ++++++++++-------- 3 files changed, 144 insertions(+), 120 deletions(-) diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py index b18a72f75a..37fd905852 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py @@ -1,11 +1,14 @@ import os -import numpy as np import shutil + +import numpy as np import xarray as xr -from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb \ - import build_mapping_file from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call + +from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb import ( # noqa: E501 + build_mapping_file, +) from compass.step import Step @@ -43,14 +46,16 @@ def setup(self): period_endyear = section.get("period_endyear") model = section.get("model") scenario = section.get("scenario") - res_ismip6 = section.get("res_ismip6") + # test case specific configurations + section = config["ismip6_ais_atmosphere"] + data_res = section.get("data_resolution") self.add_input_file(filename=mali_mesh_file, target=os.path.join(base_path_mali, mali_mesh_file)) input_file_list = \ - self._files[period_endyear][model][scenario][res_ismip6] + self._files[period_endyear][model][scenario][data_res] for file in input_file_list: self.add_input_file(filename=os.path.basename(file), @@ -84,11 +89,11 @@ def run(self): period_endyear = section.get("period_endyear") model = section.get("model") scenario = section.get("scenario") - res_ismip6 = section.get("res_ismip6") output_base_path = section.get("output_base_path") section = config["ismip6_ais_atmosphere"] method_remap = section.get("method_remap") + data_res = section.get("data_resolution") # define file names needed # input racmo climotology file @@ -103,7 +108,7 @@ def run(self): "but it is required as an input file " "to run this step. Please run `process_smb_racmo`" "step prior to running this step by setting" - "the config option 'process_smb_racmo' to 'True'.") + "the config option 'process_smb_racmo' to 'True'") # temporary remapped climatology and anomaly files clim_ismip6_temp = "clim_ismip6.nc" @@ -118,7 +123,7 @@ def run(self): # combine ismip6 forcing data covering different periods # into a single file input_file_list = \ - self._files[period_endyear][model][scenario][res_ismip6] + self._files[period_endyear][model][scenario][data_res] i = 0 for file in input_file_list: @@ -138,7 +143,7 @@ def run(self): # smb anomaly files on which climatology is calculated. logger.info(f"Calculating climatology for {model}_{scenario} forcing" f"over 1995-2017") - args = [f"ncra", "-O", "-F", "-d", "time,1,23", + args = ["ncra", "-O", "-F", "-d", "time,1,23", f"{combined_file_temp}", f"{clim_ismip6_temp}"] @@ -198,7 +203,7 @@ def run(self): dst = os.path.join(output_path, output_anomaly_ismip6) shutil.copy(src, dst) - logger.info(f"!---Done processing the file---!") + logger.info("!---Done processing the file---!") def remap_ismip6_smb_to_mali(self, input_file, output_file, mali_mesh_name, mali_mesh_file, method_remap): @@ -330,147 +335,148 @@ def correct_smb_anomaly_for_climatology(self, ds["sfcMassBal"] = ds["sfcMassBal"] + corr_clim # write metadata - ds["sfcMassBal"].attrs = {"long_name" : "surface mass balance", - "units" : "kg m-2 s-1", - "coordinates" : "lat lon"} + ds["sfcMassBal"].attrs = {"long_name": "surface mass balance", + "units": "kg m-2 s-1", + "coordinates": "lat lon"} # write to a new netCDF file write_netcdf(ds, output_file_final) ds.close() - # create a nested dictionary for the ISMIP6 original forcing files including relative path - # Note: these files needed to be explicitly listed because of inconsistencies that are - # present in file naming conventions in the ISMIP6 source dataset. + # create a nested dictionary for the ISMIP6 original forcing files + # including relative path. Note: these files needed to be explicitly + # listed because of inconsistencies that are present in file naming + # conventions in the ISMIP6 source dataset. _files = { "2100": { "CCSM4": { "RCP85": [ - "AIS/Atmosphere_Forcing/ccsm4_rcp8.5/Regridded_8km/CCSM4_8km_anomaly_1995-2100.nc"] + "AIS/Atmosphere_Forcing/ccsm4_rcp8.5/Regridded_8km/CCSM4_8km_anomaly_1995-2100.nc"] # noqa: E501 }, "CESM2": { "SSP585v1": [ - "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v1.nc"], + "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v1.nc"], # noqa: E501 "SSP585v2": [ - "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v2.nc"] + "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v2.nc"] # noqa: E501 }, "CNRM_CM6": { "SSP126": [ - "AIS/Atmosphere_Forcing/CNRM_CM6_ssp126/Regridded_8km/CNRM-CM6-1_anomaly_ssp126_1995-2100_8km_ISMIP6.nc"], + "AIS/Atmosphere_Forcing/CNRM_CM6_ssp126/Regridded_8km/CNRM-CM6-1_anomaly_ssp126_1995-2100_8km_ISMIP6.nc"], # noqa: E501 "SSP585": [ - "AIS/Atmosphere_Forcing/CNRM_CM6_ssp585/Regridded_8km/CNRM-CM6-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] + "AIS/Atmosphere_Forcing/CNRM_CM6_ssp585/Regridded_8km/CNRM-CM6-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 }, "CNRM_ESM2": { "SSP585": [ - "AIS/Atmosphere_Forcing/CNRM_ESM2_ssp585/Regridded_8km/CNRM-ESM2-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] + "AIS/Atmosphere_Forcing/CNRM_ESM2_ssp585/Regridded_8km/CNRM-ESM2-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 }, "CSIRO-Mk3-6-0": { "RCP85": [ - "AIS/Atmosphere_Forcing/CSIRO-Mk3-6-0_rcp85/Regridded_8km/CSIRO-Mk3-6-0_8km_anomaly_rcp85_1995-2100.nc"] + "AIS/Atmosphere_Forcing/CSIRO-Mk3-6-0_rcp85/Regridded_8km/CSIRO-Mk3-6-0_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 }, "HadGEM2-ES": { "RCP85": [ - "AIS/Atmosphere_Forcing/HadGEM2-ES_rcp85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc"] + "AIS/Atmosphere_Forcing/HadGEM2-ES_rcp85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 }, "IPSL-CM5A-MR": { "RCP26": [ - "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp26/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp26_1995-2100.nc"], + "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp26/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 "RCP85": [ - "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp85/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp85_1995-2100.nc"] + "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp85/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 }, "MIROC-ESM-CHEM": { "RCP85": [ - "AIS/Atmosphere_Forcing/miroc-esm-chem_rcp8.5/Regridded_8km/MIROC-ESM-CHEM_8km_anomaly_1995-2100.nc"] + "AIS/Atmosphere_Forcing/miroc-esm-chem_rcp8.5/Regridded_8km/MIROC-ESM-CHEM_8km_anomaly_1995-2100.nc"] # noqa: E501 }, "NorESM1-M": { "RCP26": [ - "AIS/Atmosphere_Forcing/noresm1-m_rcp2.6/Regridded_8km/NorESM-M_8km_anomaly_rcp26_1995-2100.nc"], + "AIS/Atmosphere_Forcing/noresm1-m_rcp2.6/Regridded_8km/NorESM-M_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 "RCP85": [ - "AIS/Atmosphere_Forcing/noresm1-m_rcp8.5/Regridded_8km/NorESM-M_8km_anomaly_1995-2100.nc"] + "AIS/Atmosphere_Forcing/noresm1-m_rcp8.5/Regridded_8km/NorESM-M_8km_anomaly_1995-2100.nc"] # noqa: E501 }, "UKESM1-0-LL": { "SSP585": [ - "AIS/Atmosphere_Forcing/UKESM1-0-LL/Regridded_8km/UKESM1-0-LL_anomaly_ssp585_1995-2100_8km.nc"] + "AIS/Atmosphere_Forcing/UKESM1-0-LL/Regridded_8km/UKESM1-0-LL_anomaly_ssp585_1995-2100_8km.nc"] # noqa: E501 } }, "2300": { "CCSM4": { "RCP85": { "4km": [ - "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_04km/CCSM4_4km_anomaly_1995-2100.nc", - "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_04km/CCSM4_4km_anomaly_2101-2300.nc"], + "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_04km/CCSM4_4km_anomaly_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_04km/CCSM4_4km_anomaly_2101-2300.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_08km/CCSM4_8km_anomaly_1995-2100.nc", - "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_08km/CCSM4_8km_anomaly_2101-2300.nc"] + "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_08km/CCSM4_8km_anomaly_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/CCSM4_RCP85/Regridded_08km/CCSM4_8km_anomaly_2101-2300.nc"] # noqa: E501 }, }, "CESM2-WACCM": { "SSP585": { "4km": [ - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_1995-2100.nc", - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_2101-2299.nc"], + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_2101-2299.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_1995-2100.nc", - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_2101-2299.nc"] + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_2101-2299.nc"] # noqa: E501 }, "SSP585-repeat": { "4km": [ - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585-repeat/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_1995-2300_v2.nc"], + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585-repeat/Regridded_4km/CESM2-WACCM_4km_anomaly_ssp585_1995-2300_v2.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585-repeat/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_1995-2300_v2.nc"] + "AIS/Atmospheric_forcing/CESM2-WACCM_ssp585-repeat/Regridded_8km/CESM2-WACCM_8km_anomaly_ssp585_1995-2300_v2.nc"] # noqa: E501 }, }, "HadGEM2-ES": { "RCP85": { "4km": [ - "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_1995-2100.nc", - "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_2101-2299.nc"], + "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_2101-2299.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc", - "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_2101-2299.nc"] + "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/HadGEM2-ES_RCP85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_2101-2299.nc"] # noqa: E501 }, "RCP85-repeat": { "4km": [ - "AIS/Atmospheric_forcing/HadGEM2-ES-RCP85-repeat/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_1995-2300_v2.nc"], + "AIS/Atmospheric_forcing/HadGEM2-ES-RCP85-repeat/Regridded_4km/HadGEM2-ES_4km_anomaly_rcp85_1995-2300_v2.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/HadGEM2-ES-RCP85-repeat/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2300_v2.nc"] + "AIS/Atmospheric_forcing/HadGEM2-ES-RCP85-repeat/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2300_v2.nc"] # noqa: E501 }, }, "NorESM1-M": { "RCP26-repeat": { "4km": [ - "AIS/Atmospheric_forcing/NorESM1-M_RCP26-repeat/Regridded_4km/NorESM1-M_4km_anomaly_rcp26_1995-2300_v3.nc"], + "AIS/Atmospheric_forcing/NorESM1-M_RCP26-repeat/Regridded_4km/NorESM1-M_4km_anomaly_rcp26_1995-2300_v3.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/NorESM1-M_RCP26-repeat/Regridded_8km/NorESM1-M_8km_anomaly_rcp26_1995-2300_v2.nc"] + "AIS/Atmospheric_forcing/NorESM1-M_RCP26-repeat/Regridded_8km/NorESM1-M_8km_anomaly_rcp26_1995-2300_v2.nc"] # noqa: E501 }, "RCP85-repeat": { "4km": [ - "AIS/Atmospheric_forcing/NorESM1-M_RCP85-repeat/Regridded_4km/NorESM1-M_4km_anomaly_1995-2300_v2.nc"], + "AIS/Atmospheric_forcing/NorESM1-M_RCP85-repeat/Regridded_4km/NorESM1-M_4km_anomaly_1995-2300_v2.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/NorESM1-M_RCP85-repeat/Regridded_8km/NorESM1-M_8km_anomaly_1995-2300_v2.nc"] + "AIS/Atmospheric_forcing/NorESM1-M_RCP85-repeat/Regridded_8km/NorESM1-M_8km_anomaly_1995-2300_v2.nc"] # noqa: E501 }, }, "UKESM1-0-LL": { "SSP126": { "4km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_4km/UKESM1-0-LL_4km_anomaly_ssp126_1995-2100.nc", - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_4km/UKESM1-0-LL_4km_anomaly_ssp126_2101-2300.nc"], + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_4km/UKESM1-0-LL_4km_anomaly_ssp126_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_4km/UKESM1-0-LL_4km_anomaly_ssp126_2101-2300.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_8km/UKESM1-0-LL_8km_anomaly_ssp126_1995-2100.nc", - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_8km/UKESM1-0-LL_8km_anomaly_ssp126_2101-2300.nc"] + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_8km/UKESM1-0-LL_8km_anomaly_ssp126_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp126/Regridded_8km/UKESM1-0-LL_8km_anomaly_ssp126_2101-2300.nc"] # noqa: E501 }, "SSP585": { "4km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_1995-2100.nc", - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_2101-2300.nc"], + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_2101-2300.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_1995-2100.nc", - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_2101-2300.nc"] + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_1995-2100.nc", # noqa: E501 + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_2101-2300.nc"] # noqa: E501 }, "SSP585-repeat": { "4km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585-repeat/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_1995-2300_v2.nc"], + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585-repeat/Regridding_4km/UKESM1-0-LL_4km_anomaly_ssp585_1995-2300_v2.nc"], # noqa: E501 "8km": [ - "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585-repeat/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_1995-2300_v2.nc"] + "AIS/Atmospheric_forcing/UKESM1-0-LL_ssp585-repeat/Regridding_8km/UKESM1-0-LL_8km_anomaly_ssp585_1995-2300_v2.nc"] # noqa: E501 } } } diff --git a/compass/landice/tests/ismip6_forcing/ismip6_forcing.cfg b/compass/landice/tests/ismip6_forcing/ismip6_forcing.cfg index 6b88abcd80..24437f2b39 100644 --- a/compass/landice/tests/ismip6_forcing/ismip6_forcing.cfg +++ b/compass/landice/tests/ismip6_forcing/ismip6_forcing.cfg @@ -29,13 +29,22 @@ mali_mesh_name = NotAvailable # MALI mesh file to be used to build mapping file (e.g.Antarctic_8to80km_20220407.nc). User has to supply. mali_mesh_file = NotAvailable +# config options for ismip6 antarctic ice shelf collpase forcing test cases +[ismip6_ais_shelf_collapse] + +# resolution of CMIP6 model data to be used +data_resolution = 8km + # config options for ismip6 antarctic ice sheet SMB forcing data test cases [ismip6_ais_atmosphere] +# resolution of CMIP6 model data to be used +data_resolution = 8km + # Remapping method used in building a mapping file. Options include: bilinear, neareststod, conserve method_remap = bilinear -# Set True to process RACMO modern climatology +# Set True to process RACMO modern climatology process_smb_racmo = True # config options for ismip6 ocean thermal forcing data test cases diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py index 8ce1b214e2..ae719cd524 100644 --- a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py @@ -1,11 +1,14 @@ import os import shutil + import numpy as np import xarray as xr -from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb \ - import build_mapping_file from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call + +from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb import ( # noqa: E501 + build_mapping_file, +) from compass.step import Step @@ -39,21 +42,30 @@ def setup(self): period_endyear = section.get("period_endyear") model = section.get("model") scenario = section.get("scenario") - res_ismip6 = section.get("res_ismip6") mali_mesh_name = section.get("mali_mesh_name") mali_mesh_file = section.get("mali_mesh_file") + # test case specific configurations + section = config["ismip6_ais_shelf_collapse"] + data_res = section.get("data_resolution") + + # create string for requested configuration (i.e. model / scenario) + model_option = "-".join([model, scenario]) + model_options = ["CCSM4-RCP85", "CESM2-WACCM-SSP585", + "HadGEM2-ES-RCP85", "UKESM1-0-LL-SSP585"] + # this is string comparison b/c `period_endyear` is a dictionary key + if period_endyear != "2300": + raise ValueError("ice shelf-collapse masks are only supported " + "for 2300 projections.") + elif model_option not in model_options: + raise ValueError("Requested model-scenario combination " + f"{ {model_option} } is not supported. " + f"ice shelf-collapse masks are provided by " + f"ISMIP6 for {*model_options,} model-scenario " + f"only. Please specify a valid model-scenario " + f"combination in the config file") + + input_file = self._files[period_endyear][model][scenario][data_res] - model_list_2300 = ["CCSM4", "CESM2-WACCM","HadGEM2-ES","UKESM1-0-LL"] - if period_endyear != 2300 and model not in model_list_2300: - raise ValueError(f"ice shelf-collapse masks are provided by the " - f"ISMIP6 only for CCSM4-RCP85, " - f"CESM2-WACCM-SSP585,HadGEM2-ES-RCP85, " - f"UKESM1-0-LL-SSP585 model&scenarios. Please " - f"set up the correct config options in " - f"the config file.") - - input_file = self._files[period_endyear][model][scenario] \ - [res_ismip6][0] self.add_input_file(filename=mali_mesh_file, target=os.path.join(base_path_mali, mali_mesh_file)) @@ -74,28 +86,28 @@ def run(self): output_base_path = section.get("output_base_path") model = section.get("model") scenario = section.get("scenario") - res_ismip6 = section.get("res_ismip6") mali_mesh_name = section.get("mali_mesh_name") mali_mesh_file = section.get("mali_mesh_file") + # test case specific configurations + section = config["ismip6_ais_shelf_collapse"] + data_res = section.get("data_resolution") # we always want neareststod for the remapping method because we want - # a value between 0 and 1 per mesh point. + # a value of 0 or 1 per mesh point. method_remap = "neareststod" # interpolate and rename the data # ismip6 input files - input_file = self._files[period_endyear][model][scenario] \ - [res_ismip6][0] - - logger.info(f"!---Start processing the file---!") - logger.info(f"processing the input file " + input_file = self._files[period_endyear][model][scenario][data_res] + logger.info("!---Start processing the file---!") + logger.info("processing the input file " f"'{os.path.basename(input_file)}'") # temporary file name. - remapped_file_temp = f"remapped.nc" + remapped_file_temp = "remapped.nc" # remap the input forcing file. - logger.info(f"Calling the remapping function...") + logger.info("Calling the remapping function...") self.remap_ismip6_shelf_mask_to_mali_vars(os.path.basename(input_file), remapped_file_temp, mali_mesh_name, @@ -105,31 +117,28 @@ def run(self): output_file = f"{mali_mesh_name}_{os.path.basename(input_file)}" # rename the ismip6 variables to MALI variables - logger.info(f"Renaming the ismip6 variables to " - f"mali variable names...") + logger.info("Renaming the ismip6 variables to " + "mali variable names...") self.rename_ismip6_shelf_mask_to_mali_vars(remapped_file_temp, output_file) - # round up/down the mask values to 1/0 - args = ["ncap2", "-O", "-s", - "where(calvingMask>=0.5) calvingMask=1", + # round up/down the mask values to 1/0. String addition + # (c.f. continuation) is needed for the nco inline script to work + args = ["ncap2", "-A", "-s", + "where(calvingMask>=0.5){calvingMask=1;} " + + "elsewhere{calvingMask=0;}", output_file] check_call(args, logger=self.logger) - args = ["ncap2", "-O", "-s", - "where(calvingMask<0.5) calvingMask=0", output_file] - check_call(args, logger=self.logger) - - logger.info(f"Remapping and renaming process done successfully. " - f"Removing the temporary files...") + logger.info("Remapping and renaming process done successfully. " + "Removing the temporary files...") # remove the temporary combined file os.remove(remapped_file_temp) # place the output file in appropriate directory - output_path = f"{output_base_path}/shelf_collapse/" \ - f"{model}_{scenario}/" + output_path = f"{output_base_path}/shelf_collapse/{model}_{scenario}/" if not os.path.exists(output_path): logger.info("Creating a new directory for the output data...") @@ -139,9 +148,9 @@ def run(self): dst = os.path.join(output_path, output_file) shutil.copy(src, dst) - logger.info(f"!---Done processing the current file---!") - logger.info(f"") - logger.info(f"") + logger.info("!---Done processing the current file---!") + logger.info("") + logger.info("") def remap_ismip6_shelf_mask_to_mali_vars(self, input_file, output_file, mali_mesh_name, mali_mesh_file, @@ -176,8 +185,8 @@ def remap_ismip6_shelf_mask_to_mali_vars(self, input_file, output_file, input_file, mapping_file, mali_mesh_file, method_remap) else: - self.logger.info(f"Mapping file exists. " - f"Remapping the input data...") + self.logger.info("Mapping file exists. " + "Remapping the input data...") # remap the input data args = ["ncremap", @@ -234,7 +243,7 @@ def rename_ismip6_shelf_mask_to_mali_vars(self, remapped_file_temp, ds["calvingMask"] = ds["calvingMask"].astype(int) # drop unnecessary variables on the regridded data on MALI mesh - ds = ds.drop_vars(["lat_vertices", "lon_vertices","lat", + ds = ds.drop_vars(["lat_vertices", "lon_vertices", "lat", "lon", "area"]) # write to a new netCDF file @@ -243,38 +252,38 @@ def rename_ismip6_shelf_mask_to_mali_vars(self, remapped_file_temp, # input files: input uniform melt rate coefficient (gamma0) _files = { - #"2100": Will be added later + # "2100": Not supported for shelf collapse experiments "2300": { "CCSM4": { "RCP85": { - "8km":[ - "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_08km.nc"], - "4km":[ - "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_04km.nc"] + "8km": + "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_08km.nc", # noqa: E501 + "4km": + "AIS/ShelfCollapse_forcing/CCSM4_RCP85/ice_shelf_collapse_mask_CCSM4_RCP85_1995-2300_04km.nc", # noqa: E501 }, }, "CESM2-WACCM": { "SSP585": { - "8km":[ - "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_08km.nc"], - "4km":[ - "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_04km.nc"] + "8km": + "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_08km.nc", # noqa: E501 + "4km": + "AIS/ShelfCollapse_forcing/CESM2_WACCM_ssp585/ice_shelf_collapse_mask_CESM2_WACCM_ssp585_1995-2300_04km.nc", # noqa: E501 }, }, "HadGEM2-ES": { "RCP85": { - "8km":[ - "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_08km.nc"], - "4km":[ - "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_04km.nc"] + "8km": + "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_08km.nc", # noqa: E501 + "4km": + "AIS/ShelfCollapse_forcing/HadGEM2-ES_RCP85/ice_shelf_collapse_mask_HadGEM2-ES_RCP85_1995-2300_04km.nc", # noqa: E501 }, }, "UKESM1-0-LL": { "SSP585": { - "8km":[ - "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_08km.nc"], - "4km":[ - "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_04km.nc"] + "8km": + "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_08km.nc", # noqa: E501 + "4km": + "AIS/ShelfCollapse_forcing/UKESM1-0-LL_ssp585/ice_shelf_collapse_mask_UKESM1-0-LL_ssp585_1995-2300_04km.nc", # noqa: E501 } } } From 4040623ad9a64091bb6b031677d95891e7114357 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Wed, 20 Mar 2024 17:26:51 -0600 Subject: [PATCH 03/10] Unify the `build_mapping_file` functions implemented in multiple places. - created a single `build_mapping_file` function at the testcase level which takes a `scrip_from_latlon` parameter that determines what coordinate varibale should be used to generate the scrip file from interpolarion. - suppressed flake8 formatting error related to lines over 80 charac. --- .../atmosphere/create_mapfile_smb.py | 204 ------------------ .../ismip6_forcing/atmosphere/process_smb.py | 8 +- .../atmosphere/process_smb_racmo.py | 21 +- .../tests/ismip6_forcing/create_mapfile.py | 146 ++++++++++--- .../ocean_basal/process_basal_melt.py | 91 ++++---- .../ocean_thermal/process_thermal_forcing.py | 6 +- .../shelf_collapse/process_shelf_collapse.py | 8 +- 7 files changed, 193 insertions(+), 291 deletions(-) delete mode 100644 compass/landice/tests/ismip6_forcing/atmosphere/create_mapfile_smb.py diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/create_mapfile_smb.py b/compass/landice/tests/ismip6_forcing/atmosphere/create_mapfile_smb.py deleted file mode 100644 index f17c39329a..0000000000 --- a/compass/landice/tests/ismip6_forcing/atmosphere/create_mapfile_smb.py +++ /dev/null @@ -1,204 +0,0 @@ -import os -import numpy as np -import shutil -import netCDF4 -import xarray as xr -from mpas_tools.scrip.from_mpas import scrip_from_mpas -from mpas_tools.logging import check_call -from pyremap.descriptor import interp_extrap_corners_2d - - -def build_mapping_file(config, cores, logger, ismip6_grid_file, - mapping_file, mali_mesh_file=None, method_remap=None): - """ - Build a mapping file if it does not exist. - Mapping file is then used to remap the ismip6 source file in polarstero - coordinate to unstructured mali mesh - - Parameters - ---------- - config : compass.config.CompassConfigParser - Configuration options for a ismip6 forcing test case - - cores : int - the number of cores for the ESMF_RegridWeightGen - - logger : logging.Logger - A logger for output from the step - - ismip6_grid_file : str - ismip6 grid file - - mapping_file : str - weights for interpolation from ismip6_grid_file to mali_mesh_file - - mali_mesh_file : str, optional - The MALI mesh file is used if mapping file does not exist - - method_remap : str, optional - Remapping method used in building a mapping file - """ - - if os.path.exists(mapping_file): - logger.info(f"Mapping file exists. Not building a new one.") - return - - # create the scrip files if mapping file does not exist - logger.info(f"Mapping file does not exist. Building one based on the" - f" input/output meshes") - logger.info(f"Creating temporary scrip files for source and " - f"destination grids...") - - if mali_mesh_file is None: - raise ValueError("Mapping file does not exist. To build one, Mali " - "mesh file with '-f' should be provided. " - "Type --help for info") - - # name temporary scrip files that will be used to build mapping file - source_grid_scripfile = "temp_source_scrip.nc" - mali_scripfile = "temp_mali_scrip.nc" - # this is the projection of ismip6 data for Antarctica - ismip6_projection = "ais-bedmap2" - - # create a scripfile for the atmosphere forcing data - create_atm_scrip(ismip6_grid_file, source_grid_scripfile) - - # create a MALI mesh scripfile - # make sure the mali mesh file uses the longitude convention of [0 2pi] - # make changes on a duplicated file to avoid making changes to the - # original mesh file - - mali_mesh_copy = f"{mali_mesh_file}_copy" - shutil.copy(mali_mesh_file, f"{mali_mesh_file}_copy") - - args = ["set_lat_lon_fields_in_planar_grid.py", - "--file", mali_mesh_copy, - "--proj", ismip6_projection] - - check_call(args, logger=logger) - - # create a MALI mesh scripfile if mapping file does not exist - scrip_from_mpas(mali_mesh_copy, mali_scripfile) - - # create a mapping file using ESMF weight gen - print(f"Creating a mapping file. Mapping method used: {method_remap}") - - if method_remap is None: - raise ValueError("Desired remapping option should be provided with " - "--method. Available options are 'bilinear'," - "'neareststod', 'conserve'.") - - parallel_executable = config.get('parallel', 'parallel_executable') - # split the parallel executable into constituents in case it includes flags - args = parallel_executable.split(' ') - args.extend(["-n", f"{cores}", - "ESMF_RegridWeightGen", - "-s", source_grid_scripfile, - "-d", mali_scripfile, - "-w", mapping_file, - "-m", method_remap, - "-i", "-64bit_offset", - "--dst_regional", "--src_regional"]) - - check_call(args, logger=logger) - - # remove the temporary scripfiles once the mapping file is generated - logger.info(f"Removing the temporary mesh and scripfiles...") - os.remove(source_grid_scripfile) - os.remove(mali_scripfile) - os.remove(mali_mesh_copy) - - -def create_atm_scrip(source_grid_file, source_grid_scripfile): - """ - create a scripfile for the ismip6 atmospheric forcing data. - Note: the atmospheric forcing data do not have 'x' and 'y' coordinates and - only have dimensions of them. This function uses 'lat' and 'lon' - coordinates to generate a scripfile. - - Parameters - ---------- - source_grid_file : str - input smb grid file - - source_grid_scripfile : str - output scrip file of the input smb data - """ - - ds = xr.open_dataset(source_grid_file) - out_file = netCDF4.Dataset(source_grid_scripfile, 'w') - - if "rlon" in ds and "rlat" in ds: # this is for RACMO's rotated-pole grid - nx = ds.sizes["rlon"] - ny = ds.sizes["rlat"] - else: - nx = ds.sizes["x"] - ny = ds.sizes["y"] - units = "degrees" - - grid_size = nx * ny - - out_file.createDimension("grid_size", grid_size) - out_file.createDimension("grid_corners", 4) - out_file.createDimension("grid_rank", 2) - - # Variables - grid_center_lat = out_file.createVariable("grid_center_lat", "f8", - ("grid_size",)) - grid_center_lat.units = units - grid_center_lon = out_file.createVariable("grid_center_lon", "f8", - ("grid_size",)) - grid_center_lon.units = units - grid_corner_lat = out_file.createVariable("grid_corner_lat", "f8", - ("grid_size", "grid_corners")) - grid_corner_lat.units = units - grid_corner_lon = out_file.createVariable("grid_corner_lon", "f8", - ("grid_size", "grid_corners")) - grid_corner_lon.units = units - grid_imask = out_file.createVariable("grid_imask", "i4", ("grid_size",)) - grid_imask.units = "unitless" - out_file.createVariable("grid_dims", "i4", ("grid_rank",)) - - out_file.variables["grid_center_lat"][:] = ds.lat.values.flat - out_file.variables["grid_center_lon"][:] = ds.lon.values.flat - out_file.variables["grid_dims"][:] = [nx, ny] - out_file.variables["grid_imask"][:] = 1 - - if "lat_bnds" in ds and "lon_bnds" in ds: - lat_corner = ds.lat_bnds - if "time" in lat_corner.dims: - lat_corner = lat_corner.isel(time=0) - - lon_corner = ds.lon_bnds - if "time" in lon_corner.dims: - lon_corner = lon_corner.isel(time=0) - - lat_corner = lat_corner.values - lon_corner = lon_corner.values - else: - # this part is used for RACMO as it does not have lat_bnds & lon_bnds - lat_corner = _unwrap_corners(interp_extrap_corners_2d(ds.lat.values)) - lon_corner = _unwrap_corners(interp_extrap_corners_2d(ds.lon.values)) - - grid_corner_lat = lat_corner.reshape((grid_size, 4)) - grid_corner_lon = lon_corner.reshape((grid_size, 4)) - - out_file.variables["grid_corner_lat"][:] = grid_corner_lat - out_file.variables["grid_corner_lon"][:] = grid_corner_lon - - out_file.close() - - -def _unwrap_corners(in_field): - """ - Turn a 2D array of corners into an array of rectangular mesh elements - """ - out_field = np.zeros(((in_field.shape[0] - 1) * - (in_field.shape[1] - 1), 4)) - # corners are counterclockwise - out_field[:, 0] = in_field[0:-1, 0:-1].flat - out_field[:, 1] = in_field[0:-1, 1:].flat - out_field[:, 2] = in_field[1:, 1:].flat - out_field[:, 3] = in_field[1:, 0:-1].flat - - return out_field diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py index 37fd905852..d37287684a 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py @@ -6,7 +6,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb import ( # noqa: E501 +from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( build_mapping_file, ) from compass.step import Step @@ -236,8 +236,10 @@ def remap_ismip6_smb_to_mali(self, input_file, output_file, mali_mesh_name, self.logger.info(f"Creating a mapping file. " f"Mapping method used: {method_remap}") build_mapping_file(self.config, self.ntasks, self.logger, - input_file, mapping_file, mali_mesh_file, - method_remap) + input_file, mapping_file, + scrip_from_latlon=True, + mali_mesh_file=mali_mesh_file, + method_remap=method_remap) else: self.logger.info("Mapping file exists. " "Remapping the input data...") diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py index 5ffdd35da7..5206db0807 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py @@ -1,11 +1,14 @@ import os import shutil + import xarray as xr from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call + +from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( + build_mapping_file, +) from compass.step import Step -from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb \ - import build_mapping_file class ProcessSmbRacmo(Step): @@ -54,9 +57,9 @@ def setup(self): f"_smb_climatology_1995-2017.nc" self.add_output_file(filename=output_file) else: - print(f"\n'Warning: process_smb_racmo' is set to 'False'." - f" This step will not run unless set 'True' in the" - f" config file.\n") + print("\n'Warning: process_smb_racmo' is set to 'False'." + " This step will not run unless set 'True' in the" + " config file.\n") def run(self): """ @@ -152,7 +155,7 @@ def run(self): dst = os.path.join(output_path, output_file) shutil.copy(src, dst) - logger.info(f"!---Done processing the file---!") + logger.info("!---Done processing the file---!") def remap_source_smb_to_mali(self, input_file, output_file, mali_mesh_name, mali_mesh_file, method_remap): @@ -185,8 +188,10 @@ def remap_source_smb_to_mali(self, input_file, output_file, mali_mesh_name, self.logger.info(f"Creating a mapping file. " f"Mapping method used: {method_remap}") build_mapping_file(self.config, self.ntasks, self.logger, - input_file, mapping_file, mali_mesh_file, - method_remap) + input_file, mapping_file, + scrip_from_latlon=True, + mali_mesh_file=mali_mesh_file, + method_remap=method_remap) else: self.logger.info("Mapping file exists. " "Remapping the input data...") diff --git a/compass/landice/tests/ismip6_forcing/create_mapfile.py b/compass/landice/tests/ismip6_forcing/create_mapfile.py index 954f5a719b..2f96266866 100644 --- a/compass/landice/tests/ismip6_forcing/create_mapfile.py +++ b/compass/landice/tests/ismip6_forcing/create_mapfile.py @@ -1,12 +1,19 @@ import os import shutil -import subprocess -from mpas_tools.scrip.from_mpas import scrip_from_mpas + +import netCDF4 +import xarray as xr from mpas_tools.logging import check_call +from mpas_tools.scrip.from_mpas import scrip_from_mpas +from pyremap.descriptor.utility import ( + create_scrip, + interp_extrap_corners_2d, + unwrap_corners, +) -def build_mapping_file(config, cores, logger, ismip6_grid_file, - mapping_file, mali_mesh_file=None, +def build_mapping_file(config, cores, logger, ismip6_grid_file, mapping_file, + scrip_from_latlon=True, mali_mesh_file=None, method_remap=None): """ Build a mapping file if it does not exist. @@ -30,42 +37,51 @@ def build_mapping_file(config, cores, logger, ismip6_grid_file, mapping_file : str weights for interpolation from ismip6_grid_file to mali_mesh_file + scrip_from_latlon : bool, optional + whether to use the `lat`/`lon` coordinates to create the SCRIP file + for the `ismip6_grid_file` passed into the function + mali_mesh_file : str, optional - The MALI mesh file if mapping file does not exist + The MALI mesh file is used if mapping file does not exist method_remap : str, optional Remapping method used in building a mapping file """ if os.path.exists(mapping_file): - logger.info(f"Mapping file exists. Not building a new one.") + logger.info("Mapping file exists. Not building a new one.") return + # create the scrip files if mapping file does not exist + logger.info("Mapping file does not exist. Building one based on the" + " input/output meshes") + logger.info("Creating temporary scrip files for source and " + "destination grids...") + if mali_mesh_file is None: raise ValueError("Mapping file does not exist. To build one, Mali " "mesh file with '-f' should be provided. " "Type --help for info") - ismip6_scripfile = "temp_ismip6_8km_scrip.nc" + # name temporary scrip files that will be used to build mapping file + source_grid_scripfile = "temp_source_scrip.nc" mali_scripfile = "temp_mali_scrip.nc" - ismip6_projection = "ais-bedmap2" - - # create the ismip6 scripfile if mapping file does not exist # this is the projection of ismip6 data for Antarctica - logger.info(f"Mapping file does not exist. Building one based on " - f"the input/ouptut meshes") - logger.info(f"Creating temporary scripfiles " - f"for ismip6 grid and mali mesh...") + ismip6_projection = "ais-bedmap2" - args = ["create_SCRIP_file_from_planar_rectangular_grid.py", - "--input", ismip6_grid_file, - "--scrip", ismip6_scripfile, - "--proj", ismip6_projection, - "--rank", "2"] + # create the scrip file for the forcing dataset + if scrip_from_latlon: + create_scrip_from_latlon(ismip6_grid_file, source_grid_scripfile) + else: + args = ["create_SCRIP_file_from_planar_rectangular_grid.py", + "--input", ismip6_grid_file, + "--scrip", source_grid_scripfile, + "--proj", ismip6_projection, + "--rank", "2"] - check_call(args, logger=logger) + check_call(args, logger=logger) - # create a MALI mesh scripfile if mapping file does not exist + # create a MALI mesh scripfile # make sure the mali mesh file uses the longitude convention of [0 2pi] # make changes on a duplicated file to avoid making changes to the # original mesh file @@ -79,33 +95,105 @@ def build_mapping_file(config, cores, logger, ismip6_grid_file, check_call(args, logger=logger) - scrip_from_mpas(mali_mesh_file, mali_scripfile) + # create a MALI mesh scripfile if mapping file does not exist + scrip_from_mpas(mali_mesh_copy, mali_scripfile) # create a mapping file using ESMF weight gen - logger.info(f"Creating a mapping file... " - f"Mapping method used: {method_remap}") + print(f"Creating a mapping file. Mapping method used: {method_remap}") if method_remap is None: raise ValueError("Desired remapping option should be provided with " "--method. Available options are 'bilinear'," "'neareststod', 'conserve'.") - parallel_executable = config.get("parallel", "parallel_executable") + parallel_executable = config.get('parallel', 'parallel_executable') # split the parallel executable into constituents in case it includes flags args = parallel_executable.split(' ') args.extend(["-n", f"{cores}", "ESMF_RegridWeightGen", - "-s", ismip6_scripfile, + "-s", source_grid_scripfile, "-d", mali_scripfile, "-w", mapping_file, "-m", method_remap, "-i", "-64bit_offset", "--dst_regional", "--src_regional"]) - check_call(args, logger) + check_call(args, logger=logger) # remove the temporary scripfiles once the mapping file is generated - logger.info(f"Removing the temporary mesh and scripfiles...") - os.remove(ismip6_scripfile) + logger.info("Removing the temporary mesh and scripfiles...") + os.remove(source_grid_scripfile) os.remove(mali_scripfile) os.remove(mali_mesh_copy) + + +def create_scrip_from_latlon(source_grid_file, source_grid_scripfile): + """ + Create a scripfile based on the `lat`/`lon` coordinates of a source + dataset. + + This function is needed, c.f. the scrip utility in the MPAS-Tools repo + (i.e. `create_SCRIP_file_from_planar_rectangular_grid.py`), when a dataset + does not have `x`/`y` coordinates to generate the scrip file from. This is + the case for the atmospheric forcing datasets from ISMIP6 + and for RACMO products. + + Parameters + ---------- + source_grid_file : str + input dataset (with `lat`/`lon` coords) to generate a scrip file for + + source_grid_scripfile : str + output scrip file of the input smb data + """ + + ds = xr.open_dataset(source_grid_file) + out_file = netCDF4.Dataset(source_grid_scripfile, 'w') + + # RACMO datasets, which use a rotated-pole grid, do not contain `x`/`y` + # dimensions, instead use `rlat`/`rlon` dimensions to find `nx`/`ny` + if "rlon" in ds and "rlat" in ds: + nx = ds.sizes["rlon"] + ny = ds.sizes["rlat"] + else: + nx = ds.sizes["x"] + ny = ds.sizes["y"] + + grid_size = nx * ny + + # generate common variables used in scrip files + create_scrip(out_file, grid_size, grid_corners=4, grid_rank=2, + units="degrees", meshName=source_grid_file) + + # place the information from our source dataset into the scrip dataset + out_file.variables["grid_center_lat"][:] = ds.lat.values.flat + out_file.variables["grid_center_lon"][:] = ds.lon.values.flat + out_file.variables["grid_dims"][:] = [nx, ny] + out_file.variables["grid_imask"][:] = 1 + + # determine the corners of gricells + if "lat_bnds" in ds and "lon_bnds" in ds: + lat_corner = ds.lat_bnds + if "time" in lat_corner.dims: + lat_corner = lat_corner.isel(time=0) + + lon_corner = ds.lon_bnds + if "time" in lon_corner.dims: + lon_corner = lon_corner.isel(time=0) + + lat_corner = lat_corner.values + lon_corner = lon_corner.values + else: + # RACMO datasets do not have `lat_bnds`/`lon_bnds` variables. Instead + # the bounds of the RACMO dataset are approximated assuming the cell + # centers are located at the midpoint b/w cell edges + lat_corner = unwrap_corners(interp_extrap_corners_2d(ds.lat.values)) + lon_corner = unwrap_corners(interp_extrap_corners_2d(ds.lon.values)) + + grid_corner_lat = lat_corner.reshape((grid_size, 4)) + grid_corner_lon = lon_corner.reshape((grid_size, 4)) + + out_file.variables["grid_corner_lat"][:] = grid_corner_lat + out_file.variables["grid_corner_lon"][:] = grid_corner_lon + + out_file.close() diff --git a/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py b/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py index f3ba4eec0c..478a4ed905 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py +++ b/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py @@ -1,10 +1,13 @@ import os import shutil + import xarray as xr -from compass.landice.tests.ismip6_forcing.create_mapfile \ - import build_mapping_file from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call + +from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( + build_mapping_file, +) from compass.step import Step @@ -72,12 +75,12 @@ def run(self): # ismip6 input files # call the function that combines data - logger.info(f"Combining the ismip6 input files") + logger.info("Combining the ismip6 input files") input_file_basin = self._file_basin[period_endyear] input_file_list = self._files_coeff[period_endyear] for i, file in enumerate(input_file_list): - logger.info(f"!---Start processing the current file---!") + logger.info("!---Start processing the current file---!") logger.info(f"processing the input file " f"'{os.path.basename(file)}'") # temporary file names. Note: The counter 'i' seems to be necessary @@ -91,7 +94,7 @@ def run(self): combined_file_temp) # remap the input forcing file. - logger.info(f"Calling the remapping function...") + logger.info("Calling the remapping function...") self.remap_ismip6_basal_melt_to_mali_vars(combined_file_temp, remapped_file_temp, mali_mesh_name, @@ -102,13 +105,13 @@ def run(self): f"{os.path.basename(file)}" # rename the ismip6 variables to MALI variables - logger.info(f"Renaming the ismip6 variables to " - f"mali variable names...") + logger.info("Renaming the ismip6 variables to " + "mali variable names...") self.rename_ismip6_basal_melt_to_mali_vars(remapped_file_temp, output_file) - logger.info(f"Remapping and renaming process done successfully. " - f"Removing the temporary files...") + logger.info("Remapping and renaming process done successfully. " + "Removing the temporary files...") # remove the temporary combined file os.remove(combined_file_temp) @@ -124,9 +127,9 @@ def run(self): dst = os.path.join(output_path, output_file) shutil.copy(src, dst) - logger.info(f"!---Done processing the current file---!") - logger.info(f"") - logger.info(f"") + logger.info("!---Done processing the current file---!") + logger.info("") + logger.info("") def combine_ismip6_inputfiles(self, basin_file, coeff_gamma0_deltat_file, combined_file_temp): @@ -183,11 +186,13 @@ def remap_ismip6_basal_melt_to_mali_vars(self, input_file, output_file, if not os.path.exists(mapping_file): # build a mapping file if it doesn't already exist build_mapping_file(self.config, self.ntasks, self.logger, - input_file, mapping_file, mali_mesh_file, - method_remap) + input_file, mapping_file, + scrip_from_latlon=False, + mali_mesh_file=mali_mesh_file, + method_remap=method_remap) else: - self.logger.info(f"Mapping file exists. " - f"Remapping the input data...") + self.logger.info("Mapping file exists. " + "Remapping the input data...") # remap the input data args = ["ncremap", @@ -241,29 +246,31 @@ def rename_ismip6_basal_melt_to_mali_vars(self, remapped_file_temp, } _files_coeff = { - "2100": ["AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_percentile.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_percentile.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_percentile.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_percentile.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median_PIGL_gamma_calibration.nc", - "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median.nc"], - - "2300": ["AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_percentile.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_percentile.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_percentile.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_pct_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_percentile.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median_PIGL_gamma_calibration.nc", - "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median.nc"] - } \ No newline at end of file + "2100": [ + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_percentile.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_percentile.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_percentile.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_percentile.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_Forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median.nc"], # noqa: E501 + + "2300": [ + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_5th_percentile.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_95th_percentile.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_local_median.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_5th_percentile.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_pct_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_95th_percentile.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median_PIGL_gamma_calibration.nc", # noqa: E501 + "AIS/Ocean_forcing/parameterizations/coeff_gamma0_DeltaT_quadratic_non_local_median.nc"] # noqa: E501 + } diff --git a/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py b/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py index 2bd86c6190..6f6df4a31b 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py +++ b/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py @@ -170,8 +170,10 @@ def remap_ismip6_thermal_forcing_to_mali_vars(self, if not os.path.exists(mapping_file): # build a mapping file if it doesn't already exist build_mapping_file(self.config, self.ntasks, self.logger, - input_file, mapping_file, mali_mesh_file, - method_remap) + input_file, mapping_file, + scrip_from_latlon=False, + mali_mesh_file=mali_mesh_file, + method_remap=method_remap) else: self.logger.info("Mapping file exists. " "Remapping the input data...") diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py index ae719cd524..54c2449534 100644 --- a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py @@ -6,7 +6,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.atmosphere.create_mapfile_smb import ( # noqa: E501 +from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( build_mapping_file, ) from compass.step import Step @@ -182,8 +182,10 @@ def remap_ismip6_shelf_mask_to_mali_vars(self, input_file, output_file, if not os.path.exists(mapping_file): # build a mapping file if it doesn't already exist build_mapping_file(self.config, self.ntasks, self.logger, - input_file, mapping_file, mali_mesh_file, - method_remap) + input_file, mapping_file, + scrip_from_latlon=True, + mali_mesh_file=mali_mesh_file, + method_remap=method_remap) else: self.logger.info("Mapping file exists. " "Remapping the input data...") From ec9efbe25c4c2232b455a9cd6dca127dc4679266 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Wed, 20 Mar 2024 17:46:50 -0600 Subject: [PATCH 04/10] Fix linter errors causing CI to fail --- compass/landice/tests/ismip6_forcing/__init__.py | 5 +++-- .../tests/ismip6_forcing/atmosphere/__init__.py | 15 +++++++++------ .../tests/ismip6_forcing/ocean_basal/__init__.py | 10 ++++++---- .../ismip6_forcing/ocean_thermal/__init__.py | 10 ++++++---- .../ismip6_forcing/shelf_collapse/__init__.py | 10 ++++++---- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/compass/landice/tests/ismip6_forcing/__init__.py b/compass/landice/tests/ismip6_forcing/__init__.py index dc088306f0..f8adcb36cf 100644 --- a/compass/landice/tests/ismip6_forcing/__init__.py +++ b/compass/landice/tests/ismip6_forcing/__init__.py @@ -1,8 +1,9 @@ -from compass.testgroup import TestGroup from compass.landice.tests.ismip6_forcing.atmosphere import Atmosphere -from compass.landice.tests.ismip6_forcing.ocean_thermal import OceanThermal from compass.landice.tests.ismip6_forcing.ocean_basal import OceanBasal +from compass.landice.tests.ismip6_forcing.ocean_thermal import OceanThermal from compass.landice.tests.ismip6_forcing.shelf_collapse import ShelfCollapse +from compass.testgroup import TestGroup + class Ismip6Forcing(TestGroup): """ diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/__init__.py b/compass/landice/tests/ismip6_forcing/atmosphere/__init__.py index f8de7e3dad..3ad28e71da 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/__init__.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/__init__.py @@ -1,10 +1,13 @@ +from compass.landice.tests.ismip6_forcing.atmosphere.process_smb import ( + ProcessSMB, +) +from compass.landice.tests.ismip6_forcing.atmosphere.process_smb_racmo import ( + ProcessSmbRacmo, +) +from compass.landice.tests.ismip6_forcing.configure import ( + configure as configure_testgroup, +) from compass.testcase import TestCase -from compass.landice.tests.ismip6_forcing.atmosphere.process_smb \ - import ProcessSMB -from compass.landice.tests.ismip6_forcing.atmosphere.process_smb_racmo \ - import ProcessSmbRacmo -from compass.landice.tests.ismip6_forcing.configure import configure as \ - configure_testgroup class Atmosphere(TestCase): diff --git a/compass/landice/tests/ismip6_forcing/ocean_basal/__init__.py b/compass/landice/tests/ismip6_forcing/ocean_basal/__init__.py index f4732ba263..188c83bbf6 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_basal/__init__.py +++ b/compass/landice/tests/ismip6_forcing/ocean_basal/__init__.py @@ -1,8 +1,10 @@ +from compass.landice.tests.ismip6_forcing.configure import ( + configure as configure_testgroup, +) +from compass.landice.tests.ismip6_forcing.ocean_basal.process_basal_melt import ( # noqa: E501 + ProcessBasalMelt, +) from compass.testcase import TestCase -from compass.landice.tests.ismip6_forcing.ocean_basal.process_basal_melt \ - import ProcessBasalMelt -from compass.landice.tests.ismip6_forcing.configure import configure as \ - configure_testgroup class OceanBasal(TestCase): diff --git a/compass/landice/tests/ismip6_forcing/ocean_thermal/__init__.py b/compass/landice/tests/ismip6_forcing/ocean_thermal/__init__.py index 5b627c5c9a..89eba7a187 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_thermal/__init__.py +++ b/compass/landice/tests/ismip6_forcing/ocean_thermal/__init__.py @@ -1,8 +1,10 @@ +from compass.landice.tests.ismip6_forcing.configure import ( + configure as configure_testgroup, +) +from compass.landice.tests.ismip6_forcing.ocean_thermal.process_thermal_forcing import ( # noqa: E501 + ProcessThermalForcing, +) from compass.testcase import TestCase -from compass.landice.tests.ismip6_forcing.ocean_thermal.\ - process_thermal_forcing import ProcessThermalForcing -from compass.landice.tests.ismip6_forcing.configure import configure as \ - configure_testgroup class OceanThermal(TestCase): diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py index dcd2fda0b9..b59edb1e18 100644 --- a/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/__init__.py @@ -1,8 +1,10 @@ +from compass.landice.tests.ismip6_forcing.configure import ( + configure as configure_testgroup, +) +from compass.landice.tests.ismip6_forcing.shelf_collapse.process_shelf_collapse import ( # noqa: E501 + ProcessShelfCollapse, +) from compass.testcase import TestCase -from compass.landice.tests.ismip6_forcing.shelf_collapse.process_shelf_collapse \ - import ProcessShelfCollapse -from compass.landice.tests.ismip6_forcing.configure import configure as \ - configure_testgroup class ShelfCollapse(TestCase): From 955afad2e4120903711d5f9df4435a5f05ab8986 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Thu, 21 Mar 2024 09:18:47 -0600 Subject: [PATCH 05/10] Fix import typo caused by unifying `build_mapping_file` functionality --- compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py | 2 +- .../tests/ismip6_forcing/atmosphere/process_smb_racmo.py | 2 +- .../tests/ismip6_forcing/ocean_basal/process_basal_melt.py | 2 +- .../ismip6_forcing/shelf_collapse/process_shelf_collapse.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py index d37287684a..2d72215ddf 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py @@ -6,7 +6,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( +from compass.landice.tests.ismip6_forcing.create_mapfile import ( build_mapping_file, ) from compass.step import Step diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py index 5206db0807..0b33e7ddad 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb_racmo.py @@ -5,7 +5,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( +from compass.landice.tests.ismip6_forcing.create_mapfile import ( build_mapping_file, ) from compass.step import Step diff --git a/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py b/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py index 478a4ed905..b413ff88df 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py +++ b/compass/landice/tests/ismip6_forcing/ocean_basal/process_basal_melt.py @@ -5,7 +5,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( +from compass.landice.tests.ismip6_forcing.create_mapfile import ( build_mapping_file, ) from compass.step import Step diff --git a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py index 54c2449534..ae536374e2 100644 --- a/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py +++ b/compass/landice/tests/ismip6_forcing/shelf_collapse/process_shelf_collapse.py @@ -6,7 +6,7 @@ from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call -from compass.landice.tests.ismip6_forcing.create_mapfile_smb import ( +from compass.landice.tests.ismip6_forcing.create_mapfile import ( build_mapping_file, ) from compass.step import Step From c44f926323c448c9da8eb227863ace2efcfd2084 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Fri, 22 Mar 2024 12:54:38 -0600 Subject: [PATCH 06/10] Standrdize atmosphere dictionary format and improve error msgs. --- .../ismip6_forcing/atmosphere/process_smb.py | 99 +++++++++++++------ 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py index 2d72215ddf..be5494d060 100644 --- a/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py +++ b/compass/landice/tests/ismip6_forcing/atmosphere/process_smb.py @@ -50,6 +50,21 @@ def setup(self): section = config["ismip6_ais_atmosphere"] data_res = section.get("data_resolution") + # find the resolutions supported for the requested: endyear, model, + # and scenario. As more resolutions are supported this error checking + # will automatically evolve + supported_res = self._files[period_endyear][model][scenario].keys() + + # check that the requested resolution is supported + if data_res not in supported_res: + raise ValueError(f"Requested data resolution of { {data_res} } " + f"is not supported for requested:\n" + f"\tperiod_endyear : {period_endyear}\n" + f"\tmodel : {model}\n" + f"\tscenario : {scenario}\n" + f"Currently supported data resolutions are " + f"{ list(supported_res) }") + self.add_input_file(filename=mali_mesh_file, target=os.path.join(base_path_mali, mali_mesh_file)) @@ -352,52 +367,80 @@ def correct_smb_anomaly_for_climatology(self, _files = { "2100": { "CCSM4": { - "RCP85": [ - "AIS/Atmosphere_Forcing/ccsm4_rcp8.5/Regridded_8km/CCSM4_8km_anomaly_1995-2100.nc"] # noqa: E501 + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/ccsm4_rcp8.5/Regridded_8km/CCSM4_8km_anomaly_1995-2100.nc"] # noqa: E501 + }, }, "CESM2": { - "SSP585v1": [ - "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v1.nc"], # noqa: E501 - "SSP585v2": [ - "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v2.nc"] # noqa: E501 + "SSP585v1": { + "8km": [ + "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v1.nc"], # noqa: E501 + }, + "SSP585v2": { + "8km": [ + "AIS/Atmosphere_Forcing/CESM2_ssp585/Regridded_8km/CESM2_anomaly_ssp585_1995-2100_8km_v2.nc"] # noqa: E501 + }, }, "CNRM_CM6": { - "SSP126": [ - "AIS/Atmosphere_Forcing/CNRM_CM6_ssp126/Regridded_8km/CNRM-CM6-1_anomaly_ssp126_1995-2100_8km_ISMIP6.nc"], # noqa: E501 - "SSP585": [ - "AIS/Atmosphere_Forcing/CNRM_CM6_ssp585/Regridded_8km/CNRM-CM6-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 + "SSP126": { + "8km": [ + "AIS/Atmosphere_Forcing/CNRM_CM6_ssp126/Regridded_8km/CNRM-CM6-1_anomaly_ssp126_1995-2100_8km_ISMIP6.nc"], # noqa: E501 + }, + "SSP585": { + "8km": [ + "AIS/Atmosphere_Forcing/CNRM_CM6_ssp585/Regridded_8km/CNRM-CM6-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 + }, }, "CNRM_ESM2": { - "SSP585": [ - "AIS/Atmosphere_Forcing/CNRM_ESM2_ssp585/Regridded_8km/CNRM-ESM2-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 + "SSP585": { + "8km": [ + "AIS/Atmosphere_Forcing/CNRM_ESM2_ssp585/Regridded_8km/CNRM-ESM2-1_anomaly_ssp585_1995-2100_8km_ISMIP6.nc"] # noqa: E501 + }, }, "CSIRO-Mk3-6-0": { - "RCP85": [ - "AIS/Atmosphere_Forcing/CSIRO-Mk3-6-0_rcp85/Regridded_8km/CSIRO-Mk3-6-0_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/CSIRO-Mk3-6-0_rcp85/Regridded_8km/CSIRO-Mk3-6-0_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + }, }, "HadGEM2-ES": { - "RCP85": [ - "AIS/Atmosphere_Forcing/HadGEM2-ES_rcp85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/HadGEM2-ES_rcp85/Regridded_8km/HadGEM2-ES_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + }, }, "IPSL-CM5A-MR": { - "RCP26": [ - "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp26/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 - "RCP85": [ - "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp85/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + "RCP26": { + "8km": [ + "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp26/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 + }, + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/IPSL-CM5A-MR_rcp85/Regridded_8km/IPSL-CM5A-MR_8km_anomaly_rcp85_1995-2100.nc"] # noqa: E501 + }, }, "MIROC-ESM-CHEM": { - "RCP85": [ - "AIS/Atmosphere_Forcing/miroc-esm-chem_rcp8.5/Regridded_8km/MIROC-ESM-CHEM_8km_anomaly_1995-2100.nc"] # noqa: E501 + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/miroc-esm-chem_rcp8.5/Regridded_8km/MIROC-ESM-CHEM_8km_anomaly_1995-2100.nc"] # noqa: E501 + }, }, "NorESM1-M": { - "RCP26": [ - "AIS/Atmosphere_Forcing/noresm1-m_rcp2.6/Regridded_8km/NorESM-M_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 - "RCP85": [ - "AIS/Atmosphere_Forcing/noresm1-m_rcp8.5/Regridded_8km/NorESM-M_8km_anomaly_1995-2100.nc"] # noqa: E501 + "RCP26": { + "8km": [ + "AIS/Atmosphere_Forcing/noresm1-m_rcp2.6/Regridded_8km/NorESM-M_8km_anomaly_rcp26_1995-2100.nc"], # noqa: E501 + }, + "RCP85": { + "8km": [ + "AIS/Atmosphere_Forcing/noresm1-m_rcp8.5/Regridded_8km/NorESM-M_8km_anomaly_1995-2100.nc"] # noqa: E501 + }, }, "UKESM1-0-LL": { - "SSP585": [ - "AIS/Atmosphere_Forcing/UKESM1-0-LL/Regridded_8km/UKESM1-0-LL_anomaly_ssp585_1995-2100_8km.nc"] # noqa: E501 + "SSP585": { + "8km": [ + "AIS/Atmosphere_Forcing/UKESM1-0-LL/Regridded_8km/UKESM1-0-LL_anomaly_ssp585_1995-2100_8km.nc"] # noqa: E501 + }, } }, "2300": { From d6d434efddf7816fa21724b6f8b3e62b921735ca Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Mon, 1 Apr 2024 10:21:19 -0600 Subject: [PATCH 07/10] Standardize the forcing/Forcing convetion for ocean thermal obs files. --- .../ocean_thermal/process_thermal_forcing.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py b/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py index 6f6df4a31b..5a37d77dfd 100644 --- a/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py +++ b/compass/landice/tests/ismip6_forcing/ocean_thermal/process_thermal_forcing.py @@ -59,7 +59,7 @@ def setup(self): mali_mesh_file)) if process_obs_data: - input_file = self._file_obs + input_file = self._file_obs[period_endyear] output_file = f"{mali_mesh_name}_obs_TF_1995-2017_8km_x_60m.nc" else: input_file = self._files[period_endyear][model][scenario] @@ -91,7 +91,7 @@ def run(self): process_obs_data = self.process_obs if process_obs_data: - input_file_list = self._file_obs + input_file_list = self._file_obs[period_endyear] output_file = f"{mali_mesh_name}_obs_TF_1995-2017_8km_x_60m.nc" output_path = f"{output_base_path}/ocean_thermal_forcing/"\ f"obs" @@ -271,7 +271,13 @@ def rename_ismip6_thermal_forcing_to_mali_vars(self, # create a nested dictionary for the ISMIP6 original forcing files # including relative path - _file_obs = ["AIS/Ocean_Forcing/climatology_from_obs_1995-2017/obs_thermal_forcing_1995-2017_8km_x_60m.nc"] # noqa: E501 + _file_obs = { + "2100": [ + "AIS/Ocean_Forcing/climatology_from_obs_1995-2017/obs_thermal_forcing_1995-2017_8km_x_60m.nc"], # noqa: E501 + "2300": [ + "AIS/Ocean_forcing/climatology_from_obs_1995-2017/obs_thermal_forcing_1995-2017_8km_x_60m.nc"] # noqa: E501 + } + _files = { "2100": { "CCSM4": { From 24a3972762573de5c7b262423546682fa61e30a7 Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Mon, 1 Apr 2024 14:43:01 -0700 Subject: [PATCH 08/10] Update developer guide and API for shelf collapse testcase. --- docs/developers_guide/landice/api.rst | 11 +++- .../landice/test_groups/ismip6_forcing.rst | 57 +++++++++++++------ 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/docs/developers_guide/landice/api.rst b/docs/developers_guide/landice/api.rst index ce64c308af..09a2d10701 100644 --- a/docs/developers_guide/landice/api.rst +++ b/docs/developers_guide/landice/api.rst @@ -293,12 +293,10 @@ ismip6_forcing Ismip6Forcing configure.configure create_mapfile.build_mapping_file + create_mapfile.create_scrip_from_latlon atmosphere.Atmosphere atmosphere.Atmosphere.configure - atmosphere.create_mapfile_smb - atmosphere.create_mapfile_smb.build_mapping_file - atmosphere.create_mapfile_smb.create_atm_scrip atmosphere.process_smb.ProcessSMB atmosphere.process_smb.ProcessSMB.setup atmosphere.process_smb.ProcessSMB.run @@ -329,6 +327,13 @@ ismip6_forcing ocean_thermal.process_thermal_forcing.ProcessThermalForcing.remap_ismip6_thermal_forcing_to_mali_vars ocean_thermal.process_thermal_forcing.ProcessThermalForcing.rename_ismip6_thermal_forcing_to_mali_vars + shelf_collapse.ShelfCollapse + shelf_collapse.ShelfCollapse.configure + shelf_collapse.process_shelf_collapse.ProcessShelfCollapse.setup + shelf_collapse.process_shelf_collapse.ProcessShelfCollapse.run + shelf_collapse.process_shelf_collapse.ProcessShelfCollapse.remap_ismip6_shelf_mask_to_mali_vars + shelf_collapse.process_shelf_collapse.ProcessShelfCollapse.rename_ismip6_shelf_mask_to_mali_vars + ismip6_run ~~~~~~~~~~ diff --git a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst index 281fef9d8d..c0a41f6696 100644 --- a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst +++ b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst @@ -7,17 +7,18 @@ The ``ismip6_forcing`` test group (:py:class:`compass.landice.tests.ismip6_ forcing.Ismip6Forcing`) processes (i.e., remapping and renaming) the atmospheric and oceanic forcing data of the Ice Sheet Model Intercomparison for CMIP6 (ISMIP6) protocol from its native polarstereo grid to -the unstructure MALI mesh. The test group includes four test cases, -``atmosphere``, ``ocean_basal``, ``ocean_thermal_obs`` and ``ocean_thermal``. -The ``atmosphere`` test case has two steps: ``process_smb`` and ``process_smb_racmo``; -the ``ocean_basal`` test case has one step, ``process_basal_melt``; -the ``ocean_thermal_obs`` and ``ocean_thermal`` share one step, ``process_thermal_forcing``. -Each step has the local methods (functions) of remapping and -renaming the original ismip6 data to the format that MALI can incorporate in -its forward simulations. In remapping the data, all test cases import the -method ``build_mapping_file`` to create or use scrip files -of the source (ISMIP6) and destination (MALI) mesh depending on the existence -of a mapping file. Below, we describe the shared framework for this +the unstructure MALI mesh. The test group includes five test cases: +``atmosphere``, ``ocean_basal``, ``ocean_thermal_obs``, ``ocean_thermal`` and +``shelf_collapse``. The ``atmosphere`` test case has two steps: +``process_smb`` and ``process_smb_racmo``; the ``ocean_basal`` and ``shelf_collpase`` +test cases each have one step, ``process_basal_melt`` and ``process_shelf_collpase`` +(respectivlye); the ``ocean_thermal_obs`` and ``ocean_thermal`` +share one step, ``process_thermal_forcing``. Each step has the local methods +(functions) of remapping and renaming the original ISMIP6 data to the format +that MALI can incorporate in its forward simulations. In remapping the data, +all test cases import the method ``build_mapping_file`` to create or use scrip +files of the source (ISMIP6) and destination (MALI) mesh depending on the +existence of a mapping file. Below, we describe the shared framework for this test group and the 3 test cases. .. _dev_landice_ismip6_forcing_framework: @@ -28,11 +29,29 @@ framework The shared config options for the ``ismip6_forcing`` test group are described in :ref:`landice_ismip6_forcing` in the User's Guide. +create_mapfile +~~~~~~~~~~~~~~ + +The module :py:class:`compass.landice.tests.ismip6_forcing.create_mapfile` defines +a unified framework for creating the SCRIP and mapping files from the ISMIP6 +source data files. The function +:py:func:`compass.landice.tests.ismip6_forcing.create_mapfile.build_mapping_file` +is the common interface to build the SCRIP files and mapping files. The +``scrip_from_latlon`` keyword argument is used to call the appropriate function +for generating the SCRIP file. If ``scrip_from_latlon`` is ``false`` the +``create_SCRIP_file_from_planar_rectangular_grid.py`` command line executable +from the ``MPAS_Tools`` conda package is used, otherwise the +:py:func:`compass.landice.tests.ismip6_forcing.create_mapfile.create_scrip_from_latlon` +function is used. Multiple methods for creating SCRIP files are necessary due to +inconsistent dimensions names across the different ISMIP6 datasets. + +Test cases +---------- .. _dev_landice_ismip6_forcing_atmosphere: atmosphere ----------- +~~~~~~~~~~ The :py:class:`compass.landice.tests.ismip6_forcing.atmosphere.Atmosphere` performs processing of the surface mass balance (SMB) forcing. @@ -43,10 +62,9 @@ correcting the SMB anomaly field for the MALI base SMB. .. _dev_landice_ismip6_forcing_ocean_basal: ocean_basal ------------- +~~~~~~~~~~~~ -The :py:class:`compass.landice.tests.ismip6_forcing.ocean_basal.OceanBasal` -performs processing of the coefficients for the basal melt parameterization +The :py:class:`compass.landice.tests.ismip6_forcing.ocean_basal.OceanBasal` performs processing of the coefficients for the basal melt parameterization utilized by the ISMIP6 protocol. Processing data includes combining the IMBIE2 basin number file and parameterization coefficients and remapping onto the MALI mesh. @@ -54,7 +72,7 @@ the MALI mesh. .. _dev_landice_ismip6_forcing_ocean_thermal: ocean_thermal -------------- +~~~~~~~~~~~~~ The :py:class:`compass.landice.tests.ismip6_forcing.ocean_thermal.OceanThermal` performs the processing of ocean thermal forcing, both observational climatology @@ -62,3 +80,10 @@ performs the processing of ocean thermal forcing, both observational climatology Processing data includes regridding the original ISMIP6 thermal forcing data from its native polarstereo grid to MALI's unstructured grid and renaming variables. +.. _dev_landice_ismip6_forcing_shelf_collapse: + +shelf_collapse +~~~~~~~~~~~~~~ +The :py:class:`compass.landice.tests.ismip6_forcing.shelf_collapse.ShelfCollapse` +test case performs the processing of ice shelf collapse masks by remapping the +original ISMIP6 forcing data to MALI's unstructured grid and renaming variables. From 4601cea0fb707fd5f38da733008f15c6be7e71cc Mon Sep 17 00:00:00 2001 From: Andrew Nolan Date: Tue, 2 Apr 2024 10:30:14 -0700 Subject: [PATCH 09/10] Update users guide for shelf collpase and fix typo in dev guide. --- .../landice/test_groups/ismip6_forcing.rst | 2 +- .../landice/test_groups/ismip6_forcing.rst | 98 ++++++++++++++----- 2 files changed, 74 insertions(+), 26 deletions(-) diff --git a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst index c0a41f6696..c508efb711 100644 --- a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst +++ b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst @@ -12,7 +12,7 @@ the unstructure MALI mesh. The test group includes five test cases: ``shelf_collapse``. The ``atmosphere`` test case has two steps: ``process_smb`` and ``process_smb_racmo``; the ``ocean_basal`` and ``shelf_collpase`` test cases each have one step, ``process_basal_melt`` and ``process_shelf_collpase`` -(respectivlye); the ``ocean_thermal_obs`` and ``ocean_thermal`` +(respectively); the ``ocean_thermal_obs`` and ``ocean_thermal`` share one step, ``process_thermal_forcing``. Each step has the local methods (functions) of remapping and renaming the original ISMIP6 data to the format that MALI can incorporate in its forward simulations. In remapping the data, diff --git a/docs/users_guide/landice/test_groups/ismip6_forcing.rst b/docs/users_guide/landice/test_groups/ismip6_forcing.rst index 83da109094..87db104316 100644 --- a/docs/users_guide/landice/test_groups/ismip6_forcing.rst +++ b/docs/users_guide/landice/test_groups/ismip6_forcing.rst @@ -7,15 +7,18 @@ The ``landice/ismip6_forcing`` test group processes (i.e., remaps and renames) the atmospheric and ocean forcing data of the Ice Sheet Model Intercomparison for CMIP6 (ISMIP6) protocol. The processed data is used to force MALI in its simulations under a relevant ISMIP6 (either the 2100 or 2300) experimental protocol. -The test group includes three test cases, ``atmosphere``, ``ocean_basal``, -``ocean_thermal_obs`` and ``ocean_thermal``; the ``atmosphere`` test case -has two steps: ``process_smb`` and ``process_smb_racmo``. The ``ocean_basal`` -and the ``ocean_thermal`` test case each has one step, ``process_basal_melt``, -and ``process_thermal_forcing``, respectively. (For more details on the steps of +The test group includes five test cases: +``atmosphere``, ``ocean_basal``, ``ocean_thermal_obs``, ``ocean_thermal`` and +``shelf_collapse``. The ``atmosphere`` test case has two steps: +``process_smb`` and ``process_smb_racmo``; the ``ocean_basal`` and ``shelf_collpase`` +test cases each have one step, ``process_basal_melt`` and ``process_shelf_collpase`` +(respectively); the ``ocean_thermal_obs`` and ``ocean_thermal`` +share one step, ``process_thermal_forcing``. (For more details on the steps of each test case, see :ref:`landice_ismip6_forcing_atmosphere`, :ref:`landice_ismip6_forcing_ocean_basal`, :ref:`landice_ismip6_forcing_ocean_thermal_obs` and -:ref:`landice_ismip6_forcing_ocean_thermal`.) +:ref:`landice_ismip6_forcing_ocean_thermal`, +:ref:`landice_ismip6_forcing_shelf_collapse`.) Approximated time for processing a single forcing file on Cori (single core) is 2 and 7 minutes for the atmosphere and ocean basal testcases, and less than a minute for ocean thermal obs and ocean thermal @@ -38,7 +41,9 @@ control runs). and end year. 4. run :ref:`landice_ismip6_forcing_atmosphere` with -``process_racmo_smb = True`` once with any model. +``process_racmo_smb = True`` once, independent of the model, scenario and +end year. + 5. run :ref:`landice_ismip6_forcing_atmosphere` for each model, scenario and end year. Users can keep ``process_racmo_smb = False`` as long as @@ -53,7 +58,7 @@ climatology files for (#3) atmospheric and (#4) ocean forcing, (#5) ISMIP6 basin number file, and (#6) the ocean basal-melt parameter files. Except for the file #3, The ISMIP6 source data (files #1, 2, 4-6) can be obtained by contacting the ISMIP6 steering -committee as described `here. `_ +committee as described `here. `_ Once the users get access to the data on `Globus `_, and within the base path directory that the user specifies in the config file (i.e., the config option ``base_path_ismip6``; @@ -63,7 +68,17 @@ and names provided in the GHub endpoints (named ``GHub-ISMIP6-Forcing`` for the 2100 CE projection protocol and ``ISMIP6-Projections-Forcing-2300`` for the 2300 CE projection protocol). That is, the directory paths and the file names must exactly match (even the letter case) those that are provided by the -GHub endpoints. For example, if a user wants to process the atmosphere (SMB) +GHub endpoints. + +.. note:: + + It is important to emphasize that a ``base_path_ismip6`` value with a bottom + level direcoty name of ``GHub-ISMIP6-Forcing`` will **only** work for the + 2100 CE projection protocol (``period_endyear`` config option below). The + same applied for a bottom level directory of ``ISMIP6-Projections-Forcing-2300`` + and the 2300 CE projection protocol. + +For example, if a user wants to process the atmosphere (SMB) forcing representing the ``SSP585`` scenario from the ``UKESM1-0-LL`` model provided by the ISMIP6-2100 protocol (i.e., from the ``GHub-ISMIP6-Forcing`` Globus endpoint), they must create the directory path @@ -128,24 +143,30 @@ and where processed files will be saved. config options -------------- -All four test cases share some set of default config options under the section +All five test cases share some set of default config options under the section ``[ismip6_ais]`` and have separate config options for each test case: -``[ismip6_ais_atmosphere]``, ``[ismip6_ais_ocean_thermal]``, and -``[ismip6_ais_ocean_basal]``. In the general config section -``[ismip6_ais]``, users need to supply base paths to input files and MALI mesh -file, and MALI mesh name, as well as the model name, climate forcing scenario -and the projection end year of the ISMIP6 forcing data, which can be chosen -from the available options as given in the config file (see the example file -below.) In the ``ismip6_ais_atmosphere`` section, users need to indicate -``True`` or ``False`` on whether to process the RACMO modern climatology -(``True`` is required to run the ``process_smb_racmo`` step, which needs to be -run before the ``process_smb`` step). - -For most the ``[ismip6_ais_atmosphere]`` and ``[ismip6_ais_ocean_thermal]`` -config sections users may choose the interpolation scheme among +``[ismip6_ais_atmosphere]``, ``[ismip6_ais_ocean_thermal]``, +``[ismip6_ais_ocean_basal]``, and ``[ismip6_ais_shelf_collpase``]. In the +general config section (``[ismip6_ais]``), users need to supply base paths to +input files and MALI mesh file, and MALI mesh name, as well as the model name, +climate forcing scenario and the projection end year of the ISMIP6 forcing data, +which can be chosen from the available options as given in the config file +(see the example file below.) In the ``ismip6_ais_atmosphere`` section, +users need to indicate ``True`` or ``False`` on whether to process the RACMO +modern climatology (``True`` is required to run the ``process_smb_racmo`` step, +which needs to be run before the ``process_smb`` step). + +The ``[ismip6_ais_atmosphere]`` and ``[ismip6_ais_ocean_thermal]`` +config sections allow users to choose the interpolation scheme among ``bilinear``, ``neareststod`` and ``conserve`` methods. The exception is that -the ``ocean basal`` test case should always use the ``neareststod`` method -because the source files have a single valued data per basin. +the ``ocean basal`` test case will always use the ``neareststod`` method +because the source files have a single valued data per basin. Futhermore, the +``[ismip6_ais_atmosphere]`` and ``[ismip6_ais_shelf_collpase]`` config sections +support a ``data_resolution`` config option, which allows the user to pick the +source data resolution most appropriate for the MALI mesh the data is being +interpolated onto. The ``[ismip6_ais_ocean_thermal]`` config section does not +support the ``data_resolution`` config option, because the source datasets are +only provided at a single resolution. Below are the default config options: @@ -190,6 +211,9 @@ Below are the default config options: # config options for ismip6 antarctic ice sheet SMB forcing data test cases [ismip6_ais_atmosphere] + + # resolution of CMIP6 model data to be used; supported options are [8km, 4km] + data_resolution = 8km # Remapping method used in building a mapping file. Options include: bilinear, neareststod, conserve method_remap = bilinear @@ -197,6 +221,12 @@ Below are the default config options: # Set True to process RACMO modern climatology process_smb_racmo = True + # config options for ismip6 antarctic ice shelf collpase forcing test cases + [ismip6_ais_shelf_collapse] + + # resolution of CMIP6 model data to be used; supported options are [8km, 4km] + data_resolution = 8km + # config options for ismip6 ocean thermal forcing data test cases [ismip6_ais_ocean_thermal] @@ -254,12 +284,21 @@ process the RACMO modern SMB climatology but not the modern thermal forcing. # config options for ismip6 antarctic ice sheet SMB forcing data test cases [ismip6_ais_atmosphere] + # resolution of CMIP6 model data to be used; supported options are [8km, 4km] + data_resolution = 8km + # Remapping method used in building a mapping file. Options include: bilinear, neareststod, conserve method_remap = bilinear # Set True to process RACMO modern climatology process_smb_racmo = True + # config options for ismip6 antarctic ice shelf collpase forcing test cases + [ismip6_ais_shelf_collapse] + + # resolution of CMIP6 model data to be used; supported options are [8km, 4km] + data_resolution = 8km + # config options for ismip6 ocean thermal forcing data test cases [ismip6_ais_ocean_thermal] @@ -313,3 +352,12 @@ The ``landice/ismip6_forcing/ocean_thermal`` test case performs the processing of ocean thermal forcing. Processing data includes regridding the original ISMIP6 thermal forcing data from its native polarstereo grid to MALI's unstructured grid and renaming variables. + +.. _landice_ismip6_forcing_shelf_collapse: + +shelf_collapse +-------------- +The ``landice/ismip6_forcing/shelf_collpase`` test case performs the processing +of ice shelf collapse masks by remapping the original ISMIP6 forcing data to +MALI's unstructured grid and renaming variables. This test case is only supported +with a ``period_endyear`` of ``2300``. From 8852d807c842b99b2d3875a4533ee2ef69011bbe Mon Sep 17 00:00:00 2001 From: Matt Hoffman Date: Tue, 2 Apr 2024 19:49:15 -0600 Subject: [PATCH 10/10] Apply suggestions from code review Minor fixup to docs --- .../landice/test_groups/ismip6_forcing.rst | 3 ++- docs/users_guide/landice/test_groups/ismip6_forcing.rst | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst index c508efb711..4f9d2e88aa 100644 --- a/docs/developers_guide/landice/test_groups/ismip6_forcing.rst +++ b/docs/developers_guide/landice/test_groups/ismip6_forcing.rst @@ -64,7 +64,8 @@ correcting the SMB anomaly field for the MALI base SMB. ocean_basal ~~~~~~~~~~~~ -The :py:class:`compass.landice.tests.ismip6_forcing.ocean_basal.OceanBasal` performs processing of the coefficients for the basal melt parameterization +The :py:class:`compass.landice.tests.ismip6_forcing.ocean_basal.OceanBasal` +performs processing of the coefficients for the basal melt parameterization utilized by the ISMIP6 protocol. Processing data includes combining the IMBIE2 basin number file and parameterization coefficients and remapping onto the MALI mesh. diff --git a/docs/users_guide/landice/test_groups/ismip6_forcing.rst b/docs/users_guide/landice/test_groups/ismip6_forcing.rst index 87db104316..e3b1f09e37 100644 --- a/docs/users_guide/landice/test_groups/ismip6_forcing.rst +++ b/docs/users_guide/landice/test_groups/ismip6_forcing.rst @@ -73,9 +73,9 @@ GHub endpoints. .. note:: It is important to emphasize that a ``base_path_ismip6`` value with a bottom - level direcoty name of ``GHub-ISMIP6-Forcing`` will **only** work for the + level directory name of ``GHub-ISMIP6-Forcing`` will **only** work for the 2100 CE projection protocol (``period_endyear`` config option below). The - same applied for a bottom level directory of ``ISMIP6-Projections-Forcing-2300`` + same applies for a bottom level directory of ``ISMIP6-Projections-Forcing-2300`` and the 2300 CE projection protocol. For example, if a user wants to process the atmosphere (SMB) @@ -146,12 +146,12 @@ config options All five test cases share some set of default config options under the section ``[ismip6_ais]`` and have separate config options for each test case: ``[ismip6_ais_atmosphere]``, ``[ismip6_ais_ocean_thermal]``, -``[ismip6_ais_ocean_basal]``, and ``[ismip6_ais_shelf_collpase``]. In the +``[ismip6_ais_ocean_basal]``, and ``[ismip6_ais_shelf_collapse``]. In the general config section (``[ismip6_ais]``), users need to supply base paths to input files and MALI mesh file, and MALI mesh name, as well as the model name, climate forcing scenario and the projection end year of the ISMIP6 forcing data, which can be chosen from the available options as given in the config file -(see the example file below.) In the ``ismip6_ais_atmosphere`` section, +(see the example file below). In the ``ismip6_ais_atmosphere`` section, users need to indicate ``True`` or ``False`` on whether to process the RACMO modern climatology (``True`` is required to run the ``process_smb_racmo`` step, which needs to be run before the ``process_smb`` step).