Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9d93fd1
Add ISMIP7 GrIS OCX (reanalysis) forcing support
trhille Sep 3, 2026
4c7116c
Fix ISMIP7 forcing atmosphere/ocean processing bugs
trhille Sep 3, 2026
198473b
Write ISMIP7 forcing output in the layout ismip7_run ingests
trhille Sep 3, 2026
828699b
Fix missing-value leakage in ISMIP7 atmosphere forcing; share extrapo…
trhille Sep 3, 2026
7047302
Fix stale autosummary reference after remap_utils refactor
trhille Sep 4, 2026
6176864
Add optional GrIS 3-D ocean thermal forcing step to ismip7_forcing
trhille Sep 4, 2026
2362e64
Add optional GrIS 3-D ocean thermal forcing ingestion to ismip7_run
trhille Sep 4, 2026
e431abe
Fix greenland_3d bugs found during first real test run; add example J…
trhille Sep 5, 2026
e40cffd
Fix 3D GrIS thermal forcing output unreadable by netCDF-C
trhille Sep 5, 2026
9b34030
Guard deltaT calibration to OCX; split melt params into own file
trhille Sep 5, 2026
7c86416
Fix garbled xtime in 3D GrIS thermal forcing output
trhille Sep 5, 2026
b91669e
Split 3D GrIS thermal forcing into N-year files; keep monthly
trhille Sep 8, 2026
9467ddc
Skip existing 3D forcing outputs on re-run instead of erroring
trhille Sep 9, 2026
92e63c4
Write 3D forcing chunks via HDF5 then nccopy to CDF-2 (~5x faster)
trhille Sep 9, 2026
43bf070
Warn when marine ice sits below the clamped anchor depth
trhille Sep 9, 2026
58923f5
Impose seasonally-varying vertical structure on 3D TF
trhille Sep 9, 2026
ae05056
Fix longitude convention mismatch in diagnostic maps
trhille Sep 9, 2026
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
77 changes: 16 additions & 61 deletions compass/landice/tests/ismip7_forcing/atmosphere/process_runoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
import os
import shutil

import numpy as np
import xarray as xr
from mpas_tools.io import write_netcdf
from mpas_tools.logging import check_call
from scipy.ndimage import distance_transform_edt

from compass.landice.tests.ismip7_forcing.create_mapfile import (
build_mapping_file,
)
from compass.landice.tests.ismip7_forcing.ice_sheet_params import get_params
from compass.landice.tests.ismip7_forcing.remap_utils import extrapolate_source
from compass.step import Step


Expand Down Expand Up @@ -72,6 +71,11 @@ def run(self):
prefix = params['prefix']
resolution = params['atm_resolution']
version = params['atm_version']
if params['atm_model'] is not None:
forcing_group = scenario
model = params['atm_model']
else:
forcing_group = f"{model}_{scenario}"
input_path = os.path.join(base_path_ismip7, "mrro", version)
file_pattern = (f"mrro_{prefix}_{model}_{scenario}_"
f"SDBN1-{resolution}_{version}_*.nc")
Expand All @@ -85,7 +89,11 @@ def run(self):
# Filter to requested year range
input_files = []
for f in all_files:
year = int(os.path.basename(f).split("_")[-1].replace(".nc", ""))
# skip non-yearly files such as climatology averages (*_avg.nc)
token = os.path.basename(f).split("_")[-1].replace(".nc", "")
if not token.isdigit():
continue
year = int(token)
if start_year <= year <= end_year:
input_files.append(f)

Expand Down Expand Up @@ -123,8 +131,7 @@ def run(self):
# so they don't pollute neighboring cells during interpolation
extrap_file = f"extrap_{basename}"
if not os.path.exists(extrap_file):
self._extrapolate_source(input_file, extrap_file, "mrro",
logger)
extrapolate_source(input_file, extrap_file, "mrro", logger)

