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

Add ismip6 forcing testgroup #410

Merged
merged 58 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5ea54bf
Add 'Ismip6Forcing' testcase to the landice core class
hollyhan Apr 15, 2022
e9fb4ac
Create a testgroup class "Ismip6Forcing"
hollyhan Apr 15, 2022
6897784
Add a testcase for processing ismip6 AIS atmosphere forcing data
hollyhan Apr 15, 2022
c5b1518
Add a testcase for processing ismip6 AIS ocean forcing data
hollyhan Apr 15, 2022
5380c07
Add a config file for the ismip6forcing test case
hollyhan Apr 15, 2022
6d2403d
Separate the ocean test case into two: ocean_basal and ocean_thermal
hollyhan Sep 8, 2022
02d110b
Make major edits to the ismip6_forcing/atmosphere testcase
hollyhan May 4, 2022
ba140ad
Change to the use of xarray.Dataarary.dt in adding xtime string
hollyhan May 4, 2022
55f056f
Edit config options & I/O filenames & nested dictionaries
hollyhan May 4, 2022
3f1e21f
Add documentation of the test group
hollyhan May 10, 2022
1e3a61d
Edit config option
hollyhan May 10, 2022
bf4aeaf
Edit print statement to include remapping method
hollyhan May 10, 2022
604d884
Modify the ocean_basal testcase to process all files at once
hollyhan May 10, 2022
a39f851
Set path to output data from config options
hollyhan May 10, 2022
2663c7f
Fix PEP8 issues
hollyhan May 10, 2022
2d0111e
Edit config file and docs
hollyhan May 11, 2022
8b85f32
Restructure base path of output forcing data (atmosphere testcase)
hollyhan May 13, 2022
d435c0b
Restructure base path of output forcing data
hollyhan May 13, 2022
e22b33a
Add an option to process observed thermal forcing
hollyhan May 13, 2022
22bb5d8
Address xarray and PEP8 issues
hollyhan May 13, 2022
0bac938
Fix doc string
hollyhan May 26, 2022
dd8fae3
Move duplicate scripts from to test group folder
hollyhan May 26, 2022
3f94c75
Edit logging statement and remove xr.broadcast method
hollyhan May 26, 2022
4da36ed
Fix users guide
hollyhan May 26, 2022
9dfa495
Update the config file
hollyhan May 26, 2022
6cd599d
Delete duplicate path check and function call
hollyhan May 27, 2022
dadf323
Add a shared configure function
hollyhan Jun 6, 2022
e7e0f70
Add parallel executable to ESMF_RegridWeightGen
hollyhan Jun 6, 2022
a118973
Add a check to ensure mali mesh longitudinal range is [0 2pi]
hollyhan Jun 6, 2022
e9e0aa3
Add a step procesing modern climatology and applies climatology corre…
hollyhan Jun 12, 2022
2bddefb
Make the output path required config option
hollyhan Jun 12, 2022
9987947
Add a warning about the config option for processing the Racmo data
hollyhan Jun 14, 2022
e987f8c
Add an xtime variable to SMB and OF climatology data
hollyhan Jun 14, 2022
494ea33
Add xtime variables to start January 1st of every year
hollyhan Jun 22, 2022
b97e491
Permute dimension of the thermal forcing variable
hollyhan Aug 10, 2022
44fbcc1
Fix a directory name for basalmelt coefficient files
hollyhan Aug 10, 2022
1476846
Update ismip6_forcing.cfg
hollyhan Aug 10, 2022
9b63d8a
Remove adding 'xtime' variable to RACMO processed data
hollyhan Aug 11, 2022
4b47fd6
Add a counter to the name of the temporary files
hollyhan Sep 8, 2022
d02f187
Change 'cores' to 'ntasks' etc.
hollyhan Sep 8, 2022
e21d2fe
Edit doc strings and revert back to using a parallel call
hollyhan Sep 9, 2022
1d7d9b1
Change single quotes to double quotes for consistency
hollyhan Sep 9, 2022
a18b4a9
Use 'check_call' from mpas tool and also pass 'logger'
hollyhan Sep 9, 2022
0423b39
Clean up doc strings
hollyhan Sep 9, 2022
d02b608
Edit users guide and developers guide
hollyhan Sep 10, 2022
4bd9ea3
Edit the order of atmosphere step to Racmo step comes first before th…
hollyhan Sep 15, 2022
59b5917
Change 'warning' to 'print' for the 'process_smb_ramo' message
hollyhan Nov 14, 2022
f1d4118
Assign a default remapping method for processs_ocean_basal
hollyhan Sep 15, 2022
5f52278
Fix dictionary keys for the forcing files
hollyhan Oct 3, 2022
f92aa72
Create a nested dictionary for basalmelt parameter files
hollyhan Oct 3, 2022
c1723a4
Remove the config section for the ocean_basal testcase
hollyhan Oct 3, 2022
8e973a9
Add RACMO file from the database
hollyhan Nov 10, 2022
5e4d994
Specify MALI mesh name in the processed file name
hollyhan Nov 10, 2022
c5956ec
Add metadata to the processed SMB file
hollyhan Nov 10, 2022
52ed5bd
Add a separate testcase for observation ocean thermal forcing data
hollyhan Nov 11, 2022
42c9f1a
Update the user guide
hollyhan Nov 15, 2022
94138aa
Fix input file list and the use of 'f-string' arguments
hollyhan Nov 15, 2022
867bab7
Fix typos
hollyhan Nov 22, 2022
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: 2 additions & 0 deletions compass/landice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from compass.landice.tests.greenland import Greenland
from compass.landice.tests.humboldt import Humboldt
from compass.landice.tests.hydro_radial import HydroRadial
from compass.landice.tests.ismip6_forcing import Ismip6Forcing
from compass.landice.tests.kangerlussuaq import Kangerlussuaq
from compass.landice.tests.koge_bugt_s import KogeBugtS
from compass.landice.tests.mismipplus import MISMIPplus
Expand All @@ -34,6 +35,7 @@ def __init__(self):
self.add_test_group(Greenland(mpas_core=self))
self.add_test_group(Humboldt(mpas_core=self))
self.add_test_group(HydroRadial(mpas_core=self))
self.add_test_group(Ismip6Forcing(mpas_core=self))
self.add_test_group(Kangerlussuaq(mpas_core=self))
self.add_test_group(KogeBugtS(mpas_core=self))
self.add_test_group(MISMIPplus(mpas_core=self))
Expand Down
26 changes: 26 additions & 0 deletions compass/landice/tests/ismip6_forcing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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


class Ismip6Forcing(TestGroup):
"""
A test group for processing the ISMIP6 ocean and atmosphere forcing data
"""

def __init__(self, mpas_core):
"""
Create the test group

Parameters
----------
mpas_core : compass.landice.Landice
the MPAS core that this test group belongs to
"""
super().__init__(mpas_core=mpas_core, name="ismip6_forcing")

self.add_test_case(Atmosphere(test_group=self))
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))
42 changes: 42 additions & 0 deletions compass/landice/tests/ismip6_forcing/atmosphere/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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):
"""
A test case for processing the ISMIP6 atmosphere forcing data.
The test case builds a mapping file for interpolation between the
ISMIP6 8km polarstereo grid and MALI mesh, regrids the forcing data
and rename 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 = "atmosphere"
subdir = name
super().__init__(test_group=test_group, name=name, subdir=subdir)

step = ProcessSmbRacmo(test_case=self)
self.add_step(step)
step = ProcessSMB(test_case=self)
self.add_step(step)

def configure(self):
"""
Configures test case
"""

configure_testgroup(config=self.config,
check_model_options=True)
204 changes: 204 additions & 0 deletions compass/landice/tests/ismip6_forcing/atmosphere/create_mapfile_smb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
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
Loading