Skip to content

Commit

Permalink
Merge pull request #863 from bmooremaley/ocean-topo-from-cubedsphere
Browse files Browse the repository at this point in the history
Modified `ocean/mesh/remap_topography` to allow smoothing
  • Loading branch information
xylar authored Jan 11, 2025
2 parents ed88baa + 44cf4d5 commit 13959a3
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 202 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
channels: conda-forge,e3sm/label/compass
channel-priority: strict
use-mamba: false
auto-update-conda: true
auto-update-conda: false
python-version: ${{ matrix.python-version }}

- if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
channels: conda-forge,e3sm/label/compass
channel-priority: strict
use-mamba: false
auto-update-conda: true
auto-update-conda: false
python-version: ${{ matrix.python-version }}

- if: ${{ steps.skip_check.outputs.should_skip != 'true' }}
Expand Down
192 changes: 96 additions & 96 deletions compass/ocean/cached_files.json

Large diffs are not rendered by default.

10 changes: 2 additions & 8 deletions compass/ocean/mesh/low_res_topography.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@
[remap_topography]

# the name of the topography file in the bathymetry database
topo_filename = BedMachineAntarctica_v2_and_GEBCO_2022_0.05_degree_20220729.nc

# variable names in topo_filename
bathy_frac_var = ocean_mask

# the description to include in metadata
description = Bathymetry is from GEBCO 2022, combined with BedMachine
Antarctica v2 around Antarctica.
topo_filename = BedMachineAntarctica-v3_GEBCO_2023_ne120_20250110.nc
src_scrip_filename = ne120_20250110.scrip.nc

# the target and minimum number of MPI tasks to use in remapping
ntasks = 64
Expand Down
29 changes: 16 additions & 13 deletions compass/ocean/mesh/remap_topography.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@
[remap_topography]

# the name of the topography file in the bathymetry database
topo_filename = BedMachineAntarctica_v3_and_GEBCO_2023_0.0125_degree_20240828.nc

# variable names in topo_filename
lon_var = lon
lat_var = lat
bathymetry_var = bathymetry
ice_thickness_var = thickness
ice_frac_var = ice_mask
grounded_ice_frac_var = grounded_mask
ocean_frac_var = ocean_mask
bathy_frac_var = bathymetry_mask
topo_filename = BedMachineAntarctica-v3_GEBCO_2023_ne3000_20250110.nc
src_scrip_filename = ne3000_20250110.scrip.nc

# weight generator function:
# `tempest` for cubed-sphere bathy or `esmf` for latlon bathy
weight_generator = tempest

# the description to include in metadata
description = Bathymetry is from GEBCO 2023, combined with BedMachine
Antarctica v3 around Antarctica.

# the target and minimum number of MPI tasks to use in remapping
ntasks = 4096
min_tasks = 360
ntasks = 1280
min_tasks = 256

# remapping method {'bilinear', 'neareststod', 'conserve'}
# must use 'conserve' for tempestremap
method = conserve

# threshold of what fraction of an MPAS cell must contain ocean in order to
Expand All @@ -31,3 +27,10 @@ renorm_threshold = 0.01

# the density of land ice from MALI (kg/m^3)
ice_density = 910.0

# smoothing parameters
# no smoothing (required for esmf):
# expandDist = 0 [m]
# expandFactor = 1 [cell fraction]
expandDist = 0
expandFactor = 1
242 changes: 194 additions & 48 deletions compass/ocean/mesh/remap_topography.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
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 pyremap import LatLonGridDescriptor, MpasCellMeshDescriptor, Remapper
from mpas_tools.logging import check_call
from pyremap import MpasCellMeshDescriptor

from compass.parallel import run_command
from compass.step import Step