logger.info(f" Remapping: {basename}")
args = ["ncremap",
Expand Down Expand Up @@ -152,8 +159,8 @@ def run(self):
os.remove(f)

# Place output in appropriate directory
output_path = os.path.join(output_base_path, "atmosphere_forcing",
f"{model}_{scenario}")
output_path = os.path.join(output_base_path, forcing_group,
"atmosphere")
if not os.path.exists(output_path):
os.makedirs(output_path)

Expand All @@ -176,7 +183,8 @@ def _combine_and_rename(self, remapped_files, output_file):
Output file path
"""
ds = xr.open_mfdataset(remapped_files, concat_dim="time",
combine="nested", engine="netcdf4")
combine="nested", engine="netcdf4",
drop_variables="time_bnds")

# Rename dimensions to MALI conventions
rename_dims = {}
Expand Down Expand Up @@ -225,56 +233,3 @@ def _combine_and_rename(self, remapped_files, output_file):
ds = ds.drop_vars("Time")

write_netcdf(ds, output_file)

def _extrapolate_source(self, input_file, output_file, varname, logger):
"""
Extrapolate fill/missing values on the source polar stereographic
grid using nearest-neighbor via distance_transform_edt. This must
be done before remapping so that fill values don't contaminate the
interpolation stencil.

Parameters
----------
input_file : str
Path to the input NetCDF file on the source grid

output_file : str
Path to write the extrapolated file

varname : str
Name of the variable to extrapolate (e.g., "mrro")

logger : logging.Logger
Logger for status messages
"""
logger.info(f" Extrapolating fill values on source grid: "
f"{os.path.basename(input_file)}")

ds = xr.open_dataset(input_file, engine="netcdf4")
data = ds[varname]

# Process each time step
# Source files have dims like (time, y, x)
values = data.values.copy()
non_spatial_shape = values.shape[:-2] # (time,)

for idx in np.ndindex(non_spatial_shape):
slab = values[idx] # shape (ny, nx)
valid_mask = np.isfinite(slab)
if valid_mask.all() or not valid_mask.any():
continue
nearest_inds = distance_transform_edt(
~valid_mask, return_distances=False, return_indices=True)
invalid = ~valid_mask
values[idx][invalid] = slab[
nearest_inds[0, invalid],
nearest_inds[1, invalid]]

ds[varname] = (data.dims, values)
ds[varname].attrs = data.attrs

# Remove _FillValue encoding so output has no masked values
if "_FillValue" in ds[varname].encoding:
del ds[varname].encoding["_FillValue"]

write_netcdf(ds, output_file)
32 changes: 26 additions & 6 deletions compass/landice/tests/ismip7_forcing/atmosphere/process_smb.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
build_mapping_file,
)
from compass.landice.tests.ismip7_forcing.ice_sheet_params import get_params
from compass.landice.tests.ismip7_forcing.remap_utils import extrapolate_source
from compass.step import Step


Expand Down Expand Up @@ -69,6 +70,11 @@ def run(self):
prefix = params['prefix']
resolution = params['atm_resolution']
version = params['atm_version']
if params['atm_model'] is not None:
forcing_group = scenario
model = params['atm_model']
else:
forcing_group = f"{model}_{scenario}"
input_path = os.path.join(base_path_ismip7, "acabf", version)
file_pattern = (f"acabf_{prefix}_{model}_{scenario}_"
f"SDBN1-{resolution}_{version}_*.nc")
Expand All @@ -82,8 +88,12 @@ def run(self):
# Filter to requested year range
input_files = []
for f in all_files:
# Extract year from filename (last part before .nc)
year = int(os.path.basename(f).split("_")[-1].replace(".nc", ""))
# Extract year from filename (last part before .nc); skip
# non-yearly files such as climatology averages (e.g. *_avg.nc)
token = os.path.basename(f).split("_")[-1].replace(".nc", "")
if not token.isdigit():
continue
year = int(token)
if start_year <= year <= end_year:
input_files.append(f)

Expand Down Expand Up @@ -117,15 +127,24 @@ def run(self):
logger.info(f" Remapped file exists, skipping: {basename}")
continue

# Extrapolate fill values on source grid before remapping
# so they don't pollute neighboring cells during interpolation
extrap_file = f"extrap_{basename}"
if not os.path.exists(extrap_file):
extrapolate_source(input_file, extrap_file, "acabf", logger)

logger.info(f" Remapping: {basename}")
args = ["ncremap",
"-i", input_file,
"-i", extrap_file,
"-o", remapped_file,
"-m", mapping_file,
"-v", "acabf"]

check_call(args, logger=logger)

# Clean up extrapolated source file
os.remove(extrap_file)

# Combine remapped files and rename to MALI conventions
logger.info("Combining remapped files and renaming variables...")
output_file = (f"{mali_mesh_name}_SMB_{model}_{scenario}_"
Expand All @@ -140,8 +159,8 @@ def run(self):
os.remove(f)

# Place output in appropriate directory
output_path = os.path.join(output_base_path, "atmosphere_forcing",
f"{model}_{scenario}")
output_path = os.path.join(output_base_path, forcing_group,
"atmosphere")
if not os.path.exists(output_path):
os.makedirs(output_path)

Expand All @@ -164,7 +183,8 @@ def _combine_and_rename(self, remapped_files, output_file):
Output file path
"""
ds = xr.open_mfdataset(remapped_files, concat_dim="time",
combine="nested", engine="netcdf4")
combine="nested", engine="netcdf4",
drop_variables="time_bnds")

# Rename dimensions to MALI conventions
rename_dims = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
build_mapping_file,
)
from compass.landice.tests.ismip7_forcing.ice_sheet_params import get_params
from compass.landice.tests.ismip7_forcing.remap_utils import extrapolate_source
from compass.step import Step


