Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified ocean/mesh/remap_topography to allow smoothing #863

Merged
merged 15 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
)
xylar marked this conversation as resolved.
Show resolved Hide resolved

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
Loading