From ac8f7908606142dcd5448f6283da31ef930fcc4d Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Tue, 14 Jul 2026 09:48:46 -0700 Subject: [PATCH 1/9] Added files_for_e3sm step to hurricane test group --- .../hurricane/files_for_e3sm/__init__.py | 40 +++++ .../hurricane/files_for_e3sm/forcing_maps.py | 167 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 compass/ocean/tests/hurricane/files_for_e3sm/__init__.py create mode 100644 compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py new file mode 100644 index 0000000000..b978aefe56 --- /dev/null +++ b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py @@ -0,0 +1,40 @@ +import os + +from compass.ocean.tests.hurricane.configure import configure_hurricane +from compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps import ForcingMaps +from compass.testcase import TestCase + + +class FilesForE3SM(TestCase): + """ + A test case for assembling files needed for MPAS-Ocean forcing in E3SM + + Attributes + ---------- + mesh : compass.ocean.tests.hurricane.mesh.Mesh + The test case that produces the mesh for this run + """ + def __init__(self, test_group, mesh=None): + """ + Create test case for creating a global MPAS-Ocean mesh + + Parameters + ---------- + test_group : compass.ocean.tests.hurricane.Hurricane + The global ocean test group that this test case belongs to + + mesh : compass.ocean.tests.hurricane.mesh.Mesh, optional + The test case that produces the mesh for this run + """ + + name = 'files_for_e3sm' + subdir = os.path.join(mesh.mesh_name, name) + super().__init__(test_group=test_group, name=name, subdir=subdir) + self.mesh = mesh + self.add_step(ForcingMaps(test_case=self, mesh=mesh)) + + def configure(self): + """ + Modify the configuration options for this test case + """ + configure_hurricane(test_case=self, mesh=self.mesh) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py new file mode 100644 index 0000000000..31752dd448 --- /dev/null +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -0,0 +1,167 @@ +import os +import pathlib + +import numpy as np +import xarray as xr +from mpas_tools.cime.constants import constants +from mpas_tools.io import write_netcdf +from mpas_tools.logging import check_call +from pyremap import MpasCellMeshDescriptor + +from compass.io import symlink +from compass.parallel import run_command +from compass.step import Step + + +class ForcingMaps(Step): + """ + A step for building mapping files for remapping forcing files + to a global MPAS-Ocean mesh + + Attributes + ---------- + base_mesh_step : compass.mesh.spherical.SphericalBaseStep + The base mesh step containing input files to this step + """ + + def __init__(self, test_case, mesh): + """ + Create a new step + + Parameters + ---------- + test_case : compass.ocean.tests.global_ocean.mesh.Mesh + The test case this step belongs to + + mesh : compass.mesh + Mesh step + """ + super().__init__(test_case, name='forcing_maps') + self.mesh = mesh + + def setup(self): + """ + Set up the step in the work directory, including downloading any + dependencies. + """ + super().setup() + + mesh_name = self.mesh.name + mesh_path = self.mesh.steps['cull_mesh'].path + CFSR_LR = f'CFSR{_resolution(361, 720)}' + CFSR_HR = f'CFSR{_resolution(880, 1760)}' + self.add_input_file(filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') + self.add_output_file(filename=f'{CFSR_LR}.scrip.nc') + self.add_output_file(filename=f'{CFSR_HR}.scrip.nc') + self.add_output_file(filename=f'{mesh_name}.scrip.nc') + self.add_output_file(filename=f"map_{CFSR_HR}_to_{CFSR_LR}_bilinear.nc") + self.add_output_file(filename=f"map_{CFSR_LR}_to_{mesh_name}_bilinear.nc") + + self._get_resources() + + def constrain_resources(self, available_resources): + """ + Constrain ``cpus_per_task`` and ``ntasks`` based on the number of + cores available to this step + + Parameters + ---------- + available_resources : dict + The total number of cores available to the step + """ + self._get_resources() + super().constrain_resources(available_resources) + + def run(self): + """ + Run this step of the test case + """ + super().run() + + CFSR_LR = f'CFSR{_resolution(361, 720)}' + CFSR_HR = f'CFSR{_resolution(880, 1760)}' + self._scrip_file_gridded(self, 'CFSR', 361, 720, 'cap', 'grn_ctr') + self._scrip_file_gridded(self, 'CFSR', 880, 1760, 'gss', 'grn_ctr') + self._scrip_file_MPAS() + self._create_weights(CFSR_HR, CFSR_LR) + self._create_weights(CFSR_LR, self.mesh.name) + + def _get_resources(self): + """ + Get resources + """ + section = self.config['hurricane'] + self.ntasks = section.getint('init_ntasks') + self.min_tasks = section.getint('init_min_tasks') + self.openmp_threads = section.getint('init_threads') + + def _scrip_file_gridded(self, name, nlat, nlon, lat_typ, lon_typ): + """ + Create gridded SCRIP file + """ + resolution = _resolution(nlat, nlon) + grid_name = f'{name} {resolution} grid' + logger = self.logger + logger.info(f'Create gridded SCRIP file for {grid_name}') + + args = [ + 'ncremap', '-G', + f"ttl='{grid_name}'" + \ + f'#latlon={nlat},{nlon}#lat_typ={lat_typ}#lon_typ={lon_typ}', + '-g', f'{name}{resolution}.scrip.nc', + ] + check_call(args, logger) + + logger.info(' Done.') + + def _scrip_file_MPAS(self): + """ + Create SCRIP file from MPAS mesh file. + """ + mesh_name = self.mesh.name + logger = self.logger + logger.info(f'Create MPAS SCRIP file for {mesh_name}') + + descriptor = MpasCellMeshDescriptor( + filename='base_mesh.nc', + mesh_name=mesh_name, + ) + descriptor.to_scrip(f'{mesh_name}.scrip.nc') + + logger.info(' Done.') + + def _create_weights(self, src, tgt): + """ + Create mapping weights file using ESMF_RegridWeightGen + """ + logger = self.logger + logger.info(f'Create {src}_to_{tgt} weights file') + + args = [ + 'ESMF_RegridWeightGen', + '--source', f'{src}.scrip.nc', + '--destination', f'{tgt}.scrip.nc', + '--weight', f'map_{src}_to_{tgt}_bilinear.nc', + '--method', 'bilinear', + '--netcdf4', + '--ignore_unmapped', + ] + + run_command( + args, self.cpus_per_task, self.ntasks, + self.openmp_threads, self.config, self.logger, + ) + + logger.info(' Done.') + + +def _resolution(nlat, nlon): + """ + Resolution string constructor + """ + + # Get resolution + res = [str(round(N / n, 1)) for N, n in zip([180, 360], [nlat, nlon])] + res = f"{'x'.join(res)}degree" + + return res \ No newline at end of file From 3f8925ff4ea1035d5f70f70c2fabc7b4f769f8e6 Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Tue, 21 Jul 2026 22:01:14 -0700 Subject: [PATCH 2/9] Add files_for_e3sm step in hurricane __init__ and fix CFSR mapping --- compass/ocean/tests/hurricane/__init__.py | 6 ++++++ .../tests/hurricane/files_for_e3sm/forcing_maps.py | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compass/ocean/tests/hurricane/__init__.py b/compass/ocean/tests/hurricane/__init__.py index 4601e44914..99b5116e61 100644 --- a/compass/ocean/tests/hurricane/__init__.py +++ b/compass/ocean/tests/hurricane/__init__.py @@ -1,6 +1,7 @@ from compass.ocean.tests.hurricane.forward import Forward from compass.ocean.tests.hurricane.init import Init from compass.ocean.tests.hurricane.mesh import Mesh +from compass.ocean.tests.hurricane.files_for_e3sm import FilesForE3SM from compass.testgroup import TestGroup @@ -43,3 +44,8 @@ def __init__(self, mpas_core): use_lts=use_lts, wetdry=wetdry) self.add_test_case(forward) + + if not use_lts: + files_for_e3sm = FilesForE3SM(test_group=self, + mesh=mesh) + self.add_test_case(files_for_e3sm) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py index 31752dd448..44172916f7 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -54,8 +54,8 @@ def setup(self): self.add_output_file(filename=f'{CFSR_LR}.scrip.nc') self.add_output_file(filename=f'{CFSR_HR}.scrip.nc') self.add_output_file(filename=f'{mesh_name}.scrip.nc') - self.add_output_file(filename=f"map_{CFSR_HR}_to_{CFSR_LR}_bilinear.nc") - self.add_output_file(filename=f"map_{CFSR_LR}_to_{mesh_name}_bilinear.nc") + self.add_output_file(filename=f"map_{CFSR_LR}_to_{CFSR_HR}_bilinear.nc") + self.add_output_file(filename=f"map_{CFSR_HR}_to_{mesh_name}_bilinear.nc") self._get_resources() @@ -80,11 +80,11 @@ def run(self): CFSR_LR = f'CFSR{_resolution(361, 720)}' CFSR_HR = f'CFSR{_resolution(880, 1760)}' - self._scrip_file_gridded(self, 'CFSR', 361, 720, 'cap', 'grn_ctr') - self._scrip_file_gridded(self, 'CFSR', 880, 1760, 'gss', 'grn_ctr') + self._scrip_file_gridded('CFSR', 361, 720, 'gss', 'grn_ctr') # Check this + self._scrip_file_gridded('CFSR', 880, 1760, 'gss', 'grn_ctr') self._scrip_file_MPAS() - self._create_weights(CFSR_HR, CFSR_LR) - self._create_weights(CFSR_LR, self.mesh.name) + self._create_weights(CFSR_LR, CFSR_HR) + self._create_weights(CFSR_HR, self.mesh.name) def _get_resources(self): """ From b6ab95b425f898396d5ec635f77f8058e73ebccd Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Sun, 9 Aug 2026 10:05:14 -0700 Subject: [PATCH 3/9] Working version of hurricane/files_for_e3sm/forcing_maps --- .../hurricane/files_for_e3sm/__init__.py | 2 +- .../hurricane/files_for_e3sm/forcing_maps.py | 108 ++++++++---------- compass/ocean/tests/hurricane/hurricane.cfg | 6 + 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py index b978aefe56..1fa7be00b0 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py @@ -31,7 +31,7 @@ def __init__(self, test_group, mesh=None): subdir = os.path.join(mesh.mesh_name, name) super().__init__(test_group=test_group, name=name, subdir=subdir) self.mesh = mesh - self.add_step(ForcingMaps(test_case=self, mesh=mesh)) + self.add_step(ForcingMaps(test_case=self)) def configure(self): """ diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py index 44172916f7..a1e47966a3 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -1,14 +1,6 @@ -import os -import pathlib - -import numpy as np -import xarray as xr -from mpas_tools.cime.constants import constants -from mpas_tools.io import write_netcdf from mpas_tools.logging import check_call from pyremap import MpasCellMeshDescriptor -from compass.io import symlink from compass.parallel import run_command from compass.step import Step @@ -20,24 +12,21 @@ class ForcingMaps(Step): Attributes ---------- - base_mesh_step : compass.mesh.spherical.SphericalBaseStep + mesh : compass.mesh.spherical.SphericalBaseStep The base mesh step containing input files to this step """ - def __init__(self, test_case, mesh): + def __init__(self, test_case): """ Create a new step Parameters ---------- - test_case : compass.ocean.tests.global_ocean.mesh.Mesh + test_case : compass.ocean.tests.hurricane.files_for_e3sm.FilesForE3SM The test case this step belongs to - - mesh : compass.mesh - Mesh step """ super().__init__(test_case, name='forcing_maps') - self.mesh = mesh + self.mesh = test_case.mesh def setup(self): """ @@ -45,17 +34,13 @@ def setup(self): dependencies. """ super().setup() - - mesh_name = self.mesh.name + mesh_path = self.mesh.steps['cull_mesh'].path - CFSR_LR = f'CFSR{_resolution(361, 720)}' - CFSR_HR = f'CFSR{_resolution(880, 1760)}' self.add_input_file(filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') - self.add_output_file(filename=f'{CFSR_LR}.scrip.nc') - self.add_output_file(filename=f'{CFSR_HR}.scrip.nc') - self.add_output_file(filename=f'{mesh_name}.scrip.nc') - self.add_output_file(filename=f"map_{CFSR_LR}_to_{CFSR_HR}_bilinear.nc") - self.add_output_file(filename=f"map_{CFSR_HR}_to_{mesh_name}_bilinear.nc") + self.add_output_file(filename='map_atm_to_ocn_bilinear.nc') + self.add_output_file(filename='map_atm_to_ocn_conserve.nc') + self.add_output_file(filename='map_ocn_to_atm_bilinear.nc') + self.add_output_file(filename='map_ocn_to_atm_conserve.nc') self._get_resources() @@ -78,14 +63,13 @@ def run(self): """ super().run() - CFSR_LR = f'CFSR{_resolution(361, 720)}' - CFSR_HR = f'CFSR{_resolution(880, 1760)}' - self._scrip_file_gridded('CFSR', 361, 720, 'gss', 'grn_ctr') # Check this - self._scrip_file_gridded('CFSR', 880, 1760, 'gss', 'grn_ctr') + self._scrip_file_gridded() self._scrip_file_MPAS() - self._create_weights(CFSR_LR, CFSR_HR) - self._create_weights(CFSR_HR, self.mesh.name) - + self._create_weights('atm', 'ocn', 'bilinear') + self._create_weights('atm', 'ocn', 'conserve') + self._create_weights('ocn', 'atm', 'bilinear') + self._create_weights('ocn', 'atm', 'conserve') + def _get_resources(self): """ Get resources @@ -94,21 +78,35 @@ def _get_resources(self): self.ntasks = section.getint('init_ntasks') self.min_tasks = section.getint('init_min_tasks') self.openmp_threads = section.getint('init_threads') - - def _scrip_file_gridded(self, name, nlat, nlon, lat_typ, lon_typ): + + def _scrip_file_gridded(self): """ - Create gridded SCRIP file + Create gridded SCRIP file for atm """ - resolution = _resolution(nlat, nlon) - grid_name = f'{name} {resolution} grid' logger = self.logger - logger.info(f'Create gridded SCRIP file for {grid_name}') - + grid_name = self.config.get('files_for_e3sm', 'atm_grid') + logger.info(f'Create gridded SCRIP file for {grid_name} grid') + + grids = { + 'T382': { + 'nlat': 576, + 'nlon': 1152, + 'lat_typ': 'gss', + 'lon_typ': 'grn_ctr', + }, + 'T574': { + 'nlat': 880, + 'nlon': 1760, + 'lat_typ': 'gss', + 'lon_typ': 'grn_ctr', + }, + } + + nlat, nlon, lat_typ, lon_typ = grids[grid_name].values() args = [ - 'ncremap', '-G', - f"ttl='{grid_name}'" + \ - f'#latlon={nlat},{nlon}#lat_typ={lat_typ}#lon_typ={lon_typ}', - '-g', f'{name}{resolution}.scrip.nc', + 'ncremap', + '-G', f'latlon={nlat},{nlon}#lat_typ={lat_typ}#lon_typ={lon_typ}', + '-g', 'atm.scrip.nc', ] check_call(args, logger) @@ -118,19 +116,19 @@ def _scrip_file_MPAS(self): """ Create SCRIP file from MPAS mesh file. """ - mesh_name = self.mesh.name + mesh_name = self.mesh.mesh_name logger = self.logger - logger.info(f'Create MPAS SCRIP file for {mesh_name}') + logger.info(f'Create MPAS SCRIP file for {mesh_name} mesh') descriptor = MpasCellMeshDescriptor( - filename='base_mesh.nc', + filename='mesh.nc', mesh_name=mesh_name, ) - descriptor.to_scrip(f'{mesh_name}.scrip.nc') + descriptor.to_scrip('ocn.scrip.nc') logger.info(' Done.') - def _create_weights(self, src, tgt): + def _create_weights(self, src, tgt, method): """ Create mapping weights file using ESMF_RegridWeightGen """ @@ -141,8 +139,8 @@ def _create_weights(self, src, tgt): 'ESMF_RegridWeightGen', '--source', f'{src}.scrip.nc', '--destination', f'{tgt}.scrip.nc', - '--weight', f'map_{src}_to_{tgt}_bilinear.nc', - '--method', 'bilinear', + '--weight', f'map_{src}_to_{tgt}_{method}.nc', + '--method', method, '--netcdf4', '--ignore_unmapped', ] @@ -153,15 +151,3 @@ def _create_weights(self, src, tgt): ) logger.info(' Done.') - - -def _resolution(nlat, nlon): - """ - Resolution string constructor - """ - - # Get resolution - res = [str(round(N / n, 1)) for N, n in zip([180, 360], [nlat, nlon])] - res = f"{'x'.join(res)}degree" - - return res \ No newline at end of file diff --git a/compass/ocean/tests/hurricane/hurricane.cfg b/compass/ocean/tests/hurricane/hurricane.cfg index 2f1678f65c..2825aba332 100644 --- a/compass/ocean/tests/hurricane/hurricane.cfg +++ b/compass/ocean/tests/hurricane/hurricane.cfg @@ -8,3 +8,9 @@ convert_culled_mesh_to_cdf5 = False latitude_threshold = 43.0 # Maximum number of sweeps to search for land-locked cells sweep_count = 20 + +# config options related to regridding and domain files for E3SM configurations +[files_for_e3sm] + +# Grid for data atmosphere (currently T382 and T574 supported) +atm_grid = T574 From 82aa43521ad326233d825bdc1b6e42d432cb7091 Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Wed, 2 Sep 2026 18:03:44 -0700 Subject: [PATCH 4/9] Working version of hurricane/files_for_e3sm/domain_files --- .../hurricane/files_for_e3sm/__init__.py | 2 + .../hurricane/files_for_e3sm/domain_files.py | 86 +++++++++++++++++++ compass/ocean/tests/hurricane/hurricane.cfg | 3 + deploy/pixi.toml.j2 | 1 + 4 files changed, 92 insertions(+) create mode 100644 compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py index 1fa7be00b0..9523b5fbd0 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py @@ -2,6 +2,7 @@ from compass.ocean.tests.hurricane.configure import configure_hurricane from compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps import ForcingMaps +from compass.ocean.tests.hurricane.files_for_e3sm.domain_files import DomainFiles from compass.testcase import TestCase @@ -32,6 +33,7 @@ def __init__(self, test_group, mesh=None): super().__init__(test_group=test_group, name=name, subdir=subdir) self.mesh = mesh self.add_step(ForcingMaps(test_case=self)) + self.add_step(DomainFiles(test_case=self)) def configure(self): """ diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py new file mode 100644 index 0000000000..42068d347c --- /dev/null +++ b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py @@ -0,0 +1,86 @@ +import os +from datetime import datetime + +from mpas_tools.logging import check_call + +from compass.step import Step + + +class DomainFiles(Step): + """ + A step for building domain files for meshes tailored to hurricane-induced + coastal flooding + + Attributes + ---------- + mesh : compass.mesh.spherical.SphericalBaseStep + The base mesh step containing input files to this step + """ + + def __init__(self, test_case): + """ + Create a new step + + Parameters + ---------- + test_case : compass.ocean.tests.hurricane.files_for_e3sm.FilesForE3SM + The test case this step belongs to + """ + super().__init__(test_case, name='domain_files') + self.mesh = test_case.mesh + + def setup(self): + """ + Set up the step in the work directory, including downloading any + dependencies. + """ + super().setup() + date_stamp = datetime.now().strftime('%Y%m%d') + self.date_stamp = date_stamp + + fname = 'map_ocn_to_atm_conserve.nc' + target = os.path.join(self.test_case.steps['forcing_maps'].path, fname) + self.add_input_file(filename=fname, work_dir_target=target) + + atm_grid = self.config.get('files_for_e3sm', 'atm_grid') + ocn_grid = self.mesh.mesh_name + self.add_output_file( + filename=f'domain.lnd.{atm_grid}_{ocn_grid}.{date_stamp}.nc') + self.add_output_file( + filename=f'domain.ocn.{atm_grid}_{ocn_grid}.{date_stamp}.nc') + self.add_output_file( + filename=f'domain.ocn.{ocn_grid}.{date_stamp}.nc') + + def run(self): + """ + Run this step of the test case + """ + super().run() + self._domain_files() + + def _domain_files(self): + """ + Create domain files + """ + + section = self.config['files_for_e3sm'] + domain_files_exe = section.get('domain_files_exe') + atm_grid = section.get('atm_grid') + ocn_grid = self.mesh.mesh_name + + logger = self.logger + logger.info( + f'Create domain files for {atm_grid}_{ocn_grid} grid pair.' + ) + + args = [ + 'python', domain_files_exe, + '--date-stamp', self.date_stamp, + '-m', 'map_ocn_to_atm_conserve.nc', + '-o', ocn_grid, + '-l', atm_grid, + ] + check_call(args, logger) + + logger.info(' Done.') + diff --git a/compass/ocean/tests/hurricane/hurricane.cfg b/compass/ocean/tests/hurricane/hurricane.cfg index 2825aba332..0089bd5111 100644 --- a/compass/ocean/tests/hurricane/hurricane.cfg +++ b/compass/ocean/tests/hurricane/hurricane.cfg @@ -14,3 +14,6 @@ sweep_count = 20 # Grid for data atmosphere (currently T382 and T574 supported) atm_grid = T574 + +# the relative or absolute path to the `generate_domain_files_E3SM.py` utility +domain_files_exe = ${paths:compass_branch}/E3SM-Project/tools/generate_domain_files/generate_domain_files_E3SM.py diff --git a/deploy/pixi.toml.j2 b/deploy/pixi.toml.j2 index 88b9049075..66860b9653 100644 --- a/deploy/pixi.toml.j2 +++ b/deploy/pixi.toml.j2 @@ -40,6 +40,7 @@ mosaic = "*" mpas_tools = "{{ mpas_tools }}.*" nco = "*" netcdf4 = {version = "*", build = "nompi_*"} +numba = "*" numpy = ">=2.0,<3.0" {%- if platform == 'linux-64' %} otps = "{{ otps }}.*" From de002a76022ed4cb3eeaff00fdd00554d37331e3 Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Wed, 9 Sep 2026 17:12:58 -0700 Subject: [PATCH 5/9] Add support for mbtempest weights file generation --- .../hurricane/files_for_e3sm/domain_files.py | 4 +- .../hurricane/files_for_e3sm/forcing_maps.py | 83 +++++++++++++++---- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py index 42068d347c..810991597b 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py @@ -38,7 +38,7 @@ def setup(self): date_stamp = datetime.now().strftime('%Y%m%d') self.date_stamp = date_stamp - fname = 'map_ocn_to_atm_conserve.nc' + fname = 'map_ocn_to_atm_traave.nc' target = os.path.join(self.test_case.steps['forcing_maps'].path, fname) self.add_input_file(filename=fname, work_dir_target=target) @@ -76,7 +76,7 @@ def _domain_files(self): args = [ 'python', domain_files_exe, '--date-stamp', self.date_stamp, - '-m', 'map_ocn_to_atm_conserve.nc', + '-m', 'map_ocn_to_atm_traave.nc', '-o', ocn_grid, '-l', atm_grid, ] diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py index a1e47966a3..c52124b199 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -37,10 +37,10 @@ def setup(self): mesh_path = self.mesh.steps['cull_mesh'].path self.add_input_file(filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') - self.add_output_file(filename='map_atm_to_ocn_bilinear.nc') - self.add_output_file(filename='map_atm_to_ocn_conserve.nc') - self.add_output_file(filename='map_ocn_to_atm_bilinear.nc') - self.add_output_file(filename='map_ocn_to_atm_conserve.nc') + self.add_output_file(filename='map_atm_to_ocn_trbilin.nc') + self.add_output_file(filename='map_atm_to_ocn_traave.nc') + self.add_output_file(filename='map_ocn_to_atm_trbilin.nc') + self.add_output_file(filename='map_ocn_to_atm_traave.nc') self._get_resources() @@ -65,10 +65,12 @@ def run(self): self._scrip_file_gridded() self._scrip_file_MPAS() - self._create_weights('atm', 'ocn', 'bilinear') - self._create_weights('atm', 'ocn', 'conserve') - self._create_weights('ocn', 'atm', 'bilinear') - self._create_weights('ocn', 'atm', 'conserve') + self._partition_scrip_file('atm') + self._partition_scrip_file('ocn') + self._create_weights('atm', 'ocn', 'trbilin') + self._create_weights('atm', 'ocn', 'traave') + self._create_weights('ocn', 'atm', 'trbilin') + self._create_weights('ocn', 'atm', 'traave') def _get_resources(self): """ @@ -128,26 +130,71 @@ def _scrip_file_MPAS(self): logger.info(' Done.') - def _create_weights(self, src, tgt, method): + def _partition_scrip_file(self, src): """ - Create mapping weights file using ESMF_RegridWeightGen + Partition SCRIP file for parallel mbtempest use """ logger = self.logger - logger.info(f'Create {src}_to_{tgt} weights file') + logger.info(f'Partition SCRIP file for {src}') + # Convert source SCRIP to mbtempest args = [ - 'ESMF_RegridWeightGen', - '--source', f'{src}.scrip.nc', - '--destination', f'{tgt}.scrip.nc', - '--weight', f'map_{src}_to_{tgt}_{method}.nc', - '--method', method, - '--netcdf4', - '--ignore_unmapped', + 'mbconvert', '-B', + f'{src}.scrip.nc', + f'{src}.scrip.h5m', ] + # run in "parallel" with one task and one thread for Intel-MPI support + run_command(args, 1, 1, 1, self.config, logger) + + # Partition source SCRIP + args = [ + 'mbpart', f'{self.ntasks}', + '-z', 'RCB', + f'{src}.scrip.h5m', + f'{src}.scrip.p{self.ntasks}.h5m', + ] + # run in "parallel" with one task and one thread for Intel-MPI support + run_command(args, 1, 1, 1, self.config, logger) + + logger.info(' Done.') + def _create_weights(self, src, tgt, method): + """ + Create mapping weights file using TempestRemap + """ + logger = self.logger + logger.info('Create weights file') + + if method not in ['traave', 'trbilin']: + raise ValueError(f'Unsupported regridding method {method}') + + src_file = f'{src}.scrip.p{self.ntasks}.h5m' + tgt_file = f'{tgt}.scrip.p{self.ntasks}.h5m' + map_file = f'map_{src}_to_{tgt}_{method}.nc' + + # Build weights file + args = [ + 'mbtempest', '--type', '5', '--weights', + '--load', src_file, + '--load', tgt_file, + '--file', map_file, + '--method', 'fv', '--order', '1', + '--method', 'fv', '--order', '1', + ] + if method == 'trbilin': + args.extend(['--fvmethod', 'bilin']) run_command( args, self.cpus_per_task, self.ntasks, self.openmp_threads, self.config, self.logger, ) + # Add attributes required by `generate_domain_files_E3SM.py` + args = [ + 'ncatted', '-O', + '-a', f'grid_file_src,global,o,c,{src_file}', + '-a', f'grid_file_dst,global,o,c,{tgt_file}', + map_file, + ] + check_call(args, logger) + logger.info(' Done.') From 66448a8f6655a0e8e31bc3738ffb5b79185408aa Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Thu, 10 Sep 2026 15:14:49 -0700 Subject: [PATCH 6/9] Make output files conform to naming conventions --- .../hurricane/files_for_e3sm/__init__.py | 2 + .../hurricane/files_for_e3sm/domain_files.py | 24 ++++---- .../hurricane/files_for_e3sm/forcing_maps.py | 59 +++++++++++-------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py index 9523b5fbd0..cef51164cb 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py @@ -1,4 +1,5 @@ import os +from datetime import datetime from compass.ocean.tests.hurricane.configure import configure_hurricane from compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps import ForcingMaps @@ -32,6 +33,7 @@ def __init__(self, test_group, mesh=None): subdir = os.path.join(mesh.mesh_name, name) super().__init__(test_group=test_group, name=name, subdir=subdir) self.mesh = mesh + self.creation_date = datetime.now().strftime('%Y%m%d') self.add_step(ForcingMaps(test_case=self)) self.add_step(DomainFiles(test_case=self)) diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py index 810991597b..b8c7cec94f 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py @@ -1,5 +1,4 @@ import os -from datetime import datetime from mpas_tools.logging import check_call @@ -28,6 +27,7 @@ def __init__(self, test_case): """ super().__init__(test_case, name='domain_files') self.mesh = test_case.mesh + self.creation_date = test_case.creation_date def setup(self): """ @@ -35,21 +35,23 @@ def setup(self): dependencies. """ super().setup() - date_stamp = datetime.now().strftime('%Y%m%d') - self.date_stamp = date_stamp - - fname = 'map_ocn_to_atm_traave.nc' - target = os.path.join(self.test_case.steps['forcing_maps'].path, fname) - self.add_input_file(filename=fname, work_dir_target=target) atm_grid = self.config.get('files_for_e3sm', 'atm_grid') ocn_grid = self.mesh.mesh_name + creation_date = self.creation_date + + map_file_path = os.path.join( + self.test_case.steps['forcing_maps'].path, + f'map_{ocn_grid}_to_{atm_grid}_traave.{creation_date}.nc', + ) + self.add_input_file( + filename='map_ocn_to_atm_traave.nc', work_dir_target=map_file_path) self.add_output_file( - filename=f'domain.lnd.{atm_grid}_{ocn_grid}.{date_stamp}.nc') + filename=f'domain.lnd.{atm_grid}_{ocn_grid}.{creation_date}.nc') self.add_output_file( - filename=f'domain.ocn.{atm_grid}_{ocn_grid}.{date_stamp}.nc') + filename=f'domain.ocn.{atm_grid}_{ocn_grid}.{creation_date}.nc') self.add_output_file( - filename=f'domain.ocn.{ocn_grid}.{date_stamp}.nc') + filename=f'domain.ocn.{ocn_grid}.{creation_date}.nc') def run(self): """ @@ -75,7 +77,7 @@ def _domain_files(self): args = [ 'python', domain_files_exe, - '--date-stamp', self.date_stamp, + '--date-stamp', self.creation_date, '-m', 'map_ocn_to_atm_traave.nc', '-o', ocn_grid, '-l', atm_grid, diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py index c52124b199..ecf0c8ee7e 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -27,6 +27,7 @@ def __init__(self, test_case): """ super().__init__(test_case, name='forcing_maps') self.mesh = test_case.mesh + self.creation_date = test_case.creation_date def setup(self): """ @@ -35,12 +36,21 @@ def setup(self): """ super().setup() + atm_grid = self.config.get('files_for_e3sm', 'atm_grid') + ocn_grid = self.mesh.mesh_name mesh_path = self.mesh.steps['cull_mesh'].path - self.add_input_file(filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') - self.add_output_file(filename='map_atm_to_ocn_trbilin.nc') - self.add_output_file(filename='map_atm_to_ocn_traave.nc') - self.add_output_file(filename='map_ocn_to_atm_trbilin.nc') - self.add_output_file(filename='map_ocn_to_atm_traave.nc') + creation_date = self.creation_date + + self.add_input_file( + filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') + self.add_output_file( + filename=f'map_{atm_grid}_to_{ocn_grid}_trbilin.{creation_date}.nc') + self.add_output_file( + filename=f'map_{atm_grid}_to_{ocn_grid}_traave.{creation_date}.nc') + self.add_output_file( + filename=f'map_{ocn_grid}_to_{atm_grid}_trbilin.{creation_date}.nc') + self.add_output_file( + filename=f'map_{ocn_grid}_to_{atm_grid}_traave.{creation_date}.nc') self._get_resources() @@ -63,14 +73,17 @@ def run(self): """ super().run() + atm_grid = self.config.get('files_for_e3sm', 'atm_grid') + ocn_grid = self.mesh.mesh_name + self._scrip_file_gridded() self._scrip_file_MPAS() - self._partition_scrip_file('atm') - self._partition_scrip_file('ocn') - self._create_weights('atm', 'ocn', 'trbilin') - self._create_weights('atm', 'ocn', 'traave') - self._create_weights('ocn', 'atm', 'trbilin') - self._create_weights('ocn', 'atm', 'traave') + self._partition_scrip_file(atm_grid) + self._partition_scrip_file(ocn_grid) + self._create_weights(atm_grid, ocn_grid, 'trbilin') + self._create_weights(atm_grid, ocn_grid, 'traave') + self._create_weights(ocn_grid, atm_grid, 'trbilin') + self._create_weights(ocn_grid, atm_grid, 'traave') def _get_resources(self): """ @@ -108,7 +121,7 @@ def _scrip_file_gridded(self): args = [ 'ncremap', '-G', f'latlon={nlat},{nlon}#lat_typ={lat_typ}#lon_typ={lon_typ}', - '-g', 'atm.scrip.nc', + '-g', f'{grid_name}.scrip.nc', ] check_call(args, logger) @@ -118,30 +131,30 @@ def _scrip_file_MPAS(self): """ Create SCRIP file from MPAS mesh file. """ - mesh_name = self.mesh.mesh_name + grid_name = self.mesh.mesh_name logger = self.logger - logger.info(f'Create MPAS SCRIP file for {mesh_name} mesh') + logger.info(f'Create MPAS SCRIP file for {grid_name} mesh') descriptor = MpasCellMeshDescriptor( filename='mesh.nc', - mesh_name=mesh_name, + mesh_name=grid_name, ) - descriptor.to_scrip('ocn.scrip.nc') + descriptor.to_scrip(f'{grid_name}.scrip.nc') logger.info(' Done.') - def _partition_scrip_file(self, src): + def _partition_scrip_file(self, grid_name): """ Partition SCRIP file for parallel mbtempest use """ logger = self.logger - logger.info(f'Partition SCRIP file for {src}') + logger.info(f'Partition SCRIP file for {grid_name}') # Convert source SCRIP to mbtempest args = [ 'mbconvert', '-B', - f'{src}.scrip.nc', - f'{src}.scrip.h5m', + f'{grid_name}.scrip.nc', + f'{grid_name}.scrip.h5m', ] # run in "parallel" with one task and one thread for Intel-MPI support run_command(args, 1, 1, 1, self.config, logger) @@ -150,8 +163,8 @@ def _partition_scrip_file(self, src): args = [ 'mbpart', f'{self.ntasks}', '-z', 'RCB', - f'{src}.scrip.h5m', - f'{src}.scrip.p{self.ntasks}.h5m', + f'{grid_name}.scrip.h5m', + f'{grid_name}.scrip.p{self.ntasks}.h5m', ] # run in "parallel" with one task and one thread for Intel-MPI support run_command(args, 1, 1, 1, self.config, logger) @@ -170,7 +183,7 @@ def _create_weights(self, src, tgt, method): src_file = f'{src}.scrip.p{self.ntasks}.h5m' tgt_file = f'{tgt}.scrip.p{self.ntasks}.h5m' - map_file = f'map_{src}_to_{tgt}_{method}.nc' + map_file = f'map_{src}_to_{tgt}_{method}.{self.creation_date}.nc' # Build weights file args = [ From 6a797971058e301343de697e3652834c034b389d Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Thu, 10 Sep 2026 16:26:27 -0700 Subject: [PATCH 7/9] Update docs --- docs/developers_guide/ocean/api.rst | 10 +++++ .../ocean/test_groups/hurricane.rst | 17 +++++++++ .../ocean/test_groups/hurricane.rst | 37 ++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/developers_guide/ocean/api.rst b/docs/developers_guide/ocean/api.rst index d91d1a76ac..7af90220f3 100644 --- a/docs/developers_guide/ocean/api.rst +++ b/docs/developers_guide/ocean/api.rst @@ -438,6 +438,16 @@ test cases and steps analysis.Analysis.plot_hwm analysis.Analysis.run + files_for_e3sm.FilesForE3SM + files_for_e3sm.FilesForE3SM.configure + files_for_e3sm.FilesForE3SM.run + files_for_e3sm.forcing_maps.ForcingMaps + files_for_e3sm.forcing_maps.ForcingMaps.setup + files_for_e3sm.forcing_maps.ForcingMaps.run + files_for_e3sm.domain_files.DomainFiles + files_for_e3sm.domain_files.DomainFiles.setup + files_for_e3sm.domain_files.DomainFiles.run + lts.mesh.lts_regions.LTSRegionsStep lts.mesh.lts_regions.LTSRegionsStep.setup lts.mesh.lts_regions.LTSRegionsStep.run diff --git a/docs/developers_guide/ocean/test_groups/hurricane.rst b/docs/developers_guide/ocean/test_groups/hurricane.rst index de81c346bf..60cbd813d0 100644 --- a/docs/developers_guide/ocean/test_groups/hurricane.rst +++ b/docs/developers_guide/ocean/test_groups/hurricane.rst @@ -276,3 +276,20 @@ The class :py:class:`compass.ocean.tests.hurricane.analysis.Analysis` defines a step to generate validation plots comparing sea surface height timeseries between modeled and observed data at several different stations. Both NOAA and USGS observations are plotted. + +.. _dev_ocean_hurricane_files_for_e3sm: + +files for e3sm test case +^^^^^^^^^^^^^^^^^^^^^^^^ +The ``files_for_e3sm`` test case builds the domain files and data atmosphere +regridding files needed to run MPAS-Ocean hurricane configurations in E3SM. + +forcing maps +"""""""""""" +The class :py:class:`compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps.ForcingMaps` +builds the regridding files between the ocean mesh and the data atmosphere grid. + +domain files +"""""""""""" +The class :py:class:`compass.ocean.tests.hurricane.files_for_e3sm.domain_files.DomainFiles` +builds the domain files needed to run MPAS-Ocean hurricane configurations in E3SM. diff --git a/docs/users_guide/ocean/test_groups/hurricane.rst b/docs/users_guide/ocean/test_groups/hurricane.rst index a626d4b55f..d3dc5cd95a 100644 --- a/docs/users_guide/ocean/test_groups/hurricane.rst +++ b/docs/users_guide/ocean/test_groups/hurricane.rst @@ -3,8 +3,8 @@ hurricane ========= -The ``ocean/hurricane`` test group defines meshes, -initial conditions, forward simulations, and validation for global, +The ``ocean/hurricane`` test group defines meshes, initial conditions, forward +simulations, validation, and E3SM domain and mapping file creation for global, realistic ocean domains with regional refinement. These simulations are forced with time-varying atmospheric reanalysis data for tropical cyclone events and tides. The meshes contain refined regions in order @@ -110,6 +110,16 @@ Note that meshes and test cases may modify these options, as noted below. plot_station_dems = False + + # config options related to regridding and domain files for E3SM configurations + [files_for_e3sm] + + # Grid for data atmosphere (currently T382 and T574 supported) + atm_grid = T574 + + # the relative or absolute path to the `generate_domain_files_E3SM.py` utility + domain_files_exe = ${paths:compass_branch}/E3SM-Project/tools/generate_domain_files/generate_domain_files_E3SM.py + .. _ocean_hurricane_meshes: Meshes @@ -281,3 +291,26 @@ are used for the validation. .. image:: images/hurricane_subgrid_spatialerror.png :width: 800 px :align: center + +.. _ocean_hurricane_files_for_e3sm: + +files for e3sm test case +^^^^^^^^^^^^^^^^^^^^^^^^ +The ``files_for_e3sm`` test case builds the domain files and data atmosphere +regridding files needed to run MPAS-Ocean hurricane configurations in E3SM. +The LTS3 and FB-LTS meshes are not currently supported. + +forcing maps step +""""""""""""""""" +The ``forcing_maps`` step builds the regridding files between the ocean mesh +and the data atmosphere grid. Currently two data atmosphere grids are supported, +T382 and T574. These correspond to the NCEP Climate Forecast System Reanalysis +(CFSR) and Climate Forecast System Version 2 (CFSv2), respectively. The grid is +specified in the ``[files_for_e3sm]`` config options. + +domain files step +""""""""""""""""" +The ``domain_files`` step builds the ocean and atmosphere domain files needed +to define the ocean and data atmosphere grids in an E3SM configuration. The +step uses the domain file creator maintained in the tools directory of the +E3SM submodule. From 2d389035c8057ca21308cab1a61125731827975c Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Thu, 10 Sep 2026 16:29:41 -0700 Subject: [PATCH 8/9] Run pre-commit linter --- compass/ocean/tests/hurricane/__init__.py | 2 +- .../ocean/tests/hurricane/files_for_e3sm/__init__.py | 8 ++++++-- .../tests/hurricane/files_for_e3sm/domain_files.py | 1 - .../tests/hurricane/files_for_e3sm/forcing_maps.py | 10 +++++----- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/compass/ocean/tests/hurricane/__init__.py b/compass/ocean/tests/hurricane/__init__.py index 99b5116e61..0a2eb79245 100644 --- a/compass/ocean/tests/hurricane/__init__.py +++ b/compass/ocean/tests/hurricane/__init__.py @@ -1,7 +1,7 @@ +from compass.ocean.tests.hurricane.files_for_e3sm import FilesForE3SM from compass.ocean.tests.hurricane.forward import Forward from compass.ocean.tests.hurricane.init import Init from compass.ocean.tests.hurricane.mesh import Mesh -from compass.ocean.tests.hurricane.files_for_e3sm import FilesForE3SM from compass.testgroup import TestGroup diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py index cef51164cb..ca00690e5e 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/__init__.py @@ -2,8 +2,12 @@ from datetime import datetime from compass.ocean.tests.hurricane.configure import configure_hurricane -from compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps import ForcingMaps -from compass.ocean.tests.hurricane.files_for_e3sm.domain_files import DomainFiles +from compass.ocean.tests.hurricane.files_for_e3sm.domain_files import ( + DomainFiles, +) +from compass.ocean.tests.hurricane.files_for_e3sm.forcing_maps import ( + ForcingMaps, +) from compass.testcase import TestCase diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py index b8c7cec94f..eaa1737ae5 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/domain_files.py @@ -85,4 +85,3 @@ def _domain_files(self): check_call(args, logger) logger.info(' Done.') - diff --git a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py index ecf0c8ee7e..dbd0461561 100644 --- a/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py +++ b/compass/ocean/tests/hurricane/files_for_e3sm/forcing_maps.py @@ -39,18 +39,18 @@ def setup(self): atm_grid = self.config.get('files_for_e3sm', 'atm_grid') ocn_grid = self.mesh.mesh_name mesh_path = self.mesh.steps['cull_mesh'].path - creation_date = self.creation_date + create_date = self.creation_date self.add_input_file( filename='mesh.nc', work_dir_target=f'{mesh_path}/culled_mesh.nc') self.add_output_file( - filename=f'map_{atm_grid}_to_{ocn_grid}_trbilin.{creation_date}.nc') + filename=f'map_{atm_grid}_to_{ocn_grid}_trbilin.{create_date}.nc') self.add_output_file( - filename=f'map_{atm_grid}_to_{ocn_grid}_traave.{creation_date}.nc') + filename=f'map_{atm_grid}_to_{ocn_grid}_traave.{create_date}.nc') self.add_output_file( - filename=f'map_{ocn_grid}_to_{atm_grid}_trbilin.{creation_date}.nc') + filename=f'map_{ocn_grid}_to_{atm_grid}_trbilin.{create_date}.nc') self.add_output_file( - filename=f'map_{ocn_grid}_to_{atm_grid}_traave.{creation_date}.nc') + filename=f'map_{ocn_grid}_to_{atm_grid}_traave.{create_date}.nc') self._get_resources() From 179158431c19ef11a5fc4f921558805b3f6e51d1 Mon Sep 17 00:00:00 2001 From: bmooremaley Date: Thu, 10 Sep 2026 16:37:49 -0700 Subject: [PATCH 9/9] Update to v2.0.0-alpha.4 --- compass/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compass/version.py b/compass/version.py index fa98dd1c47..1452326dd3 100644 --- a/compass/version.py +++ b/compass/version.py @@ -1 +1 @@ -__version__ = '2.0.0-alpha.3' +__version__ = '2.0.0-alpha.4'