Expand All @@ -23,8 +26,10 @@ class RemapTopography(Step):
The name of the MPAS mesh to include in the mapping file
"""

def __init__(self, test_case, base_mesh_step, name='remap_topography',
subdir=None, mesh_name='MPAS_mesh'):
def __init__(
self, test_case, base_mesh_step, name='remap_topography', subdir=None,
mesh_name='MPAS_mesh',
):
"""
Create a new step
Expand Down Expand Up @@ -59,20 +64,30 @@ def setup(self):
"""
super().setup()
config = self.config
topo_filename = config.get('remap_topography', 'topo_filename')
section = config['remap_topography']
topo_filename = section.get('topo_filename')
src_scrip_filename = section.get('src_scrip_filename')

self.add_input_file(
filename='topography.nc',
target=topo_filename,
database='bathymetry_database')
database='bathymetry_database',
)
self.add_input_file(
filename='source.scrip.nc',
target=src_scrip_filename,
database='bathymetry_database',
)

base_path = self.base_mesh_step.path
base_filename = self.base_mesh_step.config.get(
'spherical_mesh', 'mpas_mesh_filename')
'spherical_mesh', 'mpas_mesh_filename',
)
target = os.path.join(base_path, base_filename)
self.add_input_file(filename='base_mesh.nc', work_dir_target=target)

self.ntasks = config.getint('remap_topography', 'ntasks')
self.min_tasks = config.getint('remap_topography', 'min_tasks')
self.ntasks = section.getint('ntasks')
self.min_tasks = section.getint('min_tasks')

def constrain_resources(self, available_resources):
"""
Expand All @@ -94,64 +109,195 @@ def run(self):
Run this step of the test case
"""
config = self.config
weight_generator = config.get('remap_topography', 'weight_generator')

self._create_target_scrip_file()
if weight_generator == 'tempest':
self._partition_scrip_file('source.scrip.nc')
self._partition_scrip_file('target.scrip.nc')
self._create_weights_tempest()
elif weight_generator == 'esmf':
self._create_weights_esmf()
else:
msg = f'Unsupported weight generator function {weight_generator}'
raise ValueError(msg)
self._remap_to_target()
self._modify_remapped_bathymetry()

def _create_target_scrip_file(self):
"""
Create target SCRIP file from MPAS mesh file.
"""
logger = self.logger
parallel_executable = config.get('parallel', 'parallel_executable')
logger.info('Create source SCRIP file')

lon_var = config.get('remap_topography', 'lon_var')
lat_var = config.get('remap_topography', 'lat_var')
config = self.config
section = config['remap_topography']
expandDist = section.getfloat('expandDist')
expandFactor = section.getfloat('expandFactor')

descriptor = MpasCellMeshDescriptor(
fileName='base_mesh.nc',
meshName=self.mesh_name,
)
descriptor.to_scrip(
'target.scrip.nc',
expandDist=expandDist,
expandFactor=expandFactor,
)

logger.info(' Done.')

def _partition_scrip_file(self, in_filename):
"""
Partition SCRIP file for parallel mbtempest use
"""
logger = self.logger
logger.info('Partition SCRIP file')

stem = pathlib.Path(in_filename).stem
h5m_filename = f'{stem}.h5m'
part_filename = f'{stem}.p{self.ntasks}.h5m'

# Convert source SCRIP to mbtempest
args = [
'mbconvert', '-B',
in_filename,
h5m_filename,
]
check_call(args, logger)

# Partition source SCRIP
args = [
'mbpart', f'{self.ntasks}',
'-z', 'RCB',
h5m_filename,
part_filename,
]
check_call(args, logger)

logger.info(' Done.')

def _create_weights_tempest(self):
"""
Create mapping weights file using TempestRemap
"""
logger = self.logger
logger.info('Create weights file')

config = self.config
method = config.get('remap_topography', 'method')
renorm_threshold = config.getfloat('remap_topography',
'renorm_threshold')
ice_density = config.getfloat('remap_topography', 'ice_density')
ocean_density = constants['SHR_CONST_RHOSW']
g = constants['SHR_CONST_G']
if method != 'conserve':
raise ValueError(f'Unsupported method {method} for TempestRemap')

args = [
'mbtempest', '--type', '5',
'--load', f'source.scrip.p{self.ntasks}.h5m',
'--load', f'target.scrip.p{self.ntasks}.h5m',
'--file', f'map_source_to_target_{method}.nc',
'--weights', '--gnomonic',
'--boxeps', '1e-9',
]

run_command(
args, self.cpus_per_task, self.ntasks,
self.openmp_threads, self.config, self.logger,
)

logger.info(' Done.')

def _create_weights_esmf(self):
"""
Create mapping weights file using ESMF_RegridWeightGen
"""
logger = self.logger
logger.info('Create weights file')

in_descriptor = LatLonGridDescriptor.read(fileName='topography.nc',
lonVarName=lon_var,
latVarName=lat_var)
config = self.config
method = config.get('remap_topography', 'method')

args = [
'ESMF_RegridWeightGen',
'--source', 'source.scrip.nc',
'--destination', 'target.scrip.nc',
'--weight', f'map_source_to_target_{method}.nc',
'--method', method,
'--netcdf4',
'--ignore_unmapped',
]

run_command(
args, self.cpus_per_task, self.ntasks,
self.openmp_threads, self.config, self.logger,
)

logger.info(' Done.')

def _remap_to_target(self):
"""
Remap combined bathymetry onto MPAS target mesh
"""
logger = self.logger
logger.info('Remap to target')

in_mesh_name = in_descriptor.meshName
config = self.config
method = config.get('remap_topography', 'method')

out_mesh_name = self.mesh_name
out_descriptor = MpasCellMeshDescriptor(fileName='base_mesh.nc',
meshName=self.mesh_name)
# Build command args
args = [
'ncremap',
'-m', f'map_source_to_target_{method}.nc',
'--vrb=1',
'topography.nc', 'topography_ncremap.nc',
]
check_call(args, logger)

mapping_file_name = \
f'map_{in_mesh_name}_to_{out_mesh_name}_{method}.nc'
remapper = Remapper(in_descriptor, out_descriptor, mapping_file_name)
logger.info(' Done.')

remapper.build_mapping_file(method=method, mpiTasks=self.ntasks,
tempdir='.', logger=logger,
esmf_parallel_exec=parallel_executable)
def _modify_remapped_bathymetry(self):
"""
Modify remapped bathymetry
"""
logger = self.logger
logger.info('Modify remapped bathymetry')

remapper.remap_file(inFileName='topography.nc',
outFileName='topography_ncremap.nc',
logger=logger)
config = self.config
section = config['remap_topography']
renorm_threshold = section.getfloat('renorm_threshold')
ice_density = section.getfloat('ice_density')
ocean_density = constants['SHR_CONST_RHOSW']
g = constants['SHR_CONST_G']

ds_in = xr.open_dataset('topography_ncremap.nc')
ds_in = ds_in.rename({'ncol': 'nCells'})

ds_out = xr.Dataset()
rename = {'bathymetry_var': 'bed_elevation',
'ice_thickness_var': 'landIceThkObserved',
'ice_frac_var': 'landIceFracObserved',
'grounded_ice_frac_var': 'landIceGroundedFracObserved',
'ocean_frac_var': 'oceanFracObserved',
'bathy_frac_var': 'bathyFracObserved'}

for option, out_var in rename.items():
in_var = config.get('remap_topography', option)
rename = {
'bathymetry': 'bed_elevation',
'thickness': 'landIceThkObserved',
'ice_mask': 'landIceFracObserved',
'grounded_mask': 'landIceGroundedFracObserved',
'ocean_mask': 'oceanFracObserved',
'bathymetry_mask': 'bathyFracObserved',
}
for in_var, out_var in rename.items():
ds_out[out_var] = ds_in[in_var]

ds_out['landIceFloatingFracObserved'] = \
ds_out.landIceFracObserved - ds_out.landIceGroundedFracObserved

# make sure fractions don't exceed 1
for var in ['landIceFracObserved', 'landIceGroundedFracObserved',
'landIceFloatingFracObserved', 'oceanFracObserved',
'bathyFracObserved']:
# Make sure fractions don't exceed 1
varNames = [
'landIceFracObserved',
'landIceGroundedFracObserved',
'landIceFloatingFracObserved',
'oceanFracObserved',
'bathyFracObserved',
]
for var in varNames:
ds_out[var] = np.minimum(ds_out[var], 1.)

# renormalize elevation variables
# Renormalize elevation variables
norm = ds_out.bathyFracObserved
valid = norm > renorm_threshold
for var in ['bed_elevation', 'landIceThkObserved']:
Expand All @@ -163,13 +309,13 @@ def run(self):
# not allowed to be thicker than the flotation thickness
thickness = np.minimum(thickness, flotation_thickness)
ds_out['landIceThkObserved'] = thickness

ds_out['landIcePressureObserved'] = ice_density * g * thickness

# compute the ice draft to be consistent with the land ice pressure
# and using E3SM's density of seawater

ds_out['landIceDraftObserved'] = \
- (ice_density / ocean_density) * thickness

write_netcdf(ds_out, 'topography_remapped.nc')

logger.info(' Done.')
Loading

0 comments on commit 13959a3

Please sign in to comment.