Expand Down Expand Up @@ -70,6 +71,11 @@ def run(self):
prefix = params['prefix']
resolution = params['atm_resolution']
version = params['atm_version']
if params['atm_model'] is not None:
forcing_group = scenario
model = params['atm_model']
else:
forcing_group = f"{model}_{scenario}"
input_path = os.path.join(base_path_ismip7, "dacabfdz", version)
file_pattern = (f"dacabfdz_{prefix}_{model}_{scenario}_"
f"SDBN1-{resolution}_{version}_*.nc")
Expand All @@ -83,7 +89,11 @@ def run(self):
# Filter to requested year range
input_files = []
for f in all_files:
year = int(os.path.basename(f).split("_")[-1].replace(".nc", ""))
# skip non-yearly files such as climatology averages (*_avg.nc)
token = os.path.basename(f).split("_")[-1].replace(".nc", "")
if not token.isdigit():
continue
year = int(token)
if start_year <= year <= end_year:
input_files.append(f)

Expand Down Expand Up @@ -118,15 +128,25 @@ def run(self):
logger.info(f" Remapped file exists, skipping: {basename}")
continue

# Extrapolate fill values on source grid before remapping
# so they don't pollute neighboring cells during interpolation
extrap_file = f"extrap_{basename}"
if not os.path.exists(extrap_file):
extrapolate_source(input_file, extrap_file, "dacabfdz",
logger)

logger.info(f" Remapping: {basename}")
args = ["ncremap",
"-i", input_file,
"-i", extrap_file,
"-o", remapped_file,
"-m", mapping_file,
"-v", "dacabfdz"]

check_call(args, logger=logger)

# Clean up extrapolated source file
os.remove(extrap_file)

# Combine remapped files and rename to MALI conventions
logger.info("Combining remapped files and renaming variables...")
output_file = (f"{mali_mesh_name}_SMB_gradient_{model}_{scenario}_"
Expand All @@ -141,8 +161,8 @@ def run(self):
os.remove(f)

# Place output in appropriate directory
output_path = os.path.join(output_base_path, "atmosphere_forcing",
f"{model}_{scenario}")
output_path = os.path.join(output_base_path, forcing_group,
"atmosphere")
if not os.path.exists(output_path):
os.makedirs(output_path)

Expand All @@ -165,7 +185,8 @@ def _combine_and_rename(self, remapped_files, output_file):
Output file path
"""
ds = xr.open_mfdataset(remapped_files, concat_dim="time",
combine="nested", engine="netcdf4")
combine="nested", engine="netcdf4",
drop_variables="time_bnds")

# Rename dimensions to MALI conventions
rename_dims = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
build_mapping_file,
)
from compass.landice.tests.ismip7_forcing.ice_sheet_params import get_params
from compass.landice.tests.ismip7_forcing.remap_utils import extrapolate_source
from compass.step import Step


Expand Down Expand Up @@ -69,6 +70,11 @@ def run(self):
prefix = params['prefix']
resolution = params['atm_resolution']
version = params['atm_version']
if params['atm_model'] is not None:
forcing_group = scenario
model = params['atm_model']
else:
forcing_group = f"{model}_{scenario}"
input_path = os.path.join(base_path_ismip7, "ts", version)
file_pattern = (f"ts_{prefix}_{model}_{scenario}_"
f"SDBN1-{resolution}_{version}_*.nc")
Expand All @@ -82,7 +88,11 @@ def run(self):
# Filter to requested year range
input_files = []
for f in all_files:
year = int(os.path.basename(f).split("_")[-1].replace(".nc", ""))
# skip non-yearly files such as climatology averages (*_avg.nc)
token = os.path.basename(f).split("_")[-1].replace(".nc", "")
if not token.isdigit():
continue
year = int(token)
if start_year <= year <= end_year:
input_files.append(f)

Expand Down Expand Up @@ -117,15 +127,24 @@ def run(self):
logger.info(f" Remapped file exists, skipping: {basename}")
continue

# Extrapolate fill values on source grid before remapping
# so they don't pollute neighboring cells during interpolation
extrap_file = f"extrap_{basename}"
if not os.path.exists(extrap_file):
extrapolate_source(input_file, extrap_file, "ts", logger)

logger.info(f" Remapping: {basename}")
args = ["ncremap",
"-i", input_file,
"-i", extrap_file,
"-o", remapped_file,
"-m", mapping_file,
"-v", "ts"]

check_call(args, logger=logger)

# Clean up extrapolated source file
os.remove(extrap_file)

# Combine remapped files and rename to MALI conventions
logger.info("Combining remapped files and renaming variables...")
output_file = (f"{mali_mesh_name}_temperature_{model}_{scenario}_"
Expand All @@ -140,8 +159,8 @@ def run(self):
os.remove(f)

# Place output in appropriate directory
output_path = os.path.join(output_base_path, "atmosphere_forcing",
f"{model}_{scenario}")
output_path = os.path.join(output_base_path, forcing_group,
"atmosphere")
if not os.path.exists(output_path):
os.makedirs(output_path)

Expand All @@ -164,7 +183,8 @@ def _combine_and_rename(self, remapped_files, output_file):
Output file path
"""
ds = xr.open_mfdataset(remapped_files, concat_dim="time",
combine="nested", engine="netcdf4")
combine="nested", engine="netcdf4",
drop_variables="time_bnds")

# Rename dimensions to MALI conventions
rename_dims = {}
Expand Down
Loading
Loading