diff --git a/cime_config/config_grids.xml b/cime_config/config_grids.xml index dece4eb32abc..a2ef4adabf2e 100755 --- a/cime_config/config_grids.xml +++ b/cime_config/config_grids.xml @@ -3944,7 +3944,8 @@ OCN2WAV_SMAPNAME WAV2OCN_SMAPNAME WAV2OCN_FMAPNAME - ICE2WAV_SMAPNAME + ICE2WAV_SMAPNAME + WAV2ICE_SMAPNAME ROF2OCN_LIQ_RMAPNAME ROF2OCN_ICE_RMAPNAME @@ -5591,6 +5592,7 @@ cpl/gridmaps/wQU225EC60to30/map_wQU225EC60to30_TO_oEC60to30v3_blin.20210428.nc cpl/gridmaps/wQU225EC60to30/map_oEC60to30v3_TO_wQU225EC60to30_blin.20210428.nc cpl/gridmaps/wQU225EC60to30/map_oEC60to30v3_TO_wQU225EC60to30_blin.20210428.nc + cpl/gridmaps/wQU225EC60to30/map_wQU225EC60to30_TO_oEC60to30v3_blin.20210428.nc @@ -5598,6 +5600,7 @@ cpl/gridmaps/wQU225EC30to60E2r2/map_wQU225EC30to60E2r2_TO_EC30to60E2r2_aave.20220222.nc cpl/gridmaps/wQU225EC30to60E2r2/map_EC30to60E2r2_TO_wQU225EC30to60E2r2_blin.20220222.nc cpl/gridmaps/wQU225EC30to60E2r2/map_EC30to60E2r2_TO_wQU225EC30to60E2r2_blin.20220222.nc + cpl/gridmaps/wQU225EC30to60E2r2/map_wQU225EC30to60E2r2_TO_EC30to60E2r2_blin.20220222.nc @@ -5605,6 +5608,7 @@ cpl/gridmaps/wQU225Icos30E3r5/map_wQU225Icos30E3r5_to_IcoswISC30E3r5_esmfaave.20240910.nc cpl/gridmaps/wQU225Icos30E3r5/map_IcoswISC30E3r5_to_wQU225Icos30E3r5_esmfbilin.20240910.nc cpl/gridmaps/wQU225Icos30E3r5/map_IcoswISC30E3r5_to_wQU225Icos30E3r5_esmfbilin.20240910.nc + cpl/gridmaps/wQU225Icos30E3r5/map_wQU225Icos30E3r5_to_IcoswISC30E3r5_esmfbilin.20240910.nc diff --git a/cime_config/machines/cmake_macros/chrysalis_oneapi-ifx.cmake b/cime_config/machines/cmake_macros/chrysalis_oneapi-ifx.cmake index d4125f5dd355..674233ee559f 100644 --- a/cime_config/machines/cmake_macros/chrysalis_oneapi-ifx.cmake +++ b/cime_config/machines/cmake_macros/chrysalis_oneapi-ifx.cmake @@ -1,3 +1,11 @@ string(APPEND CMAKE_EXE_LINKER_FLAGS " -L/gpfs/fs1/soft/chrysalis/spack-latest/opt/spack/linux-rhel8-x86_64/gcc-8.5.0/gcc-11.3.0-jkpmtgq/lib64 -lstdc++") +# Workaround for oneapi ifx v2025.2.0 (and earlier than 2025.3.0): +# use -mllvm -disable-hir-temp-cleanup to avoid ICE +# See: https://github.com/argonne-lcf/AuroraBugTracking/issues/64 +if (CMAKE_Fortran_COMPILER_ID STREQUAL "IntelLLVM" + AND CMAKE_Fortran_COMPILER_VERSION VERSION_LESS "2025.3.0") + string(APPEND CMAKE_Fortran_FLAGS " -mllvm -disable-hir-temp-cleanup") +endif() + diff --git a/components/eam/src/physics/cam/bfb_math.inc b/components/eam/src/physics/cam/bfb_math.inc index 3efd6ae2adf3..d9768011750f 100644 --- a/components/eam/src/physics/cam/bfb_math.inc +++ b/components/eam/src/physics/cam/bfb_math.inc @@ -27,6 +27,7 @@ # define bfb_expm1(val) (exp(val) - 1) # define bfb_tanh(val) tanh(val) # define bfb_cos(val) cos(val) +# define bfb_sin(val) sin(val) # define bfb_sqrt(val) sqrt(val) # define bfb_erf(val) erf(val) #else @@ -40,6 +41,7 @@ # define bfb_expm1(val) scream_expm1(val) # define bfb_tanh(val) scream_tanh(val) # define bfb_cos(val) scream_cos(val) +# define bfb_sin(val) scream_sin(val) # define bfb_erf(val) scream_erf(val) #endif diff --git a/components/eam/src/physics/cam/zm/zm_conv_mcsp.F90 b/components/eam/src/physics/cam/zm/zm_conv_mcsp.F90 index d2e035912ab6..d7f38a7cb047 100644 --- a/components/eam/src/physics/cam/zm/zm_conv_mcsp.F90 +++ b/components/eam/src/physics/cam/zm/zm_conv_mcsp.F90 @@ -1,3 +1,5 @@ +#include "bfb_math.inc" + module zm_conv_mcsp !---------------------------------------------------------------------------- ! Purpose: methods for mesoscale coherent structure parameterization (MCSP) @@ -29,6 +31,7 @@ module zm_conv_mcsp !---------------------------------------------------------------------------- #ifdef SCREAM_CONFIG_IS_CMAKE use zm_eamxx_bridge_params, only: r8 + use physics_share_f2c, only: scream_sin, scream_cos #else use shr_kind_mod, only: r8=>shr_kind_r8 use cam_abortutils, only: endrun @@ -270,10 +273,10 @@ subroutine zm_conv_mcsp_tend( pcols, ncol, pver, pverp, & pdepth_total = state_pint(i,pver+1) - state_pmid(i,jctop(i)) ! specify the assumed vertical structure - if (do_mcsp_t) mcsp_tend_s(i,k) = -1*zm_param%mcsp_t_coeff * sin(2.0_r8*zm_const%pi*(pdepth_mid_k/pdepth_total)) - if (do_mcsp_q) mcsp_tend_q(i,k) = -1*zm_param%mcsp_q_coeff * sin(2.0_r8*zm_const%pi*(pdepth_mid_k/pdepth_total)) - if (do_mcsp_u) mcsp_tend_u(i,k) = zm_param%mcsp_u_coeff * (cos(zm_const%pi*(pdepth_mid_k/pdepth_total))) - if (do_mcsp_v) mcsp_tend_v(i,k) = zm_param%mcsp_v_coeff * (cos(zm_const%pi*(pdepth_mid_k/pdepth_total))) + if (do_mcsp_t) mcsp_tend_s(i,k) = -1*zm_param%mcsp_t_coeff * bfb_sin(2.0_r8*zm_const%pi*(pdepth_mid_k/pdepth_total)) + if (do_mcsp_q) mcsp_tend_q(i,k) = -1*zm_param%mcsp_q_coeff * bfb_sin(2.0_r8*zm_const%pi*(pdepth_mid_k/pdepth_total)) + if (do_mcsp_u) mcsp_tend_u(i,k) = zm_param%mcsp_u_coeff * (bfb_cos(zm_const%pi*(pdepth_mid_k/pdepth_total))) + if (do_mcsp_v) mcsp_tend_v(i,k) = zm_param%mcsp_v_coeff * (bfb_cos(zm_const%pi*(pdepth_mid_k/pdepth_total))) ! scale the vertical structure by the ZM heating/drying tendencies if (do_mcsp_t) mcsp_tend_s(i,k) = zm_avg_tend_s(i) * mcsp_tend_s(i,k) diff --git a/components/eamxx/cmake/tpls/CsmShare.cmake b/components/eamxx/cmake/tpls/CsmShare.cmake index d9e667a4e591..6a379bc04e5e 100644 --- a/components/eamxx/cmake/tpls/CsmShare.cmake +++ b/components/eamxx/cmake/tpls/CsmShare.cmake @@ -58,6 +58,7 @@ macro (CreateCsmShareTarget) target_compile_definitions(csm_share PUBLIC $<$,$>:CPRGNU> $<$,$>:CPRINTEL> + $<$,$>:CPRINTEL> $<$,$>:CPRCRAY> $<$,$>:CPRCRAY> EAMXX_STANDALONE) diff --git a/components/eamxx/docs/user/model_configuration.md b/components/eamxx/docs/user/model_configuration.md index 685ed7aaf6db..5599994c6eee 100644 --- a/components/eamxx/docs/user/model_configuration.md +++ b/components/eamxx/docs/user/model_configuration.md @@ -361,7 +361,8 @@ EAMxx allows the user to configure the desired model output via with each YAML file associated to a different output stream (i.e., a file). In order to add an output stream, one needs to run `atmchange output_yaml_files+=/path/to/my/output/yaml` -(more information on how to use `atmchange` can be found [here](#changing-model-inputs-atmchange)). +(more information on how to use `atmchange` can be found in the +[atmchange documentation](#changing-model-inputs-atmchange)). During the `buildnml` phase of the case management system, these YAML files will be copied into the RUNDIR/data folder. During this process, the files will be parsed, and any CIME-related @@ -432,6 +433,10 @@ in the run directory. - `frequency_units`: units of the output frequency. - Valid options are `nsteps` (the number of atmosphere time steps), `nsecs`, `nmins`, `nhours`, `ndays`, `nmonths`, `nyears`. +- `transpose`: optional boolean flag to enable transposed output (default: `false`). + - When set to `true`, all field dimensions will be reversed in the output file. + - For example, a field with layout `(ncol, nlev)` will be written as `(nlev, ncol)`. + - This can be useful for post-processing tools that expect a specific dimension ordering. ## Diagnostic output diff --git a/components/eamxx/scripts/atm_manip.py b/components/eamxx/scripts/atm_manip.py index b0fa829dedb2..0226a9416c0a 100755 --- a/components/eamxx/scripts/atm_manip.py +++ b/components/eamxx/scripts/atm_manip.py @@ -71,6 +71,96 @@ def reset_buffer(): ############################################################################### run_cmd_no_fail(f"./xmlchange {ATMCHANGE_BUFF_XML_NAME}=''") +############################################################################### +def get_changes_for_node(xml_root, node_name, changes): +############################################################################### + """ + Return the subset of changes from `changes` that do NOT target the given + XML node or any of its descendants. This is the filtering kernel used by + reset_node_changes and can be called directly for testing. + + NOTE: `changes` is a list of already-unescaped change strings + (commas are NOT escaped with backslash). + + >>> xml = ''' + ... + ... 0 + ... 10 + ... + ... 1 + ... 2 + ... + ... + ... ''' + >>> import xml.etree.ElementTree as ET + >>> tree = ET.fromstring(xml) + >>> ################ ERROR: node not found ####################### + >>> get_changes_for_node(tree, 'nonexistent', ['foo=5']) + Traceback (most recent call last): + SystemExit: ERROR: 'nonexistent' did not match any node in the XML file + >>> ################ Reset leaf node (foo), keep bar change ##### + >>> get_changes_for_node(tree, 'foo', ['foo=5', 'bar=2']) + ['bar=2'] + >>> ################ Reset leaf node (foo), no matching change ## + >>> get_changes_for_node(tree, 'foo', ['bar=2', 'sub::child1=99']) + ['bar=2', 'sub::child1=99'] + >>> ################ Reset non-leaf node (sub), removes children + >>> get_changes_for_node(tree, 'sub', ['sub::child1=99', 'bar=3']) + ['bar=3'] + >>> ################ Reset child only, keep sibling change ####### + >>> get_changes_for_node(tree, 'sub::child1', ['sub::child1=99', 'sub::child2=5', 'bar=3']) + ['sub::child2=5', 'bar=3'] + >>> ################ Empty changes list ########################## + >>> get_changes_for_node(tree, 'foo', []) + [] + """ + reset_targets = get_xml_nodes(xml_root, node_name) + expect(len(reset_targets) > 0, + f"'{node_name}' did not match any node in the XML file") + + if not changes: + return [] + + parent_map = create_parent_map(xml_root) + + filtered_changes = [] + for chg in changes: + chg_node_name, _, _, _ = parse_change(chg) + chg_nodes = get_xml_nodes(xml_root, chg_node_name) + # is_anchestor_of(A, B, ...) returns True when A == B too, so + # this covers both direct matches and descendant matches. + affects_reset = any( + is_anchestor_of(reset_target, chg_node, parent_map) + for chg_node in chg_nodes + for reset_target in reset_targets + ) + if not affects_reset: + filtered_changes.append(chg) + + return filtered_changes + +############################################################################### +def reset_node_changes(xml_root, node_name): +############################################################################### + """ + Remove from the SCREAM_ATMCHANGE_BUFFER all changes that target + the given node or any of its descendants. + """ + # Get current buffered changes + buff_str = run_cmd_no_fail(f"./xmlquery {ATMCHANGE_BUFF_XML_NAME} --value") + changes = [] + for item in buff_str.split(ATMCHANGE_SEP): + if item.strip(): + changes.append(item.replace(r"\,", ",").strip()) + + # Filter changes (also validates node_name exists in xml_root) + filtered_changes = get_changes_for_node(xml_root, node_name, changes) + + # Reset buffer and write back the filtered changes + run_cmd_no_fail(f"./xmlchange {ATMCHANGE_BUFF_XML_NAME}=''") + if filtered_changes: + buffer_changes(filtered_changes) + ############################################################################### def get_xml_nodes(xml_root, name): ############################################################################### diff --git a/components/eamxx/scripts/atmchange b/components/eamxx/scripts/atmchange index b39a10c3412e..397ce10986b5 100755 --- a/components/eamxx/scripts/atmchange +++ b/components/eamxx/scripts/atmchange @@ -14,7 +14,7 @@ sys.path.append(os.path.dirname(os.path.realpath(__file__))) from eamxx_buildnml_impl import check_value, is_array_type from eamxx_buildnml import create_raw_xml_file -from atm_manip import atm_config_chg_impl, buffer_changes, reset_buffer, get_xml_nodes, parse_change +from atm_manip import atm_config_chg_impl, buffer_changes, reset_buffer, reset_node_changes, get_xml_nodes, parse_change from utils import run_cmd_no_fail, expect, GoodFormatter # Add path to cime @@ -47,9 +47,17 @@ def atm_config_chg(changes, reset=False, buffer_only=False): expect(not reset, "Makes no sense for buffer_only and reset to both be on") if reset: - reset_buffer() - print("All buffered atmchanges have been removed.") hack_xml = run_cmd_no_fail("./xmlquery SCREAM_HACK_XML --value") + if reset is True: + reset_buffer() + print("All buffered atmchanges have been removed.") + else: + with open("namelist_scream.xml", "r") as fd: + tree = ET.parse(fd) + root = tree.getroot() + reset_node_changes(root, reset) + print(f"Buffered atmchanges for '{reset}' have been removed.") + if hack_xml == "TRUE": print("SCREAM_HACK_XML is on. Removing namelist_scream.xml to force regen") os.remove("namelist_scream.xml") @@ -118,8 +126,10 @@ OR parser.add_argument( "-r", "--reset", default=False, - action="store_true", - help="Forget all previous atmchanges", + nargs="?", + const=True, + metavar="NODE", + help="Forget all previous atmchanges (if NODE is given, only forget changes targeting that XML node)", ) parser.add_argument( diff --git a/components/eamxx/scripts/compare-nc-files b/components/eamxx/scripts/compare-nc-files index 2cfafc3607c0..e1b785436a87 100755 --- a/components/eamxx/scripts/compare-nc-files +++ b/components/eamxx/scripts/compare-nc-files @@ -51,6 +51,10 @@ OR parser.add_argument("-c","--compare",nargs='+', default=[], help="Compare variables from src file against variables from tgt file") + # Allow the test to continue if variables are transposes of each other. + parser.add_argument("-a","--allow_transpose",action="store_true", default=False, + help="Whether to allow comparison between variables that are transposed") + return parser.parse_args(args[1:]) ############################################################################### @@ -62,7 +66,8 @@ def _main_func(description): print (f" **** Comparing nc files **** \n" f" src file: {pncf._src_file}\n" f" tgt file: {pncf._tgt_file}\n" - f" comparisons: {cmp_str}") + f" comparisons: {cmp_str}" + f" allow transpose: {pncf._allow_transpose}") success = pncf.run() print("Comparisons result: {}".format("SUCCESS" if success else "FAIL")) diff --git a/components/eamxx/scripts/compare_nc_files.py b/components/eamxx/scripts/compare_nc_files.py index 07327fa92adc..b8e4e350f2d6 100644 --- a/components/eamxx/scripts/compare_nc_files.py +++ b/components/eamxx/scripts/compare_nc_files.py @@ -2,7 +2,10 @@ ensure_netcdf4() -from netCDF4 import Dataset +from utils import _ensure_pylib_impl +_ensure_pylib_impl("xarray") + +import xarray as xr import numpy as np import pathlib @@ -12,7 +15,7 @@ class CompareNcFiles(object): ############################################################################### ########################################################################### - def __init__(self,src_file,tgt_file=None,tolerance=0,compare=None): + def __init__(self,src_file,tgt_file=None,tolerance=0,compare=None,allow_transpose=False): ########################################################################### self._src_file = pathlib.Path(src_file).resolve().absolute() @@ -21,6 +24,7 @@ def __init__(self,src_file,tgt_file=None,tolerance=0,compare=None): self._compare = compare self._tol = tolerance + self._allow_transpose = allow_transpose if tgt_file is None: self._tgt_file = self._src_file @@ -63,10 +67,23 @@ def get_name_and_dims(self,name_dims): def compare_variables(self): ########################################################################### - ds_src = Dataset(self._src_file,'r') - ds_tgt = Dataset(self._tgt_file,'r') + ds_src = xr.open_dataset(self._src_file) + ds_tgt = xr.open_dataset(self._tgt_file) success = True + + # If no comparison is passed, compare all variables. + if self._compare is None or self._compare==[]: + self._compare = [] + for var in ds_src.variables: + if var not in ds_tgt.variables: + print (f" Comparison failed! Variable not found.\n" + f" - var name: {var}\n" + f" - file name: {self._tgt_file}") + success = False + continue + self._compare.append(var+"="+var) + for expr in self._compare: # Split the expression, to get the output var name tokens = expr.split('=') @@ -90,11 +107,11 @@ def compare_variables(self): f" - file name: {self._tgt_file}") success = False continue - lvar = ds_src.variables[lname]; - rvar = ds_tgt.variables[rname]; + lvar = ds_src[lname]; + rvar = ds_tgt[rname]; - lvar_rank = len(lvar.dimensions) - rvar_rank = len(rvar.dimensions) + lvar_rank = len(lvar.dims) + rvar_rank = len(rvar.dims) expect (len(ldims)==0 or len(ldims)==lvar_rank, f"Invalid slice specification for {lname}.\n" @@ -105,51 +122,36 @@ def compare_variables(self): f" input request: ({','.join(rdims)})\n" f" variable rank: {rvar_rank}") - - lslices = [[idim,slice] for idim,slice in enumerate(ldims) if slice!=":"] - rslices = [[idim,slice] for idim,slice in enumerate(rdims) if slice!=":"] - - lrank = lvar_rank - len(lslices) - rrank = rvar_rank - len(rslices) - - if lrank!=rrank: - print (f" Comparison failed. Rank mismatch.\n" - f" - input comparison: {expr}\n" - f" - upon slicing, rank({lname}) = {lrank}\n" - f" - upon slicing, rank({rname}) = {rrank}") - success = False - continue - - lvals = self.slice_variable(lvar,lvar[:],lslices) - rvals = self.slice_variable(rvar,rvar[:],rslices) - - diff = np.abs(lvals-rvals) - not_close = diff > (self._tol + self._tol*np.abs(rvals)) + lslices = {lvar.dims[idim]:int(slice)-1 for idim,slice in enumerate(ldims) if slice!=":"} + rslices = {rvar.dims[idim]:int(slice)-1 for idim,slice in enumerate(rdims) if slice!=":"} + lvar_sliced = lvar.sel(lslices) + rvar_sliced = rvar.sel(rslices) + expect (set(lvar_sliced.dims) == set(rvar_sliced.dims), + f"Error, even when sliced these two elements do not share the same dimensionsn\n" + f" - left var name : {lname}\n" + f" - right var name : {rname}\n" + f" - left dimensions : {lvar_sliced.dims}\n" + f" - right dimensions: {rvar_sliced.dims}\n") + + if self._allow_transpose: + rvar_sliced = rvar_sliced.transpose(*lvar_sliced.dims) + + # Apply tolerance when comparing + diff = np.abs(lvar_sliced.data - rvar_sliced.data) + not_close = diff > (self._tol + self._tol * np.abs(rvar_sliced.data)) where = np.argwhere(not_close) if where.size > 0: - idx = where[0] - lval = lvals[tuple(idx)] - rval = rvals[tuple(idx)] - print (f" Comparison failed. Values differ.\n" - f" - input comparison: {expr}\n" - f' - upon slicing, {lname}({tuple(idx)}) = {lval}\n' - f' - upon slicing, {rname}({tuple(idx)}) = {rval}') + rse = np.sqrt((lvar_sliced.data-rvar_sliced.data)**2) + nonmatch_count = np.count_nonzero(not_close) + print (f" Comparison failed. Values differ at {nonmatch_count} out of {rse.size} locations.\n" + f" - input comparison: {expr}\n" + f' - max L2 error, {rse.max()}\n' + f' - max L2 location, [{",".join(map(str,(np.array(np.unravel_index(rse.argmax(),rse.shape))+1).tolist()))}]\n' + f' - dimensions, {lvar_sliced.dims}') success = False return success - ########################################################################### - def slice_variable(self,var,vals,slices): - ########################################################################### - - if len(slices)==0: - return vals - - idim, slice_idx = slices.pop(-1) - vals = vals.take(int(slice_idx)-1,axis=int(idim)) - - return self.slice_variable(var,vals,slices) - ########################################################################### def run(self): ########################################################################### diff --git a/components/eamxx/src/control/atmosphere_driver.cpp b/components/eamxx/src/control/atmosphere_driver.cpp index 4de53ae3266e..44dd0beaf49c 100644 --- a/components/eamxx/src/control/atmosphere_driver.cpp +++ b/components/eamxx/src/control/atmosphere_driver.cpp @@ -1063,7 +1063,8 @@ void AtmosphereDriver::set_initial_conditions () if (ic_pl.isParameter(fname)) { // This is the case that the user provided an initialization // for this field in the parameter file. - if (ic_pl.isType(fname) or ic_pl.isType>(fname)) { + if (ic_pl.isType(fname) or ic_pl.isType(fname) or + ic_pl.isType>(fname)) { // Initial condition is a constant initialize_constant_field(fid, ic_pl); @@ -1507,8 +1508,13 @@ initialize_constant_field(const FieldIdentifier& fid, } } } else { - const auto& value = ic_pl.get(name); - f.deep_copy(value); + if (ic_pl.isType(name)) { + const auto& value = ic_pl.get(name); + f.deep_copy(value); + } else { + const auto& value = ic_pl.get(name); + f.deep_copy(value); + } } } diff --git a/components/eamxx/src/dynamics/homme/CMakeLists.txt b/components/eamxx/src/dynamics/homme/CMakeLists.txt index 8a638839f82c..0cf021428c6c 100644 --- a/components/eamxx/src/dynamics/homme/CMakeLists.txt +++ b/components/eamxx/src/dynamics/homme/CMakeLists.txt @@ -6,7 +6,7 @@ set (HOMME_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/homme CACHE INTERNAL "Homme bi # If using Intel, we need to tell Homme to link against mkl rather than lapack -if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") +if (CMAKE_CXX_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") option (HOMME_USE_MKL "Whether to use Intel's MKL instead of blas/lapack" ON) option (HOMME_FIND_BLASLAPACK "Whether to use system blas/lapack" OFF) else () diff --git a/components/eamxx/src/physics/cosp/eamxx_cosp.cpp b/components/eamxx/src/physics/cosp/eamxx_cosp.cpp index 6712f473533c..32e85abbf422 100644 --- a/components/eamxx/src/physics/cosp/eamxx_cosp.cpp +++ b/components/eamxx/src/physics/cosp/eamxx_cosp.cpp @@ -68,7 +68,7 @@ void Cosp::create_requests() add_field("surf_radiative_T", scalar2d , K, grid_name); //add_field("surfelev", scalar2d , m, grid_name); //add_field("landmask", scalar2d , nondim, grid_name); - add_field("sunlit_mask", scalar2d , nondim, grid_name); + add_field(FieldIdentifier("sunlit_mask", scalar2d, nondim, grid_name, DataType::IntType)); add_field("p_mid", scalar3d_mid, Pa, grid_name); add_field("p_int", scalar3d_int, Pa, grid_name); //add_field("height_mid", scalar3d_mid, m, grid_name); @@ -98,10 +98,9 @@ void Cosp::create_requests() add_field("misr_cthtau", scalar4d_cthtau, percent, grid_name, 1); // We can allocate these now - m_z_mid = Field(FieldIdentifier("z_mid",scalar3d_mid,m,grid_name)); - m_z_int = Field(FieldIdentifier("z_int",scalar3d_int,m,grid_name)); - m_z_mid.allocate_view(); - m_z_int.allocate_view(); + m_z_mid = Field(FieldIdentifier("z_mid",scalar3d_mid,m,grid_name),true); + m_z_int = Field(FieldIdentifier("z_int",scalar3d_int,m,grid_name),true); + m_sunlit_real = Field(FieldIdentifier("sunlit_mask_real",scalar2d,nondim,grid_name),true); } // ========================================================================================= @@ -110,12 +109,35 @@ void Cosp::initialize_impl (const RunType /* run_type */) // Set property checks for fields in this process CospFunc::initialize(m_num_cols, m_num_subcols, m_num_levs); + using namespace ShortFieldTagsNames; + + // Create the masks for the 4d fields + FieldLayout scalar4d_ctptau ( {COL,CMP,CMP}, + {m_num_cols,m_num_tau,m_num_ctp}, + {e2str(COL), "cosp_tau", "cosp_prs"}); + FieldLayout scalar4d_cthtau ( {COL,CMP,CMP}, + {m_num_cols,m_num_tau,m_num_cth}, + {e2str(COL), "cosp_tau", "cosp_cth"}); + + const auto nondim = ekat::units::Units::nondimensional(); + FieldIdentifier mctp_fid ("sunlit_mask_ctptau", scalar4d_ctptau, nondim, m_grid->name(), DataType::IntType); + FieldIdentifier mcth_fid ("sunlit_mask_cthtau", scalar4d_cthtau, nondim, m_grid->name(), DataType::IntType); + Field mctp(mctp_fid,true); + Field mcth(mcth_fid,true); + std::map masks = { + {"isccp_cldtot", get_field_in("sunlit_mask")}, + {"isccp_ctptau", mctp}, + {"modis_ctptau", mctp}, + {"misr_cthtau", mcth}, + }; // Set the mask field for each of the cosp computed fields std::list vnames = {"isccp_cldtot", "isccp_ctptau", "modis_ctptau", "misr_cthtau"}; for (const auto& field_name : vnames) { // the mask here is just the sunlit mask, so set it - get_field_out(field_name).get_header().set_extra_data("mask_field", get_field_in("sunlit_mask")); - get_field_out(field_name).get_header().set_may_be_filled(true); + auto& f = get_field_out(field_name); + + f.get_header().set_extra_data("valid_mask", masks.at(field_name)); + f.get_header().set_may_be_filled(true); } } @@ -150,7 +172,6 @@ void Cosp::run_impl (const double dt) get_field_in("qv").sync_to_host(); get_field_in("qc").sync_to_host(); get_field_in("qi").sync_to_host(); - get_field_in("sunlit_mask").sync_to_host(); get_field_in("surf_radiative_T").sync_to_host(); get_field_in("T_mid").sync_to_host(); get_field_in("p_mid").sync_to_host(); @@ -161,6 +182,8 @@ void Cosp::run_impl (const double dt) get_field_in("dtau067").sync_to_host(); get_field_in("dtau105").sync_to_host(); + m_sunlit_real.deep_copy(get_field_in("sunlit_mask")); + m_sunlit_real.sync_to_host(); // Compute z_mid const auto T_mid_d = get_field_in("T_mid").get_view(); const auto qv_d = get_field_in("qv").get_view(); @@ -211,7 +234,7 @@ void Cosp::run_impl (const double dt) const auto p_mid_h = get_field_in("p_mid").get_view(); const auto qc_h = get_field_in("qc").get_view(); const auto qi_h = get_field_in("qi").get_view(); - const auto sunlit_h = get_field_in("sunlit_mask").get_view(); + const auto sunlit_h = m_sunlit_real.get_view(); const auto skt_h = get_field_in("surf_radiative_T").get_view(); const auto p_int_h = get_field_in("p_int").get_view(); const auto cldfrac_h = get_field_in("cldfrac_rad").get_view(); @@ -255,6 +278,27 @@ void Cosp::run_impl (const double dt) get_field_out("isccp_ctptau").sync_to_dev(); get_field_out("modis_ctptau").sync_to_dev(); get_field_out("misr_cthtau").sync_to_dev(); + + // Update the ctptau and cthtau masks by broadcasting sunlit mask + const auto& sunlit = get_field_in("sunlit_mask"); + auto& ctptau = get_field_out("isccp_ctptau").get_header().get_extra_data("valid_mask"); + auto& cthtau = get_field_out("misr_cthtau").get_header().get_extra_data("valid_mask"); + + auto sunlit_v = sunlit.get_view(); + auto ctptau_v = ctptau.get_view(); + auto cthtau_v = cthtau.get_view(); + auto do_ctp = KOKKOS_LAMBDA (int icol, int itau, int ictp) { + ctptau_v(icol,itau,ictp) = sunlit_v(icol); + }; + auto do_cth = KOKKOS_LAMBDA (int icol, int itau, int icth) { + cthtau_v(icol,itau,icth) = sunlit_v(icol); + }; + using exec_space = typename DefaultDevice::execution_space; + using policy_t = Kokkos::MDRangePolicy>; + policy_t policy_ctp({0,0,0},{m_num_cols,m_num_tau,m_num_ctp}); + policy_t policy_cth({0,0,0},{m_num_cols,m_num_tau,m_num_cth}); + Kokkos::parallel_for(policy_ctp,do_ctp); + Kokkos::parallel_for(policy_cth,do_cth); } } diff --git a/components/eamxx/src/physics/cosp/eamxx_cosp.hpp b/components/eamxx/src/physics/cosp/eamxx_cosp.hpp index f1439b2dd199..c2a0b988d96b 100644 --- a/components/eamxx/src/physics/cosp/eamxx_cosp.hpp +++ b/components/eamxx/src/physics/cosp/eamxx_cosp.hpp @@ -74,6 +74,7 @@ class Cosp : public AtmosphereProcess // TODO: use atm buffer instead Field m_z_mid; Field m_z_int; + Field m_sunlit_real; }; // class Cosp } // namespace scream diff --git a/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.cpp b/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.cpp index aa73f0e5a5b9..dae53eb30f68 100644 --- a/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.cpp +++ b/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.cpp @@ -4,6 +4,7 @@ #include "share/property_checks/field_within_interval_check.hpp" #include +#include namespace scream { @@ -80,7 +81,7 @@ size_t IOPForcing::requested_buffer_size_in_bytes() const // Number of bytes needed by the WorkspaceManager passed to shoc_main const int nlevi_packs = ekat::npack(m_num_levs+1); const auto policy = TPF::get_default_team_policy(m_num_cols, nlevi_packs); - const size_t wsm_bytes = WorkspaceMgr::get_total_bytes_needed(nlevi_packs, 7+m_num_tracers, policy); + const size_t wsm_bytes = WorkspaceMgr::get_total_bytes_needed(nlevi_packs, 8, policy); return wsm_bytes; } @@ -99,7 +100,7 @@ void IOPForcing::init_buffers(const ATMBufferManager &buffer_manager) m_buffer.wsm_data = mem; const auto policy = TPF::get_default_team_policy(m_num_cols, nlevi_packs); - const size_t wsm_npacks = WorkspaceMgr::get_total_bytes_needed(nlevi_packs, 7+m_num_tracers, policy)/sizeof(Pack); + const size_t wsm_npacks = WorkspaceMgr::get_total_bytes_needed(nlevi_packs, 8, policy)/sizeof(Pack); mem += wsm_npacks; size_t used_mem = (reinterpret_cast(mem) - buffer_manager.get_memory())*sizeof(Real); @@ -138,7 +139,7 @@ void IOPForcing::initialize_impl (const RunType run_type) // Setup WSM for internal local variables const auto nlevi_packs = ekat::npack(m_num_levs+1); const auto policy = TPF::get_default_team_policy(m_num_cols, nlevi_packs); - m_workspace_mgr.setup(m_buffer.wsm_data, nlevi_packs, 7+m_num_tracers, policy); + m_workspace_mgr.setup(m_buffer.wsm_data, nlevi_packs, 8, policy); // Compute field for horizontal contraction weights (1/num_global_dofs) const auto iop_nudge_tq = m_iop_data_manager->get_params().get("iop_nudge_tq"); @@ -147,149 +148,72 @@ void IOPForcing::initialize_impl (const RunType run_type) if (iop_nudge_tq or iop_nudge_uv) m_helper_fields.at("horiz_mean_weights").deep_copy(one_over_num_dofs); } // ========================================================================================= + KOKKOS_FUNCTION void IOPForcing:: -advance_iop_subsidence(const MemberType& team, - const int nlevs, - const Real dt, - const Real ps, - const view_1d& ref_p_mid, - const view_1d& ref_p_int, - const view_1d& ref_p_del, - const view_1d& omega, - const Workspace& workspace, - const view_1d& u, - const view_1d& v, - const view_1d& T, - const view_2d& Q) +advance_iop_subsidence (const MemberType& team, + const int nlevs, + const Real dt, + const view_1d& ref_p_mid, + const view_1d& omega, + const Workspace& workspace, + const view_1d& u, + const view_1d& v, + const view_1d& T, + const view_2d& Q, + ekat::LinInterp& interp) { - constexpr Real Rair = C::Rair.value; + constexpr Real Rair = C::Rair.value; constexpr Real Cpair = C::Cpair.value; - const auto n_q_tracers = Q.extent_int(0); - const auto nlev_packs = ekat::npack(nlevs); + const int n_q_tracers = Q.extent_int(0); + const int nlev_packs = ekat::npack(nlevs); + + const int icol = team.league_rank(); - // Get some temporary views from WS - uview_1d omega_int, delta_u, delta_v, delta_T, tmp; - workspace.take_many_contiguous_unsafe<4>({"omega_int", "delta_u", "delta_v", "delta_T"}, - {&omega_int, &delta_u, &delta_v, &delta_T}); - const auto delta_Q_slot = workspace.take_macro_block("delta_Q", n_q_tracers); - uview_2d delta_Q(delta_Q_slot.data(), n_q_tracers, nlev_packs); - - auto s_ref_p_mid = ekat::scalarize(ref_p_mid); - auto s_omega = ekat::scalarize(omega); - auto s_delta_u = ekat::scalarize(delta_u); - auto s_delta_v = ekat::scalarize(delta_v); - auto s_delta_T = ekat::scalarize(delta_T); - auto s_delta_Q = ekat::scalarize(delta_Q); - auto s_omega_int = ekat::scalarize(omega_int); - - // Compute omega on the interface grid by using a weighted average in pressure - const int pack_begin = 1/Pack::n, pack_end = (nlevs-1)/Pack::n; - Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pack_begin, pack_end+1), [&] (const int k){ - auto range_pack = ekat::range(k*Pack::n); - range_pack.set(range_pack<1, 1); - Pack ref_p_mid_k, ref_p_mid_km1, omega_k, omega_km1; - ekat::index_and_shift<-1>(s_ref_p_mid, range_pack, ref_p_mid_k, ref_p_mid_km1); - ekat::index_and_shift<-1>(s_omega, range_pack, omega_k, omega_km1); - - const auto weight = (ref_p_int(k) - ref_p_mid_km1)/(ref_p_mid_k - ref_p_mid_km1); - omega_int(k).set(range_pack>=1 and range_pack<=nlevs-1, - weight*omega_k + (1-weight)*omega_km1); + // Workspace temporaries + uview_1d p_dep, u_new, v_new, T_new, q_new; + + // allocate temporaries from workspace + workspace.take_many_contiguous_unsafe<5>( + {"iop_p_dep","iop_u_new","iop_v_new","iop_T_new","iop_q_new"}, + {&p_dep, &u_new, &v_new, &T_new, &q_new}); + + // Compute Departure Points using large-scale vertical velocity + Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev_packs), [&](const int k) { + p_dep(k) = ref_p_mid(k) - dt * omega(k); }); - omega_int(0)[0] = 0; - omega_int(nlevs/Pack::n)[nlevs%Pack::n] = 0; - - // Compute delta views for u, v, T, and Q (e.g., u(k+1) - u(k), k=0,...,nlevs-2) - ColOps::compute_midpoint_delta(team, nlevs-1, u, delta_u); - ColOps::compute_midpoint_delta(team, nlevs-1, v, delta_v); - ColOps::compute_midpoint_delta(team, nlevs-1, T, delta_T); - for (int iq=0; iq(k*Pack::n); - const auto at_top = range_pack==0; - const auto not_at_top = not at_top; - const auto at_bot = range_pack==nlevs-1; - const auto not_at_bot = not at_bot; - const bool any_at_top = at_top.any(); - const bool any_at_bot = at_bot.any(); - - // Get delta(k-1) packs. The range pack should not - // contain index 0 (so that we don't attempt to access - // k=-1 index) or index > nlevs-2 (since delta_* views - // are size nlevs-1). - auto range_pack_for_m1_shift = range_pack; - range_pack_for_m1_shift.set(range_pack<1, 1); - range_pack_for_m1_shift.set(range_pack>nlevs-2, nlevs-2); - Pack delta_u_k, delta_u_km1, - delta_v_k, delta_v_km1, - delta_T_k, delta_T_km1; - ekat::index_and_shift<-1>(s_delta_u, range_pack_for_m1_shift, delta_u_k, delta_u_km1); - ekat::index_and_shift<-1>(s_delta_v, range_pack_for_m1_shift, delta_v_k, delta_v_km1); - ekat::index_and_shift<-1>(s_delta_T, range_pack_for_m1_shift, delta_T_k, delta_T_km1); - - // At the top and bottom of the model, set the end points for - // delta_*_k and delta_*_km1 to be the first and last entries - // of delta_*, respectively. - if (any_at_top) { - delta_u_k.set(at_top, s_delta_u(0)); - delta_v_k.set(at_top, s_delta_v(0)); - delta_T_k.set(at_top, s_delta_T(0)); - } - if (any_at_bot) { - delta_u_km1.set(at_bot, s_delta_u(nlevs-2)); - delta_v_km1.set(at_bot, s_delta_v(nlevs-2)); - delta_T_km1.set(at_bot, s_delta_T(nlevs-2)); - } + interp.setup(team, ref_p_mid, p_dep); - // Get omega_int(k+1) pack. The range pack should not - // contain index > nlevs-1 (since omega_int is size nlevs+1). - auto range_pack_for_p1_shift = range_pack; - range_pack_for_p1_shift.set(range_pack>nlevs-1, nlevs-1); - Pack omega_int_k, omega_int_kp1; - ekat::index_and_shift<1>(s_omega_int, range_pack, omega_int_k, omega_int_kp1); - - const auto fac = (dt/2)/ref_p_del(k); - - // Update u - u(k).update(not_at_bot, fac*omega_int_kp1*delta_u_k, -1, 1); - u(k).update(not_at_top, fac*omega_int_k*delta_u_km1, -1, 1); - - // Update v - v(k).update(not_at_bot, fac*omega_int_kp1*delta_v_k, -1, 1); - v(k).update(not_at_top, fac*omega_int_k*delta_v_km1, -1, 1); - - // Before updating T, first scale using thermal - // expansion term due to LS vertical advection - T(k) *= 1 + (dt*Rair/Cpair)*omega(k)/ref_p_mid(k); - - // Update T - T(k).update(not_at_bot, fac*omega_int_kp1*delta_T_k, -1, 1); - T(k).update(not_at_top, fac*omega_int_k*delta_T_km1, -1, 1); - - // Update Q - Pack delta_tracer_k, delta_tracer_km1; - for (int iq=0; iq(s_delta_tracer, range_pack_for_m1_shift, delta_tracer_k, delta_tracer_km1); - if (any_at_top) delta_tracer_k.set(at_top, s_delta_tracer(0)); - if (any_at_bot) delta_tracer_km1.set(at_bot, s_delta_tracer(nlevs-2)); - - Q(iq, k).update(not_at_bot, fac*omega_int_kp1*delta_tracer_k, -1, 1); - Q(iq, k).update(not_at_top, fac*omega_int_k*delta_tracer_km1, -1, 1); - } + interp.lin_interp(team, ref_p_mid, p_dep, u, u_new, icol); + interp.lin_interp(team, ref_p_mid, p_dep, v, v_new, icol); + interp.lin_interp(team, ref_p_mid, p_dep, T, T_new, icol); + team.team_barrier(); + + // Thermal expansion correction + write back + Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev_packs), [&](const int k) { + T_new(k) *= 1.0 + (dt * Rair / Cpair) * omega(k) / ref_p_mid(k); + u(k) = u_new(k); + v(k) = v_new(k); + T(k) = T_new(k); }); - // Release WS views - workspace.release_macro_block(delta_Q_slot, n_q_tracers); - workspace.release_many_contiguous<4>({&omega_int, &delta_u, &delta_v, &delta_T}); + // Tracers + for (int m = 0; m < n_q_tracers; ++m) { + const auto q_m = Kokkos::subview(Q, m, Kokkos::ALL()); + + interp.lin_interp(team, ref_p_mid, p_dep, q_m, q_new, icol); + team.team_barrier(); + + Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nlev_packs), [&](const int k) { + q_m(k) = q_new(k); + }); + } + + workspace.release_many_contiguous<5>( + {&p_dep,&u_new,&v_new,&T_new,&q_new}); } // ========================================================================================= KOKKOS_FUNCTION @@ -407,6 +331,10 @@ void IOPForcing::run_impl (const double dt) auto wsm = m_workspace_mgr; auto num_levs = m_num_levs; + const auto ncols = m_grid->get_num_local_dofs(); + + ekat::LinInterp subs_interp(ncols, num_levs, num_levs); + // Apply IOP forcing Kokkos::parallel_for("apply_iop_forcing", policy_iop, KOKKOS_LAMBDA (const MemberType& team) { const int icol = team.league_rank(); @@ -437,9 +365,11 @@ void IOPForcing::run_impl (const double dt) ColOps::compute_midpoint_delta(team, num_levs, ref_p_int, ref_p_del); team.team_barrier(); + auto interp_local = subs_interp; + if (iop_dosubsidence) { // Compute subsidence due to large-scale forcing - advance_iop_subsidence(team, num_levs, dt, ps_i, ref_p_mid, ref_p_int, ref_p_del, omega, ws, u_i, v_i, T_mid_i, Q_i); + advance_iop_subsidence(team, num_levs, dt, ref_p_mid, omega, ws, u_i, v_i, T_mid_i, Q_i, interp_local); } // Update T and qv according to large scale forcing as specified in IOP file. diff --git a/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.hpp b/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.hpp index c3dd2804a053..3604248a4dea 100644 --- a/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.hpp +++ b/components/eamxx/src/physics/iop_forcing/eamxx_iop_forcing_process_interface.hpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -77,16 +78,14 @@ class IOPForcing : public scream::AtmosphereProcess static void advance_iop_subsidence(const KT::MemberType& team, const int nlevs, const Real dt, - const Real ps, const view_1d& pmid, - const view_1d& pint, - const view_1d& pdel, const view_1d& omega, const Workspace& workspace, const view_1d& u, const view_1d& v, const view_1d& T, - const view_2d& Q); + const view_2d& Q, + ekat::LinInterp& interp); // Apply large scale forcing for temperature and water vapor as provided by the IOP file KOKKOS_FUNCTION diff --git a/components/eamxx/src/physics/rrtmgp/eamxx_rrtmgp_process_interface.cpp b/components/eamxx/src/physics/rrtmgp/eamxx_rrtmgp_process_interface.cpp index 100953f758f0..5247b7325ded 100644 --- a/components/eamxx/src/physics/rrtmgp/eamxx_rrtmgp_process_interface.cpp +++ b/components/eamxx/src/physics/rrtmgp/eamxx_rrtmgp_process_interface.cpp @@ -209,7 +209,7 @@ void RRTMGPRadiation::create_requests() { // 0.67 micron and 10.5 micron optical depth (needed for COSP) add_field("dtau067" , scalar3d_mid, nondim, grid_name); add_field("dtau105" , scalar3d_mid, nondim, grid_name); - add_field("sunlit_mask" , scalar2d , nondim, grid_name); + add_field(FieldIdentifier("sunlit_mask", scalar2d, nondim, grid_name, DataType::IntType)); add_field("cldfrac_rad" , scalar3d_mid, nondim, grid_name); // Cloud-top diagnostics following AeroCom recommendation add_field("T_mid_at_cldtop", scalar2d, K, grid_name); @@ -612,7 +612,7 @@ void RRTMGPRadiation::run_impl (const double dt) { // Outputs for COSP auto d_dtau067 = get_field_out("dtau067").get_view(); auto d_dtau105 = get_field_out("dtau105").get_view(); - auto d_sunlit = get_field_out("sunlit_mask").get_view(); + auto d_sunlit = get_field_out("sunlit_mask").get_view(); Kokkos::deep_copy(d_dtau067,0.0); Kokkos::deep_copy(d_dtau105,0.0); @@ -1154,11 +1154,7 @@ void RRTMGPRadiation::run_impl (const double dt) { d_dtau067(icol,k) = cld_tau_sw_bnd_k(i,k,idx_067_k); d_dtau105(icol,k) = cld_tau_lw_bnd_k(i,k,idx_105_k); }); - if (d_sw_clrsky_flux_dn(icol,0) > 0) { - d_sunlit(icol) = 1.0; - } else { - d_sunlit(icol) = 0.0; - } + d_sunlit(icol) = d_sw_clrsky_flux_dn(icol,0) > 0; }); ); } // loop over chunk diff --git a/components/eamxx/src/physics/shoc/disp/shoc_length_disp.cpp b/components/eamxx/src/physics/shoc/disp/shoc_length_disp.cpp index 72717997cfe4..940dbb6aeba2 100644 --- a/components/eamxx/src/physics/shoc/disp/shoc_length_disp.cpp +++ b/components/eamxx/src/physics/shoc/disp/shoc_length_disp.cpp @@ -13,15 +13,13 @@ ::shoc_length_disp( const Int& nlev, const Int& nlevi, const Scalar& length_fac, - const bool& shoc_1p5tke, const view_1d& dx, const view_1d& dy, - const view_2d& zt_grid, - const view_2d& zi_grid, - const view_2d& dz_zt, - const view_2d& tke, - const view_2d& thv, - const view_2d& tk, + const view_2d& zt_grid, + const view_2d& zi_grid, + const view_2d& dz_zt, + const view_2d& tke, + const view_2d& thv, const WorkspaceMgr& workspace_mgr, const view_2d& brunt, const view_2d& shoc_mix) @@ -36,14 +34,13 @@ ::shoc_length_disp( auto workspace = workspace_mgr.get_workspace(team); - shoc_length(team, nlev, nlevi, length_fac, shoc_1p5tke, + shoc_length(team, nlev, nlevi, length_fac, dx(i), dy(i), ekat::subview(zt_grid, i), ekat::subview(zi_grid, i), ekat::subview(dz_zt, i), ekat::subview(tke, i), ekat::subview(thv, i), - ekat::subview(tk, i), workspace, ekat::subview(brunt, i), ekat::subview(shoc_mix, i)); diff --git a/components/eamxx/src/physics/shoc/impl/shoc_compute_shoc_mix_shoc_length_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_compute_shoc_mix_shoc_length_impl.hpp index b0ae96932703..b018eca60bbf 100644 --- a/components/eamxx/src/physics/shoc/impl/shoc_compute_shoc_mix_shoc_length_impl.hpp +++ b/components/eamxx/src/physics/shoc/impl/shoc_compute_shoc_mix_shoc_length_impl.hpp @@ -13,12 +13,9 @@ ::compute_shoc_mix_shoc_length( const MemberType& team, const Int& nlev, const Scalar& length_fac, - const bool& shoc_1p5tke, const uview_1d& tke, const uview_1d& brunt, const uview_1d& zt_grid, - const uview_1d& dz_zt, - const uview_1d& tk, const Scalar& l_inf, const uview_1d& shoc_mix) { @@ -33,31 +30,11 @@ ::compute_shoc_mix_shoc_length( const Pack tkes = ekat::sqrt(tke(k)); const Pack brunt2 = ekat::max(0, brunt(k)); - if (shoc_1p5tke){ - // If 1.5 TKE closure then set length scale to vertical grid spacing for - // cells with unstable brunt vaisalla frequency. Otherwise, overwrite the length - // scale in stable cells with the new definition. + shoc_mix(k) = ekat::min(maxlen, + sp(2.8284)*(ekat::sqrt(1/((1/(tscale*tkes*vk*zt_grid(k))) + + (1/(tscale*tkes*l_inf)) + + sp(0.01)*(brunt2/tke(k)))))/length_fac); - // Search for stable cells - const auto stable_mask = brunt(k) > 0; - - // To avoid FPE when calculating sqrt(brunt), set brunt_tmp=0 in the case brunt<1. - Pack brunt_tmp(stable_mask, brunt(k)); - - // Define length scale for stable cells - const auto length_tmp = ekat::sqrt(sp(0.76)*tk(k)/sp(0.1)/ekat::sqrt(brunt_tmp + sp(1.e-10))); - // Limit the stability corrected length scale between 0.1*dz and dz - const auto limited_len = ekat::min(dz_zt(k),ekat::max(sp(0.1)*dz_zt(k),length_tmp)); - - // Set length scale to vertical grid if unstable, otherwise the stability adjusted value. - shoc_mix(k).set(stable_mask, limited_len, dz_zt(k)); - } else{ - shoc_mix(k) = ekat::min(maxlen, - sp(2.8284)*(ekat::sqrt(1/((1/(tscale*tkes*vk*zt_grid(k))) - + (1/(tscale*tkes*l_inf)) - + sp(0.01)*(brunt2/tke(k)))))/length_fac); - - } }); } diff --git a/components/eamxx/src/physics/shoc/impl/shoc_eddy_diffusivities_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_eddy_diffusivities_impl.hpp index d2c1f4ae4d20..48f0ee795439 100644 --- a/components/eamxx/src/physics/shoc/impl/shoc_eddy_diffusivities_impl.hpp +++ b/components/eamxx/src/physics/shoc/impl/shoc_eddy_diffusivities_impl.hpp @@ -16,7 +16,6 @@ KOKKOS_FUNCTION void Functions::eddy_diffusivities( const MemberType& team, const Int& nlev, - const bool& shoc_1p5tke, const Scalar& Ckh, const Scalar& Ckm, const Scalar& pblh, @@ -51,16 +50,8 @@ void Functions::eddy_diffusivities( tkh(k).set(condition, Ckh_s*ekat::square(shoc_mix(k))*ekat::sqrt(sterm_zt(k))); tk(k).set(condition, Ckm_s*ekat::square(shoc_mix(k))*ekat::sqrt(sterm_zt(k))); - if (shoc_1p5tke){ - // Revert to a standard 1.5 TKE closure for eddy diffusivities - tkh(k).set(!condition, Ckh*shoc_mix(k)*ekat::sqrt(tke(k))); - tk(k).set(!condition, Ckm*shoc_mix(k)*ekat::sqrt(tke(k))); - } - else{ - // Default SHOC definition of eddy diffusivity for heat and momentum - tkh(k).set(!condition, Ckh*isotropy(k)*tke(k)); - tk(k).set(!condition, Ckm*isotropy(k)*tke(k)); - } + tkh(k).set(!condition, Ckh*isotropy(k)*tke(k)); + tk(k).set(!condition, Ckm*isotropy(k)*tke(k)); }); } diff --git a/components/eamxx/src/physics/shoc/impl/shoc_length_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_length_impl.hpp index e6d321fa10a4..168d1d41b2d6 100644 --- a/components/eamxx/src/physics/shoc/impl/shoc_length_impl.hpp +++ b/components/eamxx/src/physics/shoc/impl/shoc_length_impl.hpp @@ -14,7 +14,6 @@ ::shoc_length( const Int& nlev, const Int& nlevi, const Scalar& length_fac, - const bool& shoc_1p5tke, const Scalar& dx, const Scalar& dy, const uview_1d& zt_grid, @@ -22,7 +21,6 @@ ::shoc_length( const uview_1d& dz_zt, const uview_1d& tke, const uview_1d& thv, - const uview_1d& tk, const Workspace& workspace, const uview_1d& brunt, const uview_1d& shoc_mix) @@ -39,7 +37,7 @@ ::shoc_length( Scalar l_inf = 0; compute_l_inf_shoc_length(team,nlev,zt_grid,dz_zt,tke,l_inf); - compute_shoc_mix_shoc_length(team,nlev,length_fac,shoc_1p5tke,tke,brunt,zt_grid,dz_zt,tk,l_inf,shoc_mix); + compute_shoc_mix_shoc_length(team,nlev,length_fac,tke,brunt,zt_grid,l_inf,shoc_mix); team.team_barrier(); check_length_scale_shoc_length(team,nlev,dx,dy,shoc_mix); diff --git a/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp index f0509ee6a523..b7d4879d85a5 100644 --- a/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp +++ b/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp @@ -213,10 +213,10 @@ void Functions::shoc_main_internal( // Update the turbulent length scale shoc_length(team,nlev,nlevi, // Input - length_fac,shoc_1p5tke,// Runtime Options + length_fac, // Runtime Options dx,dy, // Input zt_grid,zi_grid,dz_zt, // Input - tke,thv,tk, // Input + tke,thv, // Input workspace, // Workspace brunt,shoc_mix); // Output @@ -244,7 +244,7 @@ void Functions::shoc_main_internal( // Diagnose the second order moments diag_second_shoc_moments(team,nlev,nlevi, thl2tune, qw2tune, qwthl2tune, w2tune, // Runtime options - shoc_1p5tke, // Runtime options + shoc_1p5tke, // Runtime options thetal,qw,u_wind,v_wind, // Input tke,isotropy,tkh,tk,dz_zi,zt_grid,zi_grid, // Input shoc_mix,wthl_sfc,wqw_sfc,uw_sfc,vw_sfc, // Input @@ -480,17 +480,17 @@ void Functions::shoc_main_internal( // Update the turbulent length scale shoc_length_disp(shcol,nlev,nlevi, // Input - length_fac,shoc_1p5tke,// Runtime Options + length_fac, // Runtime Options dx,dy, // Input zt_grid,zi_grid,dz_zt, // Input - tke,thv,tk, // Input + tke,thv, // Input workspace_mgr, // Workspace mgr brunt,shoc_mix); // Output // Advance the SGS TKE equation shoc_tke_disp(shcol,nlev,nlevi,dtime, // Input - lambda_low,lambda_high,lambda_slope, // Runtime options - lambda_thresh,Ckh,Ckm,shoc_1p5tke, // Runtime options + lambda_low,lambda_high,lambda_slope, // Runtime options + lambda_thresh,Ckh,Ckm,shoc_1p5tke, // Runtime options wthv_sec, // Input shoc_mix,dz_zi,dz_zt,pres,shoc_tabs, // Input u_wind,v_wind,brunt,zt_grid, // Input @@ -510,7 +510,7 @@ void Functions::shoc_main_internal( // Diagnose the second order moments diag_second_shoc_moments_disp(shcol,nlev,nlevi, thl2tune, qw2tune, qwthl2tune, w2tune, // Runtime options - shoc_1p5tke, // Runtime options + shoc_1p5tke, // Runtime options thetal,qw,u_wind,v_wind, // Input tke,isotropy,tkh,tk,dz_zi,zt_grid,zi_grid, // Input shoc_mix,wthl_sfc,wqw_sfc,uw_sfc,vw_sfc, // Input diff --git a/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp index 89db98a9ad12..9f5fa5f8b7ab 100644 --- a/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp +++ b/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp @@ -74,7 +74,7 @@ void Functions::shoc_tke( isotropic_ts(team,nlev,lambda_low,lambda_high,lambda_slope,lambda_thresh,brunt_int,tke,a_diss,brunt,isotropy); // Compute eddy diffusivity for heat and momentum - eddy_diffusivities(team,nlev,shoc_1p5tke,Ckh,Ckm,pblh,zt_grid,tabs,shoc_mix,sterm_zt,isotropy,tke,tkh,tk); + eddy_diffusivities(team,nlev,Ckh,Ckm,pblh,zt_grid,tabs,shoc_mix,sterm_zt,isotropy,tke,tkh,tk); // Release temporary variables from the workspace workspace.template release_many_contiguous<3>( diff --git a/components/eamxx/src/physics/shoc/shoc_functions.hpp b/components/eamxx/src/physics/shoc/shoc_functions.hpp index d1f2928fbee7..164d57dc755e 100644 --- a/components/eamxx/src/physics/shoc/shoc_functions.hpp +++ b/components/eamxx/src/physics/shoc/shoc_functions.hpp @@ -277,10 +277,9 @@ template struct Functions { KOKKOS_FUNCTION static void compute_shoc_mix_shoc_length( - const MemberType &team, const Int &nlev, const Scalar &length_fac, const bool &shoc_1p5tke, + const MemberType &team, const Int &nlev, const Scalar &length_fac, const uview_1d &tke, const uview_1d &brunt, - const uview_1d &zt_grid, const uview_1d &dz_zt, - const uview_1d &tk, const Scalar &l_inf, const uview_1d &shoc_mix); + const uview_1d &zt_grid, const Scalar &l_inf, const uview_1d &shoc_mix); KOKKOS_FUNCTION static void check_tke(const MemberType &team, const Int &nlev, const uview_1d &tke); @@ -414,20 +413,20 @@ template struct Functions { KOKKOS_FUNCTION static void shoc_length(const MemberType &team, const Int &nlev, const Int &nlevi, - const Scalar &length_fac, const bool &shoc_1p5tke, const Scalar &dx, + const Scalar &length_fac, const Scalar &dx, const Scalar &dy, const uview_1d &zt_grid, const uview_1d &zi_grid, const uview_1d &dz_zt, const uview_1d &tke, const uview_1d &thv, - const uview_1d &tk, const Workspace &workspace, - const uview_1d &brunt, const uview_1d &shoc_mix); + const Workspace &workspace, const uview_1d &brunt, + const uview_1d &shoc_mix); #ifdef SCREAM_SHOC_SMALL_KERNELS static void shoc_length_disp(const Int &shcol, const Int &nlev, const Int &nlevi, - const Scalar &length_fac, const bool &tke_1p5_closure, + const Scalar &length_fac, const view_1d &dx, const view_1d &dy, const view_2d &zt_grid, const view_2d &zi_grid, const view_2d &dz_zt, const view_2d &tke, - const view_2d &thv, const view_2d &tk, + const view_2d &thv, const WorkspaceMgr &workspace_mgr, const view_2d &brunt, const view_2d &shoc_mix); #endif @@ -846,7 +845,7 @@ template struct Functions { KOKKOS_FUNCTION static void - eddy_diffusivities(const MemberType &team, const Int &nlev, const bool &shoc_1p5tke, + eddy_diffusivities(const MemberType &team, const Int &nlev, const Scalar &Ckh, const Scalar &Ckm, const Scalar &pblh, const uview_1d &zt_grid, const uview_1d &tabs, const uview_1d &shoc_mix, const uview_1d &sterm_zt, diff --git a/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.cpp b/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.cpp index 15f697081658..c695f16ef1c2 100644 --- a/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.cpp +++ b/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.cpp @@ -96,12 +96,12 @@ void adv_sgs_tke(AdvSgsTkeData& d) void eddy_diffusivities(EddyDiffusivitiesData& d) { - eddy_diffusivities_host(d.nlev, d.shcol, d.shoc_1p5tke, d.pblh, d.zt_grid, d.tabs, d.shoc_mix, d.sterm_zt, d.isotropy, d.tke, d.tkh, d.tk); + eddy_diffusivities_host(d.nlev, d.shcol, d.pblh, d.zt_grid, d.tabs, d.shoc_mix, d.sterm_zt, d.isotropy, d.tke, d.tkh, d.tk); } void shoc_length(ShocLengthData& d) { - shoc_length_host(d.shcol, d.nlev, d.nlevi, d.shoc_1p5tke, d.host_dx, d.host_dy, d.zt_grid, d.zi_grid, d.dz_zt, d.tke, d.thv, d.tk, d.brunt, d.shoc_mix); + shoc_length_host(d.shcol, d.nlev, d.nlevi, d.host_dx, d.host_dy, d.zt_grid, d.zi_grid, d.dz_zt, d.tke, d.thv, d.brunt, d.shoc_mix); } void compute_brunt_shoc_length(ComputeBruntShocLengthData& d) @@ -116,7 +116,7 @@ void compute_l_inf_shoc_length(ComputeLInfShocLengthData& d) void compute_shoc_mix_shoc_length(ComputeShocMixShocLengthData& d) { - compute_shoc_mix_shoc_length_host(d.nlev, d.shcol, d.shoc_1p5tke, d.tke, d.brunt, d.zt_grid, d.dz_zt, d.tk, d.l_inf, d.shoc_mix); + compute_shoc_mix_shoc_length_host(d.nlev, d.shcol, d.tke, d.brunt, d.zt_grid, d.l_inf, d.shoc_mix); } void check_length_scale_shoc_length(CheckLengthScaleShocLengthData& d) @@ -654,8 +654,8 @@ void shoc_pblintd_init_pot_host(Int shcol, Int nlev, Real *thl, Real* ql, Real* ekat::device_to_host({thv}, shcol, nlev, inout_views); } -void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, bool shoc_1p5tke, Real* tke, Real* brunt, - Real* zt_grid, Real* dz_zt, Real* tk, Real* l_inf, Real* shoc_mix) +void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, Real* tke, Real* brunt, + Real* zt_grid, Real* l_inf, Real* shoc_mix) { using SHF = Functions; @@ -669,8 +669,8 @@ void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, bool shoc_1p5tke, Re using MemberType = typename SHF::MemberType; std::vector temp_1d_d(1); - std::vector temp_2d_d(6); - std::vector ptr_array = {tke, brunt, zt_grid, dz_zt, tk, shoc_mix}; + std::vector temp_2d_d(4); + std::vector ptr_array = {tke, brunt, zt_grid, shoc_mix}; // Sync to device ekat::host_to_device({l_inf}, shcol, temp_1d_d); @@ -683,9 +683,7 @@ void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, bool shoc_1p5tke, Re tke_d (temp_2d_d[0]), brunt_d (temp_2d_d[1]), zt_grid_d (temp_2d_d[2]), - dz_zt_d (temp_2d_d[3]), - tk_d (temp_2d_d[4]), - shoc_mix_d (temp_2d_d[5]); + shoc_mix_d (temp_2d_d[3]); const Int nk_pack = ekat::npack(nlev); const auto policy = TPF::get_default_team_policy(shcol, nk_pack); @@ -698,12 +696,10 @@ void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, bool shoc_1p5tke, Re const auto brunt_s = ekat::subview(brunt_d, i); const auto zt_grid_s = ekat::subview(zt_grid_d, i); const auto shoc_mix_s = ekat::subview(shoc_mix_d, i); - const auto dz_zt_s = ekat::subview(dz_zt_d, i); - const auto tk_s = ekat::subview(tk_d, i); const Real length_fac = 0.5; - SHF::compute_shoc_mix_shoc_length(team, nlev, length_fac, shoc_1p5tke, tke_s, brunt_s, zt_grid_s, - dz_zt_s, tk_s, l_inf_s, shoc_mix_s); + SHF::compute_shoc_mix_shoc_length(team, nlev, length_fac, tke_s, brunt_s, zt_grid_s, + l_inf_s, shoc_mix_s); }); // Sync back to host @@ -1405,9 +1401,9 @@ void shoc_pblintd_cldcheck_host(Int shcol, Int nlev, Int nlevi, Real* zi, Real* ekat::device_to_host({pblh}, shcol, inout_views); } -void shoc_length_host(Int shcol, Int nlev, Int nlevi, bool shoc_1p5tke, Real* host_dx, Real* host_dy, +void shoc_length_host(Int shcol, Int nlev, Int nlevi, Real* host_dx, Real* host_dy, Real* zt_grid, Real* zi_grid, Real*dz_zt, Real* tke, - Real* thv, Real* tk, Real*brunt, Real* shoc_mix) + Real* thv, Real*brunt, Real* shoc_mix) { using SHF = Functions; @@ -1421,12 +1417,12 @@ void shoc_length_host(Int shcol, Int nlev, Int nlevi, bool shoc_1p5tke, Real* ho using MemberType = typename SHF::MemberType; std::vector temp_1d_d(2); - std::vector temp_2d_d(8); - std::vector dim1_sizes(8, shcol); + std::vector temp_2d_d(7); + std::vector dim1_sizes(7, shcol); std::vector dim2_sizes = {nlev, nlevi, nlev, nlev, - nlev, nlev, nlev, nlev}; + nlev, nlev, nlev}; std::vector ptr_array = {zt_grid, zi_grid, dz_zt, tke, - thv, tk, brunt, shoc_mix}; + thv, brunt, shoc_mix}; // Sync to device ekat::host_to_device({host_dx, host_dy}, shcol, temp_1d_d); ekat::host_to_device(ptr_array, dim1_sizes, dim2_sizes, temp_2d_d); @@ -1442,9 +1438,8 @@ void shoc_length_host(Int shcol, Int nlev, Int nlevi, bool shoc_1p5tke, Real* ho dz_zt_d(temp_2d_d[2]), tke_d(temp_2d_d[3]), thv_d(temp_2d_d[4]), - tk_d(temp_2d_d[5]), - brunt_d(temp_2d_d[6]), - shoc_mix_d(temp_2d_d[7]); + brunt_d(temp_2d_d[5]), + shoc_mix_d(temp_2d_d[6]); const Int nlev_packs = ekat::npack(nlev); const Int nlevi_packs = ekat::npack(nlevi); @@ -1467,16 +1462,15 @@ void shoc_length_host(Int shcol, Int nlev, Int nlevi, bool shoc_1p5tke, Real* ho const auto dz_zt_s = ekat::subview(dz_zt_d, i); const auto tke_s = ekat::subview(tke_d, i); const auto thv_s = ekat::subview(thv_d, i); - const auto tk_s = ekat::subview(tk_d, i); const auto brunt_s = ekat::subview(brunt_d, i); const auto shoc_mix_s = ekat::subview(shoc_mix_d, i); // Hardcode runtime option for F90 tests. const Scalar length_fac = 0.5; - SHF::shoc_length(team,nlev,nlevi,length_fac,shoc_1p5tke, + SHF::shoc_length(team,nlev,nlevi,length_fac, host_dx_s,host_dy_s, zt_grid_s,zi_grid_s,dz_zt_s,tke_s, - thv_s,tk_s,workspace,brunt_s,shoc_mix_s); + thv_s,workspace,brunt_s,shoc_mix_s); }); // Sync back to host @@ -2681,7 +2675,7 @@ void shoc_grid_host(Int shcol, Int nlev, Int nlevi, Real* zt_grid, Real* zi_grid ekat::device_to_host({dz_zt, dz_zi, rho_zt}, std::vector{shcol, shcol, shcol}, std::vector{nlev, nlevi, nlev}, inout_views); } -void eddy_diffusivities_host(Int nlev, Int shcol, bool shoc_1p5tke, Real* pblh, Real* zt_grid, Real* tabs, Real* shoc_mix, Real* sterm_zt, +void eddy_diffusivities_host(Int nlev, Int shcol, Real* pblh, Real* zt_grid, Real* tabs, Real* shoc_mix, Real* sterm_zt, Real* isotropy, Real* tke, Real* tkh, Real* tk) { using SHF = Functions; @@ -2740,7 +2734,7 @@ void eddy_diffusivities_host(Int nlev, Int shcol, bool shoc_1p5tke, Real* pblh, const Real Ckh = 0.1; const Real Ckm = 0.1; - SHF::eddy_diffusivities(team, nlev, shoc_1p5tke, Ckh, Ckm, pblh_s, zt_grid_s, tabs_s, shoc_mix_s, sterm_zt_s, isotropy_s, tke_s, tkh_s, tk_s); + SHF::eddy_diffusivities(team, nlev, Ckh, Ckm, pblh_s, zt_grid_s, tabs_s, shoc_mix_s, sterm_zt_s, isotropy_s, tke_s, tkh_s, tk_s); }); // Sync back to host diff --git a/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.hpp b/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.hpp index 75aca9b82e34..ca56fe962b8c 100644 --- a/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.hpp +++ b/components/eamxx/src/physics/shoc/tests/infra/shoc_test_data.hpp @@ -295,16 +295,15 @@ struct AdvSgsTkeData : public PhysicsTestData { struct EddyDiffusivitiesData : public PhysicsTestData { // Inputs Int shcol, nlev; - bool shoc_1p5tke; Real *pblh, *zt_grid, *tabs, *shoc_mix, *sterm_zt, *isotropy, *tke; // Outputs Real *tkh, *tk; - EddyDiffusivitiesData(Int shcol_, Int nlev_, bool shoc_1p5tke_) : - PhysicsTestData({{ shcol_ }, { shcol_, nlev_ }}, {{ &pblh }, { &zt_grid, &tabs, &shoc_mix, &sterm_zt, &isotropy, &tke, &tkh, &tk }}), shcol(shcol_), nlev(nlev_), shoc_1p5tke(shoc_1p5tke_) {} + EddyDiffusivitiesData(Int shcol_, Int nlev_) : + PhysicsTestData({{ shcol_ }, { shcol_, nlev_ }}, {{ &pblh }, { &zt_grid, &tabs, &shoc_mix, &sterm_zt, &isotropy, &tke, &tkh, &tk }}), shcol(shcol_), nlev(nlev_) {} - PTD_STD_DEF(EddyDiffusivitiesData, 3, shcol, nlev, shoc_1p5tke); + PTD_STD_DEF(EddyDiffusivitiesData, 2, shcol, nlev); }; struct ShocLengthData : public ShocTestGridDataBase { @@ -316,10 +315,10 @@ struct ShocLengthData : public ShocTestGridDataBase { // Outputs Real *brunt, *shoc_mix; - ShocLengthData(Int shcol_, Int nlev_, Int nlevi_, bool shoc_1p5tke_) : - ShocTestGridDataBase({{ shcol_ }, { shcol_, nlev_ }, { shcol_, nlevi_ }}, {{ &host_dx, &host_dy }, { &zt_grid, &dz_zt, &tke, &thv, &tk, &brunt, &shoc_mix }, { &zi_grid }}), shcol(shcol_), nlev(nlev_), nlevi(nlevi_), shoc_1p5tke(shoc_1p5tke_) {} + ShocLengthData(Int shcol_, Int nlev_, Int nlevi_) : + ShocTestGridDataBase({{ shcol_ }, { shcol_, nlev_ }, { shcol_, nlevi_ }}, {{ &host_dx, &host_dy }, { &zt_grid, &dz_zt, &tke, &thv, &brunt, &shoc_mix }, { &zi_grid }}), shcol(shcol_), nlev(nlev_), nlevi(nlevi_) {} - PTD_STD_DEF(ShocLengthData, 4, shcol, nlev, nlevi, shoc_1p5tke); + PTD_STD_DEF(ShocLengthData, 3, shcol, nlev, nlevi); }; struct ComputeBruntShocLengthData : public PhysicsTestData { @@ -367,16 +366,15 @@ struct ComputeConvTimeShocLengthData : public PhysicsTestData { struct ComputeShocMixShocLengthData : public PhysicsTestData { // Inputs Int shcol, nlev; - bool shoc_1p5tke; - Real *tke, *brunt, *zt_grid, *dz_zt, *tk, *l_inf; + Real *tke, *brunt, *zt_grid, *l_inf; // Outputs Real *shoc_mix; - ComputeShocMixShocLengthData(Int shcol_, Int nlev_, bool shoc_1p5tke_) : - PhysicsTestData({{ shcol_, nlev_ }, { shcol_ }}, {{ &tke, &brunt, &zt_grid, &dz_zt, &tk, &shoc_mix }, { &l_inf }}), shcol(shcol_), nlev(nlev_), shoc_1p5tke(shoc_1p5tke_) {} + ComputeShocMixShocLengthData(Int shcol_, Int nlev_) : + PhysicsTestData({{ shcol_, nlev_ }, { shcol_ }}, {{ &tke, &brunt, &zt_grid, &shoc_mix }, { &l_inf }}), shcol(shcol_), nlev(nlev_) {} - PTD_STD_DEF(ComputeShocMixShocLengthData, 3, shcol, nlev, shoc_1p5tke); + PTD_STD_DEF(ComputeShocMixShocLengthData, 2, shcol, nlev); }; struct CheckLengthScaleShocLengthData : public PhysicsTestData { @@ -1016,8 +1014,8 @@ void compute_diag_third_shoc_moment_host(Int shcol, Int nlev, Int nlevi, bool sh Real* brunt_zi, Real* w_sec_zi, Real* thetal_zi, Real* w3); void shoc_pblintd_init_pot_host(Int shcol, Int nlev, Real* thl, Real* ql, Real* q, Real* thv); -void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, bool shoc_1p5tke, Real* tke, Real* brunt, - Real* zt_grid, Real* dz_zt, Real* tk, Real* l_inf, Real* shoc_mix); +void compute_shoc_mix_shoc_length_host(Int nlev, Int shcol, Real* tke, Real* brunt, + Real* zt_grid, Real* l_inf, Real* shoc_mix); void check_tke_host(Int shcol, Int nlev, Real* tke); void linear_interp_host(Real* x1, Real* x2, Real* y1, Real* y2, Int km1, Int km2, Int ncol, Real minthresh); void clipping_diag_third_shoc_moments_host(Int nlevi, Int shcol, Real *w_sec_zi, @@ -1045,9 +1043,9 @@ void shoc_diag_obklen_host(Int shcol, Real* uw_sfc, Real* vw_sfc, Real* wthl_sfc Real* thl_sfc, Real* cldliq_sfc, Real* qv_sfc, Real* ustar, Real* kbfs, Real* obklen); void shoc_pblintd_cldcheck_host(Int shcol, Int nlev, Int nlevi, Real* zi, Real* cldn, Real* pblh); void compute_shr_prod_host(Int nlevi, Int nlev, Int shcol, Real* dz_zi, Real* u_wind, Real* v_wind, Real* sterm); -void shoc_length_host(Int shcol, Int nlev, Int nlevi, bool shoc_1p5tke, Real* host_dx, Real* host_dy, +void shoc_length_host(Int shcol, Int nlev, Int nlevi, Real* host_dx, Real* host_dy, Real* zt_grid, Real* zi_grid, Real*dz_zt, Real* tke, - Real* thv, Real* tk, Real*brunt, Real* shoc_mix); + Real* thv, Real* brunt, Real* shoc_mix); void shoc_energy_fixer_host(Int shcol, Int nlev, Int nlevi, Real dtime, Int nadv, Real* zt_grid, Real* zi_grid, Real* se_b, Real* ke_b, Real* wv_b, Real* wl_b, Real* se_a, Real* ke_a, Real* wv_a, Real* wl_a, Real* wthl_sfc, @@ -1097,7 +1095,7 @@ void pblintd_check_pblh_host(Int shcol, Int nlev, Int nlevi, Int npbl, Real* z, void pblintd_host(Int shcol, Int nlev, Int nlevi, Int npbl, Real* z, Real* zi, Real* thl, Real* ql, Real* q, Real* u, Real* v, Real* ustar, Real* obklen, Real* kbfs, Real* cldn, Real* pblh); void shoc_grid_host(Int shcol, Int nlev, Int nlevi, Real* zt_grid, Real* zi_grid, Real* pdel, Real* dz_zt, Real* dz_zi, Real* rho_zt); -void eddy_diffusivities_host(Int nlev, Int shcol, bool shoc_1p5tke, Real* pblh, Real* zt_grid, Real* tabs, Real* shoc_mix, Real* sterm_zt, Real* isotropy, +void eddy_diffusivities_host(Int nlev, Int shcol, Real* pblh, Real* zt_grid, Real* tabs, Real* shoc_mix, Real* sterm_zt, Real* isotropy, Real* tke, Real* tkh, Real* tk); void shoc_tke_host(Int shcol, Int nlev, Int nlevi, Real dtime, bool shoc_1p5tke, Real* wthv_sec, Real* shoc_mix, Real* dz_zi, Real* dz_zt, Real* pres, Real* u_wind, Real* v_wind, Real* brunt, Real* obklen, Real* zt_grid, Real* zi_grid, Real* pblh, Real* tke, diff --git a/components/eamxx/src/physics/shoc/tests/shoc_eddy_diffusivities_tests.cpp b/components/eamxx/src/physics/shoc/tests/shoc_eddy_diffusivities_tests.cpp index 19dacdff3f6c..4ae79ed1727d 100644 --- a/components/eamxx/src/physics/shoc/tests/shoc_eddy_diffusivities_tests.cpp +++ b/components/eamxx/src/physics/shoc/tests/shoc_eddy_diffusivities_tests.cpp @@ -55,11 +55,8 @@ struct UnitWrap::UnitTest::TestShocEddyDiff : public UnitWrap::UnitTest::B // Turbulent kinetic energy [m2/s2] static constexpr Real tke_reg = 0.4; - // Default SHOC formulation, not 1.5 TKE closure assumptions - const bool shoc_1p5tke = false; - // Initialize data structure for bridging to F90 - EddyDiffusivitiesData SDS(shcol, nlev, shoc_1p5tke); + EddyDiffusivitiesData SDS(shcol, nlev); // Test that the inputs are reasonable. REQUIRE( (SDS.shcol == shcol && SDS.nlev == nlev) ); @@ -252,101 +249,6 @@ struct UnitWrap::UnitTest::TestShocEddyDiff : public UnitWrap::UnitTest::B } } - // 1.5 TKE test - // Verify that eddy diffusivities behave as expected if 1.5 TKE is activated. - // For this test we simply recycle the inputs from the previous test, with exception - // of the turbulent length scale and TKE. - - // SHOC Mixing length [m] - static constexpr Real shoc_mix_1p5_t1[shcol] = {100, 500}; - // Turbulent kinetic energy [m2/s2] - static constexpr Real tke_1p5_t1[shcol] = {0.4, 0.4}; - - // Verify that input length scale is increasing with column - // and TKE is the same for each column - for(Int s = 0; s < shcol-1; ++s) { - REQUIRE(shoc_mix_1p5_t1[s+1] > shoc_mix_1p5_t1[s]); - REQUIRE(tke_1p5_t1[s+1] == tke_1p5_t1[s]); - } - - // Fill in test data on zt_grid. - for(Int s = 0; s < shcol; ++s) { - // Column only input - SDS.tabs[s] = tabs_ustab[s]; - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - - SDS.tke[offset] = tke_1p5_t1[s]; - SDS.shoc_mix[offset] = shoc_mix_1p5_t1[s]; - } - } - - // Activate 1.5 TKE closure assumptions - SDS.shoc_1p5tke = true; - - // Call the C++ implementation - eddy_diffusivities(SDS); - - // Check to make sure the diffusivities are smaller - // in the columns where length scale is smaller - for(Int s = 0; s < shcol-1; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - // Get value corresponding to next column - const auto offsets = n + (s+1) * nlev; - if (SDS.tke[offset] == SDS.tke[offsets] & - SDS.shoc_mix[offset] < SDS.shoc_mix[offsets]){ - REQUIRE(SDS.tk[offset] < SDS.tk[offsets]); - REQUIRE(SDS.tkh[offset] < SDS.tkh[offsets]); - } - } - } - - // Now we are going to do a similar but opposite test, change TKE - // while keeping SHOC mix constant - - // SHOC Mixing length [m] - static constexpr Real shoc_mix_1p5_t2[shcol] = {500, 500}; - // Turbulent kinetic energy [m2/s2] - static constexpr Real tke_1p5_t2[shcol] = {0.1, 0.4}; - - // Verify that input length scale is increasing with column - // and TKE is the same for each column - for(Int s = 0; s < shcol-1; ++s) { - REQUIRE(shoc_mix_1p5_t2[s+1] == shoc_mix_1p5_t2[s]); - REQUIRE(tke_1p5_t2[s+1] > tke_1p5_t2[s]); - } - - // Fill in test data on zt_grid. - for(Int s = 0; s < shcol; ++s) { - // Column only input - SDS.tabs[s] = tabs_ustab[s]; - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - - SDS.tke[offset] = tke_1p5_t2[s]; - SDS.shoc_mix[offset] = shoc_mix_1p5_t2[s]; - } - } - - // Call the C++ implementation - eddy_diffusivities(SDS); - - // Check to make sure the diffusivities are smaller - // in the columns where TKE is smaller - for(Int s = 0; s < shcol-1; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - // Get value corresponding to next column - const auto offsets = n + (s+1) * nlev; - if (SDS.tke[offset] < SDS.tke[offsets] & - SDS.shoc_mix[offset] == SDS.shoc_mix[offsets]){ - REQUIRE(SDS.tk[offset] < SDS.tk[offsets]); - REQUIRE(SDS.tkh[offset] < SDS.tkh[offsets]); - } - } - } - } @@ -355,10 +257,10 @@ struct UnitWrap::UnitTest::TestShocEddyDiff : public UnitWrap::UnitTest::B auto engine = Base::get_engine(); EddyDiffusivitiesData baseline_data[] = { - EddyDiffusivitiesData(10, 71, false), - EddyDiffusivitiesData(10, 12, false), - EddyDiffusivitiesData(7, 16, false), - EddyDiffusivitiesData(2, 7, false), + EddyDiffusivitiesData(10, 71), + EddyDiffusivitiesData(10, 12), + EddyDiffusivitiesData(7, 16), + EddyDiffusivitiesData(2, 7), }; // Generate random input data diff --git a/components/eamxx/src/physics/shoc/tests/shoc_length_tests.cpp b/components/eamxx/src/physics/shoc/tests/shoc_length_tests.cpp index 25e8e91d6e32..130b48938eef 100644 --- a/components/eamxx/src/physics/shoc/tests/shoc_length_tests.cpp +++ b/components/eamxx/src/physics/shoc/tests/shoc_length_tests.cpp @@ -48,11 +48,6 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas static constexpr Real thv[nlev] = {315, 310, 305, 300, 295}; // Turbulent kinetc energy [m2/s2] static constexpr Real tke[nlev] = {0.1, 0.15, 0.2, 0.25, 0.3}; - // Eddy viscosity [m2/s] - static constexpr Real tk[nlev] = {0.1, 10.0, 12.0, 15.0, 20.0}; - - // Default SHOC formulation, not 1.5 TKE closure assumptions - const bool shoc_1p5tke = false; // compute geometric grid mesh const auto grid_mesh = sqrt(host_dx*host_dy); @@ -67,7 +62,7 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas } // Initialize data structure for bridging to F90 - ShocLengthData SDS(shcol, nlev, nlevi, shoc_1p5tke); + ShocLengthData SDS(shcol, nlev, nlevi); // Load up input data for(Int s = 0; s < shcol; ++s) { @@ -83,8 +78,6 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas SDS.zt_grid[offset] = zt_grid[n]; SDS.thv[offset] = thv[n]; SDS.dz_zt[offset] = dz_zt[n]; - // eddy viscosity below not relevant for default SHOC - SDS.tk[offset] = 0; } // Fill in test data on zi_grid @@ -154,50 +147,6 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas } } - // Repeat this test but for 1.5 TKE closure option activated - - // Activate 1.5 TKE closure assumptions - SDS.shoc_1p5tke = true; - - // We will use the same input data as above but with the SGS buoyancy - // flux set to zero, as will be the case with the 1.5 TKE option. - // Additionally, we will fill the value of the brunt vaisala frequency. - for(Int s = 0; s < shcol; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - - SDS.tk[offset] = tk[n]; - } - } - - // Call the C++ implementation - shoc_length(SDS); - - // Verify output - for(Int s = 0; s < shcol; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - // Require mixing length is greater than zero and is - // less than geometric grid mesh length + 1 m - REQUIRE(SDS.shoc_mix[offset] >= minlen); - REQUIRE(SDS.shoc_mix[offset] <= maxlen); - REQUIRE(SDS.shoc_mix[offset] < 1.0+grid_mesh); - - // Be sure brunt vaisalla frequency is reasonable - REQUIRE(SDS.brunt[offset] < 1); - - // Ensure length scale is equal to dz if brunt =< 0, else - // length scale should be less then dz - if (SDS.brunt[offset] <= 0){ - REQUIRE(SDS.shoc_mix[offset] == SDS.dz_zt[offset]); - } - else{ - REQUIRE(SDS.shoc_mix[offset] < SDS.dz_zt[offset]); - } - - } - } - // TEST TWO // Small grid mesh test. Given a very small grid mesh, verify that // the length scale is confined to this value. Input from first @@ -208,9 +157,6 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas // Defin the host grid box size y-direction [m] static constexpr Real host_dy_small = 5; - // Call default SHOC closure assumptions - SDS.shoc_1p5tke = false; - // compute geometric grid mesh const auto grid_mesh_small = sqrt(host_dx_small*host_dy_small); @@ -235,26 +181,6 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas } } - // Repeat this test but for 1.5 TKE closure option activated - - // Activate 1.5 TKE closure assumptions - SDS.shoc_1p5tke = true; - - // call C++ implementation - shoc_length(SDS); - - // Verify output - for(Int s = 0; s < shcol; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - // Require mixing length is greater than zero and is - // less than geometric grid mesh length + 1 m - REQUIRE(SDS.shoc_mix[offset] > 0); - REQUIRE(SDS.shoc_mix[offset] <= maxlen); - REQUIRE(SDS.shoc_mix[offset] < 1.0+grid_mesh_small); - } - } - } void run_bfb() @@ -263,10 +189,10 @@ struct UnitWrap::UnitTest::TestShocLength : public UnitWrap::UnitTest::Bas ShocLengthData SDS_baseline[] = { // shcol, nlev, nlevi - ShocLengthData(12, 71, 72, false), - ShocLengthData(10, 12, 13, false), - ShocLengthData(7, 16, 17, false), - ShocLengthData(2, 7, 8, false), + ShocLengthData(12, 71, 72), + ShocLengthData(10, 12, 13), + ShocLengthData(7, 16, 17), + ShocLengthData(2, 7, 8), }; // Generate random input data diff --git a/components/eamxx/src/physics/shoc/tests/shoc_mix_length_tests.cpp b/components/eamxx/src/physics/shoc/tests/shoc_mix_length_tests.cpp index b1a3f99d7b1d..b2fb321f6d89 100644 --- a/components/eamxx/src/physics/shoc/tests/shoc_mix_length_tests.cpp +++ b/components/eamxx/src/physics/shoc/tests/shoc_mix_length_tests.cpp @@ -42,11 +42,8 @@ struct UnitWrap::UnitTest::TestCompShocMixLength : public UnitWrap::UnitTest< // Define the heights on the zt grid [m] static constexpr Real zt_grid[nlev] = {5000, 3000, 2000, 1000, 500}; - // Default SHOC formulation, not 1.5 TKE closure assumptions - const bool shoc_1p5tke = false; - // Initialize data structure for bridging to F90 - ComputeShocMixShocLengthData SDS(shcol, nlev, shoc_1p5tke); + ComputeShocMixShocLengthData SDS(shcol, nlev); // Test that the inputs are reasonable. // For this test shcol MUST be at least 2 @@ -64,9 +61,6 @@ struct UnitWrap::UnitTest::TestCompShocMixLength : public UnitWrap::UnitTest< SDS.tke[offset] = (1.0+s)*tke_cons; SDS.brunt[offset] = brunt_cons; SDS.zt_grid[offset] = zt_grid[n]; - // do not consider below for default SHOC - SDS.tk[offset] = 0; - SDS.dz_zt[offset] = 0; } } @@ -116,55 +110,6 @@ struct UnitWrap::UnitTest::TestCompShocMixLength : public UnitWrap::UnitTest< } } - // 1.5 TKE test - // Verify that length scale behaves as expected when 1.5 TKE closure - // assumptions are used. Will recycle all previous data, except we - // need to define dz, brunt vaisalla frequency, and tk. - - // Brunt Vaisalla frequency [s-1] - static constexpr Real brunt_1p5[nlev] = {0.01,-0.01,0.01,-0.01,0.01}; - // Define the heights on the zt grid [m] - static constexpr Real dz_zt_1p5[nlev] = {50, 100, 30, 20, 10}; - // Eddy viscocity [m2 s-1] - static constexpr Real tk_cons_1p5 = 0.1; - - // Activate 1.5 TKE closure - SDS.shoc_1p5tke = true; - - // Fill in test data on zt_grid. - for(Int s = 0; s < shcol; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - - // do not consider below for default SHOC - SDS.tk[offset] = tk_cons_1p5; - SDS.dz_zt[offset] = dz_zt_1p5[n]; - SDS.brunt[offset] = brunt_1p5[n]; - } - } - - // Call the C++ implementation - compute_shoc_mix_shoc_length(SDS); - - // Check the result - - // Verify that if Brunt Vaisalla frequency is unstable that mixing length - // is equal to vertical grid spacing. If brunt is stable, then verify that - // mixing length is less than the vertical grid spacing. - for(Int s = 0; s < shcol; ++s) { - for(Int n = 0; n < nlev; ++n) { - const auto offset = n + s * nlev; - if (SDS.brunt[offset] <= 0){ - REQUIRE(SDS.shoc_mix[offset] == SDS.dz_zt[offset]); - } - else{ - REQUIRE(SDS.shoc_mix[offset] < SDS.dz_zt[offset]); - REQUIRE(SDS.shoc_mix[offset] >= 0.1*SDS.dz_zt[offset]); - } - - } - } - } void run_bfb() @@ -173,10 +118,10 @@ struct UnitWrap::UnitTest::TestCompShocMixLength : public UnitWrap::UnitTest< ComputeShocMixShocLengthData SDS_baseline[] = { // shcol, nlev - ComputeShocMixShocLengthData(10, 71, false), - ComputeShocMixShocLengthData(10, 12, false), - ComputeShocMixShocLengthData(7, 16, false), - ComputeShocMixShocLengthData(2, 7, false) + ComputeShocMixShocLengthData(10, 71), + ComputeShocMixShocLengthData(10, 12), + ComputeShocMixShocLengthData(7, 16), + ComputeShocMixShocLengthData(2, 7) }; // Generate random input data diff --git a/components/eamxx/src/physics/zm/CMakeLists.txt b/components/eamxx/src/physics/zm/CMakeLists.txt index e24a77d69bbb..eafdf6919811 100644 --- a/components/eamxx/src/physics/zm/CMakeLists.txt +++ b/components/eamxx/src/physics/zm/CMakeLists.txt @@ -54,6 +54,7 @@ if (NOT EAMXX_ENABLE_GPU OR Kokkos_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE OR Kokkos eti/zm_compute_dilute_parcel.cpp eti/zm_compute_cape_from_parcel.cpp eti/zm_zm_conv_mcsp_calculate_shear.cpp + eti/zm_zm_conv_mcsp_tend.cpp ) # ZM ETI SRCS endif() diff --git a/components/eamxx/src/physics/zm/eti/zm_zm_conv_mcsp_tend.cpp b/components/eamxx/src/physics/zm/eti/zm_zm_conv_mcsp_tend.cpp new file mode 100644 index 000000000000..74136312b098 --- /dev/null +++ b/components/eamxx/src/physics/zm/eti/zm_zm_conv_mcsp_tend.cpp @@ -0,0 +1,14 @@ +#include "impl/zm_zm_conv_mcsp_tend_impl.hpp" + +namespace scream { +namespace zm { + +/* + * Explicit instantiation for doing zm_conv_mcsp_tend on Reals using the + * default device. + */ + +template struct Functions; + +} // namespace zm +} // namespace scream diff --git a/components/eamxx/src/physics/zm/impl/zm_compute_cape_from_parcel_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_compute_cape_from_parcel_impl.hpp index 209b3a9254a7..b51a743e1e47 100644 --- a/components/eamxx/src/physics/zm/impl/zm_compute_cape_from_parcel_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_compute_cape_from_parcel_impl.hpp @@ -50,19 +50,19 @@ void Functions::compute_cape_from_parcel( eql_klev = pver - 1; cape = 0.0; - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, num_cin), [&] (const Int& n) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, num_cin), [&] (const Int& n) { eql_klev_tmp(n) = pver - 1; cape_tmp(n) = 0.0; }); team.team_barrier(); - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { buoyancy(k) = 0.0; }); team.team_barrier(); // Calculate buoyancy - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, num_msg, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, num_msg, pver), [&] (const Int& k) { // Define buoyancy from launch level to equilibrium level if (k <= msemax_klev && lcl_pmid >= ZMC::lcl_pressure_threshold) { buoyancy(k) = parcel_vtemp(k) - tv(k) + runtime_opt.tiedke_add; @@ -86,10 +86,13 @@ void Functions::compute_cape_from_parcel( } } }); + team.team_barrier(); - // Integrate buoyancy to obtain possible CAPE values + // Integrate buoyancy to obtain possible CAPE values. For some reason, the + // sum order does not match the serial fortran, so tiny roundoff differences + // exist compared to fortran for cape. for (Int n = 0; n < num_cin; ++n) { - Kokkos::parallel_reduce(Kokkos::TeamThreadRange(team, num_msg, pver), + Kokkos::parallel_reduce(Kokkos::TeamVectorRange(team, num_msg, pver), [&] (const Int& k, Real& cape_n) { if (lcl_pmid >= ZMC::lcl_pressure_threshold && k <= msemax_klev && k > eql_klev_tmp(n)) { diff --git a/components/eamxx/src/physics/zm/impl/zm_compute_dilute_cape_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_compute_dilute_cape_impl.hpp index 49f3ac2179be..247d7f205933 100644 --- a/components/eamxx/src/physics/zm/impl/zm_compute_dilute_cape_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_compute_dilute_cape_impl.hpp @@ -74,7 +74,7 @@ void Functions::compute_dilute_cape( //---------------------------------------------------------------------------- // Copy the incoming temperature and specific humidity values to local arrays - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { temperature(k) = temperature_in(k); sp_humidity(k) = sp_humidity_in(k); }); @@ -97,14 +97,14 @@ void Functions::compute_dilute_cape( //---------------------------------------------------------------------------- // calculate virtual temperature - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { tv(k) = temperature(k) * (1 + PC::ZVIR * sp_humidity(k)) / (1 + sp_humidity(k)); }); team.team_barrier(); //---------------------------------------------------------------------------- // Initialize parcel properties - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { parcel_temp(k) = temperature(k); parcel_qsat(k) = sp_humidity(k); parcel_vtemp(k) = tv(k); @@ -124,6 +124,7 @@ void Functions::compute_dilute_cape( } }); } + team.team_barrier(); //---------------------------------------------------------------------------- // Set level of max moist static energy for parcel initialization @@ -140,6 +141,7 @@ void Functions::compute_dilute_cape( temperature, zmid, sp_humidity, msemax_klev, mse_max_val); } + team.team_barrier(); //---------------------------------------------------------------------------- // Save launching level T, q for output diff --git a/components/eamxx/src/physics/zm/impl/zm_compute_dilute_parcel_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_compute_dilute_parcel_impl.hpp index a21bd1b77e57..4d34aae23625 100644 --- a/components/eamxx/src/physics/zm/impl/zm_compute_dilute_parcel_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_compute_dilute_parcel_impl.hpp @@ -53,7 +53,7 @@ void Functions::compute_dilute_parcel( } // Initialize arrays - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { tmix(k) = 0.0; qtmix(k) = 0.0; qsmix(k) = 0.0; diff --git a/components/eamxx/src/physics/zm/impl/zm_input_state_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_input_state_impl.hpp index 42cb882ef343..1ad930030e17 100644 --- a/components/eamxx/src/physics/zm/impl/zm_input_state_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_input_state_impl.hpp @@ -33,28 +33,40 @@ void Functions::ZmInputState::transpose(int ncol, int nlev_mid) const auto loc_f_z_int = f_z_int; const auto loc_f_p_int = f_p_int; + const auto loc_z_mid = z_mid; + const auto loc_p_mid = p_mid; + const auto loc_p_del = p_del; + const auto loc_T_mid = T_mid; + const auto loc_qv = qv; + const auto loc_uwind = uwind; + const auto loc_vwind = vwind; + const auto loc_omega = omega; + const auto loc_cldfrac = cldfrac; + const auto loc_z_int = z_int; + const auto loc_p_int = p_int; + //---------------------------------------------------------------------- // mid-point level variables Kokkos::parallel_for("zm_output_tx_mid",KT::RangePolicy(0, ncol*nlev_mid_packs), KOKKOS_LAMBDA (const int i) { const int icol = i/nlev_mid_packs; const int klev = i%nlev_mid_packs; - loc_f_z_mid (icol,klev) = z_mid (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_p_mid (icol,klev) = p_mid (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_p_del (icol,klev) = p_del (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_T_mid (icol,klev) = T_mid (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_qv (icol,klev) = qv (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_uwind (icol,klev) = uwind (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_vwind (icol,klev) = vwind (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_omega (icol,klev) = omega (icol,klev/Pack::n)[klev%Pack::n]; - loc_f_cldfrac (icol,klev) = cldfrac (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_z_mid (icol,klev) = loc_z_mid (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_p_mid (icol,klev) = loc_p_mid (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_p_del (icol,klev) = loc_p_del (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_T_mid (icol,klev) = loc_T_mid (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_qv (icol,klev) = loc_qv (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_uwind (icol,klev) = loc_uwind (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_vwind (icol,klev) = loc_vwind (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_omega (icol,klev) = loc_omega (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_cldfrac (icol,klev) = loc_cldfrac (icol,klev/Pack::n)[klev%Pack::n]; }); // interface level variables Kokkos::parallel_for("zm_output_tx_mid",KT::RangePolicy(0, ncol*nlev_int_packs), KOKKOS_LAMBDA (const int i) { const int icol = i/nlev_int_packs; const int klev = i%nlev_int_packs; - f_z_int (icol,klev) = z_int (icol,klev/Pack::n)[klev%Pack::n]; - f_p_int (icol,klev) = p_int (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_z_int (icol,klev) = loc_z_int (icol,klev/Pack::n)[klev%Pack::n]; + loc_f_p_int (icol,klev) = loc_p_int (icol,klev/Pack::n)[klev%Pack::n]; }); //---------------------------------------------------------------------- diff --git a/components/eamxx/src/physics/zm/impl/zm_output_tend_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_output_tend_impl.hpp index d42f1ecfc108..84d96a2bd384 100644 --- a/components/eamxx/src/physics/zm/impl/zm_output_tend_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_output_tend_impl.hpp @@ -47,26 +47,36 @@ void Functions::ZmOutputTend::transpose(int ncol, int nlev_mid) const auto loc_snow_flux = snow_flux; const auto loc_mass_flux = mass_flux; + const auto loc_f_tend_t = f_tend_t; + const auto loc_f_tend_qv = f_tend_qv; + const auto loc_f_tend_u = f_tend_u; + const auto loc_f_tend_v = f_tend_v; + const auto loc_f_rain_prod = f_rain_prod; + const auto loc_f_snow_prod = f_snow_prod; + const auto loc_f_prec_flux = f_prec_flux; + const auto loc_f_snow_flux = f_snow_flux; + const auto loc_f_mass_flux = f_mass_flux; + //---------------------------------------------------------------------- // mid-point level variables Kokkos::parallel_for("zm_output_tx_mid",KT::RangePolicy(0, ncol*nlev_mid_packs), KOKKOS_LAMBDA (const int i) { const int icol = i/nlev_mid_packs; const int klev = i%nlev_mid_packs; - loc_tend_t (icol,klev/Pack::n)[klev%Pack::n] = f_tend_t (icol,klev); - loc_tend_qv (icol,klev/Pack::n)[klev%Pack::n] = f_tend_qv (icol,klev); - loc_tend_u (icol,klev/Pack::n)[klev%Pack::n] = f_tend_u (icol,klev); - loc_tend_v (icol,klev/Pack::n)[klev%Pack::n] = f_tend_v (icol,klev); - loc_rain_prod(icol,klev/Pack::n)[klev%Pack::n] = f_rain_prod(icol,klev); - loc_snow_prod(icol,klev/Pack::n)[klev%Pack::n] = f_snow_prod(icol,klev); + loc_tend_t (icol,klev/Pack::n)[klev%Pack::n] = loc_f_tend_t (icol,klev); + loc_tend_qv (icol,klev/Pack::n)[klev%Pack::n] = loc_f_tend_qv (icol,klev); + loc_tend_u (icol,klev/Pack::n)[klev%Pack::n] = loc_f_tend_u (icol,klev); + loc_tend_v (icol,klev/Pack::n)[klev%Pack::n] = loc_f_tend_v (icol,klev); + loc_rain_prod(icol,klev/Pack::n)[klev%Pack::n] = loc_f_rain_prod(icol,klev); + loc_snow_prod(icol,klev/Pack::n)[klev%Pack::n] = loc_f_snow_prod(icol,klev); }); // interface level variables Kokkos::parallel_for("zm_output_tx_mid",KT::RangePolicy(0, ncol*nlev_int_packs), KOKKOS_LAMBDA (const int i) { const int icol = i/nlev_int_packs; const int klev = i%nlev_int_packs; - loc_prec_flux(icol,klev/Pack::n)[klev%Pack::n] = f_prec_flux(icol,klev); - loc_snow_flux(icol,klev/Pack::n)[klev%Pack::n] = f_snow_flux(icol,klev); - loc_mass_flux(icol,klev/Pack::n)[klev%Pack::n] = f_mass_flux(icol,klev); + loc_prec_flux(icol,klev/Pack::n)[klev%Pack::n] = loc_f_prec_flux(icol,klev); + loc_snow_flux(icol,klev/Pack::n)[klev%Pack::n] = loc_f_snow_flux(icol,klev); + loc_mass_flux(icol,klev/Pack::n)[klev%Pack::n] = loc_f_mass_flux(icol,klev); }); } // *********************************************************************** diff --git a/components/eamxx/src/physics/zm/impl/zm_zm_conv_mcsp_tend_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_zm_conv_mcsp_tend_impl.hpp new file mode 100644 index 000000000000..4f60e73487c8 --- /dev/null +++ b/components/eamxx/src/physics/zm/impl/zm_zm_conv_mcsp_tend_impl.hpp @@ -0,0 +1,218 @@ +#ifndef ZM_ZM_CONV_MCSP_TEND_IMPL_HPP +#define ZM_ZM_CONV_MCSP_TEND_IMPL_HPP + +#include "zm_functions.hpp" // for ETI only but harmless for GPU + +namespace scream { +namespace zm { + +/* + * Implementation of zm zm_conv_mcsp_tend. Clients should NOT + * #include this file, but include zm_functions.hpp instead. + */ + +template +KOKKOS_FUNCTION +void Functions::zm_conv_mcsp_tend( + // Inputs + const MemberType& team, + const Workspace& workspace, + const ZmRuntimeOpt& runtime_opt, + const Int& pver, // number of mid-point vertical levels + const Int& pverp, // number of interface vertical levels + const Real& ztodt, // 2x physics time step + const Int& jctop, // cloud top level indices + const uview_1d& state_pmid, // physics state mid-point pressure + const uview_1d& state_pint, // physics state interface pressure + const uview_1d& state_pdel, // physics state pressure thickness + const uview_1d& state_s, // physics state dry energy + const uview_1d& state_q, // physics state specific humidity + const uview_1d& state_u, // physics state u momentum + const uview_1d& state_v, // physics state v momentum + const uview_1d& ptend_zm_s, // input ZM tendency for dry energy (DSE) + const uview_1d& ptend_zm_q, // input ZM tendency for specific humidity (qv) + // Inputs/Outputs + const uview_1d& ptend_s, // output tendency of DSE + const uview_1d& ptend_q, // output tendency of qv + const uview_1d& ptend_u, // output tendency of u-wind + const uview_1d& ptend_v, // output tendency of v-wind + // Outputs + const uview_1d& mcsp_dt_out, // final MCSP tendency for DSE + const uview_1d& mcsp_dq_out, // final MCSP tendency for qv + const uview_1d& mcsp_du_out, // final MCSP tendency for u wind + const uview_1d& mcsp_dv_out, // final MCSP tendency for v wind + Real& mcsp_freq, // MSCP frequency for output + Real& mcsp_shear, // shear used to check against threshold + Real& zm_depth) // pressure depth of ZM heating +{ + //---------------------------------------------------------------------------- + // Purpose: perform MCSP tendency calculations + //---------------------------------------------------------------------------- + + if (!runtime_opt.mcsp_enabled) return; + + //---------------------------------------------------------------------------- + // initialize variables + + const bool do_mcsp_t = (runtime_opt.mcsp_t_coeff > 0); + const bool do_mcsp_q = (runtime_opt.mcsp_q_coeff > 0); + const bool do_mcsp_u = (runtime_opt.mcsp_u_coeff > 0); + const bool do_mcsp_v = (runtime_opt.mcsp_v_coeff > 0); + + // Allocate temporary arrays + uview_1d mcsp_tend_s, mcsp_tend_q, mcsp_tend_u, mcsp_tend_v; + workspace.template take_many_contiguous_unsafe<4>( + {"mcsp_tend_s", "mcsp_tend_q", "mcsp_tend_u", "mcsp_tend_v"}, + {&mcsp_tend_s, &mcsp_tend_q, &mcsp_tend_u, &mcsp_tend_v}); + + Real zm_avg_tend_s = 0.0; // mass weighted column average DSE tendency from ZM + Real zm_avg_tend_q = 0.0; // mass weighted column average qv tendency from ZM + Real pdel_sum = 0.0; // column integrated pressure thickness + Real mcsp_avg_tend_s = 0.0; // mass weighted column average MCSP tendency of DSE + Real mcsp_avg_tend_q = 0.0; // mass weighted column average MCSP tendency of qv + Real mcsp_avg_tend_k = 0.0; // mass weighted column average MCSP tendency of kinetic energy + + // Initialize arrays + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver), [&] (const Int& k) { + mcsp_tend_s(k) = 0.0; + mcsp_tend_q(k) = 0.0; + mcsp_tend_u(k) = 0.0; + mcsp_tend_v(k) = 0.0; + mcsp_dt_out(k) = 0.0; + mcsp_dq_out(k) = 0.0; + mcsp_du_out(k) = 0.0; + mcsp_dv_out(k) = 0.0; + }); + team.team_barrier(); + + //---------------------------------------------------------------------------- + // calculate shear + + zm_conv_mcsp_calculate_shear(team, pver, state_pmid, state_u, mcsp_shear); + + //---------------------------------------------------------------------------- + // calculate mass weighted column average tendencies from ZM + + zm_depth = 0.0; + if (jctop != pver - 1) { + // integrate pressure and ZM tendencies over column using parallel reduction + Kokkos::parallel_reduce(Kokkos::TeamVectorRange(team, jctop, pver), + [&] (const Int& k, Real& tend_s_sum, Real& tend_q_sum, Real& pdel_tot) { + tend_s_sum += ptend_zm_s(k) * state_pdel(k); + tend_q_sum += ptend_zm_q(k) * state_pdel(k); + pdel_tot += state_pdel(k); + }, + zm_avg_tend_s, zm_avg_tend_q, pdel_sum); + team.team_barrier(); + + // normalize integrated ZM tendencies by total mass + zm_avg_tend_s /= pdel_sum; + zm_avg_tend_q /= pdel_sum; + // calculate diagnostic zm_depth + zm_depth = state_pint(pver) - state_pmid(jctop); + } + team.team_barrier(); + + //---------------------------------------------------------------------------- + // Note: To conserve total energy we need to account for the kinteic energy tendency + // which we can obtain from the velocity tendencies based on the following: + // KE_new = (u_new^2 + v_new^2)/2 + // = [ (u_old+du)^2 + (v_old+dv)^2 ]/2 + // = [ ( u_old^2 + 2*u_old*du + du^2 ) + ( v_old^2 + 2*v_old*dv + dv^2 ) ]/2 + // = ( u_old^2 + v_old^2 )/2 + ( 2*u_old*du + du^2 + 2*v_old*dv + dv^2 )/2 + // = KE_old + [ 2*u_old*du + du^2 + 2*v_old*dv + dv^2 ] /2 + + //---------------------------------------------------------------------------- + // calculate MCSP tendencies + + // check that ZM produced tendencies over a depth that exceeds the threshold + if (zm_depth >= ZMC::MCSP_conv_depth_min) { + // check that ZM provided a non-zero column total heating + if (zm_avg_tend_s > 0.0) { + // check that there is sufficient wind shear to justify coherent organization + if (std::abs(mcsp_shear) >= ZMC::MCSP_shear_min && + std::abs(mcsp_shear) < ZMC::MCSP_shear_max) { + // Calculate tendencies and integrate them using parallel reduce + Kokkos::parallel_reduce(Kokkos::TeamVectorRange(team, jctop, pver), + [&] (const Int& k, Real& avg_s, Real& avg_q, Real& avg_k) { + + // See eq 7-8 of Moncrieff et al. (2017) - also eq (5) of Moncrieff & Liu (2006) + const Real pdepth_mid_k = state_pint(pver) - state_pmid(k); + const Real pdepth_total = state_pint(pver) - state_pmid(jctop); + + // specify the assumed vertical structure + if (do_mcsp_t) mcsp_tend_s(k) = -1 * runtime_opt.mcsp_t_coeff * std::sin(2 * PC::Pi * (pdepth_mid_k / pdepth_total)); + if (do_mcsp_q) mcsp_tend_q(k) = -1 * runtime_opt.mcsp_q_coeff * std::sin(2 * PC::Pi * (pdepth_mid_k / pdepth_total)); + if (do_mcsp_u) mcsp_tend_u(k) = runtime_opt.mcsp_u_coeff * (std::cos(PC::Pi * (pdepth_mid_k / pdepth_total))); + if (do_mcsp_v) mcsp_tend_v(k) = runtime_opt.mcsp_v_coeff * (std::cos(PC::Pi * (pdepth_mid_k / pdepth_total))); + + // scale the vertical structure by the ZM heating/drying tendencies + if (do_mcsp_t) mcsp_tend_s(k) = zm_avg_tend_s * mcsp_tend_s(k); + if (do_mcsp_q) mcsp_tend_q(k) = zm_avg_tend_q * mcsp_tend_q(k); + + // integrate the DSE/qv tendencies for energy/mass fixer + if (do_mcsp_t) avg_s += mcsp_tend_s(k) * state_pdel(k) / pdel_sum; + if (do_mcsp_q) avg_q += mcsp_tend_q(k) * state_pdel(k) / pdel_sum; + + // integrate the change in kinetic energy (KE) for energy fixer + if (do_mcsp_u || do_mcsp_v) { + const Real tend_k = (2 * mcsp_tend_u(k) * ztodt * state_u(k) + mcsp_tend_u(k) * mcsp_tend_u(k) * ztodt * ztodt + + 2 * mcsp_tend_v(k) * ztodt * state_v(k) + mcsp_tend_v(k) * mcsp_tend_v(k) * ztodt * ztodt) / 2 / ztodt; + avg_k += tend_k * state_pdel(k) / pdel_sum; + } + }, + mcsp_avg_tend_s, mcsp_avg_tend_q, mcsp_avg_tend_k); + team.team_barrier(); + } // shear threshold + } // zm_avg_tend_s > 0 + } // zm_depth >= MCSP_conv_depth_min + + //---------------------------------------------------------------------------- + // calculate final output tendencies + + mcsp_freq = 0.0; + bool any_tend = false; + + // Calculate final tendencies and check frequency in a single parallel_reduce + Kokkos::parallel_reduce(Kokkos::TeamVectorRange(team, jctop, pver), + [&] (const Int& k, bool& local_freq) { + // subtract mass weighted average tendencies for energy/mass conservation + mcsp_dt_out(k) = mcsp_tend_s(k) - mcsp_avg_tend_s; + mcsp_dq_out(k) = mcsp_tend_q(k) - mcsp_avg_tend_q; + mcsp_du_out(k) = mcsp_tend_u(k); + mcsp_dv_out(k) = mcsp_tend_v(k); + + // make sure kinetic energy correction is added to DSE tendency + // to conserve total energy whenever momentum tendencies are calculated + if (do_mcsp_u || do_mcsp_v) { + mcsp_dt_out(k) = mcsp_dt_out(k) - mcsp_avg_tend_k; + } + + // update output tendencies + if (do_mcsp_t) ptend_s(k) = ptend_s(k) + mcsp_dt_out(k); + if (do_mcsp_q) ptend_q(k) = ptend_q(k) + mcsp_dq_out(k); + if (do_mcsp_u) ptend_u(k) = ptend_u(k) + mcsp_du_out(k); + if (do_mcsp_v) ptend_v(k) = ptend_v(k) + mcsp_dv_out(k); + + // adjust units for diagnostic outputs + if (do_mcsp_t) mcsp_dt_out(k) = mcsp_dt_out(k) / PC::Cpair.value; + + // update frequency if MCSP contributes any tendency in the column + if (std::abs(mcsp_tend_s(k)) > 0.0 || std::abs(mcsp_tend_q(k)) > 0.0 || + std::abs(mcsp_tend_u(k)) > 0.0 || std::abs(mcsp_tend_v(k)) > 0.0) { + local_freq = true; + } + }, + Kokkos::LOr(any_tend)); + team.team_barrier(); + + if (any_tend) mcsp_freq = 1.0; + + workspace.template release_many_contiguous<4>( + {&mcsp_tend_s, &mcsp_tend_q, &mcsp_tend_u, &mcsp_tend_v}); +} + +} // namespace zm +} // namespace scream + +#endif diff --git a/components/eamxx/src/physics/zm/impl/zm_zm_transport_momentum_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_zm_transport_momentum_impl.hpp index 2042565528fb..eb180c100667 100644 --- a/components/eamxx/src/physics/zm/impl/zm_zm_transport_momentum_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_zm_transport_momentum_impl.hpp @@ -70,7 +70,7 @@ void Functions::zm_transport_momentum( mflux(mflux_1d.data(), nwind, pverp); // Initialize outputs - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, pver*nwind), [&] (const Int& idx) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, pver*nwind), [&] (const Int& idx) { const Int k = idx / nwind; const Int m = idx % nwind; wind_tend(k,m) = 0.0; @@ -92,7 +92,7 @@ void Functions::zm_transport_momentum( team.team_barrier(); // Loop over each wind component using team parallelism - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, nwind), [&] (const Int& m) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nwind), [&] (const Int& m) { // Gather up the winds for (Int k = 0; k < pver; ++k) { @@ -223,7 +223,7 @@ void Functions::zm_transport_momentum( //---------------------------------------------------------------------------- // Need to add an energy fix to account for the dissipation of kinetic energy // Formulation follows from Boville and Bretherton (2003) - modified by Phil Rasch - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, ktm, pver), [&] (const Int& k) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, ktm, pver), [&] (const Int& k) { const Int km1 = ekat::impl::max(0, k-1); const Int kp1 = ekat::impl::min(pver-1, k+1); diff --git a/components/eamxx/src/physics/zm/impl/zm_zm_transport_tracer_impl.hpp b/components/eamxx/src/physics/zm/impl/zm_zm_transport_tracer_impl.hpp index a1ccbbff73d0..559423556a77 100644 --- a/components/eamxx/src/physics/zm/impl/zm_zm_transport_tracer_impl.hpp +++ b/components/eamxx/src/physics/zm/impl/zm_zm_transport_tracer_impl.hpp @@ -58,7 +58,7 @@ void Functions::zm_transport_tracer( dptmp(dptmp1d.data(), ncnst, pver); // Parallel loop over each constituent (skip water vapor at m=0) - Kokkos::parallel_for(Kokkos::TeamThreadRange(team, 1, ncnst), [&] (const Int& m) { + Kokkos::parallel_for(Kokkos::TeamVectorRange(team, 1, ncnst), [&] (const Int& m) { if (!doconvtran(m)) return; // Initialize temporary arrays (always use moist formulation) diff --git a/components/eamxx/src/physics/zm/tests/CMakeLists.txt b/components/eamxx/src/physics/zm/tests/CMakeLists.txt index 7a070aedc9c1..6b457889cd3d 100644 --- a/components/eamxx/src/physics/zm/tests/CMakeLists.txt +++ b/components/eamxx/src/physics/zm/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(ZM_TESTS_SRCS zm_compute_dilute_parcel_tests.cpp zm_compute_cape_from_parcel_tests.cpp zm_zm_conv_mcsp_calculate_shear_tests.cpp + zm_zm_conv_mcsp_tend_tests.cpp ) # ZM_TESTS_SRCS # All tests should understand the same baseline args diff --git a/components/eamxx/src/physics/zm/tests/infra/zm_c2f_bridge.f90 b/components/eamxx/src/physics/zm/tests/infra/zm_c2f_bridge.f90 index 233b7eb95409..e61875bb8622 100644 --- a/components/eamxx/src/physics/zm/tests/infra/zm_c2f_bridge.f90 +++ b/components/eamxx/src/physics/zm/tests/infra/zm_c2f_bridge.f90 @@ -181,4 +181,27 @@ subroutine zm_conv_mcsp_calculate_shear_bridge_f(pcols, ncol, pver, state_pmid, call zm_conv_mcsp_calculate_shear(pcols, ncol, pver, state_pmid, state_u, state_v, mcsp_shear) end subroutine zm_conv_mcsp_calculate_shear_bridge_f +subroutine zm_conv_mcsp_tend_bridge_f(pcols, ncol, pver, pverp, ztodt, jctop, state_pmid, state_pint, state_pdel, state_s, state_q, state_u, state_v, ptend_zm_s, ptend_zm_q, ptend_s, ptend_q, ptend_u, ptend_v, mcsp_dt_out, mcsp_dq_out, mcsp_du_out, mcsp_dv_out, mcsp_freq, mcsp_shear, zm_depth) bind(C) + use zm_conv_mcsp, only : zm_conv_mcsp_tend + use zm_conv_types, only: zm_const_t, zm_param_t + use zm_conv_types, only: zm_param_set_for_testing, zm_const_set_for_testing + + integer(kind=c_int) , value, intent(in) :: pcols, ncol, pver, pverp + real(kind=c_real) , value, intent(in) :: ztodt + integer(kind=c_int) , intent(in), dimension(pcols) :: jctop + real(kind=c_real) , intent(in), dimension(pcols, pver) :: state_pmid, state_pdel, state_s, state_q, state_u, state_v, ptend_zm_s, ptend_zm_q + real(kind=c_real) , intent(in), dimension(pcols, pverp) :: state_pint + real(kind=c_real) , intent(inout), dimension(pcols, pver) :: ptend_s, ptend_q, ptend_u, ptend_v + real(kind=c_real) , intent(out), dimension(pcols, pver) :: mcsp_dt_out, mcsp_dq_out, mcsp_du_out, mcsp_dv_out + real(kind=c_real) , intent(out), dimension(pcols) :: mcsp_freq, mcsp_shear, zm_depth + + type(zm_const_t) :: zm_const ! derived type to hold ZM constants + type(zm_param_t) :: zm_param ! derived type to hold ZM tunable parameters + !----------------------------------------------------------------------------- + call zm_param_set_for_testing(zm_param) + call zm_const_set_for_testing(zm_const) + + call zm_conv_mcsp_tend(pcols, ncol, pver, pverp, ztodt, jctop, zm_const, zm_param, state_pmid, state_pint, state_pdel, state_s, state_q, state_u, state_v, ptend_zm_s, ptend_zm_q, ptend_s, ptend_q, ptend_u, ptend_v, mcsp_dt_out, mcsp_dq_out, mcsp_du_out, mcsp_dv_out, mcsp_freq, mcsp_shear, zm_depth) +end subroutine zm_conv_mcsp_tend_bridge_f + end module zm_c2f_bridge diff --git a/components/eamxx/src/physics/zm/tests/infra/zm_test_data.cpp b/components/eamxx/src/physics/zm/tests/infra/zm_test_data.cpp index e7240f42b99c..db8d14e8ed26 100644 --- a/components/eamxx/src/physics/zm/tests/infra/zm_test_data.cpp +++ b/components/eamxx/src/physics/zm/tests/infra/zm_test_data.cpp @@ -54,6 +54,8 @@ void compute_dilute_parcel_bridge_f(Int pcols, Int ncol, Int pver, Int num_msg, void compute_cape_from_parcel_bridge_f(Int pcols, Int ncol, Int pver, Int pverp, Int num_cin, Int num_msg, Real* temperature, Real* tv, Real* sp_humidity, Real* pint, Int* msemax_klev, Real* lcl_pmid, Int* lcl_klev, Real* parcel_qsat, Real* parcel_temp, Real* parcel_vtemp, Int* eql_klev, Real* cape); void zm_conv_mcsp_calculate_shear_bridge_f(Int pcols, Int ncol, Int pver, Real* state_pmid, Real* state_u, Real* state_v, Real* mcsp_shear); + +void zm_conv_mcsp_tend_bridge_f(Int pcols, Int ncol, Int pver, Int pverp, Real ztodt, Int* jctop, Real* state_pmid, Real* state_pint, Real* state_pdel, Real* state_s, Real* state_q, Real* state_u, Real* state_v, Real* ptend_zm_s, Real* ptend_zm_q, Real* ptend_s, Real* ptend_q, Real* ptend_u, Real* ptend_v, Real* mcsp_dt_out, Real* mcsp_dq_out, Real* mcsp_du_out, Real* mcsp_dv_out, Real* mcsp_freq, Real* mcsp_shear, Real* zm_depth); } // extern "C" : end _f decls // Inits and finalizes are not intended to be called outside this comp unit @@ -742,8 +744,7 @@ void compute_cape_from_parcel(ComputeCapeFromParcelData& d) pint_d(vec2dr_in[3]), sp_humidity_d(vec2dr_in[4]), temperature_d(vec2dr_in[5]), - tv_d(vec2dr_in[6]), - zmid_d(vec2dr_in[7]); + tv_d(vec2dr_in[6]); view1di_d eql_klev_d(vec1di_in[0]), @@ -863,6 +864,130 @@ void zm_conv_mcsp_calculate_shear(ZmConvMcspCalculateShearData& d) zm_finalize_cxx(); } +void zm_conv_mcsp_tend_f(ZmConvMcspTendData& d) +{ + d.transition(); + zm_common_init_f(); + zm_conv_mcsp_tend_bridge_f(d.pcols, d.ncol, d.pver, d.pverp, d.ztodt, d.jctop, d.state_pmid, d.state_pint, d.state_pdel, d.state_s, d.state_q, d.state_u, d.state_v, d.ptend_zm_s, d.ptend_zm_q, d.ptend_s, d.ptend_q, d.ptend_u, d.ptend_v, d.mcsp_dt_out, d.mcsp_dq_out, d.mcsp_du_out, d.mcsp_dv_out, d.mcsp_freq, d.mcsp_shear, d.zm_depth); + zm_common_finalize_f(); + d.transition(); +} + +void zm_conv_mcsp_tend(ZmConvMcspTendData& d) +{ + zm_common_init(); + + // create device views and copy + std::vector vec1dr_in(3); + ekat::host_to_device({d.mcsp_freq, d.mcsp_shear, d.zm_depth}, d.pcols, vec1dr_in); + + std::vector vec2dr_in(17); + std::vector vec2dr_in_0_sizes = {d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols, d.pcols}; + std::vector vec2dr_in_1_sizes = {d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pver, d.pverp, d.pver, d.pver, d.pver, d.pver, d.pver}; + ekat::host_to_device({d.mcsp_dq_out, d.mcsp_dt_out, d.mcsp_du_out, d.mcsp_dv_out, d.ptend_q, d.ptend_s, d.ptend_u, d.ptend_v, d.ptend_zm_q, d.ptend_zm_s, d.state_pdel, d.state_pint, d.state_pmid, d.state_q, d.state_s, d.state_u, d.state_v}, vec2dr_in_0_sizes, vec2dr_in_1_sizes, vec2dr_in); + + std::vector vec1di_in(1); + ekat::host_to_device({d.jctop}, d.pcols, vec1di_in); + + view1dr_d + mcsp_freq_d(vec1dr_in[0]), + mcsp_shear_d(vec1dr_in[1]), + zm_depth_d(vec1dr_in[2]); + + view2dr_d + mcsp_dq_out_d(vec2dr_in[0]), + mcsp_dt_out_d(vec2dr_in[1]), + mcsp_du_out_d(vec2dr_in[2]), + mcsp_dv_out_d(vec2dr_in[3]), + ptend_q_d(vec2dr_in[4]), + ptend_s_d(vec2dr_in[5]), + ptend_u_d(vec2dr_in[6]), + ptend_v_d(vec2dr_in[7]), + ptend_zm_q_d(vec2dr_in[8]), + ptend_zm_s_d(vec2dr_in[9]), + state_pdel_d(vec2dr_in[10]), + state_pint_d(vec2dr_in[11]), + state_pmid_d(vec2dr_in[12]), + state_q_d(vec2dr_in[13]), + state_s_d(vec2dr_in[14]), + state_u_d(vec2dr_in[15]), + state_v_d(vec2dr_in[16]); + + view1di_d + jctop_d(vec1di_in[0]); + + const auto policy = ekat::TeamPolicyFactory::get_default_team_policy(d.pcols, d.pver); + + WSM wsm(d.pver, 4, policy); + ZMF::ZmRuntimeOpt init_cp = ZMF::s_common_init; + + // unpack data scalars because we do not want the lambda to capture d + const Real ztodt = d.ztodt; + const Int pver = d.pver; + const Int pverp = d.pverp; + + Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const MemberType& team) { + const Int i = team.league_rank(); + + // Get single-column subviews of all inputs, shouldn't need any i-indexing + // after this. + const auto state_pmid_c = ekat::subview(state_pmid_d, i); + const auto state_pint_c = ekat::subview(state_pint_d, i); + const auto state_pdel_c = ekat::subview(state_pdel_d, i); + const auto state_s_c = ekat::subview(state_s_d, i); + const auto state_q_c = ekat::subview(state_q_d, i); + const auto state_u_c = ekat::subview(state_u_d, i); + const auto state_v_c = ekat::subview(state_v_d, i); + const auto ptend_zm_s_c = ekat::subview(ptend_zm_s_d, i); + const auto ptend_zm_q_c = ekat::subview(ptend_zm_q_d, i); + const auto ptend_s_c = ekat::subview(ptend_s_d, i); + const auto ptend_q_c = ekat::subview(ptend_q_d, i); + const auto ptend_u_c = ekat::subview(ptend_u_d, i); + const auto ptend_v_c = ekat::subview(ptend_v_d, i); + const auto mcsp_dt_out_c = ekat::subview(mcsp_dt_out_d, i); + const auto mcsp_dq_out_c = ekat::subview(mcsp_dq_out_d, i); + const auto mcsp_du_out_c = ekat::subview(mcsp_du_out_d, i); + const auto mcsp_dv_out_c = ekat::subview(mcsp_dv_out_d, i); + + ZMF::zm_conv_mcsp_tend( + team, + wsm.get_workspace(team), + init_cp, + pver, + pverp, + ztodt, + jctop_d(i), + state_pmid_c, + state_pint_c, + state_pdel_c, + state_s_c, + state_q_c, + state_u_c, + state_v_c, + ptend_zm_s_c, + ptend_zm_q_c, + ptend_s_c, + ptend_q_c, + ptend_u_c, + ptend_v_c, + mcsp_dt_out_c, + mcsp_dq_out_c, + mcsp_du_out_c, + mcsp_dv_out_c, + mcsp_freq_d(i), + mcsp_shear_d(i), + zm_depth_d(i)); + }); + + // Now get arrays + std::vector vec1dr_out = {mcsp_freq_d, mcsp_shear_d, zm_depth_d}; + ekat::device_to_host({d.mcsp_freq, d.mcsp_shear, d.zm_depth}, d.pcols, vec1dr_out); + + std::vector vec2dr_out = {mcsp_dq_out_d, mcsp_dt_out_d, mcsp_du_out_d, mcsp_dv_out_d, ptend_q_d, ptend_s_d, ptend_u_d, ptend_v_d}; + ekat::device_to_host({d.mcsp_dq_out, d.mcsp_dt_out, d.mcsp_du_out, d.mcsp_dv_out, d.ptend_q, d.ptend_s, d.ptend_u, d.ptend_v}, d.pcols, d.pver, vec2dr_out); + + zm_finalize_cxx(); +} // end glue impls } // namespace zm diff --git a/components/eamxx/src/physics/zm/tests/infra/zm_test_data.hpp b/components/eamxx/src/physics/zm/tests/infra/zm_test_data.hpp index 345d64a1b8d7..5cb153f60ae5 100644 --- a/components/eamxx/src/physics/zm/tests/infra/zm_test_data.hpp +++ b/components/eamxx/src/physics/zm/tests/infra/zm_test_data.hpp @@ -201,7 +201,6 @@ struct ComputeDiluteCapeData : public PhysicsTestData { for (Int c = 0; c < pcols; ++c) { std::sort(pmid + (c*pver), pmid + ((c+1)*pver)); } - } }; @@ -372,7 +371,52 @@ struct ZmConvMcspCalculateShearData : public PhysicsTestData { std::sort(state_pmid + (c*pver), state_pmid + ((c+1)*pver)); } } +}; + +struct ZmConvMcspTendData : public PhysicsTestData { + // Inputs + Int pcols, ncol, pver, pverp; + Int *jctop; + Real ztodt; + Real *state_pmid, *state_pint, *state_pdel, *state_s, *state_q, *state_u, *state_v, *ptend_zm_s, *ptend_zm_q; + + // Inputs/Outputs + Real *ptend_s, *ptend_q, *ptend_u, *ptend_v; + + // Outputs + Real *mcsp_dt_out, *mcsp_dq_out, *mcsp_du_out, *mcsp_dv_out, *mcsp_freq, *mcsp_shear, *zm_depth; + + ZmConvMcspTendData(Int pcols_, Int ncol_, Int pver_, Int pverp_, Real ztodt_) : + PhysicsTestData({ + {pcols_, pver_}, + {pcols_, pverp_}, + {pcols_}, + {pcols_} + }, + { + {&state_pmid, &state_pdel, &state_s, &state_q, &state_u, &state_v, &ptend_zm_s, &ptend_zm_q, &ptend_s, &ptend_q, &ptend_u, &ptend_v, &mcsp_dt_out, &mcsp_dq_out, &mcsp_du_out, &mcsp_dv_out}, + {&state_pint}, + {&mcsp_freq, &mcsp_shear, &zm_depth} + }, + { + {&jctop} + }), + pcols(pcols_), ncol(ncol_), pver(pver_), pverp(pverp_), ztodt(ztodt_) + {} + + PTD_STD_DEF(ZmConvMcspTendData, 5, pcols, ncol, pver, pverp, ztodt); + template + void randomize(Engine& engine) + { + PhysicsTestData::randomize(engine, { {state_pmid, {600e2-1000, 600e2+1000}}, {state_pint, {1300e2 - 1000, 1300e2 + 1000}}, {state_u, {50, 150}} }); + + // Make sure each column is sorted + for (Int c = 0; c < pcols; ++c) { + std::sort(state_pmid + (c*pver), state_pmid + ((c+1)*pver)); + std::sort(state_pint + (c*(pverp)), state_pint + ((c+1)*(pverp))); + } + } }; // Glue functions for host test data. We can call either fortran or CXX with this data (_f -> fortran) @@ -394,6 +438,8 @@ void compute_cape_from_parcel_f(ComputeCapeFromParcelData& d); void compute_cape_from_parcel(ComputeCapeFromParcelData& d); void zm_conv_mcsp_calculate_shear_f(ZmConvMcspCalculateShearData& d); void zm_conv_mcsp_calculate_shear(ZmConvMcspCalculateShearData& d); +void zm_conv_mcsp_tend_f(ZmConvMcspTendData& d); +void zm_conv_mcsp_tend(ZmConvMcspTendData& d); // End glue function decls } // namespace zm diff --git a/components/eamxx/src/physics/zm/tests/infra/zm_unit_tests_common.hpp b/components/eamxx/src/physics/zm/tests/infra/zm_unit_tests_common.hpp index 034bb8980906..241b761f70d9 100644 --- a/components/eamxx/src/physics/zm/tests/infra/zm_unit_tests_common.hpp +++ b/components/eamxx/src/physics/zm/tests/infra/zm_unit_tests_common.hpp @@ -74,6 +74,7 @@ struct UnitWrap { struct TestComputeDiluteParcel; struct TestComputeCapeFromParcel; struct TestZmConvMcspCalculateShear; + struct TestZmConvMcspTend; }; // UnitWrap }; diff --git a/components/eamxx/src/physics/zm/tests/zm_compute_dilute_cape_tests.cpp b/components/eamxx/src/physics/zm/tests/zm_compute_dilute_cape_tests.cpp index 1998fb90fe43..5ac1c9b487fe 100644 --- a/components/eamxx/src/physics/zm/tests/zm_compute_dilute_cape_tests.cpp +++ b/components/eamxx/src/physics/zm/tests/zm_compute_dilute_cape_tests.cpp @@ -60,6 +60,9 @@ struct UnitWrap::UnitTest::TestComputeDiluteCape : public UnitWrap::UnitTest< } } + const auto margin = std::numeric_limits::epsilon() * + (ekat::is_single_precision::value ? 1000 : 1); + // Verify BFB results, all data should be in C layout if (SCREAM_BFB_TESTING && this->m_baseline_action == COMPARE) { for (Int i = 0; i < num_runs; ++i) { @@ -80,7 +83,7 @@ struct UnitWrap::UnitTest::TestComputeDiluteCape : public UnitWrap::UnitTest< REQUIRE(d_baseline.total(d_baseline.lcl_temperature) == d_test.total(d_test.eql_klev)); for (Int k = 0; k < d_baseline.total(d_baseline.lcl_temperature); ++k) { REQUIRE(d_baseline.lcl_temperature[k] == d_test.lcl_temperature[k]); - REQUIRE(d_baseline.cape[k] == d_test.cape[k]); + REQUIRE(d_baseline.cape[k] == Approx(d_test.cape[k]).margin(margin)); REQUIRE(d_baseline.q_mx[k] == d_test.q_mx[k]); REQUIRE(d_baseline.t_mx[k] == d_test.t_mx[k]); REQUIRE(d_baseline.msemax_klev[k] == d_test.msemax_klev[k]); diff --git a/components/eamxx/src/physics/zm/tests/zm_compute_dilute_parcel_tests.cpp b/components/eamxx/src/physics/zm/tests/zm_compute_dilute_parcel_tests.cpp index 362b135a9bbe..1c8336321d51 100644 --- a/components/eamxx/src/physics/zm/tests/zm_compute_dilute_parcel_tests.cpp +++ b/components/eamxx/src/physics/zm/tests/zm_compute_dilute_parcel_tests.cpp @@ -60,9 +60,6 @@ struct UnitWrap::UnitTest::TestComputeDiluteParcel : public UnitWrap::UnitTes } } - const auto margin = std::numeric_limits::epsilon() * - (ekat::is_single_precision::value ? 1000 : 1); - // Verify BFB results, all data should be in C layout if (SCREAM_BFB_TESTING && this->m_baseline_action == COMPARE) { for (Int i = 0; i < num_runs; ++i) { @@ -74,7 +71,7 @@ struct UnitWrap::UnitTest::TestComputeDiluteParcel : public UnitWrap::UnitTes for (Int k = 0; k < d_baseline.total(d_baseline.parcel_temp); ++k) { REQUIRE(d_baseline.parcel_temp[k] == d_test.parcel_temp[k]); REQUIRE(d_baseline.parcel_vtemp[k] == d_test.parcel_vtemp[k]); - REQUIRE(d_baseline.parcel_qsat[k] == Approx(d_test.parcel_qsat[k]).margin(margin)); + REQUIRE(d_baseline.parcel_qsat[k] == d_test.parcel_qsat[k]); } REQUIRE(d_baseline.total(d_baseline.lcl_pmid) == d_test.total(d_test.lcl_pmid)); REQUIRE(d_baseline.total(d_baseline.lcl_pmid) == d_test.total(d_test.lcl_temperature)); diff --git a/components/eamxx/src/physics/zm/tests/zm_zm_conv_mcsp_tend_tests.cpp b/components/eamxx/src/physics/zm/tests/zm_zm_conv_mcsp_tend_tests.cpp new file mode 100644 index 000000000000..285034b52d02 --- /dev/null +++ b/components/eamxx/src/physics/zm/tests/zm_zm_conv_mcsp_tend_tests.cpp @@ -0,0 +1,124 @@ +#include "catch2/catch.hpp" + +#include "share/core/eamxx_types.hpp" +#include "physics/zm/zm_functions.hpp" +#include "physics/zm/tests/infra/zm_test_data.hpp" + +#include "zm_unit_tests_common.hpp" + +namespace scream { +namespace zm { +namespace unit_test { + +template +struct UnitWrap::UnitTest::TestZmConvMcspTend : public UnitWrap::UnitTest::Base { + + void run_bfb() + { + auto engine = Base::get_engine(); + + // Set up inputs + ZmConvMcspTendData baseline_data[] = { + // pcols, ncol, pver, pverp, ztodt + ZmConvMcspTendData(4, 4, 72, 73, 2.0), + ZmConvMcspTendData(4, 4, 72, 73, 3.0), + ZmConvMcspTendData(4, 4, 128, 129, 4.0), + ZmConvMcspTendData(4, 4, 128, 129, 5.0), + }; + + static constexpr Int num_runs = sizeof(baseline_data) / sizeof(ZmConvMcspTendData); + + // Generate random input data + // Alternatively, you can use the baseline_data constructors/initializer lists to hardcode data + for (auto& d : baseline_data) { + d.randomize(engine); + } + + // Create copies of data for use by test. Needs to happen before read calls so that + // inout data is in original state + ZmConvMcspTendData test_data[] = { + ZmConvMcspTendData(baseline_data[0]), + ZmConvMcspTendData(baseline_data[1]), + ZmConvMcspTendData(baseline_data[2]), + ZmConvMcspTendData(baseline_data[3]), + }; + + // Read baseline data + if (this->m_baseline_action == COMPARE) { + for (auto& d : baseline_data) { + d.read(Base::m_ifile); + } + } + + // Get data from test + for (auto& d : test_data) { + if (this->m_baseline_action == GENERATE) { + zm_conv_mcsp_tend_f(d); + } + else { + zm_conv_mcsp_tend(d); + } + } + + // zm_conv_mcsp_test does a few sum reductions and we can't guarantee + // order of operations consistency with fortran, so we need approx + const auto margin = std::numeric_limits::epsilon() * + (ekat::is_single_precision::value ? 1000 : 1); + + // Verify BFB results, all data should be in C layout + if (SCREAM_BFB_TESTING && this->m_baseline_action == COMPARE) { + for (Int i = 0; i < num_runs; ++i) { + ZmConvMcspTendData& d_baseline = baseline_data[i]; + ZmConvMcspTendData& d_test = test_data[i]; + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.ptend_s)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.ptend_q)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.ptend_u)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.ptend_v)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.mcsp_dt_out)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.mcsp_dq_out)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.mcsp_du_out)); + REQUIRE(d_baseline.total(d_baseline.ptend_s) == d_test.total(d_test.mcsp_dv_out)); + for (Int k = 0; k < d_baseline.total(d_baseline.ptend_s); ++k) { + REQUIRE(d_baseline.ptend_s[k] == Approx(d_test.ptend_s[k]).margin(margin)); + REQUIRE(d_baseline.ptend_q[k] == d_test.ptend_q[k]); + REQUIRE(d_baseline.ptend_u[k] == d_test.ptend_u[k]); + REQUIRE(d_baseline.ptend_v[k] == d_test.ptend_v[k]); + REQUIRE(d_baseline.mcsp_dt_out[k] == Approx(d_test.mcsp_dt_out[k]).margin(margin)); + REQUIRE(d_baseline.mcsp_dq_out[k] == d_test.mcsp_dq_out[k]); + REQUIRE(d_baseline.mcsp_du_out[k] == d_test.mcsp_du_out[k]); + REQUIRE(d_baseline.mcsp_dv_out[k] == d_test.mcsp_dv_out[k]); + } + REQUIRE(d_baseline.total(d_baseline.mcsp_freq) == d_test.total(d_test.mcsp_freq)); + REQUIRE(d_baseline.total(d_baseline.mcsp_freq) == d_test.total(d_test.mcsp_shear)); + REQUIRE(d_baseline.total(d_baseline.mcsp_freq) == d_test.total(d_test.zm_depth)); + for (Int k = 0; k < d_baseline.total(d_baseline.mcsp_freq); ++k) { + REQUIRE(d_baseline.mcsp_freq[k] == d_test.mcsp_freq[k]); + REQUIRE(d_baseline.mcsp_shear[k] == d_test.mcsp_shear[k]); + REQUIRE(d_baseline.zm_depth[k] == d_test.zm_depth[k]); + } + } + } + else if (this->m_baseline_action == GENERATE) { + for (Int i = 0; i < num_runs; ++i) { + test_data[i].write(Base::m_ofile); + } + } + } // run_bfb + +}; + +} // namespace unit_test +} // namespace zm +} // namespace scream + +namespace { + +TEST_CASE("zm_conv_mcsp_tend_bfb", "[zm]") +{ + using TestStruct = scream::zm::unit_test::UnitWrap::UnitTest::TestZmConvMcspTend; + + TestStruct t; + t.run_bfb(); +} + +} // empty namespace diff --git a/components/eamxx/src/physics/zm/zm_functions.hpp b/components/eamxx/src/physics/zm/zm_functions.hpp index 27f017feaec6..2ee7a51058f8 100644 --- a/components/eamxx/src/physics/zm/zm_functions.hpp +++ b/components/eamxx/src/physics/zm/zm_functions.hpp @@ -97,6 +97,12 @@ struct Functions { static inline constexpr Real ull_upper_launch_pressure = 600.0; // upper search limit for unrestricted launch level (ULL) static inline constexpr Real MCSP_storm_speed_pref = 600e2; // pressure level for winds in MCSP calculation [Pa] + + static inline constexpr Real MCSP_conv_depth_min = 700e2; // pressure thickness of convective heating [Pa] + + static inline constexpr Real MCSP_shear_min = 3.0; // min shear value for MCSP to be active + + static inline constexpr Real MCSP_shear_max = 200.0; // max shear value for MCSP to be active }; //---------------------------------------------------------------------------- @@ -522,10 +528,44 @@ struct Functions { // Outputs Real& mcsp_shear); + KOKKOS_FUNCTION + static void zm_conv_mcsp_tend( + // Inputs + const MemberType& team, + const Workspace& workspace, + const ZmRuntimeOpt& runtime_opt, + const Int& pver, // number of mid-point vertical levels + const Int& pverp, // number of interface vertical levels + const Real& ztodt, // 2x physics time step + const Int& jctop, // cloud top level indices + const uview_1d& state_pmid, // physics state mid-point pressure + const uview_1d& state_pint, // physics state interface pressure + const uview_1d& state_pdel, // physics state pressure thickness + const uview_1d& state_s, // physics state dry static energy + const uview_1d& state_q, // physics state specific humidity + const uview_1d& state_u, // physics state u momentum + const uview_1d& state_v, // physics state v momentum + const uview_1d& ptend_zm_s, // input ZM tendency for dry static energy (DSE) + const uview_1d& ptend_zm_q, // input ZM tendency for specific humidity (qv) + // Inputs/Outputs + const uview_1d& ptend_s, // output tendency of DSE + const uview_1d& ptend_q, // output tendency of qv + const uview_1d& ptend_u, // output tendency of u-wind + const uview_1d& ptend_v, // output tendency of v-wind + // Outputs + const uview_1d& mcsp_dt_out, // final MCSP tendency for DSE + const uview_1d& mcsp_dq_out, // final MCSP tendency for qv + const uview_1d& mcsp_du_out, // final MCSP tendency for u wind + const uview_1d& mcsp_dv_out, // final MCSP tendency for v wind + Real& mcsp_freq, // MSCP frequency for output + Real& mcsp_shear, // shear used to check against threshold + Real& zm_depth); // pressure depth of ZM heating + // // --------- Members --------- // inline static ZmRuntimeOpt s_common_init; + }; // struct Functions } // namespace zm @@ -545,5 +585,6 @@ struct Functions { # include "impl/zm_compute_dilute_parcel_impl.hpp" # include "impl/zm_compute_cape_from_parcel_impl.hpp" # include "impl/zm_zm_conv_mcsp_calculate_shear_impl.hpp" +# include "impl/zm_zm_conv_mcsp_tend_impl.hpp" #endif // GPU && !KOKKOS_ENABLE_*_RELOCATABLE_DEVICE_CODE #endif // ZM_FUNCTIONS_HPP diff --git a/components/eamxx/src/share/atm_process/atmosphere_process_group.cpp b/components/eamxx/src/share/atm_process/atmosphere_process_group.cpp index 9773b42a20d0..43d102b139f4 100644 --- a/components/eamxx/src/share/atm_process/atmosphere_process_group.cpp +++ b/components/eamxx/src/share/atm_process/atmosphere_process_group.cpp @@ -306,6 +306,9 @@ void AtmosphereProcessGroup::add_postcondition_nan_checks () const { group->add_postcondition_nan_checks(); } else { for (const auto& f : proc->get_fields_out()) { + if (f.data_type()==DataType::IntType) + continue; + const auto& grid_name = f.get_header().get_identifier().get_grid_name(); auto nan_check = std::make_shared(f,m_grids_manager->get_grid(grid_name)); proc->add_postcondition_check(nan_check, CheckFailHandling::Fatal); @@ -314,6 +317,9 @@ void AtmosphereProcessGroup::add_postcondition_nan_checks () const { for (const auto& g : proc->get_groups_out()) { const auto& grid = m_grids_manager->get_grid(g.grid_name()); for (const auto& f : g.m_individual_fields) { + if (f.second->data_type()==DataType::IntType) + continue; + auto nan_check = std::make_shared(*f.second,grid); proc->add_postcondition_check(nan_check, CheckFailHandling::Fatal); } diff --git a/components/eamxx/src/share/atm_process/atmosphere_process_hash.cpp b/components/eamxx/src/share/atm_process/atmosphere_process_hash.cpp index 2a848e804b3e..a09575f2b2f4 100644 --- a/components/eamxx/src/share/atm_process/atmosphere_process_hash.cpp +++ b/components/eamxx/src/share/atm_process/atmosphere_process_hash.cpp @@ -14,7 +14,21 @@ namespace { using ExeSpace = KokkosTypes::ExeSpace; using bfbhash::HashType; -void hash (const Field::view_dev_t& v, +template +void hash (const Field::view_dev_t& v, + HashType& accum_out) { + HashType accum = 0; + Kokkos::parallel_reduce( + Kokkos::RangePolicy(0, 1), + KOKKOS_LAMBDA(const int, HashType& accum) { + bfbhash::hash(v(), accum); + }, bfbhash::HashReducer<>(accum)); + Kokkos::fence(); + bfbhash::hash(accum, accum_out); +} + +template +void hash (const Field::view_dev_t& v, const FieldLayout& lo, HashType& accum_out) { HashType accum = 0; Kokkos::parallel_reduce( @@ -26,7 +40,8 @@ void hash (const Field::view_dev_t& v, bfbhash::hash(accum, accum_out); } -void hash (const Field::view_dev_t& v, +template +void hash (const Field::view_dev_t& v, const FieldLayout& lo, HashType& accum_out) { HashType accum = 0; const auto& dims = lo.extents(); @@ -41,7 +56,8 @@ void hash (const Field::view_dev_t& v, bfbhash::hash(accum, accum_out); } -void hash (const Field::view_dev_t& v, +template +void hash (const Field::view_dev_t& v, const FieldLayout& lo, HashType& accum_out) { HashType accum = 0; const auto& dims = lo.extents(); @@ -56,7 +72,8 @@ void hash (const Field::view_dev_t& v, bfbhash::hash(accum, accum_out); } -void hash (const Field::view_dev_t& v, +template +void hash (const Field::view_dev_t& v, const FieldLayout& lo, HashType& accum_out) { HashType accum = 0; const auto& dims = lo.extents(); @@ -71,7 +88,8 @@ void hash (const Field::view_dev_t& v, bfbhash::hash(accum, accum_out); } -void hash (const Field::view_dev_t& v, +template +void hash (const Field::view_dev_t& v, const FieldLayout& lo, HashType& accum_out) { HashType accum = 0; const auto& dims = lo.extents(); @@ -91,13 +109,31 @@ void hash (const Field& f, HashType& accum) { const auto& id = hd.get_identifier(); const auto& lo = id.get_layout(); const auto rank = lo.rank(); - switch (rank) { - case 1: hash(f.get_view(), lo, accum); break; - case 2: hash(f.get_view(), lo, accum); break; - case 3: hash(f.get_view(), lo, accum); break; - case 4: hash(f.get_view(), lo, accum); break; - case 5: hash(f.get_view(), lo, accum); break; - default: break; + if (f.data_type()==DataType::RealType) { + switch (rank) { + case 0: hash(f.get_view(), accum); break; + case 1: hash(f.get_view(), lo, accum); break; + case 2: hash(f.get_view(), lo, accum); break; + case 3: hash(f.get_view(), lo, accum); break; + case 4: hash(f.get_view(), lo, accum); break; + case 5: hash(f.get_view(), lo, accum); break; + default: break; + } + } else if (f.data_type()==DataType::IntType) { + switch (rank) { + case 0: hash(f.get_view(), accum); break; + case 1: hash(f.get_view(), lo, accum); break; + case 2: hash(f.get_view(), lo, accum); break; + case 3: hash(f.get_view(), lo, accum); break; + case 4: hash(f.get_view(), lo, accum); break; + case 5: hash(f.get_view(), lo, accum); break; + default: break; + } + } else { + EKAT_ERROR_MSG ( + "Error! Cannot hash field, unsupported data type.\n" + " - name: " + f.name() + "\n" + " - type: " + e2str(f.data_type()) + "\n"); } } diff --git a/components/eamxx/src/share/data_managers/field_manager.cpp b/components/eamxx/src/share/data_managers/field_manager.cpp index 587774bc536f..6877db4180ce 100644 --- a/components/eamxx/src/share/data_managers/field_manager.cpp +++ b/components/eamxx/src/share/data_managers/field_manager.cpp @@ -57,13 +57,9 @@ void FieldManager::register_field (const FieldRequest& req) // Get or create the new field if (!has_field(id.name(), grid_name)) { - - EKAT_REQUIRE_MSG (id.data_type()==field_valid_data_types().at(), - "Error! While refactoring, we only allow the Field data type to be Real.\n" - " If you're done with refactoring, go back and fix things.\n"); m_fields[grid_name][id.name()] = std::make_shared(id); } else { - // Make sure the input field has the same layout and units as the field already stored. + // Make sure the input field has the same layout, units, and data type as the field already stored. // TODO: this is the easiest way to ensure everyone uses the same units. // However, in the future, we *may* allow different units, providing // the users with conversion routines perhaps. @@ -79,6 +75,12 @@ void FieldManager::register_field (const FieldRequest& req) " - input id: " + id.get_id_string() + "\n" " - stored id: " + id0.get_id_string() + "\n" " Please, check and make sure all atmosphere processes use the same layout for a given field.\n"); + + EKAT_REQUIRE_MSG(id.data_type()==id0.data_type(), + "Error! Field '" + id.name() + "' already registered with different data_type:\n" + " - input id: " + id.get_id_string() + "\n" + " - stored id: " + id0.get_id_string() + "\n" + " Please, check and make sure all atmosphere processes use the same data_type for a given field.\n"); } // Make sure the field can accommodate the requested value type @@ -867,7 +869,7 @@ void FieldManager::pre_process_monolithic_group_requests () { // to the layout on the src grid const auto src_fid = m_fields.at(registered_grid).at(field_name)->get_header().get_identifier(); const auto fl = m_grids_mgr->get_grid(grid_name)->equivalent_layout(src_fid.get_layout()); - FieldIdentifier fid(field_name, fl, src_fid.get_units(), grid_name); + auto fid = src_fid.clone().reset_layout(fl).reset_grid(grid_name); // Register the field for each group req for (auto greq : m_group_requests.at(grid_name).at(group_name)) { diff --git a/components/eamxx/src/share/diagnostics/aodvis.cpp b/components/eamxx/src/share/diagnostics/aodvis.cpp index d5e6884010fd..349993733ba4 100644 --- a/components/eamxx/src/share/diagnostics/aodvis.cpp +++ b/components/eamxx/src/share/diagnostics/aodvis.cpp @@ -31,7 +31,7 @@ create_requests() // The fields required for this diagnostic to be computed add_field("aero_tau_sw", vector3d, nondim, grid_name); - add_field("sunlit_mask", scalar2d, nondim, grid_name); + add_field(FieldIdentifier("sunlit_mask", scalar2d, nondim, grid_name, DataType::IntType)); // Construct and allocate the aodvis field FieldIdentifier fid(name(), scalar2d, nondim, grid_name); @@ -41,7 +41,7 @@ create_requests() } void AODVis::initialize_impl(const RunType /*run_type*/) { - m_diagnostic_output.get_header().set_extra_data("mask_field", get_field_in("sunlit_mask")); + m_diagnostic_output.get_header().set_extra_data("valid_mask", get_field_in("sunlit_mask")); } void AODVis::compute_diagnostic_impl() { @@ -56,18 +56,18 @@ void AODVis::compute_diagnostic_impl() { const auto tau_vis = get_field_in("aero_tau_sw") .subfield(1, m_vis_bnd) .get_view(); - const auto sunlit = get_field_in("sunlit_mask").get_view(); + const auto sunlit = get_field_in("sunlit_mask").get_view(); const auto num_levs = m_nlevs; const auto policy = TPF::get_default_team_policy(m_ncols, m_nlevs); Kokkos::parallel_for( "Compute " + m_diagnostic_output.name(), policy, KOKKOS_LAMBDA(const MT &team) { const int icol = team.league_rank(); - if(sunlit(icol) == 0.0) { - aod(icol) = fill_value; - } else { + if(sunlit(icol)) { auto tau_icol = ekat::subview(tau_vis, icol); aod(icol) = RU::view_reduction(team, 0, num_levs, tau_icol); + } else { + aod(icol) = fill_value; } }); } diff --git a/components/eamxx/src/share/diagnostics/atm_backtend.cpp b/components/eamxx/src/share/diagnostics/atm_backtend.cpp index fed531421892..e9686bf27e3d 100644 --- a/components/eamxx/src/share/diagnostics/atm_backtend.cpp +++ b/components/eamxx/src/share/diagnostics/atm_backtend.cpp @@ -26,10 +26,8 @@ create_requests() void AtmBackTendDiag::initialize_impl(const RunType /*run_type*/) { const auto &f = get_field_in(m_name); const auto &fid = f.get_header().get_identifier(); - const auto &gn = fid.get_grid_name(); // Sanity checks - const auto &layout = fid.get_layout(); EKAT_REQUIRE_MSG( f.data_type() == DataType::RealType, "Error! AtmBackTendDiag only supports Real data type field.\n" @@ -44,13 +42,11 @@ void AtmBackTendDiag::initialize_impl(const RunType /*run_type*/) { auto diag_units = fid.get_units() / s; // All good, create the diag output - FieldIdentifier d_fid(m_name + "_atm_backtend", layout.clone(), diag_units, gn); - m_diagnostic_output = Field(d_fid); + m_diagnostic_output = Field(fid.clone(m_name + "_atm_backtend").reset_units(diag_units)); m_diagnostic_output.allocate_view(); // Let's also create the previous field - FieldIdentifier prev_fid(m_name + "_atm_backtend_prev", layout.clone(), diag_units, gn); - m_f_prev = Field(prev_fid); + m_f_prev = Field(fid.clone(m_name + "_atm_backtend_prev").reset_units(diag_units)); m_f_prev.allocate_view(); } diff --git a/components/eamxx/src/share/diagnostics/conditional_sampling.cpp b/components/eamxx/src/share/diagnostics/conditional_sampling.cpp index 9f7da1eee267..f17e3a6cf054 100644 --- a/components/eamxx/src/share/diagnostics/conditional_sampling.cpp +++ b/components/eamxx/src/share/diagnostics/conditional_sampling.cpp @@ -242,16 +242,12 @@ void ConditionalSampling::initialize_impl(const RunType /*run_type*/) { if (m_input_f != "count") { auto ifid = get_field_in(m_input_f).get_header().get_identifier(); - FieldIdentifier d_fid(m_diag_name, ifid.get_layout().clone(), ifid.get_units(), - ifid.get_grid_name()); - m_diagnostic_output = Field(d_fid); + m_diagnostic_output = Field(ifid.clone(m_diag_name)); m_diagnostic_output.allocate_view(); } else { if (m_condition_f != "lev") { auto ifid = get_field_in(m_condition_f).get_header().get_identifier(); - FieldIdentifier d_fid(m_diag_name, ifid.get_layout().clone(), ifid.get_units(), - ifid.get_grid_name()); - m_diagnostic_output = Field(d_fid); + m_diagnostic_output = Field(ifid.clone(m_diag_name)); m_diagnostic_output.allocate_view(); } else { using namespace ShortFieldTagsNames; @@ -264,8 +260,7 @@ void ConditionalSampling::initialize_impl(const RunType /*run_type*/) { } auto ifid = m_diagnostic_output.get_header().get_identifier(); - FieldIdentifier mask_fid(m_diag_name + "_mask", ifid.get_layout().clone(), ifid.get_units(), ifid.get_grid_name()); - Field diag_mask(mask_fid); + Field diag_mask(ifid.clone(m_diag_name + "_mask")); diag_mask.allocate_view(); const auto var_fill_value = constants::fill_value; diff --git a/components/eamxx/src/share/diagnostics/field_at_height.cpp b/components/eamxx/src/share/diagnostics/field_at_height.cpp index f7a4192c4efb..3f5b56aef798 100644 --- a/components/eamxx/src/share/diagnostics/field_at_height.cpp +++ b/components/eamxx/src/share/diagnostics/field_at_height.cpp @@ -97,7 +97,7 @@ initialize_impl (const RunType /*run_type*/) m_z_suffix = tag==LEV ? "_mid" : "_int"; // All good, create the diag output - FieldIdentifier d_fid (m_diag_name,layout.clone().strip_dim(tag),fid.get_units(),fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_layout(layout.clone().strip_dim(tag)); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); diff --git a/components/eamxx/src/share/diagnostics/field_at_level.cpp b/components/eamxx/src/share/diagnostics/field_at_level.cpp index 5618ed2d8697..be5eddb009fa 100644 --- a/components/eamxx/src/share/diagnostics/field_at_level.cpp +++ b/components/eamxx/src/share/diagnostics/field_at_level.cpp @@ -67,7 +67,7 @@ initialize_impl (const RunType /*run_type*/) } // All good, create the diag output - FieldIdentifier d_fid (m_diag_name,layout.clone().strip_dim(tag),fid.get_units(),fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_layout(layout.clone().strip_dim(tag)); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); diff --git a/components/eamxx/src/share/diagnostics/field_at_pressure_level.cpp b/components/eamxx/src/share/diagnostics/field_at_pressure_level.cpp index 632afd3fdf14..f11ec51864cc 100644 --- a/components/eamxx/src/share/diagnostics/field_at_pressure_level.cpp +++ b/components/eamxx/src/share/diagnostics/field_at_pressure_level.cpp @@ -66,13 +66,11 @@ initialize_impl (const RunType /*run_type*/) " - field layout: " + layout.to_string() + "\n"); // All good, create the diag output - FieldIdentifier d_fid (m_diag_name,layout.clone().strip_dim(tag),fid.get_units(),fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_layout(layout.clone().strip_dim(tag)); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); m_pressure_name = tag==LEV ? "p_mid" : "p_int"; - m_num_levs = layout.dims().back(); - auto num_cols = layout.dims().front(); // Take care of mask tracking for this field, in case it is needed. This has two steps: // 1. We need to actually track the masked columns, so we create a 2d (COL only) field. @@ -85,13 +83,12 @@ initialize_impl (const RunType /*run_type*/) // Add a field representing the mask as extra data to the diagnostic field. auto nondim = ekat::units::Units::nondimensional(); const auto& gname = fid.get_grid_name(); + auto mlayout = layout.clone().strip_dim(tag); std::string mask_name = m_diag_name + " mask"; - FieldLayout mask_layout( {COL}, {num_cols}); - FieldIdentifier mask_fid (mask_name,mask_layout, nondim, gname); - Field diag_mask(mask_fid); - diag_mask.allocate_view(); - m_diagnostic_output.get_header().set_extra_data("mask_field",diag_mask); + FieldIdentifier mask_fid (mask_name,mlayout, nondim, gname, DataType::IntType); + Field diag_mask(mask_fid,true); + m_diagnostic_output.get_header().set_extra_data("valid_mask",diag_mask); m_diagnostic_output.get_header().set_may_be_filled(true); using stratts_t = std::map; @@ -128,7 +125,7 @@ void FieldAtPressureLevel::compute_diagnostic_impl() if (rank==2) { auto policy = KT::RangePolicy(0,ncols); auto diag = m_diagnostic_output.get_view(); - auto mask = m_diagnostic_output.get_header().get_extra_data("mask_field").get_view(); + auto mask = m_diagnostic_output.get_header().get_extra_data("valid_mask").get_view(); auto f_v = f.get_view(); Kokkos::parallel_for(policy,KOKKOS_LAMBDA(const int icol) { auto x1 = ekat::subview(p_src_v,icol); @@ -159,7 +156,7 @@ void FieldAtPressureLevel::compute_diagnostic_impl() const int ndims = f.get_header().get_identifier().get_layout().get_vector_dim(); auto policy = KT::TeamPolicy(ncols,ndims); auto diag = m_diagnostic_output.get_view(); - auto mask = m_diagnostic_output.get_header().get_extra_data("mask_field").get_view(); + auto mask = m_diagnostic_output.get_header().get_extra_data("valid_mask").get_view(); auto f_v = f.get_view(); Kokkos::parallel_for(policy,KOKKOS_LAMBDA(const MemberType& team) { int icol = team.league_rank(); @@ -169,10 +166,8 @@ void FieldAtPressureLevel::compute_diagnostic_impl() auto last = beg + (nlevs-1); Kokkos::parallel_for(Kokkos::TeamVectorRange(team,ndims),[&](const int idim) { if (p_tgt<*beg or p_tgt>*last) { - diag(icol,idim) = fval; - Kokkos::single(Kokkos::PerTeam(team),[&]{ - mask(icol) = 0; - }); + diag(icol,idim) = fval; // TODO: don't bother setting an arbitrary value + mask(icol,idim) = 0; } else { auto y1 = ekat::subview(f_v,icol,idim); auto ub = ekat::upper_bound(beg,end,p_tgt); @@ -187,9 +182,7 @@ void FieldAtPressureLevel::compute_diagnostic_impl() // General case: interpolate between k1 and k1-1 diag(icol,idim) = y1(k1-1) + (y1(k1)-y1(k1-1))/(x1(k1) - x1(k1-1)) * (p_tgt-x1(k1-1)); } - Kokkos::single(Kokkos::PerTeam(team),[&]{ - mask(icol) = 1; - }); + mask(icol,idim) = 1; } }); }); diff --git a/components/eamxx/src/share/diagnostics/field_at_pressure_level.hpp b/components/eamxx/src/share/diagnostics/field_at_pressure_level.hpp index bb3806f03040..19c05bd294f7 100644 --- a/components/eamxx/src/share/diagnostics/field_at_pressure_level.hpp +++ b/components/eamxx/src/share/diagnostics/field_at_pressure_level.hpp @@ -36,7 +36,6 @@ class FieldAtPressureLevel : public AtmosphereDiagnostic std::string m_diag_name; Real m_pressure_level; - int m_num_levs; }; // class FieldAtPressureLevel } //namespace scream diff --git a/components/eamxx/src/share/diagnostics/histogram.cpp b/components/eamxx/src/share/diagnostics/histogram.cpp index de2963ce896d..6a82f441a486 100644 --- a/components/eamxx/src/share/diagnostics/histogram.cpp +++ b/components/eamxx/src/share/diagnostics/histogram.cpp @@ -59,8 +59,7 @@ void HistogramDiag::initialize_impl(const RunType /*run_type*/) { // allocate field for bin values FieldLayout bin_values_layout({CMP}, {num_bins+1}, {"bin"}); - FieldIdentifier bin_values_id(m_diag_name + "_bin_values", bin_values_layout, - field_id.get_units(), field_id.get_grid_name()); + auto bin_values_id = field_id.clone(m_diag_name + "_bin_values").reset_layout(bin_values_layout); m_bin_values = Field(bin_values_id); m_bin_values.allocate_view(); diff --git a/components/eamxx/src/share/diagnostics/horiz_avg.cpp b/components/eamxx/src/share/diagnostics/horiz_avg.cpp index dc028a6716a2..f4499c169f83 100644 --- a/components/eamxx/src/share/diagnostics/horiz_avg.cpp +++ b/components/eamxx/src/share/diagnostics/horiz_avg.cpp @@ -44,8 +44,7 @@ void HorizAvgDiag::initialize_impl(const RunType /*run_type*/) { " - field layout: " + layout.to_string() + "\n"); - FieldIdentifier d_fid(m_diag_name, layout.clone().strip_dim(COL), - fid.get_units(), fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_layout(layout.clone().strip_dim(COL)); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); diff --git a/components/eamxx/src/share/diagnostics/tests/aodvis_test.cpp b/components/eamxx/src/share/diagnostics/tests/aodvis_test.cpp index 84f3bb2841f3..bf3d1c685db1 100644 --- a/components/eamxx/src/share/diagnostics/tests/aodvis_test.cpp +++ b/components/eamxx/src/share/diagnostics/tests/aodvis_test.cpp @@ -31,8 +31,6 @@ TEST_CASE("aodvis") { using namespace ShortFieldTagsNames; using namespace ekat::units; - Real some_limit = 0.0025; - // A world comm ekat::Comm comm(MPI_COMM_WORLD); @@ -61,7 +59,7 @@ TEST_CASE("aodvis") { tau.get_header().get_tracking().update_time_stamp(t0); // Input (randomized) sunlit FieldLayout scalar2d_layout = grid->get_2d_scalar_layout(); - FieldIdentifier sunlit_fid("sunlit_mask", scalar2d_layout, nondim, grid->name()); + FieldIdentifier sunlit_fid("sunlit_mask", scalar2d_layout, nondim, grid->name(), DataType::IntType); Field sunlit(sunlit_fid); sunlit.allocate_view(); sunlit.get_header().get_tracking().update_time_stamp(t0); @@ -80,7 +78,7 @@ TEST_CASE("aodvis") { randomize_uniform(tau, seed++, 0, 0.005); // Randomize sunlit - randomize_uniform(sunlit, seed++, 0, 0.005); + randomize_uniform(sunlit, seed++, 0, 1); // Create and set up the diagnostic ekat::ParameterList params; @@ -90,16 +88,6 @@ TEST_CASE("aodvis") { diag->set_required_field(sunlit); diag->initialize(t0, RunType::Initial); - auto sun_h = sunlit.get_view(); - for(int icol = 0; icol < grid->get_num_local_dofs(); ++icol) { - // zero out all sun_h if below 0.05 - if(sun_h(icol) < some_limit) { - sun_h(icol) = 0.0; - } - } - // sync to device - sunlit.sync_to_dev(); - // Run diag diag->compute_diagnostic(); @@ -107,21 +95,22 @@ TEST_CASE("aodvis") { tau.sync_to_host(); diag->get_diagnostic().sync_to_host(); + const auto sun_h = sunlit.get_view(); const auto tau_h = tau.get_view(); const auto aod_hf = diag->get_diagnostic(); - const auto aod_mask = aod_hf.get_header().get_extra_data("mask_field"); + const auto aod_mask = aod_hf.get_header().get_extra_data("valid_mask"); Field aod_tf = diag->get_diagnostic().clone(); auto aod_t = aod_tf.get_view(); for(int icol = 0; icol < grid->get_num_local_dofs(); ++icol) { - if(sun_h(icol) < some_limit) { - aod_t(icol) = constants::fill_value; - } else { + if(sun_h(icol)) { aod_t(icol) = 0; for(int ilev = 0; ilev < nlevs; ++ilev) { aod_t(icol) += tau_h(icol, swvis, ilev); } + } else { + aod_t(icol) = constants::fill_value; } } aod_hf.sync_to_dev(); diff --git a/components/eamxx/src/share/diagnostics/tests/field_at_pressure_level_tests.cpp b/components/eamxx/src/share/diagnostics/tests/field_at_pressure_level_tests.cpp index 6167ba35346c..7a7105b062b9 100644 --- a/components/eamxx/src/share/diagnostics/tests/field_at_pressure_level_tests.cpp +++ b/components/eamxx/src/share/diagnostics/tests/field_at_pressure_level_tests.cpp @@ -103,13 +103,13 @@ TEST_CASE("field_at_pressure_level_p2") diag_f.sync_to_host(); auto test2_diag_v = diag_f.get_view(); // Check the mask field inside the diag_f - auto mask_f = diag_f.get_header().get_extra_data("mask_field"); + auto mask_f = diag_f.get_header().get_extra_data("valid_mask"); mask_f.sync_to_host(); - auto test2_mask_v = mask_f.get_view(); + auto test2_mask_v = mask_f.get_view(); // for (int icol=0;icol(); // Check the mask field inside the diag_f - auto mask_f = diag_f.get_header().get_extra_data("mask_field"); + auto mask_f = diag_f.get_header().get_extra_data("valid_mask"); mask_f.sync_to_host(); - auto test2_mask_v = mask_f.get_view(); + auto test2_mask_v = mask_f.get_view(); for (int icol=0;icol)); - REQUIRE(approx(test2_mask_v(icol),Real(0.0))); + REQUIRE(test2_mask_v(icol)==0); } } } diff --git a/components/eamxx/src/share/diagnostics/vert_contract.cpp b/components/eamxx/src/share/diagnostics/vert_contract.cpp index 928fee515081..cc45087f91b0 100644 --- a/components/eamxx/src/share/diagnostics/vert_contract.cpp +++ b/components/eamxx/src/share/diagnostics/vert_contract.cpp @@ -114,7 +114,7 @@ void VertContractDiag::initialize_impl(const RunType /*run_type*/) { VertContractDiag::scale_wts(m_weighting, m_weighting_sum); } - FieldIdentifier d_fid(m_diag_name, layout.clone().strip_dim(LEV), diag_units, fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_layout(layout.clone().strip_dim(LEV)).reset_units(diag_units); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); diff --git a/components/eamxx/src/share/diagnostics/vert_derivative.cpp b/components/eamxx/src/share/diagnostics/vert_derivative.cpp index 184b6ce5023a..3f8cfcee74ec 100644 --- a/components/eamxx/src/share/diagnostics/vert_derivative.cpp +++ b/components/eamxx/src/share/diagnostics/vert_derivative.cpp @@ -61,7 +61,7 @@ void VertDerivativeDiag::initialize_impl(const RunType /*run_type*/) { diag_units = fid.get_units() / m; } - FieldIdentifier d_fid(m_diag_name, layout, diag_units, fid.get_grid_name()); + auto d_fid = fid.clone(m_diag_name).reset_units(diag_units); m_diagnostic_output = Field(d_fid); m_diagnostic_output.allocate_view(); } diff --git a/components/eamxx/src/share/diagnostics/zonal_avg.cpp b/components/eamxx/src/share/diagnostics/zonal_avg.cpp index 0e271559eda4..2c29b55297c9 100644 --- a/components/eamxx/src/share/diagnostics/zonal_avg.cpp +++ b/components/eamxx/src/share/diagnostics/zonal_avg.cpp @@ -145,9 +145,8 @@ void ZonalAvgDiag::initialize_impl(const RunType /*run_type*/) { field_layout.to_string() + "\n"); FieldLayout diagnostic_layout = - field_layout.clone().strip_dim(COL).prepend_dim({CMP}, {m_num_zonal_bins}, {"bin"}); - FieldIdentifier diagnostic_id(m_diag_name, diagnostic_layout, field_id.get_units(), - field_id.get_grid_name()); + field_layout.clone().strip_dim(COL).prepend_dim(CMP, m_num_zonal_bins, "bin"); + auto diagnostic_id = field_id.clone(m_diag_name).reset_layout(diagnostic_layout); m_diagnostic_output = Field(diagnostic_id); m_diagnostic_output.allocate_view(); @@ -193,7 +192,7 @@ void ZonalAvgDiag::initialize_impl(const RunType /*run_type*/) { val = ncols_per_bin_view(bin_i) > val ? ncols_per_bin_view(bin_i) : val; }, Kokkos::Max(max_ncols_per_bin)); - FieldLayout bin_to_cols_layout = ncols_per_bin_layout.append_dim({COL}, {1+max_ncols_per_bin}); + FieldLayout bin_to_cols_layout = ncols_per_bin_layout.append_dim(COL, 1+max_ncols_per_bin); FieldIdentifier bin_to_cols_id("columns in each zonal bin", bin_to_cols_layout, FieldIdentifier::Units::nondimensional(), field_id.get_grid_name(), DataType::IntType); @@ -223,16 +222,12 @@ void ZonalAvgDiag::initialize_impl(const RunType /*run_type*/) { // allocate zonal area const FieldIdentifier &area_id = m_scaled_area.get_header().get_identifier(); FieldLayout zonal_area_layout({CMP}, {m_num_zonal_bins}, {"bin"}); - FieldIdentifier zonal_area_id("zonal area", zonal_area_layout, area_id.get_units(), - area_id.get_grid_name()); + auto zonal_area_id = area_id.clone("zonal area").reset_layout(zonal_area_layout); Field zonal_area(zonal_area_id); zonal_area.allocate_view(); // compute zonal area - FieldLayout ones_layout = area_id.get_layout().clone(); - FieldIdentifier ones_id("ones", ones_layout, area_id.get_units(), area_id.get_grid_name()); - Field ones(ones_id); - ones.allocate_view(); + auto ones = m_scaled_area.clone("ones"); ones.deep_copy(1.0); compute_zonal_sum(zonal_area, m_scaled_area, ones, m_bin_to_cols, &m_comm); diff --git a/components/eamxx/src/share/field/field.cpp b/components/eamxx/src/share/field/field.cpp index 50d4e9c536b7..1f517add41e2 100644 --- a/components/eamxx/src/share/field/field.cpp +++ b/components/eamxx/src/share/field/field.cpp @@ -58,6 +58,17 @@ void update_checks (const Field& y, const Field* x = nullptr, } } // anonymous namespace +Field:: +Field () + : Field (identifier_type("UNSET", + FieldLayout::invalid(), + ekat::units::Units::invalid(), + "UNKNOWN", + DataType::Invalid)) +{ + // Nothing to do here +} + Field:: Field (const identifier_type& id, bool allocate) { @@ -97,8 +108,7 @@ Field Field::clone(const std::string& name, const std::string& grid_name) const { // Create new field const auto& my_fid = get_header().get_identifier(); - FieldIdentifier fid(name,my_fid.get_layout(),my_fid.get_units(), - grid_name,my_fid.data_type()); + auto fid = my_fid.clone(name).reset_grid(grid_name); Field f(fid); // Ensure alloc props match @@ -151,6 +161,12 @@ subfield (const std::string& sf_name, const ekat::units::Units& sf_units, sf.initialize_contiguous_helper_field(); } + if (m_header->has_extra_data("valid_mask")) { + const auto& mask = m_header->get_extra_data("valid_mask"); + const auto& mfid = mask.get_header().get_identifier(); + sf.m_header->set_extra_data("valid_mask",mask.subfield(mask.name(),mfid.get_units(),idim,index,dynamic)); + } + return sf; } diff --git a/components/eamxx/src/share/field/field.hpp b/components/eamxx/src/share/field/field.hpp index f384cb3f6577..26837e803b4f 100644 --- a/components/eamxx/src/share/field/field.hpp +++ b/components/eamxx/src/share/field/field.hpp @@ -91,7 +91,7 @@ class Field { static constexpr int MaxRank = 6; // Constructor(s) - Field () = default; + Field (); explicit Field (const identifier_type& id, bool allocate = false); Field (const Field& src) = default; ~Field () = default; @@ -293,9 +293,7 @@ class Field { "Error! We should not setup contiguous helper field for a field " "when host and device share a memory space.\n"); - auto id = m_header->get_identifier(); - Field contig(id.alias(name()+std::string("_contiguous"))); - contig.allocate_view(); + auto contig = clone(name()+"_contiguous"); // Sanity check EKAT_REQUIRE_MSG(contig.get_header().get_alloc_properties().contiguous(), diff --git a/components/eamxx/src/share/field/field_alloc_prop.cpp b/components/eamxx/src/share/field/field_alloc_prop.cpp index 33a698da80c0..f391d338a006 100644 --- a/components/eamxx/src/share/field/field_alloc_prop.cpp +++ b/components/eamxx/src/share/field/field_alloc_prop.cpp @@ -169,6 +169,10 @@ void FieldAllocProp::commit (const layout_type& layout) } // Sanity checks: we must have requested at least one value type, and the identifier needs all dimensions set by now. + EKAT_REQUIRE_MSG(m_scalar_type_size>0, + "Error! Cannot commit allocation for a field with invalid (zero) scalar type size.\n" + " This likely means the field was created with DataType::Invalid.\n" + " Ensure the field is properly initialized with a valid DataType before allocating.\n"); EKAT_REQUIRE_MSG(m_value_type_sizes.size()>0, "Error! No value types requested for the allocation.\n"); EKAT_REQUIRE_MSG(layout.are_dimensions_set(), diff --git a/components/eamxx/src/share/field/field_get_view_impl.hpp b/components/eamxx/src/share/field/field_get_view_impl.hpp index 045ab99cdd73..1e8f5f222c72 100644 --- a/components/eamxx/src/share/field/field_get_view_impl.hpp +++ b/components/eamxx/src/share/field/field_get_view_impl.hpp @@ -37,7 +37,11 @@ auto Field::get_view () const // Check the reinterpret cast makes sense for the Dst value types (need integer sizes ratio) EKAT_REQUIRE_MSG(alloc_prop.template is_compatible(), - "Error! Source field allocation is not compatible with the requested value type.\n"); + "Error! Source field allocation is not compatible with the requested value type.\n" + " - field name: " + name() + "\n" + " - field data type: " + e2str(data_type()) + "\n" + " - alloc pack size: " + std::to_string(alloc_prop.get_largest_pack_size()) + "\n" + " - DstValueType: " + std::string(typeid(DstValueType).name()) + "\n"); // Start by reshaping into a ND view with all dynamic extents const auto view_ND = get_ND_view(); diff --git a/components/eamxx/src/share/field/field_header.cpp b/components/eamxx/src/share/field/field_header.cpp index daf50ae52e21..9d1104a3ba49 100644 --- a/components/eamxx/src/share/field/field_header.cpp +++ b/components/eamxx/src/share/field/field_header.cpp @@ -33,7 +33,7 @@ set_extra_data (const std::string& key, } std::shared_ptr FieldHeader::alias(const std::string& name) const { - auto fh = std::make_shared(get_identifier().alias(name)); + auto fh = std::make_shared(get_identifier().clone(name)); if (get_parent() != nullptr) { // If we're aliasing, we MUST keep track of the parent fh->create_parent_child_link(get_parent()); diff --git a/components/eamxx/src/share/field/field_identifier.cpp b/components/eamxx/src/share/field/field_identifier.cpp index f1fe26d0b2ec..8ed97c226ae0 100644 --- a/components/eamxx/src/share/field/field_identifier.cpp +++ b/components/eamxx/src/share/field/field_identifier.cpp @@ -32,13 +32,46 @@ FieldIdentifier (const std::string& name, FieldIdentifier FieldIdentifier:: -alias (const std::string& name) const +clone (const std::string& name) const { auto fid = *this; fid.m_name = name; + fid.update_identifier(); return fid; } +FieldIdentifier& FieldIdentifier:: +reset_layout (const FieldLayout& layout) +{ + m_layout = layout; + update_identifier(); + return *this; +} + +FieldIdentifier& FieldIdentifier:: +reset_units (const Units& units) +{ + m_units = units; + update_identifier(); + return *this; +} + +FieldIdentifier& FieldIdentifier:: +reset_grid (const std::string& grid) +{ + m_grid_name = grid; + update_identifier(); + return *this; +} + +FieldIdentifier& FieldIdentifier:: +reset_dtype (const DataType dtype) +{ + m_data_type = dtype; + update_identifier(); + return *this; +} + void FieldIdentifier::update_identifier () { // Create a verbose identifier string. m_identifier = m_name + "[" + m_grid_name + "] <" + e2str(m_data_type); diff --git a/components/eamxx/src/share/field/field_identifier.hpp b/components/eamxx/src/share/field/field_identifier.hpp index bd3c3803fdfd..44f3500d2518 100644 --- a/components/eamxx/src/share/field/field_identifier.hpp +++ b/components/eamxx/src/share/field/field_identifier.hpp @@ -71,7 +71,14 @@ class FieldIdentifier { DataType data_type () const { return m_data_type; } // Returns a copy of this identifier, but with a different name - FieldIdentifier alias (const std::string& name) const; + FieldIdentifier clone () const { return clone(m_name); } + FieldIdentifier clone (const std::string& name) const; + + // In-place modifiers + FieldIdentifier& reset_layout (const FieldLayout& layout); + FieldIdentifier& reset_units (const Units& units); + FieldIdentifier& reset_grid (const std::string& grid); + FieldIdentifier& reset_dtype (const DataType dtype); // The identifier string const std::string& get_id_string () const { return m_identifier; } diff --git a/components/eamxx/src/share/field/field_layout.hpp b/components/eamxx/src/share/field/field_layout.hpp index 38ce16202c2d..9b8d96889475 100644 --- a/components/eamxx/src/share/field/field_layout.hpp +++ b/components/eamxx/src/share/field/field_layout.hpp @@ -172,6 +172,7 @@ class FieldLayout { }; bool operator== (const FieldLayout& fl1, const FieldLayout& fl2); +inline bool operator!= (const FieldLayout& fl1, const FieldLayout& fl2) { return not (fl1==fl2); } // ========================== IMPLEMENTATION ======================= // diff --git a/components/eamxx/src/share/field/tests/field_tests.cpp b/components/eamxx/src/share/field/tests/field_tests.cpp index 1aee54c770d8..8f7621d9f7f0 100644 --- a/components/eamxx/src/share/field/tests/field_tests.cpp +++ b/components/eamxx/src/share/field/tests/field_tests.cpp @@ -260,6 +260,17 @@ TEST_CASE("field", "") { REQUIRE(v1(i) == v0()); } } + SECTION ("default ctor") { + // Default constructor should create a field with a valid header + Field f; + REQUIRE (f.get_header_ptr() != nullptr); + REQUIRE (f.name() == "UNSET"); + REQUIRE (f.get_header().get_identifier().get_grid_name() == "UNKNOWN"); + REQUIRE (f.data_type() == DataType::Invalid); + REQUIRE (not f.is_allocated()); + // Attempting to allocate should fail + REQUIRE_THROWS (f.allocate_view()); + } } } // anonymous namespace diff --git a/components/eamxx/src/share/field/tests/field_update_tests.cpp b/components/eamxx/src/share/field/tests/field_update_tests.cpp index 64e33e8f6dae..93380364ff56 100644 --- a/components/eamxx/src/share/field/tests/field_update_tests.cpp +++ b/components/eamxx/src/share/field/tests/field_update_tests.cpp @@ -75,7 +75,7 @@ TEST_CASE ("update") { } // Compute mask where f1>0 (should be all even cols) - auto mask = f_int.clone("mask"); + auto mask = f_int.clone("valid_mask"); compute_mask(f1,0,Comparison::GT,mask); // Set f3=1 where mask=1 diff --git a/components/eamxx/src/share/field/tests/subfield_tests.cpp b/components/eamxx/src/share/field/tests/subfield_tests.cpp index 5d6783a00c52..789350cd2629 100644 --- a/components/eamxx/src/share/field/tests/subfield_tests.cpp +++ b/components/eamxx/src/share/field/tests/subfield_tests.cpp @@ -41,6 +41,19 @@ TEST_CASE("field", "") { for (int k = 0; k < d1[3]; ++k) { REQUIRE(v4d_h(i, ivar, j, k) == v3d_h(i, j, k)); } + + // Check that valid_mask extra data is kept in sync + FieldIdentifier mfid("my_mask", {t1, d1}, m / s, "some_grid", DataType::IntType); + Field mask(mfid,true); + f1.get_header().set_extra_data("valid_mask",mask); + + auto f3 = f1.subfield(idim,ivar); + randomize_uniform(mask,seed++,0,1); + + REQUIRE (f3.get_header().has_extra_data("valid_mask")); + auto f3_mask = f3.get_header().get_extra_data("valid_mask"); + auto mask_sf = mask.subfield(idim,ivar); + REQUIRE (views_are_equal(f3_mask,mask_sf)); } SECTION("multi-sliced subfield") { diff --git a/components/eamxx/src/share/field/utils/compute_mask.cpp b/components/eamxx/src/share/field/utils/compute_mask.cpp index c335a0cdb3f9..4d64210affba 100644 --- a/components/eamxx/src/share/field/utils/compute_mask.cpp +++ b/components/eamxx/src/share/field/utils/compute_mask.cpp @@ -217,8 +217,9 @@ void compute_mask (const Field& f, const ScalarWrapper value, Comparison CMP, Fi Field compute_mask (const Field& x, const ScalarWrapper value, Comparison CMP) { const auto& fid_x = x.get_header().get_identifier(); - const auto nondim = ekat::units::Units::nondimensional(); - FieldIdentifier fid(x.name()+"_mask",fid_x.get_layout(),nondim,fid_x.get_grid_name(),DataType::IntType); + auto fid = fid_x.clone(x.name()+"_mask") + .reset_units(ekat::units::Units::nondimensional()) + .reset_dtype(DataType::IntType); Field mask(fid,true); compute_mask(x,value,CMP,mask); diff --git a/components/eamxx/src/share/field/utils/transpose.cpp b/components/eamxx/src/share/field/utils/transpose.cpp index 59728f68448e..0de1af2b9beb 100644 --- a/components/eamxx/src/share/field/utils/transpose.cpp +++ b/components/eamxx/src/share/field/utils/transpose.cpp @@ -73,12 +73,17 @@ void transpose (const Field& src, Field& tgt) " - src field layout: " + src_id.get_layout().to_string() + "\n" " - tgt field layout: " + tgt_id.get_layout().to_string() + "\n"); - EKAT_REQUIRE_MSG (src_id.get_units()==tgt_id.get_units(), - "Error! Input transpose field units are incompatible with src field.\n" - " - src field name: " + src.name() + "\n" - " - tgt field name: " + tgt.name() + "\n" - " - src field units: " + src_id.get_units().get_si_string() + "\n" - " - tgt field units: " + tgt_id.get_units().get_si_string() + "\n"); + // Check that the source and target field have the same units. We skip this check if + // the units for either field are INVALID + using namespace ekat::units; + if (src_id.get_units() != Units::invalid() and tgt_id.get_units() != Units::invalid()) { + EKAT_REQUIRE_MSG (src_id.get_units()==tgt_id.get_units(), + "Error! Input transpose field units are incompatible with src field.\n" + " - src field name: " + src.name() + "\n" + " - tgt field name: " + tgt.name() + "\n" + " - src field units: " + src_id.get_units().get_si_string() + "\n" + " - tgt field units: " + tgt_id.get_units().get_si_string() + "\n"); + } EKAT_REQUIRE_MSG (src_id.data_type()==tgt_id.data_type(), "Error! Input transpose field data type is incompatible with src field.\n" @@ -113,9 +118,7 @@ void transpose (const Field& src, Field& tgt) Field transpose (const Field& src) { const auto& src_id = src.get_header().get_identifier(); - FieldIdentifier id(src_id.name()+"_transpose", src_id.get_layout().transpose(), - src_id.get_units(), src_id.get_grid_name(), - src_id.data_type()); + auto id = src_id.clone(src_id.name()+"_transpose").reset_layout(src_id.get_layout().transpose()); Field ft (id,true); transpose(src,ft); return ft; diff --git a/components/eamxx/src/share/io/scorpio_output.cpp b/components/eamxx/src/share/io/scorpio_output.cpp index 65de463f75e0..2ac9aaef1360 100644 --- a/components/eamxx/src/share/io/scorpio_output.cpp +++ b/components/eamxx/src/share/io/scorpio_output.cpp @@ -37,6 +37,13 @@ transfer_extra_data(const scream::Field &src, scream::Field &tgt) } }; +// Helper function to get the name of a transposed helper field from a layout and data type +std::string +get_transposed_helper_name(const scream::FieldLayout& layout, const scream::DataType data_type) +{ + return "transposed_" + layout.transpose().to_string() + "_" + e2str(data_type); +} + // Note: this is also declared in eamxx_scorpio_interface.cpp. Move it somewhere else? template std::string @@ -100,6 +107,11 @@ AtmosphereOutput::AtmosphereOutput(const ekat::Comm &comm, const ekat::Parameter // the name of the yaml file where the options are read from. m_stream_name = params.name(); + // Is this output set to be transposed? + if (params.isParameter("transpose")) { + m_transpose = params.get("transpose"); + } + auto gm = field_mgr->get_grids_manager(); // Figure out what kind of averaging is requested @@ -346,7 +358,27 @@ void AtmosphereOutput::init() // Store the field layout, so that calls to setup_output_file are easier const auto& layout = fid.get_layout(); - m_vars_dims[fname] = get_var_dimnames(layout); + m_vars_dims[fname] = get_var_dimnames(m_transpose ? layout.transpose() : layout); + + // Initialize a helper_field for each unique layout. This can be used for operations + // such as writing transposed output. + if (m_transpose) { + const auto helper_layout = layout.transpose(); + const auto data_type = fid.data_type(); + // Note: helper name is based on the ORIGINAL layout (not transposed) and data type, so that + // when we look up the helper during write, we use the field's original layout and data type + const std::string helper_name = get_transposed_helper_name(layout, data_type); + if (m_helper_fields.find(helper_name) == m_helper_fields.end()) { + // We can add a new helper field for this layout and data type + // Use Units::invalid() since this helper is reused for fields with same layout but different units + using namespace ekat::units; + FieldIdentifier fid_helper(helper_name,helper_layout,Units::invalid(),fid.get_grid_name(),data_type); + Field helper(fid_helper); + helper.get_header().get_alloc_properties().request_allocation(); + helper.allocate_view(); + m_helper_fields[helper_name] = helper; + } + } // Now check that all the dims of this field are already set to be registered. const auto& tags = layout.tags(); @@ -395,7 +427,7 @@ void AtmosphereOutput::init() if (m_track_avg_cnt) { // Create and store a Field to track the averaging count for this layout - set_avg_cnt_tracking(fname,layout); + set_avg_cnt_tracking(fid); } } @@ -500,7 +532,7 @@ run (const std::string& filename, // if we later need it. E.g, if no AvgCount AND no hremap, we don't need it. ////////////////////////////////////////////////////// auto field = fm_after_hr->get_field(fname); - auto mask = count.get_header().get_extra_data("mask"); + auto mask = count.get_header().get_extra_data("valid_mask"); // Find where the field is NOT equal to fill_value compute_mask(field,constants::fill_value,Comparison::NE,mask); @@ -514,7 +546,18 @@ run (const std::string& filename, count.sync_to_host(); auto func_start = std::chrono::steady_clock::now(); - scorpio::write_var(filename,count.name(),count.get_internal_view_data()); + if (m_transpose) { + const auto& id = count.get_header().get_identifier(); + const auto& layout = id.get_layout(); + const auto data_type = id.data_type(); + const std::string helper_name = get_transposed_helper_name(layout, data_type); + auto& temp = m_helper_fields.at(helper_name); + transpose(count,temp); + temp.sync_to_host(); + scorpio::write_var(filename,count.name(),temp.get_internal_view_data()); + } else { + scorpio::write_var(filename,count.name(),count.get_internal_view_data()); + } auto func_finish = std::chrono::steady_clock::now(); auto duration_loc = std::chrono::duration_cast(func_finish - func_start); duration_write += duration_loc.count(); @@ -579,7 +622,7 @@ run (const std::string& filename, f_out.scale_inv(avg_count); - const auto& mask = avg_count.get_header().get_extra_data("mask"); + const auto& mask = avg_count.get_header().get_extra_data("valid_mask"); f_out.deep_copy(constants::fill_value,mask); } else { // Divide by steps count only when the summation is complete @@ -587,12 +630,22 @@ run (const std::string& filename, } } - // Bring data to host - f_out.sync_to_host(); - // Write to file auto func_start = std::chrono::steady_clock::now(); - scorpio::write_var(filename,field_name,f_out.get_internal_view_data()); + if (m_transpose) { + const auto& id = f_out.get_header().get_identifier(); + const auto& layout = id.get_layout(); + const auto data_type = id.data_type(); + const std::string helper_name = get_transposed_helper_name(layout, data_type); + auto& temp = m_helper_fields.at(helper_name); + transpose(f_out,temp); + temp.sync_to_host(); + scorpio::write_var(filename,field_name,temp.get_internal_view_data()); + } else { + // Bring data to host (only needed for non-transposed output) + f_out.sync_to_host(); + scorpio::write_var(filename,field_name,f_out.get_internal_view_data()); + } auto func_finish = std::chrono::steady_clock::now(); auto duration_loc = std::chrono::duration_cast(func_finish - func_start); duration_write += duration_loc.count(); @@ -640,9 +693,11 @@ res_dep_memory_footprint () const return rdmf; } -void AtmosphereOutput::set_avg_cnt_tracking(const std::string& name, const FieldLayout& layout) +void AtmosphereOutput::set_avg_cnt_tracking(const FieldIdentifier& fid) { // Now create a Field to track the averaging count for this layout + const std::string& name = fid.name(); + const auto& layout = fid.get_layout(); const auto& avg_cnt_suffix = m_field_to_avg_cnt_suffix[name]; const auto tags = layout.tags(); std::string avg_cnt_name = "avg_count" + avg_cnt_suffix; @@ -670,16 +725,17 @@ void AtmosphereOutput::set_avg_cnt_tracking(const std::string& name, const Field } // We have not created this avg count field yet. - m_vars_dims[avg_cnt_name] = get_var_dimnames(layout); + m_vars_dims[avg_cnt_name] = get_var_dimnames(m_transpose ? layout.transpose() : layout); auto nondim = ekat::units::Units::nondimensional(); - FieldIdentifier count_id (avg_cnt_name,layout,nondim,m_io_grid->name(),DataType::IntType); + auto count_id = fid.clone(avg_cnt_name).reset_units(nondim).reset_dtype(DataType::IntType); Field count(count_id); count.allocate_view(); - // We will use a helper field for updating cnt, so store it inside the field header - auto mask = count.clone(count.name()+"_mask"); - count.get_header().set_extra_data("mask",mask); + // We will use a helper field for updating cnt, so store it inside the field header. + // Create the valid_mask explicitly as an IntType field with the same layout/grid. + Field mask(count_id.clone(count.name()+"_mask"),true); + count.get_header().set_extra_data("valid_mask",mask); m_avg_counts.push_back(count); m_field_to_avg_count[name] = count; @@ -840,6 +896,11 @@ register_variables(const std::string& filename, if (fid.get_layout().has_tag(COL)) { scorpio::set_attribute(filename, field_name, "coordinates", "lat lon"); } + + // If this is transposed output, mark the variable + if (m_transpose) { + scorpio::set_attribute(filename, field_name, "transposed_output", "true"); + } } } @@ -1018,12 +1079,12 @@ process_requested_fields() // Helper lambda to check if this fm_model field should trigger avg count auto check_for_avg_cnt = [&](const Field& f) { // We need avg-count tracking for any averaged (non-instant) field that: - // - supplies explicit mask info (mask_data or mask_field), OR + // - supplies explicit mask info (mask_data or valid_mask) // - is marked as potentially containing fill values (may_be_filled()). // Without this, fill-aware updates skip fill_value during accumulation (good) // but we would still divide by the raw nsteps, biasing the result low. if (m_avg_type!=OutputAvgType::Instant) { - const bool has_mask = f.get_header().has_extra_data("mask_data") || f.get_header().has_extra_data("mask_field"); + const bool has_mask = f.get_header().has_extra_data("mask_data") || f.get_header().has_extra_data("valid_mask"); const bool may_be_filled = f.get_header().may_be_filled(); if (has_mask || may_be_filled) { m_track_avg_cnt = true; diff --git a/components/eamxx/src/share/io/scorpio_output.hpp b/components/eamxx/src/share/io/scorpio_output.hpp index f91eb8b046e7..8c886037b20d 100644 --- a/components/eamxx/src/share/io/scorpio_output.hpp +++ b/components/eamxx/src/share/io/scorpio_output.hpp @@ -30,6 +30,7 @@ * ------ * filename_prefix: STRING * averaging_type: STRING + * transpose: BOOL (default: false) * max_snapshots_per_file: INT (default: 1) * fields: * GRID_NAME_1: @@ -57,8 +58,11 @@ * Here, 'time interval' is described by ${Output frequency} and ${Output frequency_units}. * E.g., with 'Output frequency'=10 and 'Output frequency_units'="Days", the time interval is 10 days. + * - transpose: optional boolean flag to enable transposed output (default: false). + * When set to true, all field dimensions will be reversed in the output file. + * For example, a field with layout (ncol, nlev) will be written as (nlev, ncol). * - fields: parameters specifying fields to output - * - GRID_NAME: parameters specifyign fields to output from grid $GRID_NAME + * - GRID_NAME: parameters specifying fields to output from grid $GRID_NAME * - field_names: names of fields defined on grid $grid_name that need to be outputed * - output_data_layout: attempt to 'remap' fields to this data layout first. * This option is mostly used to enable dyn->phys_gll remap (to save storage), @@ -168,10 +172,11 @@ class AtmosphereOutput strvec_t get_var_dimnames(const FieldLayout &layout) const; // Tracking the averaging of any filled values: - void set_avg_cnt_tracking(const std::string &name, const FieldLayout &layout); + void set_avg_cnt_tracking(const FieldIdentifier& fid); // --- Internal variables --- // ekat::Comm m_comm; + bool m_transpose = false; // We store separate shared pointers for field mgrs at different stages of IO: // More specifically, the order of operations is as follows: @@ -201,6 +206,7 @@ class AtmosphereOutput Scorpio // Output fields to pass to scorpio (may differ from the above in case of packing) }; std::map> m_field_mgrs; + std::map m_helper_fields; std::shared_ptr m_io_grid; std::shared_ptr m_horiz_remapper; @@ -229,7 +235,7 @@ class AtmosphereOutput DefaultMetadata m_default_metadata; bool m_add_time_dim; - bool m_track_avg_cnt = false; + bool m_track_avg_cnt = false; bool m_latlon_output = false; std::string m_decomp_dimname = ""; diff --git a/components/eamxx/src/share/io/tests/CMakeLists.txt b/components/eamxx/src/share/io/tests/CMakeLists.txt index f154a619d546..3ec9b315ce3c 100644 --- a/components/eamxx/src/share/io/tests/CMakeLists.txt +++ b/components/eamxx/src/share/io/tests/CMakeLists.txt @@ -54,6 +54,41 @@ if (NOT SCREAM_ONLY_GENERATE_BASELINES) MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS} ) + ## Test transposed output (all averaging types) + CreateUnitTest(io_transpose "io_transpose.cpp" + LIBS eamxx_io LABELS io + MPI_RANKS 1 ${SCREAM_TEST_MAX_RANKS} + FIXTURES_SETUP_INDIVIDUAL io_transpose_output + ) + + # For each averaging type, compare transposed vs non-transposed output + # to verify they contain the same data (just with dimensions reversed) + set(AVG_TYPES INSTANT MAX MIN AVERAGE) + foreach (AVG_TYPE IN LISTS AVG_TYPES) + foreach (MPI_RANKS RANGE 1 ${SCREAM_TEST_MAX_RANKS}) + set(prefix_N "io_transpose_N") + set(prefix_T "io_transpose_T") + set(suffix "${AVG_TYPE}.nsteps_x3.np${MPI_RANKS}.2023-02-17-00000.nc") + set(file_N "${prefix_N}.${suffix}") + set(file_T "${prefix_T}.${suffix}") + + # Add test to compare files using compare-nc-files script + # The script should handle transposed dimensions automatically with -a flag + add_test( + NAME io_transpose_check_${AVG_TYPE}_np${MPI_RANKS} + COMMAND ${SCREAM_BASE_DIR}/scripts/compare-nc-files + -s ${file_N} -t ${file_T} + -c "f_0=f_0" "f_1=f_1" "f_2=f_2" + -a + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties(io_transpose_check_${AVG_TYPE}_np${MPI_RANKS} PROPERTIES + LABELS "io" + FIXTURES_REQUIRED io_transpose_output_np${MPI_RANKS}_omp1 + ) + endforeach() + endforeach() + # Test output on SE grid CreateUnitTest(io_se_grid "io_se_grid.cpp" LIBS eamxx_io LABELS io diff --git a/components/eamxx/src/share/io/tests/io_transpose.cpp b/components/eamxx/src/share/io/tests/io_transpose.cpp new file mode 100644 index 000000000000..159717f32ddf --- /dev/null +++ b/components/eamxx/src/share/io/tests/io_transpose.cpp @@ -0,0 +1,264 @@ +#include + +#include "share/io/eamxx_output_manager.hpp" +#include "share/io/scorpio_input.hpp" + +#include "share/data_managers/mesh_free_grids_manager.hpp" + +#include "share/field/field_utils.hpp" +#include "share/field/field.hpp" +#include "share/data_managers/field_manager.hpp" + +#include "share/util/eamxx_universal_constants.hpp" +#include "share/core/eamxx_setup_random_test.hpp" +#include "share/util/eamxx_time_stamp.hpp" +#include "share/core/eamxx_types.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace scream { + +constexpr int num_output_steps = 3; + +void add (const Field& f, const double v) { + auto data = f.get_internal_view_data(); + auto nscalars = f.get_header().get_alloc_properties().get_num_scalars(); + for (int i=0; i +get_gm (const ekat::Comm& comm) +{ + const int ngcols = std::max(comm.size(),1); + const int nlevs = 4; + auto gm = create_mesh_free_grids_manager(comm,0,0,nlevs,ngcols); + gm->build_grids(); + return gm; +} + +std::shared_ptr +get_fm (const std::shared_ptr& grid, + const util::TimeStamp& t0, int seed) +{ + using FL = FieldLayout; + using FID = FieldIdentifier; + using namespace ShortFieldTagsNames; + + // Note: we use a discrete set of random values, so we can + // check answers without risk of non-bfb diffs due to ops order + std::vector values; + for (int i=0; i<=100; ++i) + values.push_back(static_cast(i)); + + const int nlcols = grid->get_num_local_dofs(); + const int nlevs = grid->get_num_vertical_levels(); + + std::vector layouts = + { + FL({COL }, {nlcols }), + FL({COL, LEV}, {nlcols, nlevs}), + FL({COL,CMP,ILEV}, {nlcols,2,nlevs+1}) + }; + + auto fm = std::make_shared(grid); + + const auto units = ekat::units::Units::nondimensional(); + int count=0; + using stratts_t = std::map; + for (const auto& fl : layouts) { + FID fid("f_"+std::to_string(count),fl,units,grid->name()); + Field f(fid); + f.allocate_view(); + auto& str_atts = f.get_header().get_extra_data("io: string attributes"); + str_atts["test"] = f.name(); + randomize_discrete (f,seed++,values); + f.get_header().get_tracking().update_time_stamp(t0); + fm->add_field(f); + ++count; + } + + return fm; +} + +// Write output with or without transpose +void write (const std::string& avg_type, const std::string& freq_units, + const int freq, const int seed, const ekat::Comm& comm, + const bool transpose) +{ + // Create grid + auto gm = get_gm(comm); + auto grid = gm->get_grid("point_grid"); + + // Time advance parameters + auto t0 = get_t0(); + const int dt = get_dt(freq_units); + + // Create some fields + auto fm = get_fm(grid,t0,seed); + std::vector fnames; + for (auto it : fm->get_repo()) { + fnames.push_back(it.second->name()); + } + + // Create output params + ekat::ParameterList om_pl; + std::string prefix = transpose ? "io_transpose_T" : "io_transpose_N"; + om_pl.set("filename_prefix", prefix); + om_pl.set("field_names",fnames); + om_pl.set("averaging_type", avg_type); + if (transpose) { + om_pl.set("transpose", true); // Enable transposed output + } + auto& ctrl_pl = om_pl.sublist("output_control"); + ctrl_pl.set("frequency_units",freq_units); + ctrl_pl.set("frequency",freq); + ctrl_pl.set("save_grid_data",false); + + int max_snaps = num_output_steps; + if (avg_type=="INSTANT") { + ++max_snaps; + } + om_pl.set("max_snapshots_per_file", max_snaps); + + // Create Output manager + OutputManager om; + om_pl.set("floating_point_precision",std::string("single")); + om.initialize(comm,om_pl,t0,false); + om.setup(fm,gm->get_grid_names()); + + // Time loop + const int nsteps = num_output_steps*freq; + auto t = t0; + for (int n=0; nget_field(name); + add(f,1.0); + } + + om.run (t); + } + + // Check that the file was closed + const auto& file_specs = om.output_file_specs(); + REQUIRE (not file_specs.is_open); + + om.finalize(); +} + +// Verify transposed data matches non-transposed data +void verify_transpose(const std::string& avg_type, const ekat::Comm& comm) { + using namespace scorpio; + + const int freq = 3; + std::string prefix_N = "io_transpose_N"; + std::string prefix_T = "io_transpose_T"; + std::string suffix = avg_type + ".nsteps_x3.np" + std::to_string(comm.size()) + ".2023-02-17-00000.nc"; + std::string file_N = prefix_N + "." + suffix; + std::string file_T = prefix_T + "." + suffix; + + // Verify the dimensions match between normal and transposed files + register_file(file_N, Read); + int ncol_N = get_dimlen(file_N, "ncol"); + int lev_N = get_dimlen(file_N, "lev"); + int time_N = get_dimlen(file_N, "time"); + release_file(file_N); + + register_file(file_T, Read); + int ncol_T = get_dimlen(file_T, "ncol"); + int lev_T = get_dimlen(file_T, "lev"); + int time_T = get_dimlen(file_T, "time"); + release_file(file_T); + + // Dimensions should be the same size (just reordered in variables) + REQUIRE(ncol_N == ncol_T); + REQUIRE(lev_N == lev_T); + REQUIRE(time_N == time_T); + + // Note: Detailed data validation is performed by the compare-nc-files + // script (called in CMakeLists.txt test), which uses xarray to verify + // that data_N[col,lev] == data_T[lev,col] at every position. + // This provides comprehensive validation using Python NetCDF tools + // which can properly handle dimension reordering. +} + +TEST_CASE ("io_transpose") { + std::vector avg_type = { + "INSTANT", + "MAX", + "MIN", + "AVERAGE" + }; + + ekat::Comm comm(MPI_COMM_WORLD); + scorpio::init_subsystem(comm); + + auto seed = get_random_test_seed(&comm); + + const int freq = 3; + const std::string freq_units = "nsteps"; + + auto print = [&] (const std::string& s, int line_len = -1) { + if (comm.am_i_root()) { + if (line_len<0) { + std::cout << s; + } else { + std::cout << std::left << std::setw(line_len) << std::setfill('.') << s; + } + } + }; + + print ("Testing transposed output\n"); + for (const auto& avg : avg_type) { + print(" -> Averaging type: " + avg + " ", 40); + + // Write both transposed and non-transposed versions + write(avg,freq_units,freq,seed,comm,false); // Non-transposed + write(avg,freq_units,freq,seed,comm,true); // Transposed + + // Verify the transposed data is correct + verify_transpose(avg, comm); + + print(" PASS\n"); + } + + scorpio::finalize_subsystem(); +} + +} // namespace scream diff --git a/components/eamxx/src/share/physics/physics_share.cpp b/components/eamxx/src/share/physics/physics_share.cpp index b3d181fc7610..327190d7583d 100644 --- a/components/eamxx/src/share/physics/physics_share.cpp +++ b/components/eamxx/src/share/physics/physics_share.cpp @@ -49,6 +49,7 @@ static Scalar wrap_name(Scalar input) { \ cuda_wrap_single_arg(expm1, std::expm1) cuda_wrap_single_arg(tanh, std::tanh) cuda_wrap_single_arg(cos, std::cos) + cuda_wrap_single_arg(sin, std::sin) cuda_wrap_single_arg(erf, std::erf) #undef cuda_wrap_single_arg @@ -146,6 +147,15 @@ Real scream_cos(Real input) #endif } +Real scream_sin(Real input) +{ +#ifdef EAMXX_ENABLE_GPU + return CudaWrap::sin(input); +#else + return std::sin(input); +#endif +} + Real scream_erf(Real input) { #ifdef EAMXX_ENABLE_GPU diff --git a/components/eamxx/src/share/physics/physics_share.hpp b/components/eamxx/src/share/physics/physics_share.hpp index dc4f9fa062af..0281dc47f96a 100644 --- a/components/eamxx/src/share/physics/physics_share.hpp +++ b/components/eamxx/src/share/physics/physics_share.hpp @@ -17,6 +17,7 @@ Real scream_exp(Real input); Real scream_expm1(Real input); Real scream_tanh(Real input); Real scream_cos(Real input); +Real scream_sin(Real input); Real scream_erf(Real input); } diff --git a/components/eamxx/src/share/physics/physics_share_f2c.F90 b/components/eamxx/src/share/physics/physics_share_f2c.F90 index c29e236a1f88..663a835cd69b 100644 --- a/components/eamxx/src/share/physics/physics_share_f2c.F90 +++ b/components/eamxx/src/share/physics/physics_share_f2c.F90 @@ -122,6 +122,16 @@ pure function scream_cos(input) bind(C) real(kind=c_real) :: scream_cos end function scream_cos + pure function scream_sin(input) bind(C) + import :: c_real + + !arguments: + real(kind=c_real), value, intent(in) :: input + + ! return + real(kind=c_real) :: scream_sin + end function scream_sin + pure function scream_erf(input) bind(C) import :: c_real diff --git a/components/eamxx/src/share/remap/abstract_remapper.cpp b/components/eamxx/src/share/remap/abstract_remapper.cpp index 0cfa10ea225b..09229b8a756f 100644 --- a/components/eamxx/src/share/remap/abstract_remapper.cpp +++ b/components/eamxx/src/share/remap/abstract_remapper.cpp @@ -159,20 +159,14 @@ const Field& AbstractRemapper::get_tgt_field (const int i) const FieldIdentifier AbstractRemapper::create_src_fid (const FieldIdentifier& tgt_fid) const { - const auto& name = tgt_fid.name(); const auto& layout = create_src_layout(tgt_fid.get_layout()); - const auto& units = tgt_fid.get_units(); - - return FieldIdentifier(name,layout,units,m_src_grid->name()); + return tgt_fid.clone().reset_layout(layout).reset_grid(m_src_grid->name()); } FieldIdentifier AbstractRemapper::create_tgt_fid (const FieldIdentifier& src_fid) const { - const auto& name = src_fid.name(); const auto& layout = create_tgt_layout(src_fid.get_layout()); - const auto& units = src_fid.get_units(); - - return FieldIdentifier(name,layout,units,m_tgt_grid->name()); + return src_fid.clone().reset_layout(layout).reset_grid(m_tgt_grid->name()); } FieldLayout AbstractRemapper:: diff --git a/components/eamxx/src/share/remap/horizontal_remapper.cpp b/components/eamxx/src/share/remap/horizontal_remapper.cpp index ec0944633a99..fe24107cb0fb 100644 --- a/components/eamxx/src/share/remap/horizontal_remapper.cpp +++ b/components/eamxx/src/share/remap/horizontal_remapper.cpp @@ -96,79 +96,85 @@ HorizontalRemapper:: void HorizontalRemapper:: registration_ends_impl () { + using namespace ShortFieldTagsNames; + if (m_track_mask) { // We store masks (src-tgt) here, and register them AFTER we parse all currently registered fields. // That makes the for loop below easier, since we can take references without worrring that they // would get invalidated. In fact, if you call register_field inside the loop, the src/tgt fields // vectors will grow, which may cause reallocation and references invalidation - std::vector> masks; - - auto get_mask_idx = [&](const FieldIdentifier& src_mask_fid) { - // Masks will be registered AFTER all fields, so the 1st mask will - // be right after the last registered "regular" field. - int idx = 0; - for (const auto& it : masks) { - if (it.first.get_header().get_identifier()==src_mask_fid) { - return idx; - } - ++idx; - } - return -1; - }; + // NOTE: there are quite a few "mask" fields here, so let's recap: + // - src/tgt_mask: these are the int-valued fields attached to src/tgt field + // - src/tgt_mask_real: these are the real-valued fields that we remap, to correctly rescale tgt fields later - for (int i=0; i("mask_field"); - - // Make sure fields representing masks are not themselves meant to be masked. - EKAT_REQUIRE_MSG(not src_mask.get_header().has_extra_data("mask_field"), - "Error! A mask field cannot be itself masked.\n" - " - field name: " + src.name() + "\n" - " - mask field name: " + src_mask.name() + "\n"); + const auto& f_name = src_hdr.get_identifier().name(); + const auto& src_mask = src_hdr.get_extra_data("valid_mask"); // Check that the mask field has the correct layout - const auto& f_lt = src.get_header().get_identifier().get_layout(); + const auto& f_lt = src_hdr.get_identifier().get_layout(); const auto& m_lt = src_mask.get_header().get_identifier().get_layout(); - using namespace ShortFieldTagsNames; - EKAT_REQUIRE_MSG(f_lt.has_tag(COL) == m_lt.has_tag(COL), + EKAT_REQUIRE_MSG(f_lt == m_lt, "Error! Incompatible field and mask layouts.\n" - " - field name: " + src.name() + "\n" - " - field layout: " + f_lt.to_string() + "\n" - " - mask layout: " + m_lt.to_string() + "\n"); - EKAT_REQUIRE_MSG(f_lt.has_tag(LEV) == m_lt.has_tag(LEV), - "Error! Incompatible field and mask layouts.\n" - " - field name: " + src.name() + "\n" + " - field name: " + f_name + "\n" " - field layout: " + f_lt.to_string() + "\n" " - mask layout: " + m_lt.to_string() + "\n"); - // If it's the first time we find this mask, store it, so we can register later - const auto& src_mask_fid = src_mask.get_header().get_identifier(); - int mask_idx = get_mask_idx(src_mask_fid); - if (mask_idx==-1) { - Field tgt_mask(create_tgt_fid(src_mask_fid)); - auto src_pack_size = src_mask.get_header().get_alloc_properties().get_largest_pack_size(); - tgt_mask.get_header().get_alloc_properties().request_allocation(src_pack_size); - tgt_mask.allocate_view(); - - masks.push_back(std::make_pair(src_mask,tgt_mask)); - mask_idx = masks.size()-1; + if (not m_lt.has_tag(COL)) { + // This field doesn't really need to be remapped, so tgt can use the same mask field as src + tgt_hdr.set_extra_data("valid_mask",src_mask); + continue; } - tgt.get_header().set_extra_data("mask_field",masks[mask_idx].second); - } - // Add all masks to the fields to remap - for (const auto& it : masks) { - register_field(it.first,it.second); + const auto& mask_name = src_mask.name(); + + if (m_name_to_tgt_int_mask.count(mask_name)==1) { + // There was another src field with the same src mask, which was already registerred. Recycle it. + const auto& tgt_mask = m_name_to_tgt_int_mask.at(mask_name); + tgt_hdr.set_extra_data("valid_mask",tgt_mask); + continue; + } + + m_name_to_src_int_mask[src_mask.name()] = src_mask; + + // Make sure fields representing masks are not themselves meant to be masked. + EKAT_REQUIRE_MSG(not src_mask.get_header().has_extra_data("valid_mask"), + "Error! A mask field cannot be itself masked.\n" + " - field name: " + f_name + "\n" + " - mask field name: " + src_mask.name() + "\n"); + + auto ps = src_hdr.get_alloc_properties().get_largest_pack_size(); + + // Create the real-valued mask field, to use during remap + const auto& src_fid = src_mask.get_header().get_identifier(); + auto src_fid_r = src_fid.clone(src_fid.name()+"_real").reset_dtype(DataType::RealType); + Field src_mask_real(src_fid_r); + src_mask_real.get_header().get_alloc_properties().request_allocation(ps); + src_mask_real.allocate_view(); + m_name_to_src_real_mask[mask_name] = src_mask_real; + + // Create the int-valued tgt mask from the newly created real-valued tgt mask + auto tgt_mask_real = register_field_from_src(src_mask_real); + m_name_to_tgt_real_mask[mask_name] = tgt_mask_real; + + auto tgt_fid = create_tgt_fid(src_fid); + Field tgt_mask(tgt_fid); + tgt_mask.get_header().get_alloc_properties().request_allocation(ps); + tgt_mask.allocate_view(); + tgt_hdr.set_extra_data("valid_mask",tgt_mask); + + m_name_to_tgt_int_mask[mask_name] = tgt_mask; } } - using namespace ShortFieldTagsNames; - m_needs_remap.resize(m_num_fields,1); for (int i=0; im_overlap_grid->get_num_local_dofs(); const auto ov_gn = m_remap_data->m_overlap_grid->name(); - const auto dt = DataType::RealType; for (int i=0; im_coarsening ? m_src_fields[i] : m_tgt_fields[i]; const auto& fid = f.get_header().get_identifier(); @@ -215,7 +220,7 @@ void HorizontalRemapper::create_ov_fields () } const auto layout = fid.get_layout().clone().reset_dim(0,num_ov_gids); - FieldIdentifier ov_fid (fid.name(),layout,fid.get_units(),ov_gn,dt); + auto ov_fid = fid.clone().reset_layout(layout).reset_grid(ov_gn); auto& ov_f = m_ov_fields.emplace_back(ov_fid); @@ -244,9 +249,17 @@ void HorizontalRemapper::remap_fwd_impl () // Helper function, to establish if a field can be handled with packs auto can_pack_field = [](const Field& f) { const auto& ap = f.get_header().get_alloc_properties(); - return (ap.get_last_extent() % SCREAM_PACK_SIZE) == 0; + return ap.get_largest_pack_size() >= SCREAM_PACK_SIZE; }; + if (m_track_mask) { + // Before any MPI or mat-vec, ensure real-valued mask matches the int-valued mask + for (const auto& [name, int_mask] : m_name_to_src_int_mask) { + auto real_mask = m_name_to_src_real_mask.at(name); + real_mask.deep_copy(int_mask); + } + } + bool coarsen = m_remap_data->m_coarsening; if (not coarsen) { @@ -266,16 +279,13 @@ void HorizontalRemapper::remap_fwd_impl () const auto& x = coarsen ? m_src_fields[i] : m_ov_fields[i]; const auto& y = coarsen ? m_ov_fields[i] : m_tgt_fields[i]; - const bool masked = m_track_mask and x.get_header().has_extra_data("mask_field"); + const bool masked = m_track_mask and x.get_header().has_extra_data("valid_mask"); if (masked) { - // Pass the mask to the local_mat_vec routine - const auto& mask = x.get_header().get_extra_data("mask_field"); - // If possible, dispatch kernel with SCREAM_PACK_SIZE - if (can_pack_field(x) and can_pack_field(y) and can_pack_field(mask)) { - local_mat_vec(x,y,mask); + if (can_pack_field(x) and can_pack_field(y)) { + local_mat_vec_masked(x,y); } else { - local_mat_vec<1>(x,y,mask); + local_mat_vec_masked<1>(x,y); } } else { // If possible, dispatch kernel with SCREAM_PACK_SIZE @@ -301,17 +311,20 @@ void HorizontalRemapper::remap_fwd_impl () " - send rank: " + std::to_string(comm.rank()) + "\n"); } - // Rescale any fields that had the mask applied. + // Rescale any fields that had the mask applied, and compute tgt field mask by comparing tgt_mask_real against threshold if (m_track_mask) { for (int i=0; i("mask_field"); - if (can_pack_field(f_tgt) and can_pack_field(mask)) { - rescale_masked_fields(f_tgt,mask); + auto& f_tgt = m_tgt_fields[i]; + if (f_tgt.get_header().has_extra_data("valid_mask")) { + const auto& mask_name = f_tgt.get_header().get_extra_data("valid_mask").name(); + const auto& real_mask = m_name_to_tgt_real_mask.at(mask_name); + // TODO: + // 1. compute mask=real_mask>thresh via compute_mask + // 2. replace rescale_masked_fields with f_tgt.scale_inv(real_mask,mask) once it's available + if (can_pack_field(f_tgt) and can_pack_field(real_mask)) { + rescale_masked_fields(f_tgt,real_mask); } else { - rescale_masked_fields<1>(f_tgt,mask); + rescale_masked_fields<1>(f_tgt,real_mask); } } } @@ -446,7 +459,7 @@ local_mat_vec (const Field& x, const Field& y) const template void HorizontalRemapper:: -rescale_masked_fields (const Field& x, const Field& mask) const +rescale_masked_fields (const Field& x, const Field& real_mask) const { if (m_timers_enabled) start_timer(name()+" rescale"); @@ -455,6 +468,7 @@ rescale_masked_fields (const Field& x, const Field& mask) const using MemberType = typename KT::MemberType; using TPF = ekat::TeamPolicyFactory; using Pack = ekat::Pack; + using Mask = ekat::Mask; using PackInfo = ekat::PackInfo; constexpr auto fill_val = constants::fill_value; @@ -464,18 +478,22 @@ rescale_masked_fields (const Field& x, const Field& mask) const const int ncols = m_tgt_grid->get_num_local_dofs(); const Real mask_threshold = std::numeric_limits::epsilon(); // TODO: Should we not hardcode the threshold for simply masking out the column. + Pack fv_pack(fill_val); + auto& mask = x.get_header().get_extra_data("valid_mask"); switch (rank) { case 1: { // Unlike get_view, get_strided_view returns a LayoutStride view, // therefore allowing the 1d field to be a subfield of a 2d field // along the 2nd dimension. - auto x_view = x.get_strided_view< Real*>(); - auto m_view = mask.get_strided_view(); + auto x_view = x.get_strided_view(); + auto mr_view = real_mask.get_view(); + auto m_view = mask.get_view(); Kokkos::parallel_for(RangePolicy(0,ncols), KOKKOS_LAMBDA(const int& icol) { - if (m_view(icol)>mask_threshold) { - x_view(icol) /= m_view(icol); + m_view(icol) = mr_view(icol)>mask_threshold; + if (m_view(icol)) { + x_view(icol) /= mr_view(icol); } else { x_view(icol) = fill_val; } @@ -484,106 +502,62 @@ rescale_masked_fields (const Field& x, const Field& mask) const } case 2: { - auto x_view = x.get_view< Pack**>(); - bool mask1d = mask.rank()==1; - view_1d mask_1d; - view_2d mask_2d; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - if (mask.rank()==1) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto x_view = x.get_view(); + auto mr_view = real_mask.get_view(); + auto m_view = mask.get_view(); const int dim1 = PackInfo::num_packs(layout.dim(1)); auto policy = TPF::get_default_team_policy(ncols,dim1); Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const MemberType& team) { const auto icol = team.league_rank(); auto x_sub = ekat::subview(x_view,icol); - if (mask1d) { - auto mask = mask_1d(icol); - if (mask>mask_threshold) { - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1), - [&](const int j){ - x_sub(j) /= mask; - }); + auto mr_sub = ekat::subview(mr_view,icol); + auto m_sub = ekat::subview(m_view,icol); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1), + [&](const int j){ + m_sub(j) = mr_sub(j) > mask_threshold; + if (m_sub(j).any()) { + x_sub(j).set(m_sub(j),x_sub(j)/mr_sub(j),fv_pack); + } else { + x_sub(j) = fv_pack; } - } else { - auto m_sub = ekat::subview(mask_2d,icol); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1), - [&](const int j){ - auto masked = m_sub(j) > mask_threshold; - if (masked.any()) { - x_sub(j).set(masked,x_sub(j)/m_sub(j)); - } - x_sub(j).set(!masked,fill_val); - }); - } + }); }); break; } case 3: { - auto x_view = x.get_view< Pack***>(); - bool mask1d = mask.rank()==1; - view_1d mask_1d; - view_2d mask_2d; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - if (mask.rank()==1) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto x_view = x.get_view(); + auto mr_view = real_mask.get_view(); + auto m_view = mask.get_view(); const int dim1 = layout.dim(1); const int dim2 = PackInfo::num_packs(layout.dim(2)); auto policy = TPF::get_default_team_policy(ncols,dim1*dim2); Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const MemberType& team) { const auto icol = team.league_rank(); - if (mask1d) { - auto mask = mask_1d(icol); - if (mask>mask_threshold) { - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2), - [&](const int idx){ - const int j = idx / dim2; - const int k = idx % dim2; - auto x_sub = ekat::subview(x_view,icol,j); - x_sub(k) /= mask; - }); + auto x_sub = ekat::subview(x_view,icol); + auto mr_sub = ekat::subview(mr_view,icol); + auto m_sub = ekat::subview(m_view,icol); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2), + [&](const int idx){ + const int j = idx / dim2; + const int k = idx % dim2; + m_sub(j,k) = mr_sub(j,k) > mask_threshold; + if (m_sub(j,k).any()) { + x_sub(j,k).set(m_sub(j,k),x_sub(j,k)/mr_sub(j,k),fv_pack); + } else { + x_sub(j,k) = fv_pack; } - } else { - auto m_sub = ekat::subview(mask_2d,icol); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2), - [&](const int idx){ - const int j = idx / dim2; - const int k = idx % dim2; - auto x_sub = ekat::subview(x_view,icol,j); - auto masked = m_sub(k) > mask_threshold; - - if (masked.any()) { - x_sub(k).set(masked,x_sub(k)/m_sub(k)); - } - x_sub(k).set(!masked,fill_val); - }); - } + }); }); break; } case 4: { auto x_view = x.get_view(); - bool mask1d = mask.rank()==1; - view_1d mask_1d; - view_2d mask_2d; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - if (mask.rank()==1) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto mr_view = real_mask.get_view(); + auto m_view = mask.get_view(); const int dim1 = layout.dim(1); const int dim2 = layout.dim(2); const int dim3 = PackInfo::num_packs(layout.dim(3)); @@ -591,34 +565,21 @@ rescale_masked_fields (const Field& x, const Field& mask) const Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const MemberType& team) { const auto icol = team.league_rank(); - if (mask1d) { - auto mask = mask_1d(icol); - if (mask>mask_threshold) { - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2*dim3), - [&](const int idx){ - const int j = (idx / dim3) / dim2; - const int k = (idx / dim3) % dim2; - const int l = idx % dim3; - auto x_sub = ekat::subview(x_view,icol,j,k); - x_sub(l) /= mask; - }); + auto x_sub = ekat::subview(x_view,icol); + auto mr_sub = ekat::subview(mr_view,icol); + auto m_sub = ekat::subview(m_view,icol); + Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2*dim3), + [&](const int idx){ + const int j = (idx / dim3) / dim2; + const int k = (idx / dim3) % dim2; + const int l = idx % dim3; + m_sub(j,k,l) = mr_sub(j,k,l) > mask_threshold; + if (m_sub(j,k,l).any()) { + x_sub(j,k,l).set(m_sub(j,k,l),x_sub(j,k,l)/mr_sub(j,k,l),fv_pack); + } else { + x_sub(j,k,l) = fv_pack; } - } else { - auto m_sub = ekat::subview(mask_2d,icol); - Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1*dim2*dim3), - [&](const int idx){ - const int j = (idx / dim3) / dim2; - const int k = (idx / dim3) % dim2; - const int l = idx % dim3; - auto x_sub = ekat::subview(x_view,icol,j,k); - auto masked = m_sub(l) > mask_threshold; - - if (masked.any()) { - x_sub(l).set(masked,x_sub(l)/m_sub(l)); - } - x_sub(l).set(!masked,fill_val); - }); - } + }); }); break; } @@ -629,7 +590,7 @@ rescale_masked_fields (const Field& x, const Field& mask) const template void HorizontalRemapper:: -local_mat_vec (const Field& x, const Field& y, const Field& mask) const +local_mat_vec_masked (const Field& x, const Field& y) const { if (m_timers_enabled) start_timer(name()+" mat-vec (masked)"); @@ -641,6 +602,8 @@ local_mat_vec (const Field& x, const Field& y, const Field& mask) const using PackInfo = ekat::PackInfo; const auto& src_layout = x.get_header().get_identifier().get_layout(); + const auto& mask_name = x.get_header().get_extra_data("valid_mask").name(); + const auto& mask = m_name_to_src_real_mask.at(mask_name); const int rank = src_layout.rank(); const int nrows = m_remap_data->m_overlap_grid->get_num_local_dofs(); auto row_offsets = m_remap_data->m_row_offsets; @@ -650,21 +613,22 @@ local_mat_vec (const Field& x, const Field& y, const Field& mask) const // Note: in each case, handle 1st contribution to each row separately, // using = instead of +=. This allows to avoid doing an extra // loop to zero out y before the mat-vec. + // Note: we ASSUME mask fields are ALWAYS contiguous (they are not subfields) case 1: { // Unlike get_view, get_strided_view returns a LayoutStride view, // therefore allowing the 1d field to be a subfield of a 2d field // along the 2nd dimension. - auto x_view = x.get_strided_view(); - auto y_view = y.get_strided_view< Real*>(); - auto mask_view = mask.get_strided_view(); + auto x_view = x.get_strided_view(); + auto y_view = y.get_strided_view< Real*>(); + auto m_view = mask.get_view(); Kokkos::parallel_for(RangePolicy(0,nrows), KOKKOS_LAMBDA(const int& row) { const auto beg = row_offsets(row); const auto end = row_offsets(row+1); - y_view(row) = weights(beg)*x_view(col_lids(beg))*mask_view(col_lids(beg)); + y_view(row) = weights(beg)*x_view(col_lids(beg))*m_view(col_lids(beg)); for (int icol=beg+1; icol(); auto y_view = y.get_view< Pack**>(); - view_1d mask_1d; - view_2d mask_2d; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - bool mask1d = mask.rank()==1; - if (mask1d) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto m_view = mask.get_view(); const int dim1 = PackInfo::num_packs(src_layout.dim(1)); auto policy = TPF::get_default_team_policy(nrows,dim1); Kokkos::parallel_for(policy, @@ -693,11 +648,9 @@ local_mat_vec (const Field& x, const Field& y, const Field& mask) const const auto end = row_offsets(row+1); Kokkos::parallel_for(Kokkos::TeamVectorRange(team,dim1), [&](const int j){ - y_view(row,j) = weights(beg)*x_view(col_lids(beg),j) * - (mask1d ? mask_1d (col_lids(beg)) : mask_2d(col_lids(beg),j)); + y_view(row,j) = weights(beg)*x_view(col_lids(beg),j)*m_view(col_lids(beg),j); for (int icol=beg+1; icol(); auto y_view = y.get_view< Pack***>(); - // Note, the mask is still assumed to be defined on COLxLEV so still only 2D for case 3. - view_1d mask_1d; - view_2d mask_2d; - bool mask1d = mask.rank()==1; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - if (mask1d) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto m_view = mask.get_view(); const int dim1 = src_layout.dim(1); const int dim2 = PackInfo::num_packs(src_layout.dim(2)); auto policy = TPF::get_default_team_policy(nrows,dim1*dim2); @@ -731,11 +674,9 @@ local_mat_vec (const Field& x, const Field& y, const Field& mask) const [&](const int idx){ const int j = idx / dim2; const int k = idx % dim2; - y_view(row,j,k) = weights(beg)*x_view(col_lids(beg),j,k) * - (mask1d ? mask_1d (col_lids(beg)) : mask_2d(col_lids(beg),k)); + y_view(row,j,k) = weights(beg)*x_view(col_lids(beg),j,k)*m_view(col_lids(beg),j,k); for (int icol=beg+1; icol(); auto y_view = y.get_view< Pack****>(); - // Note, the mask is still assumed to be defined on COLxLEV so still only 2D for case 3. - view_1d mask_1d; - view_2d mask_2d; - bool mask1d = mask.rank()==1; - // If the mask comes from FieldAtLevel, it's only defined on columns (rank=1) - // If the mask comes from vert interpolation remapper, it is defined on ncols x nlevs (rank=2) - if (mask1d) { - mask_1d = mask.get_view(); - } else { - mask_2d = mask.get_view(); - } + auto m_view = mask.get_view(); const int dim1 = src_layout.dim(1); const int dim2 = src_layout.dim(2); const int dim3 = PackInfo::num_packs(src_layout.dim(3)); @@ -771,11 +702,9 @@ local_mat_vec (const Field& x, const Field& y, const Field& mask) const const int j = (idx / dim3) / dim2; const int k = (idx / dim3) % dim2; const int l = idx % dim3; - y_view(row,j,k,l) = weights(beg)*x_view(col_lids(beg),j,k,l) * - (mask1d ? mask_1d (col_lids(beg)) : mask_2d(col_lids(beg),l)); + y_view(row,j,k,l) = weights(beg)*x_view(col_lids(beg),j,k,l)*m_view(col_lids(beg),j,k,l); for (int icol=beg+1; icol void local_mat_vec (const Field& f_src, const Field& f_tgt) const; template - void local_mat_vec (const Field& f_src, const Field& f_tgt, const Field& mask) const; + void local_mat_vec_masked (const Field& f_src, const Field& f_tgt) const; template void rescale_masked_fields (const Field& f_tgt, const Field& f_mask) const; void pack_and_send (); @@ -138,6 +138,12 @@ class HorizontalRemapper : public AbstractRemapper // Send/recv persistent requests std::vector m_send_req; std::vector m_recv_req; + + // Keep track of all src/tgt int/real mask fields (only if m_track_mask=true) + std::map m_name_to_src_int_mask; + std::map m_name_to_tgt_int_mask; + std::map m_name_to_src_real_mask; + std::map m_name_to_tgt_real_mask; }; } // namespace scream diff --git a/components/eamxx/src/share/remap/vertical_remapper.cpp b/components/eamxx/src/share/remap/vertical_remapper.cpp index 9de0661ad904..a3f73396a89b 100644 --- a/components/eamxx/src/share/remap/vertical_remapper.cpp +++ b/components/eamxx/src/share/remap/vertical_remapper.cpp @@ -121,8 +121,8 @@ set_pressure (const Field& p, const std::string& src_or_tgt, const ProfileType p const auto vtag = p_layout.tags().back(); const auto vdim = p_layout.dims().back(); - FieldTag expected_tag; - int expected_dim; + FieldTag expected_tag = FieldTag::Invalid; + int expected_dim = -1; if (ptype==Midpoints or ptype==Both) { expected_tag = LEV; expected_dim = nlevs; @@ -188,29 +188,39 @@ registration_ends_impl () // and that fields with multiple components will have the same masking for each component // at a specific COL,LEV - auto src_layout_no_cmp = src_layout.clone(); - src_layout_no_cmp.strip_dims({CMP}); - auto tgt_layout = create_tgt_layout(src_layout_no_cmp); + auto tgt_layout = create_tgt_layout(src_layout); // I this mask has already been created, retrieve it, otherwise create it - // CAVEAT: the tgt layout ALWAYS has LEV as vertical dim tag. But we NEED different masks for - // src fields defined at LEV and ILEV. So use src_layout_no_cmp to craft the mask name - const auto mask_name = m_tgt_grid->name() + "_" + ekat::join(src_layout_no_cmp.names(),"_") + "_mask"; + // CAVEATS: + // 1. the tgt layout ALWAYS has LEV as vertical dim tag. But we NEED different masks for + // src fields defined at LEV and ILEV. So use src_layout to craft the mask name + // 2. for vector dimensions, we must include the vector dim length, as there may be + // 2+ vector fields with different vector length, which need 2 different masks + std::vector tagdim_names; + for (int i=0; iname() + "_" + ekat::join(tagdim_names,"_") + "_mask"; auto& mask = m_masks[mask_name]; if (not mask.is_allocated()) { auto nondim = ekat::units::Units::nondimensional(); // Create this src/tgt mask fields, and assign them to these src/tgt fields extra data - FieldIdentifier mask_fid (mask_name, tgt_layout, nondim, m_tgt_grid->name() ); + FieldIdentifier mask_fid (mask_name, tgt_layout, nondim, m_tgt_grid->name(), DataType::IntType ); mask = Field (mask_fid); + if (ft.packed) + mask.get_header().get_alloc_properties().request_allocation(SCREAM_PACK_SIZE); mask.allocate_view(); } - EKAT_REQUIRE_MSG(not tgt.get_header().has_extra_data("mask_field"), + EKAT_REQUIRE_MSG(not tgt.get_header().has_extra_data("valid_mask"), "[VerticalRemapper::registration_ends_impl] Error! Target field already has mask data assigned.\n" " - tgt field name: " + tgt.name() + "\n"); - tgt.get_header().set_extra_data("mask_field",mask); + tgt.get_header().set_extra_data("valid_mask",mask); // Since we do mask (at top and/or bot), the tgt field MAY be contain fill_value entries tgt.get_header().set_may_be_filled(true); @@ -219,12 +229,12 @@ registration_ends_impl () // If a field does not have LEV or ILEV it may still have fill_value tracking assigned from somewhere else. // For instance, this could be a 2d field computed by FieldAtPressureLevel diagnostic. // In those cases we want to copy that fill_value tracking to the target field. - if (src.get_header().has_extra_data("mask_field")) { - EKAT_REQUIRE_MSG(not tgt.get_header().has_extra_data("mask_field"), + if (src.get_header().has_extra_data("valid_mask")) { + EKAT_REQUIRE_MSG(not tgt.get_header().has_extra_data("valid_mask"), "[VerticalRemapper::registration_ends_impl] Error! Target field already has mask data assigned.\n" " - tgt field name: " + tgt.name() + "\n"); - auto src_mask = src.get_header().get_extra_data("mask_field"); - tgt.get_header().set_extra_data("mask_field",src_mask); + auto src_mask = src.get_header().get_extra_data("valid_mask"); + tgt.get_header().set_extra_data("valid_mask",src_mask); } if (src.get_header().may_be_filled()) { tgt.get_header().set_may_be_filled(true); @@ -422,9 +432,9 @@ void VerticalRemapper::remap_fwd_impl () // so just copy it over. Note, if this field has its own mask data make // sure that is copied too. f_tgt.deep_copy(f_src); - if (f_tgt.get_header().has_extra_data("mask_field")) { - auto f_tgt_mask = f_tgt.get_header().get_extra_data("mask_field"); - auto f_src_mask = f_src.get_header().get_extra_data("mask_field"); + if (f_tgt.get_header().has_extra_data("valid_mask")) { + auto f_tgt_mask = f_tgt.get_header().get_extra_data("valid_mask"); + auto f_src_mask = f_src.get_header().get_extra_data("valid_mask"); f_tgt_mask.deep_copy(f_src_mask); } } @@ -622,16 +632,15 @@ extrapolate (const Field& f_src, auto ebot = m_etype_bot; auto mid = nlevs_tgt / 2; auto do_mask = etop==Mask or ebot==Mask; - decltype(f_tgt.get_view()) mask_v; - if (do_mask) { - mask_v = f_tgt.get_header().get_extra_data("mask_field").get_view(); - } switch(f_src.rank()) { case 2: { auto f_src_v = f_src.get_view(); auto f_tgt_v = f_tgt.get_view< Real**>(); + auto mask_v = do_mask ? f_tgt.get_header().get_extra_data("valid_mask").get_view() + : typename Field::view_dev_t{}; + auto policy = TPF::get_default_team_policy(ncols,nlevs_tgt); using MemberType = typename decltype(policy)::member_type; @@ -684,6 +693,8 @@ extrapolate (const Field& f_src, { auto f_src_v = f_src.get_view(); auto f_tgt_v = f_tgt.get_view< Real***>(); + auto mask_v = do_mask ? f_tgt.get_header().get_extra_data("valid_mask").get_view() + : typename Field::view_dev_t{}; const int ncomps = f_tgt_l.get_vector_dim(); auto policy = TPF::get_default_team_policy(ncols*ncomps,nlevs_tgt); @@ -713,7 +724,7 @@ extrapolate (const Field& f_src, y_tgt[ilev] = y_src[nlevs_src-1]; } else { y_tgt[ilev] = fill_val; - mask_v(icol,ilev) = 0; + mask_v(icol,icmp,ilev) = 0; } } } else { @@ -723,7 +734,7 @@ extrapolate (const Field& f_src, y_tgt[ilev] = y_src[0]; } else { y_tgt[ilev] = fill_val; - mask_v(icol,ilev) = 0; + mask_v(icol,icmp,ilev) = 0; } } } diff --git a/components/eamxx/src/share/scorpio_interface/eamxx_scorpio_interface.cpp b/components/eamxx/src/share/scorpio_interface/eamxx_scorpio_interface.cpp index ab59171b1071..d8ac04fb569e 100644 --- a/components/eamxx/src/share/scorpio_interface/eamxx_scorpio_interface.cpp +++ b/components/eamxx/src/share/scorpio_interface/eamxx_scorpio_interface.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace scream { namespace scorpio { @@ -794,12 +795,18 @@ void set_var_decomp (PIOVar& var, " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n" " - decomp dims: " + ekat::join(decomposed_dims,",") + "\n"); - EKAT_REQUIRE_MSG (last_decomp==(num_decomp-1), - "Error! We cannot decompose this variable, as the decomp dims are not the slowest striding ones.\n" - " - filename: " + filename + "\n" - " - varname : " + var.name + "\n" - " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n" - " - decomp dims: " + ekat::join(decomposed_dims,",") + "\n"); + // Check if this is transposed output - if so, skip the stride check + // For transposed output, decomposed dimensions may not be the slowest striding + bool is_transposed = has_attribute(filename, var.name, "transposed_output"); + + if (!is_transposed) { + EKAT_REQUIRE_MSG (last_decomp==(num_decomp-1), + "Error! We cannot decompose this variable, as the decomp dims are not the slowest striding ones.\n" + " - filename: " + filename + "\n" + " - varname : " + var.name + "\n" + " - var dims: " + ekat::join(var.dims,get_entity_name,",") + "\n" + " - decomp dims: " + ekat::join(decomposed_dims,",") + "\n"); + } // Create decomp name: dtype-dim1_dim2_..._dimk auto get_dimtag = [](const auto dim) { @@ -861,11 +868,57 @@ void set_var_decomp (PIOVar& var, const auto& dim_offsets = decomp->dim_decomp->offsets; int decomp_loc_len = dim_offsets.size(); decomp->offsets.resize (non_decomp_dim_prod*decomp_loc_len); - for (int idof=0; idofoffsets.begin()+ idof*non_decomp_dim_prod; - auto end = beg + non_decomp_dim_prod; - std::iota (beg,end,non_decomp_dim_prod*dof_offset); + + // For transposed output, decomposed dimension may not be slowest-striding + // We need to calculate offsets based on actual dimension order + if (is_transposed) { + // Calculate stride for each dimension based on file layout order + std::vector strides(ndims); + strides[ndims-1] = 1; // Fastest dimension has stride 1 + for (int idim = ndims-2; idim >= 0; --idim) { + strides[idim] = strides[idim+1] * gdimlen[idim+1]; + } + + // Find which dimension is decomposed + int decomp_dim_idx = -1; + for (int idim = 0; idim < ndims; ++idim) { + if (var.dims[idim]->decomp_rank > 0) { + decomp_dim_idx = idim; + break; + } + } + EKAT_REQUIRE_MSG(decomp_dim_idx >= 0, "Error! Could not find decomposed dimension"); + + // Generate offsets in memory order (C-order, last dimension fastest) + // The offsets array maps local buffer positions to global file positions + int idx = 0; + std::function generate = [&](int dim, PIO_Offset offset, int dof_idx) { + if (dim == ndims) { + decomp->offsets[idx++] = offset; + return; + } + + if (dim == decomp_dim_idx) { + // For decomposed dimension, iterate through local DOFs + for (int i = 0; i < decomp_loc_len; ++i) { + generate(dim + 1, offset + dim_offsets[i] * strides[dim], i); + } + } else { + // For non-decomposed dimension, iterate through all indices + for (int i = 0; i < var.dims[dim]->length; ++i) { + generate(dim + 1, offset + i * strides[dim], dof_idx); + } + } + }; + generate(0, 0, 0); + } else { + // Original non-transposed logic + for (int idof=0; idofoffsets.begin()+ idof*non_decomp_dim_prod; + auto end = beg + non_decomp_dim_prod; + std::iota (beg,end,non_decomp_dim_prod*dof_offset); + } } // Create PIO decomp diff --git a/components/eamxx/src/share/util/eamxx_bfbhash.hpp b/components/eamxx/src/share/util/eamxx_bfbhash.hpp index f3eeed9098cd..4e7d53e33590 100644 --- a/components/eamxx/src/share/util/eamxx_bfbhash.hpp +++ b/components/eamxx/src/share/util/eamxx_bfbhash.hpp @@ -29,6 +29,10 @@ KOKKOS_INLINE_FUNCTION void hash (const float v, HashType& accum) { hash(double(v), accum); } +KOKKOS_INLINE_FUNCTION void hash (const int v, HashType& accum) { + hash(double(v), accum); +} + // For Kokkos::parallel_reduce. template struct HashReducer { diff --git a/components/eamxx/src/share/util/eamxx_data_type.hpp b/components/eamxx/src/share/util/eamxx_data_type.hpp index c2a7ba86be7a..002d781edc33 100644 --- a/components/eamxx/src/share/util/eamxx_data_type.hpp +++ b/components/eamxx/src/share/util/eamxx_data_type.hpp @@ -61,6 +61,7 @@ inline int get_type_size (const DataType data_type) { case DataType::IntType: return sizeof(int); case DataType::FloatType: return sizeof(float); case DataType::DoubleType: return sizeof(double); + case DataType::Invalid: return 0; default: EKAT_ERROR_MSG("Error! Unsupported DataType value.\n"); } diff --git a/components/eamxx/tests/single-process/cosp/input.yaml b/components/eamxx/tests/single-process/cosp/input.yaml index 380ec60f1af0..d2202f68a11c 100644 --- a/components/eamxx/tests/single-process/cosp/input.yaml +++ b/components/eamxx/tests/single-process/cosp/input.yaml @@ -34,7 +34,7 @@ initial_conditions: cldfrac_rad: 0.5 eff_radius_qc: 10.0 eff_radius_qi: 10.0 - sunlit_mask: 1.0 + sunlit_mask: 1 surf_radiative_T: 288.0 pseudo_density: 1.0 diff --git a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates/user_nl_elm b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates/user_nl_elm index 4ea97fe852ba..8b34383ca2fc 100644 --- a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates/user_nl_elm +++ b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates/user_nl_elm @@ -3,28 +3,77 @@ hist_mfilt = 365 hist_nhtfrq = -24 hist_empty_htapes = .true. fates_spitfire_mode = 1 -hist_fincl1 = 'FATES_NCOHORTS', 'FATES_TRIMMING', 'FATES_AREA_PLANTS', -'FATES_AREA_TREES', 'FATES_COLD_STATUS', 'FATES_GDD', -'FATES_NCHILLDAYS', 'FATES_NCOLDDAYS', 'FATES_DAYSINCE_COLDLEAFOFF', +hist_fincl1 = +'FATES_AREA_PLANTS', +'FATES_AREA_TREES', +'FATES_AUTORESP_CANOPY', +'FATES_AUTORESP_USTORY', +'FATES_AUTORESP', +'FATES_BURNFRAC', +'FATES_CANOPY_SPREAD', +'FATES_CANOPY_VEGC', +'FATES_CBALANCE_ERROR', +'FATES_COLD_STATUS', +'FATES_CROOT_ALLOC', +'FATES_DAYSINCE_COLDLEAFOFF', 'FATES_DAYSINCE_COLDLEAFON', -'FATES_CANOPY_SPREAD', 'FATES_NESTEROV_INDEX', 'FATES_IGNITIONS', 'FATES_FDI', -'FATES_ROS','FATES_EFFECT_WSPEED', 'FATES_FUELCONSUMED', 'FATES_FIRE_INTENSITY', -'FATES_FIRE_INTENSITY_BURNFRAC', 'FATES_BURNFRAC', 'FATES_FUEL_MEF', -'FATES_FUEL_BULKD', 'FATES_FUEL_EFF_MOIST', 'FATES_FUEL_SAV', -'FATES_FUEL_AMOUNT', 'FATES_LITTER_IN', 'FATES_LITTER_OUT', -'FATES_SEED_BANK', 'FATES_SEEDS_IN', 'FATES_STOREC', 'FATES_VEGC', -'FATES_SAPWOODC', 'FATES_LEAFC', 'FATES_FROOTC', 'FATES_REPROC', -'FATES_STRUCTC', 'FATES_NONSTRUCTC', 'FATES_VEGC_ABOVEGROUND', -'FATES_CANOPY_VEGC', 'FATES_USTORY_VEGC', 'FATES_PRIMARY_PATCHFUSION_ERR', -'FATES_HARVEST_WOODPROD_C_FLUX', 'FATES_DISTURBANCE_RATE_FIRE', -'FATES_DISTURBANCE_RATE_LOGGING', 'FATES_DISTURBANCE_RATE_TREEFALL', -'FATES_STOMATAL_COND', 'FATES_LBLAYER_COND', 'FATES_NPP', 'FATES_GPP', -'FATES_AUTORESP', 'FATES_GROWTH_RESP', 'FATES_MAINT_RESP', 'FATES_GPP_CANOPY', -'FATES_AUTORESP_CANOPY', 'FATES_GPP_USTORY', 'FATES_AUTORESP_USTORY', -'FATES_DEMOTION_CARBONFLUX', 'FATES_PROMOTION_CARBONFLUX', -'FATES_MORTALITY_CFLUX_CANOPY', 'FATES_MORTALITY_CFLUX_USTORY', -'FATES_NEP', 'FATES_HET_RESP', 'FATES_FIRE_CLOSS', 'FATES_FIRE_FLUX_EL', -'FATES_CBALANCE_ERROR', 'FATES_LEAF_ALLOC', -'FATES_SEED_ALLOC', 'FATES_STEM_ALLOC', 'FATES_FROOT_ALLOC', -'FATES_CROOT_ALLOC', 'FATES_STORE_ALLOC', -'FATES_PATCHAREA_LU', 'FATES_DISTURBANCE_RATE_MATRIX_LULU' +'FATES_DEMOTION_CARBONFLUX', +'FATES_DISTURBANCE_RATE_FIRE', +'FATES_DISTURBANCE_RATE_LOGGING', +'FATES_DISTURBANCE_RATE_MATRIX_LULU', +'FATES_DISTURBANCE_RATE_TREEFALL', +'FATES_EFFECT_WSPEED', +'FATES_FDI', +'FATES_FIRE_CLOSS', +'FATES_FIRE_FLUX_EL', +'FATES_FIRE_INTENSITY_BURNFRAC', +'FATES_FIRE_INTENSITY', +'FATES_FROOT_ALLOC', +'FATES_FROOTC', +'FATES_FUEL_AMOUNT', +'FATES_FUEL_BULKD', +'FATES_FUEL_EFF_MOIST', +'FATES_FUEL_MEF', +'FATES_FUEL_SAV', +'FATES_FUELCONSUMED', +'FATES_GDD', +'FATES_GPP_CANOPY', +'FATES_GPP_USTORY', +'FATES_GPP', +'FATES_GROWTH_RESP', +'FATES_HARVEST_WOODPROD_C_FLUX', +'FATES_HET_RESP', +'FATES_IGNITIONS', +'FATES_LBLAYER_COND', +'FATES_LEAF_ALLOC', +'FATES_LEAFC', +'FATES_LITTER_IN', +'FATES_LITTER_OUT', +'FATES_MAINT_RESP', +'FATES_MORTALITY_CFLUX_CANOPY', +'FATES_MORTALITY_CFLUX_USTORY', +'FATES_NCHILLDAYS', +'FATES_NCOHORTS', +'FATES_NCOLDDAYS', +'FATES_NEP', +'FATES_NESTEROV_INDEX', +'FATES_NONSTRUCTC', +'FATES_NPP', +'FATES_PATCHAREA_LU', +'FATES_PRIMARY_PATCHFUSION_ERR', +'FATES_PROMOTION_CARBONFLUX', +'FATES_REPROC', +'FATES_ROS', +'FATES_SAPWOODC', +'FATES_SEED_ALLOC', +'FATES_SEED_BANK', +'FATES_SEEDS_IN', +'FATES_STEM_ALLOC', +'FATES_STOMATAL_COND', +'FATES_STORE_ALLOC', +'FATES_STOREC', +'FATES_STRUCTC', +'FATES_TRIMMING', +'FATES_USTORY_VEGC', +'FATES_VEGC_ABOVEGROUND', +'FATES_VEGC' diff --git a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_allvars/user_nl_elm b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_allvars/user_nl_elm index c7cbdbab1cd4..f25a7611ae9b 100644 --- a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_allvars/user_nl_elm +++ b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_allvars/user_nl_elm @@ -5,58 +5,207 @@ fates_spitfire_mode = 1 fates_history_dimlevel(1) = 2 fates_history_dimlevel(2) = 2 use_fates_tree_damage = .true. -hist_fincl1 = 'FATES_TLONGTERM', -'FATES_TGROWTH','FATES_SEEDS_IN_GRIDCELL_PF','FATES_SEEDS_OUT_GRIDCELL_PF', -'FATES_NCL','FATES_NCL_AP','FATES_NPATCH_AP','FATES_PATCHAREA', -'FATES_VEGC_AP','FATES_PRIMARY_AREA','FATES_PRIMARY_AREA_AP', -'FATES_SECONDARY_AREA','FATES_SECONDARY_AREA_ANTHRO_AP','FATES_SECONDARY_AREA_AP', -'FATES_FUEL_AMOUNT_APFC','FATES_STOREC_TF_USTORY_SZPF','FATES_STOREC_TF_CANOPY_SZPF', -'FATES_CROWNAREA_CLLL','FATES_ABOVEGROUND_MORT_SZPF','FATES_CANOPYAREA', -'FATES_ABOVEGROUND_PROD_SZPF','FATES_NPLANT_SZAP','FATES_NPLANT_CANOPY_SZAP', -'FATES_NPLANT_USTORY_SZAP','FATES_DDBH_CANOPY_SZAP','FATES_DDBH_USTORY_SZAP', -'FATES_MORTALITY_CANOPY_SZAP','FATES_MORTALITY_USTORY_SZAP','FATES_NPLANT_SZAPPF', -'FATES_NPP_APPF','FATES_VEGC_APPF','FATES_SCORCH_HEIGHT_APPF','FATES_SCORCH_HEIGHT_PF', -'FATES_GPP_SZPF','FATES_GPP_CANOPY_SZPF','FATES_AUTORESP_CANOPY_SZPF','FATES_GPP_USTORY_SZPF', -'FATES_AUTORESP_USTORY_SZPF','FATES_NPP_SZPF','FATES_LEAF_ALLOC_SZPF', -'FATES_SEED_ALLOC_SZPF','FATES_FROOT_ALLOC_SZPF','FATES_BGSAPWOOD_ALLOC_SZPF', -'FATES_BGSTRUCT_ALLOC_SZPF','FATES_AGSAPWOOD_ALLOC_SZPF','FATES_AGSTRUCT_ALLOC_SZPF', -'FATES_STORE_ALLOC_SZPF','FATES_DDBH_SZPF','FATES_GROWTHFLUX_SZPF','FATES_GROWTHFLUX_FUSION_SZPF', -'FATES_DDBH_CANOPY_SZPF','FATES_DDBH_USTORY_SZPF','FATES_BASALAREA_SZPF','FATES_VEGC_ABOVEGROUND_SZPF', -'FATES_NPLANT_SZPF','FATES_NPLANT_ACPF','FATES_MORTALITY_BACKGROUND_SZPF','FATES_MORTALITY_HYDRAULIC_SZPF', -'FATES_MORTALITY_CSTARV_SZPF','FATES_MORTALITY_IMPACT_SZPF','FATES_MORTALITY_WILDFIRE_SZPF', -'FATES_MORTALITY_WILDFIRE_CROWN_SZPF','FATES_MORTALITY_WILDFIRE_CAMBIAL_SZPF','FATES_MORTALITY_TERMINATION_SZPF', -'FATES_MORTALITY_LOGGING_SZPF','FATES_MORTALITY_FREEZING_SZPF','FATES_MORTALITY_SENESCENCE_SZPF', -'FATES_MORTALITY_AGESCEN_SZPF','FATES_MORTALITY_AGESCEN_ACPF','FATES_MORTALITY_CANOPY_SZPF', -'FATES_M3_MORTALITY_CANOPY_SZPF','FATES_M3_MORTALITY_USTORY_SZPF', -'FATES_STOREC_CANOPY_SZPF','FATES_LEAFC_CANOPY_SZPF','FATES_LAI_CANOPY_SZPF','FATES_CROWNAREA_CANOPY_SZPF', -'FATES_CROWNAREA_USTORY_SZPF','FATES_NPLANT_CANOPY_SZPF','FATES_MORTALITY_USTORY_SZPF','FATES_STOREC_USTORY_SZPF', -'FATES_LEAFC_USTORY_SZPF','FATES_LAI_USTORY_SZPF','FATES_NPLANT_USTORY_SZPF','FATES_CWD_ABOVEGROUND_DC', -'FATES_CWD_BELOWGROUND_DC','FATES_CWD_ABOVEGROUND_IN_DC','FATES_CWD_BELOWGROUND_IN_DC', -'FATES_CWD_ABOVEGROUND_OUT_DC','FATES_CWD_BELOWGROUND_OUT_DC','FATES_YESTCANLEV_CANOPY_SZ', -'FATES_YESTCANLEV_USTORY_SZ','FATES_VEGC_SZ','FATES_DEMOTION_RATE_SZ','FATES_PROMOTION_RATE_SZ', -'FATES_SAI_CANOPY_SZ','FATES_M3_MORTALITY_CANOPY_SZ','FATES_M3_MORTALITY_USTORY_SZ','FATES_SAI_USTORY_SZ', -'FATES_NPP_CANOPY_SZ','FATES_NPP_USTORY_SZ','FATES_TRIMMING_CANOPY_SZ','FATES_TRIMMING_USTORY_SZ', -'FATES_CROWNAREA_CANOPY_SZ','FATES_CROWNAREA_USTORY_SZ','FATES_LEAFCTURN_CANOPY_SZ','FATES_FROOTCTURN_CANOPY_SZ', -'FATES_STORECTURN_CANOPY_SZ','FATES_STRUCTCTURN_CANOPY_SZ','FATES_SAPWOODCTURN_CANOPY_SZ','FATES_SEED_PROD_CANOPY_SZ', -'FATES_LEAF_ALLOC_CANOPY_SZ','FATES_FROOT_ALLOC_CANOPY_SZ','FATES_SAPWOOD_ALLOC_CANOPY_SZ','FATES_STRUCT_ALLOC_CANOPY_SZ', -'FATES_SEED_ALLOC_CANOPY_SZ','FATES_STORE_ALLOC_CANOPY_SZ','FATES_LEAFCTURN_USTORY_SZ','FATES_FROOTCTURN_USTORY_SZ', -'FATES_STORECTURN_USTORY_SZ','FATES_STRUCTCTURN_USTORY_SZ','FATES_SAPWOODCTURN_USTORY_SZ', -'FATES_SEED_PROD_USTORY_SZ','FATES_LEAF_ALLOC_USTORY_SZ','FATES_FROOT_ALLOC_USTORY_SZ','FATES_SAPWOOD_ALLOC_USTORY_SZ', -'FATES_STRUCT_ALLOC_USTORY_SZ','FATES_SEED_ALLOC_USTORY_SZ','FATES_STORE_ALLOC_USTORY_SZ','FATES_CROWNAREA_CANOPY_CD', -'FATES_CROWNAREA_USTORY_CD','FATES_NPLANT_CDPF','FATES_NPLANT_CANOPY_CDPF','FATES_NPLANT_USTORY_CDPF', -'FATES_M3_CDPF','FATES_M11_SZPF','FATES_M11_CDPF','FATES_MORTALITY_CDPF','FATES_M3_MORTALITY_CANOPY_CDPF', -'FATES_M3_MORTALITY_USTORY_CDPF','FATES_M11_MORTALITY_CANOPY_CDPF','FATES_M11_MORTALITY_USTORY_CDPF', -'FATES_MORTALITY_CANOPY_CDPF','FATES_MORTALITY_USTORY_CDPF','FATES_DDBH_CDPF','FATES_DDBH_CANOPY_CDPF', -'FATES_DDBH_USTORY_CDPF','FATES_VEGC_SZPF','FATES_LEAFC_SZPF','FATES_FROOTC_SZPF','FATES_SAPWOODC_SZPF', -'FATES_STOREC_SZPF','FATES_REPROC_SZPF','FATES_NPP_AP','FATES_GPP_AP','FATES_RDARK_USTORY_SZ', -'FATES_LSTEMMAINTAR_USTORY_SZ','FATES_CROOTMAINTAR_USTORY_SZ','FATES_FROOTMAINTAR_USTORY_SZ','FATES_GROWAR_USTORY_SZ', -'FATES_MAINTAR_USTORY_SZ','FATES_RDARK_CANOPY_SZ','FATES_CROOTMAINTAR_CANOPY_SZ','FATES_FROOTMAINTAR_CANOPY_SZ', -'FATES_GROWAR_CANOPY_SZ','FATES_MAINTAR_CANOPY_SZ','FATES_LSTEMMAINTAR_CANOPY_SZ','FATES_AUTORESP_SZPF', -'FATES_GROWAR_SZPF','FATES_MAINTAR_SZPF','FATES_RDARK_SZPF','FATES_AGSAPMAINTAR_SZPF','FATES_BGSAPMAINTAR_SZPF', -'FATES_FROOTMAINTAR_SZPF','FATES_PARSUN_CLLL','FATES_PARSHA_CLLL','FATES_PARSUN_CLLLPF','FATES_PARSHA_CLLLPF', -'FATES_PARSUN_CL','FATES_PARSHA_CL','FATES_LAISUN_CLLL','FATES_LAISHA_CLLL','FATES_LAISUN_CLLLPF', -'FATES_LAISHA_CLLLPF','FATES_PARPROF_DIR_CLLLPF','FATES_PARPROF_DIF_CLLLPF','FATES_LAISUN_CL','FATES_LAISHA_CL', -'FATES_PARPROF_DIR_CLLL','FATES_PARPROF_DIF_CLLL','FATES_NET_C_UPTAKE_CLLL','FATES_CROWNFRAC_CLLLPF', -'FATES_LBLAYER_COND_AP','FATES_STOMATAL_COND_AP','FATES_TLONGTERM','FATES_NPP_LU','FATES_GPP_LU', -'FATES_SEED_BANK_PF','FATES_UNGERM_SEED_BANK_PF','FATES_SEEDLING_POOL_PF','FATES_SEEDS_IN_PF','FATES_SEEDS_IN_LOCAL_PF', -'FATES_SAPWOOD_AREA_SZPF' +hist_fincl1 = +'FATES_ABOVEGROUND_MORT_SZPF', +'FATES_ABOVEGROUND_PROD_SZPF', +'FATES_AGSAPMAINTAR_SZPF', +'FATES_AGSAPWOOD_ALLOC_SZPF', +'FATES_AGSTRUCT_ALLOC_SZPF', +'FATES_AUTORESP_CANOPY_SZPF', +'FATES_AUTORESP_SZPF', +'FATES_AUTORESP_USTORY_SZPF', +'FATES_BASALAREA_SZPF', +'FATES_BGSAPMAINTAR_SZPF', +'FATES_BGSAPWOOD_ALLOC_SZPF', +'FATES_BGSTRUCT_ALLOC_SZPF', +'FATES_C13DISC_SZPF', +'FATES_CANOPYAREA', +'FATES_CROOTMAINTAR_CANOPY_SZ', +'FATES_CROOTMAINTAR_USTORY_SZ', +'FATES_CROWNAREA_CANOPY_CD', +'FATES_CROWNAREA_CANOPY_SZ', +'FATES_CROWNAREA_CANOPY_SZPF', +'FATES_CROWNAREA_CLLL', +'FATES_CROWNAREA_USTORY_CD', +'FATES_CROWNAREA_USTORY_SZ', +'FATES_CROWNAREA_USTORY_SZPF', +'FATES_CROWNFRAC_CLLLPF', +'FATES_CWD_ABOVEGROUND_DC', +'FATES_CWD_ABOVEGROUND_IN_DC', +'FATES_CWD_ABOVEGROUND_OUT_DC', +'FATES_CWD_BELOWGROUND_DC', +'FATES_CWD_BELOWGROUND_IN_DC', +'FATES_CWD_BELOWGROUND_OUT_DC', +'FATES_DDBH_CANOPY_CDPF', +'FATES_DDBH_CANOPY_SZAP', +'FATES_DDBH_CANOPY_SZPF', +'FATES_DDBH_CDPF', +'FATES_DDBH_SZPF', +'FATES_DDBH_USTORY_CDPF', +'FATES_DDBH_USTORY_SZAP', +'FATES_DDBH_USTORY_SZPF', +'FATES_DEMOTION_RATE_SZ', +'FATES_FROOT_ALLOC_CANOPY_SZ', +'FATES_FROOT_ALLOC_SZPF', +'FATES_FROOT_ALLOC_USTORY_SZ', +'FATES_FROOTC_SZPF', +'FATES_FROOTCTURN_CANOPY_SZ', +'FATES_FROOTCTURN_USTORY_SZ', +'FATES_FROOTMAINTAR_CANOPY_SZ', +'FATES_FROOTMAINTAR_SZPF', +'FATES_FROOTMAINTAR_USTORY_SZ', +'FATES_FUEL_AMOUNT_APFC', +'FATES_GPP_AP', +'FATES_GPP_CANOPY_SZPF', +'FATES_GPP_SZPF', +'FATES_GPP_USTORY_SZPF', +'FATES_GROWAR_CANOPY_SZ', +'FATES_GROWAR_SZPF', +'FATES_GROWAR_USTORY_SZ', +'FATES_GROWTHFLUX_FUSION_SZPF', +'FATES_GROWTHFLUX_SZPF', +'FATES_LAI_CANOPY_SZPF', +'FATES_LAI_USTORY_SZPF', +'FATES_LAISHA_CL', +'FATES_LAISHA_CLLL', +'FATES_LAISHA_CLLLPF', +'FATES_LAISUN_CL', +'FATES_LAISUN_CLLL', +'FATES_LAISUN_CLLLPF', +'FATES_LBLAYER_COND_AP', +'FATES_LEAF_ALLOC_CANOPY_SZ', +'FATES_LEAF_ALLOC_SZPF', +'FATES_LEAF_ALLOC_USTORY_SZ', +'FATES_LEAFC_CANOPY_SZPF', +'FATES_LEAFC_SZPF', +'FATES_LEAFC_USTORY_SZPF', +'FATES_LEAFCTURN_CANOPY_SZ', +'FATES_LEAFCTURN_USTORY_SZ', +'FATES_LSTEMMAINTAR_CANOPY_SZ', +'FATES_LSTEMMAINTAR_USTORY_SZ', +'FATES_M11_CDPF', +'FATES_M11_MORTALITY_CANOPY_CDPF', +'FATES_M11_MORTALITY_USTORY_CDPF', +'FATES_M11_SZPF', +'FATES_M3_CDPF', +'FATES_M3_MORTALITY_CANOPY_CDPF', +'FATES_M3_MORTALITY_CANOPY_SZ', +'FATES_M3_MORTALITY_CANOPY_SZPF', +'FATES_M3_MORTALITY_USTORY_CDPF', +'FATES_M3_MORTALITY_USTORY_SZ', +'FATES_M3_MORTALITY_USTORY_SZPF', +'FATES_MAINTAR_CANOPY_SZ', +'FATES_MAINTAR_SZPF', +'FATES_MAINTAR_USTORY_SZ', +'FATES_MORTALITY_AGESCEN_ACPF', +'FATES_MORTALITY_AGESCEN_SZPF', +'FATES_MORTALITY_BACKGROUND_SZPF', +'FATES_MORTALITY_CANOPY_CDPF', +'FATES_MORTALITY_CANOPY_SZAP', +'FATES_MORTALITY_CANOPY_SZPF', +'FATES_MORTALITY_CDPF', +'FATES_MORTALITY_CSTARV_SZPF', +'FATES_MORTALITY_FREEZING_SZPF', +'FATES_MORTALITY_HYDRAULIC_SZPF', +'FATES_MORTALITY_IMPACT_SZPF', +'FATES_MORTALITY_LOGGING_SZPF', +'FATES_MORTALITY_RXCAMBIAL_SZPF', +'FATES_MORTALITY_RXCROWN_SZPF', +'FATES_MORTALITY_RXFIRE_SZPF', +'FATES_MORTALITY_SENESCENCE_SZPF', +'FATES_MORTALITY_TERMINATION_SZPF', +'FATES_MORTALITY_USTORY_CDPF', +'FATES_MORTALITY_USTORY_SZAP', +'FATES_MORTALITY_USTORY_SZPF', +'FATES_MORTALITY_WILDFIRE_CAMBIAL_SZPF', +'FATES_MORTALITY_WILDFIRE_CROWN_SZPF', +'FATES_MORTALITY_WILDFIRE_SZPF', +'FATES_NCL_AP', +'FATES_NCL', +'FATES_NET_C_UPTAKE_CLLL', +'FATES_NPATCH_AP', +'FATES_NPLANT_ACPF', +'FATES_NPLANT_CANOPY_CDPF', +'FATES_NPLANT_CANOPY_SZAP', +'FATES_NPLANT_CANOPY_SZPF', +'FATES_NPLANT_CDPF', +'FATES_NPLANT_SZAP', +'FATES_NPLANT_SZAPPF', +'FATES_NPLANT_SZPF', +'FATES_NPLANT_USTORY_CDPF', +'FATES_NPLANT_USTORY_SZAP', +'FATES_NPLANT_USTORY_SZPF', +'FATES_NPP_AP', +'FATES_NPP_APPF', +'FATES_NPP_CANOPY_SZ', +'FATES_NPP_LU', +'FATES_NPP_SZPF', +'FATES_NPP_USTORY_SZ', +'FATES_PARPROF_DIF_CLLL', +'FATES_PARPROF_DIF_CLLLPF', +'FATES_PARPROF_DIR_CLLL', +'FATES_PARPROF_DIR_CLLLPF', +'FATES_PARSHA_CL', +'FATES_PARSHA_CLLL', +'FATES_PARSHA_CLLLPF', +'FATES_PARSUN_CL', +'FATES_PARSUN_CLLL', +'FATES_PARSUN_CLLLPF', +'FATES_PATCHAREA', +'FATES_PRIMARY_AREA_AP', +'FATES_PROMOTION_RATE_SZ', +'FATES_RDARK_CANOPY_SZ', +'FATES_RDARK_SZPF', +'FATES_RDARK_USTORY_SZ', +'FATES_REPROC_SZPF', +'FATES_SAI_CANOPY_SZ', +'FATES_SAI_USTORY_SZ', +'FATES_SAPWOOD_ALLOC_CANOPY_SZ', +'FATES_SAPWOOD_ALLOC_USTORY_SZ', +'FATES_SAPWOOD_AREA_SZPF', +'FATES_SAPWOODC_SZPF', +'FATES_SAPWOODCTURN_CANOPY_SZ', +'FATES_SAPWOODCTURN_USTORY_SZ', +'FATES_SCORCH_HEIGHT_APPF', +'FATES_SCORCH_HEIGHT_PF', +'FATES_SECONDARY_AGB_ANTHROAGE_AP', +'FATES_SECONDARY_AREA_ANTHRO_AP', +'FATES_SECONDARY_AREA_AP', +'FATES_SEED_ALLOC_CANOPY_SZ', +'FATES_SEED_ALLOC_SZPF', +'FATES_SEED_ALLOC_USTORY_SZ', +'FATES_SEED_BANK_PF', +'FATES_SEED_PROD_CANOPY_SZ', +'FATES_SEED_PROD_USTORY_SZ', +'FATES_SEEDLING_POOL_PF', +'FATES_SEEDS_IN_GRIDCELL_PF', +'FATES_SEEDS_IN_LOCAL_PF', +'FATES_SEEDS_IN_PF', +'FATES_SEEDS_OUT_GRIDCELL_PF', +'FATES_STOMATAL_COND_AP', +'FATES_STORE_ALLOC_CANOPY_SZ', +'FATES_STORE_ALLOC_SZPF', +'FATES_STORE_ALLOC_USTORY_SZ', +'FATES_STOREC_CANOPY_SZPF', +'FATES_STOREC_SZPF', +'FATES_STOREC_TF_CANOPY_SZPF', +'FATES_STOREC_TF_USTORY_SZPF', +'FATES_STOREC_USTORY_SZPF', +'FATES_STORECTURN_CANOPY_SZ', +'FATES_STORECTURN_USTORY_SZ', +'FATES_STRUCT_ALLOC_CANOPY_SZ', +'FATES_STRUCT_ALLOC_USTORY_SZ', +'FATES_STRUCTCTURN_CANOPY_SZ', +'FATES_STRUCTCTURN_USTORY_SZ', +'FATES_TGROWTH', +'FATES_TLONGTERM', +'FATES_TRIMMING_CANOPY_SZ', +'FATES_TRIMMING_USTORY_SZ', +'FATES_UNGERM_SEED_BANK_PF', +'FATES_VEGC_ABOVEGROUND_SZPF', +'FATES_VEGC_AP', +'FATES_VEGC_APPF', +'FATES_VEGC_SZ', +'FATES_VEGC_SZPF', +'FATES_YESTCANLEV_CANOPY_SZ', +'FATES_YESTCANLEV_USTORY_SZ' \ No newline at end of file diff --git a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_luh2/user_nl_elm b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_luh2/user_nl_elm index 3f089bcf49d2..3c2cd1504652 100644 --- a/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_luh2/user_nl_elm +++ b/components/elm/cime_config/testdefs/testmods_dirs/elm/fates_cold_luh2/user_nl_elm @@ -3,3 +3,86 @@ use_fates_nocomp = .true. use_fates_fixed_biogeog = .true. use_fates_sp = .false. use_fates_potentialveg = .false. +hist_fincl1 = +'FATES_AREA_PLANTS', +'FATES_AREA_TREES', +'FATES_AUTORESP_CANOPY', +'FATES_AUTORESP_USTORY', +'FATES_AUTORESP', +'FATES_BURNFRAC', +'FATES_CANOPY_SPREAD', +'FATES_CANOPY_VEGC', +'FATES_CBALANCE_ERROR', +'FATES_COLD_STATUS', +'FATES_CROOT_ALLOC', +'FATES_DAYSINCE_COLDLEAFOFF', +'FATES_DAYSINCE_COLDLEAFON', +'FATES_DEMOTION_CARBONFLUX', +'FATES_DISTURBANCE_RATE_FIRE', +'FATES_DISTURBANCE_RATE_LOGGING', +'FATES_DISTURBANCE_RATE_MATRIX_LULU', +'FATES_DISTURBANCE_RATE_TREEFALL', +'FATES_EFFECT_WSPEED', +'FATES_FDI', +'FATES_FIRE_CLOSS', +'FATES_FIRE_FLUX_EL', +'FATES_FIRE_INTENSITY_BURNFRAC', +'FATES_FIRE_INTENSITY', +'FATES_FROOT_ALLOC', +'FATES_FROOTC', +'FATES_FUEL_AMOUNT', +'FATES_FUEL_BULKD', +'FATES_FUEL_EFF_MOIST', +'FATES_FUEL_MEF', +'FATES_FUEL_SAV', +'FATES_FUELCONSUMED', +'FATES_GDD', +'FATES_GPP_CANOPY', +'FATES_GPP_LU', +'FATES_GPP_USTORY', +'FATES_GPP', +'FATES_GROWTH_RESP', +'FATES_HARVEST_WOODPROD_C_FLUX', +'FATES_HET_RESP', +'FATES_IGNITIONS', +'FATES_LBLAYER_COND', +'FATES_LEAF_ALLOC', +'FATES_LEAFC', +'FATES_LHFLUX_LU', +'FATES_LITTER_IN', +'FATES_LITTER_OUT', +'FATES_MAINT_RESP', +'FATES_MORTALITY_CFLUX_CANOPY', +'FATES_MORTALITY_CFLUX_USTORY', +'FATES_NCHILLDAYS', +'FATES_NCOHORTS', +'FATES_NCOLDDAYS', +'FATES_NEP', +'FATES_NESTEROV_INDEX', +'FATES_NETLW_LU', +'FATES_NOCOMP_PATCHAREA_LUPF', +'FATES_NONSTRUCTC', +'FATES_NPP', +'FATES_PATCHAREA_LU', +'FATES_PRIMARY_PATCHFUSION_ERR', +'FATES_PROMOTION_CARBONFLUX', +'FATES_REPROC', +'FATES_ROS', +'FATES_SAPWOODC', +'FATES_SEED_ALLOC', +'FATES_SEED_BANK', +'FATES_SEEDS_IN', +'FATES_SHFLUX_LU', +'FATES_STEM_ALLOC', +'FATES_STOMATAL_COND', +'FATES_STORE_ALLOC', +'FATES_STOREC', +'FATES_STRUCTC', +'FATES_SWABS_LU', +'FATES_TRIMMING', +'FATES_TSA_LU', +'FATES_TVEG_LU', +'FATES_USTORY_VEGC', +'FATES_VEGC_ABOVEGROUND', +'FATES_VEGC_LUPF', +'FATES_VEGC' diff --git a/components/elm/src/biogeochem/EcosystemDynMod.F90 b/components/elm/src/biogeochem/EcosystemDynMod.F90 index cab5156acdda..d12a9dbf34e4 100644 --- a/components/elm/src/biogeochem/EcosystemDynMod.F90 +++ b/components/elm/src/biogeochem/EcosystemDynMod.F90 @@ -47,7 +47,6 @@ module EcosystemDynMod use timeinfoMod - use perfMod_GPU use VegetationDataType , only : veg_cf_summary, veg_cf_summary_for_ch4, veg_cf_summary_rr use VegetationDataType , only : veg_nf_summary, veg_ns_summary, veg_cs_Summary use VegetationDataType , only : veg_pf_summary, veg_ps_summary @@ -156,42 +155,36 @@ subroutine EcosystemDynLeaching(bounds, num_soilc, filter_soilc, & type(frictionvel_type) , intent(in) :: frictionvel_vars type(canopystate_type) , intent(inout) :: canopystate_vars - character(len=64) :: event real(r8) :: dt !----------------------------------------------------------------------- dt = dtime_mod; ! only do if ed is off - event = 'PhosphorusWeathering' - call t_start_lnd(event) + call t_startf('PhosphorusWeathering') call PhosphorusWeathering(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusWeathering') - event = 'PhosphorusAdsportion' - call t_start_lnd(event) + call t_startf('PhosphorusAdsportion') call PhosphorusAdsportion(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusAdsportion') - event = 'PhosphorusDesoprtion' - call t_start_lnd(event) + call t_startf('PhosphorusDesoprtion') call PhosphorusDesoprtion(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusDesoprtion') - event = 'PhosphorusOcclusion' - call t_start_lnd(event) + call t_startf('PhosphorusOcclusion') call PhosphorusOcclusion(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusOcclusion') if (.not. nu_com_phosphatase) then - event = 'PhosphorusBiochemMin' - call t_start_lnd(event) + call t_startf('PhosphorusBiochemMin') call PhosphorusBiochemMin(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusBiochemMin') else ! nu_com_phosphatase is true !call t_startf('PhosphorusBiochemMin') @@ -209,19 +202,16 @@ subroutine EcosystemDynLeaching(bounds, num_soilc, filter_soilc, & end if !(.not. (pf_cmode .and. pf_hmode)) !----------------------------------------------------------------------- - event = 'CNUpdate3' - call t_start_lnd(event) + call t_startf('CNUpdate3') call NitrogenStateUpdate3(num_soilc, filter_soilc, num_soilp, filter_soilp,dt) - call t_stop_lnd(event) + call t_stopf('CNUpdate3') - event = 'PUpdate3' - call t_start_lnd(event) + call t_startf('PUpdate3') call PhosphorusStateUpdate3(bounds,num_soilc, filter_soilc, num_soilp, filter_soilp, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PUpdate3') - event = 'CNPsum' - call t_start_lnd(event) + call t_startf('CNPsum') call PrecisionControl(num_soilc, filter_soilc, num_soilp, filter_soilp ) call col_cf_summary_for_ch4(col_cf,bounds, num_soilc, filter_soilc) @@ -272,7 +262,7 @@ subroutine EcosystemDynLeaching(bounds, num_soilc, filter_soilc, & call col_pf_Summary(col_pf,bounds, num_soilc, filter_soilc) call col_ps_Summary(col_ps,bounds, num_soilc, filter_soilc) - call t_stop_lnd(event) + call t_stopf('CNPsum') if (use_fates) then call alm_fates%wrap_FatesAtmosphericCarbonFluxes(bounds, num_soilc, filter_soilc) @@ -340,7 +330,6 @@ subroutine EcosystemDynNoLeaching1(bounds, & type(photosyns_type) , intent(in) :: photosyns_vars type(frictionvel_type) , intent(in) :: frictionvel_vars - character(len=64) :: event real(r8) :: dt, dayspyr integer :: year, mon, day, sec !----------------------------------------------------------------------- @@ -355,8 +344,7 @@ subroutine EcosystemDynNoLeaching1(bounds, & ! zero the C and N fluxes ! -------------------------------------------------- - event = 'CNZero' - call t_start_lnd(event) + call t_startf('CNZero') if(.not.use_fates) then call veg_cf_SetValues(veg_cf, num_soilp, filter_soilp, 0._r8) @@ -376,37 +364,34 @@ subroutine EcosystemDynNoLeaching1(bounds, & call col_nf_SetValues(col_nf,num_soilc, filter_soilc, 0._r8) call col_pf_SetValues(col_pf,num_soilc, filter_soilc, 0._r8) - call t_stop_lnd(event) + call t_stopf('CNZero') ! -------------------------------------------------- ! Nitrogen Deposition, Fixation and Respiration, phosphorus dynamics ! -------------------------------------------------- - event = 'CNDeposition' - call t_start_lnd(event) + call t_startf('CNDeposition') call NitrogenDeposition(bounds, atm2lnd_vars) if (use_fan) then call fan_eval(bounds, num_soilc, filter_soilc, & atm2lnd_vars, soilstate_vars, frictionvel_vars) end if - call t_stop_lnd(event) + call t_stopf('CNDeposition') - event = 'CNFixation' if ( (.not. nu_com_nfix) .or. use_fates) then - call t_start_lnd(event) + call t_startf('CNFixation') call NitrogenFixation( bounds, num_soilc, filter_soilc, dayspyr) - call t_stop_lnd(event) + call t_stopf('CNFixation') else ! nu_com_nfix is true - call t_start_lnd(event) + call t_startf('CNFixation') call NitrogenFixation_balance( num_soilc, filter_soilc, cnstate_vars ) - call t_stop_lnd(event) + call t_stopf('CNFixation') end if if(.not.use_fates)then - event = 'MaintenanceResp' - call t_start_lnd(event) + call t_startf('MaintenanceResp') if (crop_prog) then call NitrogenFert(bounds, num_soilc,filter_soilc, num_pcropp, filter_pcropp, & num_ppercropp, filter_ppercropp) @@ -418,7 +403,7 @@ subroutine EcosystemDynNoLeaching1(bounds, & ! This is auto-trophic respiration, thus don't call this for FATES call MaintenanceResp(bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & canopystate_vars, soilstate_vars, photosyns_vars ) - call t_stop_lnd(event) + call t_stopf('MaintenanceResp') end if @@ -426,23 +411,21 @@ subroutine EcosystemDynNoLeaching1(bounds, & if ( nu_com .ne. 'RD') then ! for P competition purpose, calculate P fluxes that will potentially increase solution P pool ! then competitors take up solution P - event ='PhosphorusWeathering' - call t_start_lnd(event) + call t_startf('PhosphorusWeathering') call PhosphorusWeathering(num_soilc, filter_soilc, cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusWeathering') - event = 'PhosphorusBiochemMin' if (.not. nu_com_phosphatase) then - call t_start_lnd(event) + call t_startf('PhosphorusBiochemMin') call PhosphorusBiochemMin(num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusBiochemMin') else ! nu_com_phosphatase is true - call t_start_lnd(event) + call t_startf('PhosphorusBiochemMin') call PhosphorusBiochemMin_balance(bounds,num_soilc, filter_soilc, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('PhosphorusBiochemMin') end if end if @@ -450,10 +433,9 @@ subroutine EcosystemDynNoLeaching1(bounds, & ! Phosphorus Deposition ! X.SHI ! -------------------------------------------------- - event = 'PhosphorusDeposition' - call t_start_lnd(event) + call t_startf('PhosphorusDeposition') call PhosphorusDeposition(bounds, atm2lnd_vars ) - call t_stop_lnd(event) + call t_stopf('PhosphorusDeposition') !------------------------------------------------------------------------------------------------- ! plfotran: 'decomp_rate_constants' must be calculated before entering "clm_interface" @@ -484,13 +466,12 @@ subroutine EcosystemDynNoLeaching1(bounds, & ! pflotran: call 'Allocation1' to obtain potential N demand for support initial GPP if(.not.use_fates)then - event = 'CNAllocation - phase-1' - call t_start_lnd(event) + call t_startf('CNAllocation - phase-1') call Allocation1_PlantNPDemand (bounds , & num_soilc, filter_soilc, num_soilp, filter_soilp , & photosyns_vars, crop_vars, canopystate_vars, cnstate_vars , & dt, year ) - call t_stop_lnd(event) + call t_stopf('CNAllocation - phase-1') end if @@ -566,7 +547,6 @@ subroutine EcosystemDynNoLeaching2(bounds, & type(solarabs_type) , intent(in) :: solarabs_vars ! type(sedflux_type) , intent(in) :: sedflux_vars - character(len=64) :: event real(r8) :: dt integer :: c13, c14 c13 = 0 @@ -575,8 +555,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & dt = dtime_mod ! Call the main CN routines - event = 'SoilLittDecompAlloc' - call t_start_lnd(event) + call t_startf('SoilLittDecompAlloc') !---------------------------------------------------------------- if(.not.use_elm_interface) then ! directly run elm-bgc @@ -590,8 +569,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & call t_stopf('SoilLittDecompAlloc') - event = 'SoilLittDecompAlloc2' - call t_start_lnd(event) + call t_startf('SoilLittDecompAlloc2') !---------------------------------------------------------------- ! SoilLittDecompAlloc2 is called by both elm-bgc & pflotran ! pflotran: call 'SoilLittDecompAlloc2' to calculate some diagnostic variables and 'fpg' for plant N uptake @@ -601,7 +579,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & cnstate_vars, ch4_vars, & crop_vars, atm2lnd_vars, & dt ) - call t_stop_lnd(event) + call t_stopf('SoilLittDecompAlloc2') !---------------------------------------------------------------- @@ -615,22 +593,20 @@ subroutine EcosystemDynNoLeaching2(bounds, & ! depends on current time-step fluxes to new growth on the last ! litterfall timestep in deciduous systems - event = 'Phenology' - call t_start_lnd(event) + call t_startf('Phenology') call Phenology(num_soilc, filter_soilc, num_soilp, filter_soilp, & num_pcropp, filter_pcropp, num_ppercropp, filter_ppercropp, doalb, atm2lnd_vars, & crop_vars, canopystate_vars, soilstate_vars, & cnstate_vars, solarabs_vars) - call t_stop_lnd(event) + call t_stopf('Phenology') !-------------------------------------------- ! Growth respiration !-------------------------------------------- - event = 'GrowthResp' - call t_start_lnd(event) + call t_startf('GrowthResp') call GrowthResp(num_soilp, filter_soilp ) - call t_stop_lnd(event) + call t_stopf('GrowthResp') call veg_cf_summary_rr(veg_cf,bounds, num_soilp, filter_soilp, num_soilc, filter_soilc, col_cf) if(use_c13) then @@ -646,21 +622,19 @@ subroutine EcosystemDynNoLeaching2(bounds, & !-------------------------------------------- if( use_dynroot ) then - event = 'RootDynamics' - call t_start_lnd(event) + call t_startf('RootDynamics') call RootDynamics(bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & canopystate_vars, & cnstate_vars, crop_vars, energyflux_vars, soilstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('RootDynamics') end if !-------------------------------------------- ! CNUpdate0 !-------------------------------------------- - event = 'CNUpdate0' - call t_start_lnd(event) + call t_startf('CNUpdate0') call CarbonStateUpdate0(num_soilp, filter_soilp,veg_cs,veg_cf, dt) if ( use_c13 ) then call CarbonStateUpdate0(num_soilp, filter_soilp,c13_veg_cs,c13_veg_cf, dt) @@ -668,30 +642,27 @@ subroutine EcosystemDynNoLeaching2(bounds, & if ( use_c14 ) then call CarbonStateUpdate0(num_soilp, filter_soilp,c14_veg_cs,c14_veg_cf ,dt) end if - call t_stop_lnd(event) + call t_stopf('CNUpdate0') !-------------------------------------------- if(use_pheno_flux_limiter)then - event = 'phenology_flux_limiter' - call t_start_lnd(event) + call t_startf('phenology_flux_limiter') call phenology_flux_limiter(bounds, num_soilc, filter_soilc,& num_soilp, filter_soilp, crop_vars, cnstate_vars, & veg_cf, veg_cs, & c13_veg_cf, c13_veg_cs, & c14_veg_cf, c14_veg_cs, & veg_nf, veg_ns, veg_pf, veg_ps) - call t_stop_lnd(event) + call t_stopf('phenology_flux_limiter') endif - event = 'CNLitterToColumn' - call t_start_lnd(event) + call t_startf('CNLitterToColumn') call CNLitterToColumn(num_soilp, filter_soilp, cnstate_vars ) - call t_stop_lnd(event) + call t_stopf('CNLitterToColumn') !-------------------------------------------- ! Update1 !-------------------------------------------- - event = 'CNUpdate1' - call t_start_lnd(event) + call t_startf('CNUpdate1') if ( use_c13 ) then call CarbonIsoFlux1(num_soilc, filter_soilc, num_soilp, filter_soilp, & @@ -704,7 +675,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & cnstate_vars , & isotope=c14, isocol_cs=c14_col_cs, isoveg_cs=c14_veg_cs, isocol_cf=c14_col_cf, isoveg_cf=c14_veg_cf) end if - call t_stop_lnd(event) + call t_stopf('CNUpdate1') end if ! if(.not.use_fates) @@ -718,8 +689,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & call alm_fates%UpdateLitterFluxes(bounds) end if - event = 'CNUpdate1' - call t_start_lnd(event) + call t_startf('CNUpdate1') call CarbonStateUpdate1(bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & crop_vars, col_cs, veg_cs, col_cf, veg_cf, dt) @@ -740,25 +710,22 @@ subroutine EcosystemDynNoLeaching2(bounds, & cnstate_vars, dt) - call t_stop_lnd(event) + call t_stopf('CNUpdate1') - event = 'SoilLittVertTransp' - call t_start_lnd(event) + call t_startf('SoilLittVertTransp') call SoilLittVertTransp( num_soilc, filter_soilc, & canopystate_vars, cnstate_vars ) - call t_stop_lnd(event) + call t_stopf('SoilLittVertTransp') if(.not.use_fates)then - event = 'CNGapMortality' - call t_start_lnd(event) + call t_startf('CNGapMortality') call GapMortality( num_soilc, filter_soilc, num_soilp, filter_soilp,& cnstate_vars, crop_vars) - call t_stop_lnd(event) + call t_stopf('CNGapMortality') !-------------------------------------------- ! Update2 !-------------------------------------------- - event = 'CNUpdate2' - call t_start_lnd(event) + call t_startf('CNUpdate2') if ( use_c13 ) then call CarbonIsoFlux2(num_soilc, filter_soilc, num_soilp, filter_soilp, & @@ -838,7 +805,7 @@ subroutine EcosystemDynNoLeaching2(bounds, & cnstate_vars) - call t_stop_lnd(event) + call t_stopf('CNUpdate2') else call alm_fates%wrap_WoodProducts(bounds, num_soilc, filter_soilc) @@ -850,10 +817,9 @@ subroutine EcosystemDynNoLeaching2(bounds, & end if if ( ero_ccycle ) then - event = 'ErosionFluxes' - call t_start_lnd(event) + call t_startf('ErosionFluxes') call ErosionFluxes(bounds, num_soilc, filter_soilc, soilstate_vars, sedflux_vars ) - call t_stop_lnd(event) + call t_stopf('ErosionFluxes') end if !-------------------------------------------- diff --git a/components/elm/src/biogeochem/SoilLittDecompMod.F90 b/components/elm/src/biogeochem/SoilLittDecompMod.F90 index cb59aac65034..8cab5b3e23e9 100644 --- a/components/elm/src/biogeochem/SoilLittDecompMod.F90 +++ b/components/elm/src/biogeochem/SoilLittDecompMod.F90 @@ -10,7 +10,6 @@ module SoilLittDecompMod use shr_const_mod , only : SHR_CONST_TKFRZ use decompMod , only : bounds_type use perf_mod , only : t_startf, t_stopf - use perfMod_GPU use elm_varctl , only : iulog, use_lch4, use_century_decomp use elm_varcon , only : dzsoi_decomp use elm_varpar , only : nlevdecomp, ndecomp_cascade_transitions, ndecomp_pools @@ -142,7 +141,6 @@ subroutine SoilLittDecompAlloc (bounds, num_soilc, filter_soilc, & real(r8):: phr_vr(bounds%begc:bounds%endc,1:nlevdecomp) !potential HR (gC/m3/s) real(r8):: hrsum(bounds%begc:bounds%endc,1:nlevdecomp) !sum of HR (gC/m2/s) - character(len=256) :: event !----------------------------------------------------------------------- associate( & @@ -388,14 +386,13 @@ subroutine SoilLittDecompAlloc (bounds, num_soilc, filter_soilc, & ! to resolve the competition between plants and soil heterotrophs ! for available soil mineral N resource. ! in addition, calculate fpi_vr, fpi_p_vr, & fgp - event = 'CNAllocation - phase-2' - call t_start_lnd(event) + call t_startf('CNAllocation - phase-2') call Allocation2_ResolveNPLimit(bounds, & num_soilc, filter_soilc, num_soilp, filter_soilp, & cnstate_vars, & soilstate_vars, dtime, & alm_fates) - call t_stop_lnd(event) + call t_stopf('CNAllocation - phase-2') ! column loop to calculate actual immobilization and decomp rates, following @@ -607,7 +604,6 @@ subroutine SoilLittDecompAlloc2 (bounds, num_soilc, filter_soilc, num_soilp, fil ! For methane code real(r8):: hrsum(bounds%begc:bounds%endc,1:nlevdecomp) !sum of HR (gC/m2/s) - character(len=256) :: event !----------------------------------------------------------------------- associate( & @@ -760,8 +756,7 @@ subroutine SoilLittDecompAlloc2 (bounds, num_soilc, filter_soilc, num_soilp, fil !------------------------------------------------------------------ ! phase-3 Allocation for plants if(.not.use_fates)then - event = 'CNAllocation - phase-3' - call t_start_lnd(event) + call t_startf('CNAllocation - phase-3') if(nu_com .eq. 'RD') then call PlantCNPAlloc_RD(bounds , & num_soilc, filter_soilc, num_soilp, filter_soilp , & @@ -773,7 +768,7 @@ subroutine SoilLittDecompAlloc2 (bounds, num_soilc, filter_soilc, num_soilp, fil canopystate_vars , & cnstate_vars, crop_vars, dt) endif - call t_stop_lnd(event) + call t_stopf('CNAllocation - phase-3') end if !------------------------------------------------------------------ diff --git a/components/elm/src/biogeophys/CanopyFluxesMod.F90 b/components/elm/src/biogeophys/CanopyFluxesMod.F90 index 1c278c75ce97..d29d89323023 100644 --- a/components/elm/src/biogeophys/CanopyFluxesMod.F90 +++ b/components/elm/src/biogeophys/CanopyFluxesMod.F90 @@ -46,7 +46,7 @@ module CanopyFluxesMod !!! using elm_instMod messes with the compilation order use elm_instMod , only : alm_fates, soil_water_retention_curve use TemperatureType , only : temperature_vars - use perfMod_GPU + use perf_mod, only: t_startf, t_stopf use timeinfoMod use spmdmod , only: masterproc ! @@ -316,7 +316,6 @@ subroutine CanopyFluxes(bounds, num_nolakeurbanp, filter_nolakeurbanp, & real(r8) :: prev_tau(bounds%begp:bounds%endp) ! Previous iteration tau real(r8) :: prev_tau_diff(bounds%begp:bounds%endp) ! Previous difference in iteration tau real(r8) :: slope_rad, deg2rad - character(len=64) :: event !! timing event ! Indices for raw and rah integer, parameter :: above_canopy = 1 ! Above canopy @@ -784,8 +783,7 @@ subroutine CanopyFluxes(bounds, num_nolakeurbanp, filter_nolakeurbanp, & fporig(1:fn) = filterp(1:fn) ! Begin stability iteration - event = 'can_iter' - call t_start_lnd(event) + call t_startf('can_iter') ITERATION : do while (itlef <= itmax .and. fn > 0) ! Determine friction velocity, and potential temperature and humidity @@ -1206,7 +1204,7 @@ subroutine CanopyFluxes(bounds, num_nolakeurbanp, filter_nolakeurbanp, & end if end do ITERATION ! End stability iteration - call t_stop_lnd(event) + call t_stopf('can_iter') fn = fnorig filterp(1:fn) = fporig(1:fn) diff --git a/components/elm/src/biogeophys/SoilFluxesMod.F90 b/components/elm/src/biogeophys/SoilFluxesMod.F90 index f3f3df8a9db8..d0043f4d1d76 100644 --- a/components/elm/src/biogeophys/SoilFluxesMod.F90 +++ b/components/elm/src/biogeophys/SoilFluxesMod.F90 @@ -9,7 +9,7 @@ module SoilFluxesMod use decompMod , only : bounds_type use abortutils , only : endrun use elm_varctl , only : iulog, use_firn_percolation_and_compaction, use_finetop_rad - use perfMod_GPU + use perf_mod, only: t_startf, t_stopf use elm_varpar , only : nlevsno, nlevgrnd, nlevurb, max_patch_per_col use atm2lndType , only : atm2lnd_type use CanopyStateType , only : canopystate_type @@ -84,7 +84,6 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & real(r8) :: fsno_eff real(r8) :: temp - character(len=256) :: event !----------------------------------------------------------------------- associate( & @@ -169,8 +168,7 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & deg2rad = SHR_CONST_PI/180._r8 dtime = dtime_mod - event = 'bgp2_loop_1' - call t_start_lnd(event) + call t_startf('bgp2_loop_1') do fc = 1,num_nolakec c = filter_nolakec(fc) j = col_pp%snl(c)+1 @@ -247,9 +245,8 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & end do end do - call t_stop_lnd(event) - event = 'bgp2_loop_2' - call t_start_lnd(event) + call t_stopf('bgp2_loop_1') + call t_startf('bgp2_loop_2') ! Calculate ratio for rescaling pft-level fluxes to meet availability do fc = 1,num_nolakec @@ -375,10 +372,9 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & eflx_lh_grnd(p) = qflx_evap_soi(p) * htvp(c) end do - call t_stop_lnd(event) + call t_stopf('bgp2_loop_2') - event = 'bgp2_loop_3' - call t_start_lnd(event) + call t_startf('bgp2_loop_3') ! Soil Energy balance check do fp = 1,num_nolakep @@ -416,10 +412,9 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & end if end do end do - call t_stop_lnd(event) + call t_stopf('bgp2_loop_3') - event = 'bgp2_loop_4' - call t_start_lnd(event) + call t_startf('bgp2_loop_4') ! Outgoing long-wave radiation from vegetation + ground ! For conservation we put the increase of ground longwave to outgoing ! For urban patches, ulrad=0 and (1-fracveg_nosno)=1, and eflx_lwrad_out and eflx_lwrad_net @@ -471,7 +466,7 @@ subroutine SoilFluxes (bounds, num_urbanl, filter_urbanl, & call p2c(bounds, num_nolakec, filter_nolakec, & errsoi_patch(bounds%begp:bounds%endp), & errsoi_col(bounds%begc:bounds%endc)) - call t_stop_lnd(event) + call t_stopf('bgp2_loop_4') end associate diff --git a/components/elm/src/biogeophys/SoilTemperatureMod.F90 b/components/elm/src/biogeophys/SoilTemperatureMod.F90 index 27a7206b6b7e..bea06e4e89eb 100644 --- a/components/elm/src/biogeophys/SoilTemperatureMod.F90 +++ b/components/elm/src/biogeophys/SoilTemperatureMod.F90 @@ -28,7 +28,7 @@ module SoilTemperatureMod use VegetationType , only : veg_pp use VegetationDataType, only : veg_ef, veg_wf use timeinfoMod - use perfMod_GPU + use perf_mod, only: t_startf, t_stopf use ExternalModelConstants , only : EM_ID_PTM use ExternalModelConstants , only : EM_PTM_TBASED_SOLVE_STAGE use ExternalModelInterfaceMod, only : EMI_Driver @@ -756,7 +756,6 @@ subroutine SolveTemperature(bounds, num_filter, filter, dtime, & real(r8) :: bmatrix(bounds%begc:bounds%endc,nband,-nlevsno:nlevgrnd) ! banded matrix for numerical solution of temperature real(r8) :: rvector(bounds%begc:bounds%endc,-nlevsno:nlevgrnd) ! RHS vector for numerical solution of temperature - character(len=64) :: event !----------------------------------------------------------------------- associate( & @@ -799,12 +798,11 @@ subroutine SolveTemperature(bounds, num_filter, filter, dtime, & ! Solve the system - event = 'SoilTempBandDiag' - call t_start_lnd(event) + call t_startf('SoilTempBandDiag') call BandDiagonal(bounds, -nlevsno, nlevgrnd, jtop(begc:endc), jbot(begc:endc), & num_filter, filter, nband, bmatrix(begc:endc, :, :), & rvector(begc:endc, :), tvector(begc:endc, :)) - call t_stop_lnd(event) + call t_stopf('SoilTempBandDiag') end associate @@ -857,8 +855,6 @@ subroutine SoilThermProp (bounds, num_nolakec, filter_nolakec, & real(r8) :: fl ! volume fraction of liquid or unfrozen water to total water real(r8) :: satw ! relative total water content of soil. real(r8) :: zh2osfc - character(len=64) :: event - real(r8), parameter :: rho_ice = 917._r8 real(r8) :: k_snw_vals(5) real(r8) :: k_snw_tmps(5) @@ -870,8 +866,7 @@ subroutine SoilThermProp (bounds, num_nolakec, filter_nolakec, & data k_snw_coe2(:) /-0.059_r8, 0.015_r8, 0.073_r8, 0.107_r8, 0.147_r8/ data k_snw_coe3(:) /0.0205_r8, 0.0252_r8, 0.0336_r8, 0.0386_r8, 0.0455_r8/ !----------------------------------------------------------------------- - event = 'SoilThermProp' - call t_start_lnd( event ) + call t_startf('SoilThermProp') associate( & snl => col_pp%snl , & ! Input: [integer (:) ] number of snow layers @@ -1077,7 +1072,7 @@ subroutine SoilThermProp (bounds, num_nolakec, filter_nolakec, & end if end do end do - call t_stop_lnd( event ) + call t_stopf('SoilThermProp') end associate @@ -1118,10 +1113,8 @@ subroutine PhaseChangeH2osfc (bounds, num_nolakec, filter_nolakec, & real(r8) :: c1 real(r8) :: c2 - character(len=64) :: event !----------------------------------------------------------------------- - event = 'PhaseChangeH2osfc' - call t_start_lnd( event ) + call t_startf('PhaseChangeH2osfc') associate( & snl => col_pp%snl , & ! Input: [integer (:) ] number of snow layers @@ -1290,7 +1283,7 @@ subroutine PhaseChangeH2osfc (bounds, num_nolakec, filter_nolakec, & endif endif enddo - call t_stop_lnd( event ) + call t_stopf('PhaseChangeH2osfc') end associate @@ -1345,10 +1338,8 @@ subroutine Phasechange_beta (bounds, num_nolakec, filter_nolakec, dhsdT, & real(r8) :: tinc(bounds%begc:bounds%endc,-nlevsno+1:nlevgrnd) !t(n+1)-t(n) (K) real(r8) :: smp !frozen water potential (mm) - character(len=64) :: event !----------------------------------------------------------------------- - event = 'PhaseChangebeta' - call t_start_lnd( event ) + call t_startf('PhaseChangebeta') associate( & snl => col_pp%snl , & ! Input: [integer (:) ] number of snow layers @@ -1689,7 +1680,7 @@ subroutine Phasechange_beta (bounds, num_nolakec, filter_nolakec, dhsdT, & end if end do - call t_stop_lnd( event ) + call t_stopf('PhaseChangebeta') do j = -nlevsno+1,0 do fc = 1,num_nolakec c = filter_nolakec(fc) diff --git a/components/elm/src/external_models/fates b/components/elm/src/external_models/fates index 55ec6c776806..e027a4030d2a 160000 --- a/components/elm/src/external_models/fates +++ b/components/elm/src/external_models/fates @@ -1 +1 @@ -Subproject commit 55ec6c7768061148fc107a0810d3690763d85320 +Subproject commit e027a4030d2a0f09039fb337ad67ced7461dd4f0 diff --git a/components/elm/src/main/elm_driver.F90 b/components/elm/src/main/elm_driver.F90 index e9ee1045739e..76f34cd4858f 100644 --- a/components/elm/src/main/elm_driver.F90 +++ b/components/elm/src/main/elm_driver.F90 @@ -1310,7 +1310,7 @@ subroutine elm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate) call alm_fates%WrapUpdateFatesRmean(nc) ! Update high-frequency history diagnostics for FATES - call alm_fates%wrap_update_hifrq_hist(bounds_clump) + call alm_fates%wrap_update_hifrq_hist(bounds_clump, solarabs_vars) if ( is_beg_curr_day() ) then ! run ED at the start of each day call alm_fates%dynamics_driv( bounds_clump, top_as, & top_af, atm2lnd_vars, soilstate_vars, & diff --git a/components/elm/src/main/elmfates_interfaceMod.F90 b/components/elm/src/main/elmfates_interfaceMod.F90 index 9911879bbcfa..2e1af69c5f0a 100644 --- a/components/elm/src/main/elmfates_interfaceMod.F90 +++ b/components/elm/src/main/elmfates_interfaceMod.F90 @@ -123,7 +123,7 @@ module ELMFatesInterfaceMod use ColumnType , only : col_pp use ColumnDataType , only : col_es, col_ws, col_wf, col_cs, col_cf use ColumnDataType , only : col_nf, col_pf - use VegetationDataType, only : veg_es, veg_wf, veg_ws + use VegetationDataType, only : veg_es, veg_wf, veg_ws, veg_ef use LandunitType , only : lun_pp use landunit_varcon , only : istsoil @@ -3074,24 +3074,31 @@ end subroutine WrapUpdateFatesSeedInOut ! ====================================================================================== - subroutine wrap_update_hifrq_hist(this, bounds_clump ) + subroutine wrap_update_hifrq_hist(this, bounds_clump, solarabs_inst) ! Arguments class(hlm_fates_interface_type), intent(inout) :: this type(bounds_type), intent(in) :: bounds_clump + type(solarabs_type) , intent(in) :: solarabs_inst ! locals real(r8) :: dtime integer :: nstep logical :: is_beg_day integer :: s,c,nc + integer :: ifp,p associate(& hr => col_cf%hr, & ! (gC/m2/s) total heterotrophic respiration totsomc => col_cs%totsomc, & ! (gC/m2) total soil organic matter carbon - totlitc => col_cs%totlitc) ! (gC/m2) total litter carbon in BGC pools + totlitc => col_cs%totlitc, & ! (gC/m2) total litter carbon in BGC pools + eflx_lh_tot => veg_ef%eflx_lh_tot, & ! (W/m2) latent heat flux + eflx_sh_tot => veg_ef%eflx_sh_tot, & ! (W/m2) sensible heat flux + fsa_patch => solarabs_inst%fsa_patch, & ! (W/m2) absorbed solar flux + eflx_lwrad_net=> veg_ef%eflx_lwrad_net, & ! (W/m2) net longwave radiative flux + t_ref2m => veg_es%t_ref2m) ! (K) 2-m air temperature nc = bounds_clump%clump_index dtime = real(get_step_size(),r8) @@ -3104,6 +3111,22 @@ subroutine wrap_update_hifrq_hist(this, bounds_clump ) this%fates(nc)%bc_in(s)%tot_litc = totlitc(c) end do + ! summarize biophysical variables that we want ot output on FATES dimensions + + do s = 1, this%fates(nc)%nsites + c = this%f2hmap(nc)%fcolumn(s) + do ifp = 0, this%fates(nc)%sites(s)%youngest_patch%patchno !!!CDK was 1 + p = ifp+col_pp%pfti(c) + + this%fates(nc)%bc_in(s)%lhflux_pa(ifp) = eflx_lh_tot(p) + this%fates(nc)%bc_in(s)%shflux_pa(ifp) = eflx_sh_tot(p) + this%fates(nc)%bc_in(s)%swabs_pa(ifp) = fsa_patch(p) + this%fates(nc)%bc_in(s)%netlw_pa(ifp) = eflx_lwrad_net(p) + this%fates(nc)%bc_in(s)%t2m_pa(ifp) = t_ref2m(p) + + end do + end do + ! Update history variables that track these variables call fates_hist%update_history_hifrq(nc, & this%fates(nc)%nsites, & diff --git a/components/homme/cmake/SetCompilerFlags.cmake b/components/homme/cmake/SetCompilerFlags.cmake index 3d7da0498ba7..37286d946cdc 100644 --- a/components/homme/cmake/SetCompilerFlags.cmake +++ b/components/homme/cmake/SetCompilerFlags.cmake @@ -26,7 +26,7 @@ ELSE () ADD_DEFINITIONS(-DCPRPGI) ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL PathScale) SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -extend-source") - ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL Intel) + ELSEIF (CMAKE_Fortran_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -assume byterecl") SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fp-model fast -ftz") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fp-model fast -ftz") @@ -129,7 +129,7 @@ ELSE () ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL PGI) SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -O2") ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL PathScale) - ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL Intel) + ELSEIF (CMAKE_Fortran_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -O3") #SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -mavx -DTEMP_INTEL_COMPILER_WORKAROUND_001") ELSEIF (CMAKE_Fortran_COMPILER_ID STREQUAL XL) @@ -146,7 +146,7 @@ ELSE () SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") ELSEIF (CMAKE_C_COMPILER_ID STREQUAL PGI) ELSEIF (CMAKE_C_COMPILER_ID STREQUAL PathScale) - ELSEIF (CMAKE_C_COMPILER_ID STREQUAL Intel) + ELSEIF (CMAKE_C_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3") #SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx -DTEMP_INTEL_COMPILER_WORKAROUND_001") ELSEIF (CMAKE_C_COMPILER_ID STREQUAL XL) @@ -163,7 +163,7 @@ ELSE () SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG") ELSEIF (CMAKE_CXX_COMPILER_ID STREQUAL PGI) ELSEIF (CMAKE_CXX_COMPILER_ID STREQUAL PathScale) - ELSEIF (CMAKE_CXX_COMPILER_ID STREQUAL Intel) + ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG") #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx -DTEMP_INTEL_COMPILER_WORKAROUND_001") ELSEIF (CMAKE_CXX_COMPILER_ID STREQUAL XL) @@ -292,7 +292,7 @@ ENDIF () OPTION(ENABLE_INTEL_PHI "Whether to build with Intel Xeon Phi (MIC) support" FALSE) IF (ENABLE_INTEL_PHI) - IF (NOT ${CMAKE_Fortran_COMPILER_ID} STREQUAL Intel) + IF (NOT CMAKE_Fortran_COMPILER_ID MATCHES "^(Intel|IntelLLVM)$") MESSAGE(FATAL_ERROR "Intel Phi acceleration only supported through the Intel compiler") ELSE () SET(INTEL_PHI_FLAGS "-mmic") diff --git a/components/homme/cmake/machineFiles/alvarez-cpu-bfb.cmake b/components/homme/cmake/machineFiles/alvarez-cpu-bfb.cmake new file mode 100644 index 000000000000..6d41a8227953 --- /dev/null +++ b/components/homme/cmake/machineFiles/alvarez-cpu-bfb.cmake @@ -0,0 +1,54 @@ +# CMake initial cache file +# +# This machine file works with either Intel or gnu +# (selected by which modules are loaded) +# +# +# Perlmutter generic MPI enabled compiler wrappers: +SET (CMAKE_Fortran_COMPILER ftn CACHE FILEPATH "") +SET (CMAKE_C_COMPILER cc CACHE FILEPATH "") +SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") + + +# Set kokkos arch, to get correct avx flags +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") + +SET (WITH_PNETCDF FALSE CACHE FILEPATH "") + +EXECUTE_PROCESS(COMMAND nf-config --prefix + RESULT_VARIABLE NFCONFIG_RESULT + OUTPUT_VARIABLE NFCONFIG_OUTPUT + ERROR_VARIABLE NFCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_Fortran_PATH "${NFCONFIG_OUTPUT}" CACHE STRING "") + +EXECUTE_PROCESS(COMMAND nc-config --prefix + RESULT_VARIABLE NCCONFIG_RESULT + OUTPUT_VARIABLE NCCONFIG_OUTPUT + ERROR_VARIABLE NCCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_C_PATH "${NCCONFIG_OUTPUT}" CACHE STRING "") + +SET (USE_QUEUING FALSE CACHE BOOL "") +# for standalone HOMME builds: +SET(CPRNC_DIR /global/cfs/cdirs/e3sm/tools/cprnc CACHE FILEPATH "") + +SET (HOMMEXX_BFB_TESTING TRUE CACHE BOOL "") +SET (BUILD_HOMME_PREQX_KOKKOS TRUE CACHE BOOL "") +SET (BUILD_HOMME_THETA_KOKKOS TRUE CACHE BOOL "") +SET (HOMME_TESTING_PROFILE "short" CACHE STRING "") + +SET (HOMME_FIND_BLASLAPACK TRUE CACHE BOOL "") +IF(DEFINED ENV{MKLROOT}) + SET (HOMME_USE_MKL "TRUE" CACHE FILEPATH "") + # turn on additional intel compiler flags + SET (ADD_Fortran_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_C_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_CXX_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") +ENDIF() + + +SET(USE_MPIEXEC "srun" CACHE STRING "") +SET(USE_MPI_OPTIONS "-K --cpu_bind=cores" CACHE STRING "") diff --git a/components/homme/cmake/machineFiles/alvarez-cpu.cmake b/components/homme/cmake/machineFiles/alvarez-cpu.cmake new file mode 100644 index 000000000000..782445545625 --- /dev/null +++ b/components/homme/cmake/machineFiles/alvarez-cpu.cmake @@ -0,0 +1,49 @@ +# CMake initial cache file +# +# This machine file works with either Intel or gnu +# (selected by which modules are loaded) +# +# +# Perlmutter generic MPI enabled compiler wrappers: +SET (CMAKE_Fortran_COMPILER ftn CACHE FILEPATH "") +SET (CMAKE_C_COMPILER cc CACHE FILEPATH "") +SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") + + +# Set kokkos arch, to get correct avx flags +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") + +SET (WITH_PNETCDF FALSE CACHE FILEPATH "") + +EXECUTE_PROCESS(COMMAND nf-config --prefix + RESULT_VARIABLE NFCONFIG_RESULT + OUTPUT_VARIABLE NFCONFIG_OUTPUT + ERROR_VARIABLE NFCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_Fortran_PATH "${NFCONFIG_OUTPUT}" CACHE STRING "") + +EXECUTE_PROCESS(COMMAND nc-config --prefix + RESULT_VARIABLE NCCONFIG_RESULT + OUTPUT_VARIABLE NCCONFIG_OUTPUT + ERROR_VARIABLE NCCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_C_PATH "${NCCONFIG_OUTPUT}" CACHE STRING "") + +SET (USE_QUEUING FALSE CACHE BOOL "") +# for standalone HOMME builds: +SET(CPRNC_DIR /global/cfs/cdirs/e3sm/tools/cprnc CACHE FILEPATH "") + +SET (HOMME_FIND_BLASLAPACK TRUE CACHE BOOL "") +IF(DEFINED ENV{MKLROOT}) + SET (HOMME_USE_MKL "TRUE" CACHE FILEPATH "") + # turn on additional intel compiler flags + SET (ADD_Fortran_FLAGS "-traceback" CACHE STRING "") + SET (ADD_C_FLAGS "-traceback" CACHE STRING "") + SET (ADD_CXX_FLAGS "-traceback" CACHE STRING "") +ENDIF() + + +SET(USE_MPIEXEC "srun" CACHE STRING "") +SET(USE_MPI_OPTIONS "-K --cpu_bind=cores" CACHE STRING "") diff --git a/components/homme/cmake/machineFiles/muller-cpu-bfb.cmake b/components/homme/cmake/machineFiles/muller-cpu-bfb.cmake new file mode 100644 index 000000000000..6d41a8227953 --- /dev/null +++ b/components/homme/cmake/machineFiles/muller-cpu-bfb.cmake @@ -0,0 +1,54 @@ +# CMake initial cache file +# +# This machine file works with either Intel or gnu +# (selected by which modules are loaded) +# +# +# Perlmutter generic MPI enabled compiler wrappers: +SET (CMAKE_Fortran_COMPILER ftn CACHE FILEPATH "") +SET (CMAKE_C_COMPILER cc CACHE FILEPATH "") +SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") + + +# Set kokkos arch, to get correct avx flags +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") + +SET (WITH_PNETCDF FALSE CACHE FILEPATH "") + +EXECUTE_PROCESS(COMMAND nf-config --prefix + RESULT_VARIABLE NFCONFIG_RESULT + OUTPUT_VARIABLE NFCONFIG_OUTPUT + ERROR_VARIABLE NFCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_Fortran_PATH "${NFCONFIG_OUTPUT}" CACHE STRING "") + +EXECUTE_PROCESS(COMMAND nc-config --prefix + RESULT_VARIABLE NCCONFIG_RESULT + OUTPUT_VARIABLE NCCONFIG_OUTPUT + ERROR_VARIABLE NCCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_C_PATH "${NCCONFIG_OUTPUT}" CACHE STRING "") + +SET (USE_QUEUING FALSE CACHE BOOL "") +# for standalone HOMME builds: +SET(CPRNC_DIR /global/cfs/cdirs/e3sm/tools/cprnc CACHE FILEPATH "") + +SET (HOMMEXX_BFB_TESTING TRUE CACHE BOOL "") +SET (BUILD_HOMME_PREQX_KOKKOS TRUE CACHE BOOL "") +SET (BUILD_HOMME_THETA_KOKKOS TRUE CACHE BOOL "") +SET (HOMME_TESTING_PROFILE "short" CACHE STRING "") + +SET (HOMME_FIND_BLASLAPACK TRUE CACHE BOOL "") +IF(DEFINED ENV{MKLROOT}) + SET (HOMME_USE_MKL "TRUE" CACHE FILEPATH "") + # turn on additional intel compiler flags + SET (ADD_Fortran_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_C_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_CXX_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") +ENDIF() + + +SET(USE_MPIEXEC "srun" CACHE STRING "") +SET(USE_MPI_OPTIONS "-K --cpu_bind=cores" CACHE STRING "") diff --git a/components/homme/cmake/machineFiles/muller-cpu.cmake b/components/homme/cmake/machineFiles/muller-cpu.cmake new file mode 100644 index 000000000000..782445545625 --- /dev/null +++ b/components/homme/cmake/machineFiles/muller-cpu.cmake @@ -0,0 +1,49 @@ +# CMake initial cache file +# +# This machine file works with either Intel or gnu +# (selected by which modules are loaded) +# +# +# Perlmutter generic MPI enabled compiler wrappers: +SET (CMAKE_Fortran_COMPILER ftn CACHE FILEPATH "") +SET (CMAKE_C_COMPILER cc CACHE FILEPATH "") +SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") + + +# Set kokkos arch, to get correct avx flags +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") + +SET (WITH_PNETCDF FALSE CACHE FILEPATH "") + +EXECUTE_PROCESS(COMMAND nf-config --prefix + RESULT_VARIABLE NFCONFIG_RESULT + OUTPUT_VARIABLE NFCONFIG_OUTPUT + ERROR_VARIABLE NFCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_Fortran_PATH "${NFCONFIG_OUTPUT}" CACHE STRING "") + +EXECUTE_PROCESS(COMMAND nc-config --prefix + RESULT_VARIABLE NCCONFIG_RESULT + OUTPUT_VARIABLE NCCONFIG_OUTPUT + ERROR_VARIABLE NCCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_C_PATH "${NCCONFIG_OUTPUT}" CACHE STRING "") + +SET (USE_QUEUING FALSE CACHE BOOL "") +# for standalone HOMME builds: +SET(CPRNC_DIR /global/cfs/cdirs/e3sm/tools/cprnc CACHE FILEPATH "") + +SET (HOMME_FIND_BLASLAPACK TRUE CACHE BOOL "") +IF(DEFINED ENV{MKLROOT}) + SET (HOMME_USE_MKL "TRUE" CACHE FILEPATH "") + # turn on additional intel compiler flags + SET (ADD_Fortran_FLAGS "-traceback" CACHE STRING "") + SET (ADD_C_FLAGS "-traceback" CACHE STRING "") + SET (ADD_CXX_FLAGS "-traceback" CACHE STRING "") +ENDIF() + + +SET(USE_MPIEXEC "srun" CACHE STRING "") +SET(USE_MPI_OPTIONS "-K --cpu_bind=cores" CACHE STRING "") diff --git a/components/homme/cmake/machineFiles/pm-cpu-bfb.cmake b/components/homme/cmake/machineFiles/pm-cpu-bfb.cmake new file mode 100644 index 000000000000..6d41a8227953 --- /dev/null +++ b/components/homme/cmake/machineFiles/pm-cpu-bfb.cmake @@ -0,0 +1,54 @@ +# CMake initial cache file +# +# This machine file works with either Intel or gnu +# (selected by which modules are loaded) +# +# +# Perlmutter generic MPI enabled compiler wrappers: +SET (CMAKE_Fortran_COMPILER ftn CACHE FILEPATH "") +SET (CMAKE_C_COMPILER cc CACHE FILEPATH "") +SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") + + +# Set kokkos arch, to get correct avx flags +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") + +SET (WITH_PNETCDF FALSE CACHE FILEPATH "") + +EXECUTE_PROCESS(COMMAND nf-config --prefix + RESULT_VARIABLE NFCONFIG_RESULT + OUTPUT_VARIABLE NFCONFIG_OUTPUT + ERROR_VARIABLE NFCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_Fortran_PATH "${NFCONFIG_OUTPUT}" CACHE STRING "") + +EXECUTE_PROCESS(COMMAND nc-config --prefix + RESULT_VARIABLE NCCONFIG_RESULT + OUTPUT_VARIABLE NCCONFIG_OUTPUT + ERROR_VARIABLE NCCONFIG_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +SET (NetCDF_C_PATH "${NCCONFIG_OUTPUT}" CACHE STRING "") + +SET (USE_QUEUING FALSE CACHE BOOL "") +# for standalone HOMME builds: +SET(CPRNC_DIR /global/cfs/cdirs/e3sm/tools/cprnc CACHE FILEPATH "") + +SET (HOMMEXX_BFB_TESTING TRUE CACHE BOOL "") +SET (BUILD_HOMME_PREQX_KOKKOS TRUE CACHE BOOL "") +SET (BUILD_HOMME_THETA_KOKKOS TRUE CACHE BOOL "") +SET (HOMME_TESTING_PROFILE "short" CACHE STRING "") + +SET (HOMME_FIND_BLASLAPACK TRUE CACHE BOOL "") +IF(DEFINED ENV{MKLROOT}) + SET (HOMME_USE_MKL "TRUE" CACHE FILEPATH "") + # turn on additional intel compiler flags + SET (ADD_Fortran_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_C_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") + SET (ADD_CXX_FLAGS "-traceback -fp-model strict -O1" CACHE STRING "") +ENDIF() + + +SET(USE_MPIEXEC "srun" CACHE STRING "") +SET(USE_MPI_OPTIONS "-K --cpu_bind=cores" CACHE STRING "") diff --git a/components/homme/cmake/machineFiles/pm-cpu.cmake b/components/homme/cmake/machineFiles/pm-cpu.cmake index c3b8d36d3178..782445545625 100644 --- a/components/homme/cmake/machineFiles/pm-cpu.cmake +++ b/components/homme/cmake/machineFiles/pm-cpu.cmake @@ -11,7 +11,7 @@ SET (CMAKE_CXX_COMPILER CC CACHE FILEPATH "") # Set kokkos arch, to get correct avx flags -SET (Kokkos_ARCH_ZEN2 ON CACHE BOOL "") +SET (Kokkos_ARCH_ZEN3 ON CACHE BOOL "") SET (WITH_PNETCDF FALSE CACHE FILEPATH "") diff --git a/components/mpas-framework/src/framework/mpas_log.F b/components/mpas-framework/src/framework/mpas_log.F index 69ac45f1f1a7..84181cb89bd2 100644 --- a/components/mpas-framework/src/framework/mpas_log.F +++ b/components/mpas-framework/src/framework/mpas_log.F @@ -824,8 +824,9 @@ subroutine log_abort() ! local variables !----------------------------------------------------------------- #ifdef _MPI - integer :: mpi_ierr, mpi_errcode, err + integer :: mpi_ierr, mpi_errcode #endif + integer :: err character(len=8) :: date character(len=10) :: time diff --git a/components/mpas-framework/src/framework/xml_stream_parser.c b/components/mpas-framework/src/framework/xml_stream_parser.c index 77068b7e0227..1f60abb3d78a 100644 --- a/components/mpas-framework/src/framework/xml_stream_parser.c +++ b/components/mpas-framework/src/framework/xml_stream_parser.c @@ -459,6 +459,146 @@ int attribute_check(ezxml_t stream) } +/********************************************************************************* + * + * Function: has_attribute_name + * + * Returns non-zero if attr_name appears in the allowed_names list. + * + *********************************************************************************/ +int has_attribute_name(const char *attr_name, const char **allowed_names, int n_allowed) +{ + int i; + + for (i = 0; i < n_allowed; i++) { + if (strcmp(attr_name, allowed_names[i]) == 0) { + return 1; + } + } + + return 0; +} + + +/********************************************************************************* + * + * Function: unknown_attribute_check + * + * Checks that all attributes of a tag are part of an allowed list. + * + *********************************************************************************/ +int unknown_attribute_check(ezxml_t xml, const char *tag_name, const char **allowed_names, int n_allowed) +{ + int i; + const char *stream_name; + char msgbuf[MSGSIZE]; + + if (xml == NULL || xml->attr == NULL) { + return 0; + } + + stream_name = ezxml_attr(xml, "name"); + + for (i = 0; xml->attr[i] != NULL; i += 2) { + if (!has_attribute_name(xml->attr[i], allowed_names, n_allowed)) { + if (stream_name != NULL) { + snprintf(msgbuf, MSGSIZE, "tag \"%s\" with name \"%s\" has unrecognized attribute \"%s\".", tag_name, stream_name, xml->attr[i]); + } + else { + snprintf(msgbuf, MSGSIZE, "tag \"%s\" has unrecognized attribute \"%s\".", tag_name, xml->attr[i]); + } + fmt_err(msgbuf); + return 1; + } + } + + return 0; +} + + +/********************************************************************************* + * + * Function: has_child_tag_name + * + * Returns non-zero if child_name appears in the allowed_names list. + * + *********************************************************************************/ +int has_child_tag_name(const char *child_name, const char **allowed_names, int n_allowed) +{ + int i; + + for (i = 0; i < n_allowed; i++) { + if (strcmp(child_name, allowed_names[i]) == 0) { + return 1; + } + } + + return 0; +} + + +/********************************************************************************* + * + * Function: unknown_child_check + * + * Checks that all child tags of an element are part of an allowed list. + * + *********************************************************************************/ +int unknown_child_check(ezxml_t xml, const char *tag_name, const char **allowed_names, int n_allowed) +{ + ezxml_t child; + const char *stream_name; + char msgbuf[MSGSIZE]; + + if (xml == NULL || xml->child == NULL) { + return 0; + } + + stream_name = ezxml_attr(xml, "name"); + + for (child = xml->child; child != NULL; child = child->ordered) { + if (!has_child_tag_name(child->name, allowed_names, n_allowed)) { + if (stream_name != NULL) { + snprintf(msgbuf, MSGSIZE, "tag \"%s\" with name \"%s\" has unrecognized child tag \"%s\".", tag_name, stream_name, child->name); + } + else { + snprintf(msgbuf, MSGSIZE, "tag \"%s\" has unrecognized child tag \"%s\".", tag_name, child->name); + } + fmt_err(msgbuf); + return 1; + } + } + + return 0; +} + + +/********************************************************************************* + * + * Function: check_streams_root + * + * Checks that the root element of the streams XML is . + * + *********************************************************************************/ +int check_streams_root(ezxml_t streams) +{ + char msgbuf[MSGSIZE]; + + if (streams == NULL || streams->name == NULL) { + fmt_err("run-time I/O config has an invalid or missing root element."); + return 1; + } + + if (strcmp(streams->name, "streams") != 0) { + snprintf(msgbuf, MSGSIZE, "run-time I/O config root element must be \"streams\" but found \"%s\".", streams->name); + fmt_err(msgbuf); + return 1; + } + + return 0; +} + + /********************************************************************************* * * Function: uniqueness_check @@ -512,13 +652,75 @@ int check_streams(ezxml_t streams) ezxml_t stream2_xml; ezxml_t test_xml; ezxml_t test2_xml; + ezxml_t var_xml; + ezxml_t vararray_xml; + ezxml_t varstruct_xml; + ezxml_t substream_xml; const char *name; const char *filename; + static const char *stream_attrs[] = { + "name", "type", "filename_template", "filename_interval", "input_interval", "output_interval", + "reference_time", "record_interval", "precision", "packages", "clobber_mode", "useMissingValMask", + "io_type", "runtime_format" + }; + static const char *member_attrs[] = {"name", "packages"}; + static const char *top_children[] = {"stream", "immutable_stream"}; + static const char *stream_children[] = {"file", "var", "var_array", "var_struct", "stream"}; + static const char *no_children[] = {}; char msgbuf[MSGSIZE]; + if (check_streams_root(streams) != 0) { + return 1; + } + + if (unknown_child_check(streams, "streams", top_children, 2) != 0) { + return 1; + } + /* Check immutable streams */ for (stream_xml = ezxml_child(streams, "immutable_stream"); stream_xml; stream_xml = ezxml_next(stream_xml)) { + if (unknown_child_check(stream_xml, "immutable_stream", stream_children, 5) != 0) { + return 1; + } + + if (unknown_attribute_check(stream_xml, "immutable_stream", stream_attrs, 14) != 0) { + return 1; + } + + for (var_xml = ezxml_child(stream_xml, "var"); var_xml; var_xml = ezxml_next(var_xml)) { + if (unknown_child_check(var_xml, "var", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(var_xml, "var", member_attrs, 2) != 0) { + return 1; + } + } + for (vararray_xml = ezxml_child(stream_xml, "var_array"); vararray_xml; vararray_xml = ezxml_next(vararray_xml)) { + if (unknown_child_check(vararray_xml, "var_array", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(vararray_xml, "var_array", member_attrs, 2) != 0) { + return 1; + } + } + for (varstruct_xml = ezxml_child(stream_xml, "var_struct"); varstruct_xml; varstruct_xml = ezxml_next(varstruct_xml)) { + if (unknown_child_check(varstruct_xml, "var_struct", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(varstruct_xml, "var_struct", member_attrs, 2) != 0) { + return 1; + } + } + for (substream_xml = ezxml_child(stream_xml, "stream"); substream_xml; substream_xml = ezxml_next(substream_xml)) { + if (unknown_child_check(substream_xml, "stream", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(substream_xml, "stream", member_attrs, 2) != 0) { + return 1; + } + } + if (attribute_check(stream_xml) != 0) { return 1; } @@ -538,6 +740,55 @@ int check_streams(ezxml_t streams) for (stream_xml = ezxml_child(streams, "stream"); stream_xml; stream_xml = ezxml_next(stream_xml)) { name = ezxml_attr(stream_xml, "name"); + if (unknown_child_check(stream_xml, "stream", stream_children, 5) != 0) { + return 1; + } + + if (unknown_attribute_check(stream_xml, "stream", stream_attrs, 14) != 0) { + return 1; + } + + for (test_xml = ezxml_child(stream_xml, "file"); test_xml; test_xml = ezxml_next(test_xml)) { + if (unknown_child_check(test_xml, "file", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(test_xml, "file", member_attrs, 2) != 0) { + return 1; + } + } + for (var_xml = ezxml_child(stream_xml, "var"); var_xml; var_xml = ezxml_next(var_xml)) { + if (unknown_child_check(var_xml, "var", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(var_xml, "var", member_attrs, 2) != 0) { + return 1; + } + } + for (vararray_xml = ezxml_child(stream_xml, "var_array"); vararray_xml; vararray_xml = ezxml_next(vararray_xml)) { + if (unknown_child_check(vararray_xml, "var_array", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(vararray_xml, "var_array", member_attrs, 2) != 0) { + return 1; + } + } + for (varstruct_xml = ezxml_child(stream_xml, "var_struct"); varstruct_xml; varstruct_xml = ezxml_next(varstruct_xml)) { + if (unknown_child_check(varstruct_xml, "var_struct", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(varstruct_xml, "var_struct", member_attrs, 2) != 0) { + return 1; + } + } + for (substream_xml = ezxml_child(stream_xml, "stream"); substream_xml; substream_xml = ezxml_next(substream_xml)) { + if (unknown_child_check(substream_xml, "stream", no_children, 0) != 0) { + return 1; + } + if (unknown_attribute_check(substream_xml, "stream", member_attrs, 2) != 0) { + return 1; + } + } + if (attribute_check(stream_xml) != 0) { return 1; } @@ -1090,6 +1341,11 @@ void xml_stream_parser(char *fname, void *manager, int *mpi_comm, int *status) return; } + if (xml_syntax_check(xml_buf, bufsize) != 0) { + *status = 1; + return; + } + streams = ezxml_parse_str(xml_buf, bufsize); if (!streams) { snprintf(msgbuf, MSGSIZE, "Problems encountered while parsing run-time I/O config file %s", fname); @@ -1098,6 +1354,11 @@ void xml_stream_parser(char *fname, void *manager, int *mpi_comm, int *status) return; } + if (check_streams(streams) != 0) { + *status = 1; + return; + } + err = 0; /* First, handle changes to immutable stream filename templates, intervals, etc. */ diff --git a/components/mpas-ocean/cime_config/buildnml b/components/mpas-ocean/cime_config/buildnml index cfcc1bbaa939..62ef671b6bed 100755 --- a/components/mpas-ocean/cime_config/buildnml +++ b/components/mpas-ocean/cime_config/buildnml @@ -885,7 +885,7 @@ def buildnml(case, caseroot, compname): lines.append(' filename_interval="00-01-00_00:00:00"') lines.append(' reference_time="01-01-01_00:00:00"') lines.append(' output_interval="none"') - lines.append(' ulobber_mode="truncate"') + lines.append(' clobber_mode="truncate"') lines.append(' packages="zonalMeanAMPKG">') lines.append('') lines.append(' ') diff --git a/components/mpas-seaice/bld/build-namelist b/components/mpas-seaice/bld/build-namelist index 462454f828d5..39b2f342f123 100755 --- a/components/mpas-seaice/bld/build-namelist +++ b/components/mpas-seaice/bld/build-namelist @@ -422,6 +422,8 @@ my $RUN_STARTDATE = "$xmlvars{'RUN_STARTDATE'}"; my $START_TOD = "$xmlvars{'START_TOD'}"; my $RUN_REFDATE = "$xmlvars{'RUN_REFDATE'}"; my $CONTINUE_RUN = "$xmlvars{'CONTINUE_RUN'}"; +my $wave_spec = "$xmlvars{'WAV_SPEC'}"; +my $wave_comp = "$xmlvars{'COMP_WAV'}"; my $SSTICE_GRID_FILENAME; my $SSTICE_DATA_FILENAME; @@ -526,9 +528,22 @@ add_default($nl, 'config_restart_timestamp_name'); ############################## add_default($nl, 'config_nCategories'); -add_default($nl, 'config_nFloeCategories'); add_default($nl, 'config_nIceLayers'); add_default($nl, 'config_nSnowLayers'); +if ($wave_comp eq 'ww3' or $wave_comp eq 'dwav') { + add_default($nl, 'config_nFloeCategories', 'val'=>"12"); +} else { + add_default($nl, 'config_nFloeCategories'); +} +if ($wave_spec eq 'sp25x36') { + add_default($nl, 'config_nFrequencies', 'val'=>"25"); +} elsif ($wave_spec eq 'sp36x36' or $wave_comp eq 'dwav') { + add_default($nl, 'config_nFrequencies', 'val'=>"36"); +} elsif ($wave_spec eq 'sp50x36') { + add_default($nl, 'config_nFrequencies', 'val'=>"50"); +} else { + add_default($nl, 'config_nFrequencies'); +} ############################## # Namelist group: initialize # @@ -635,6 +650,11 @@ if ($ice_bgc eq 'ice_bgc') { add_default($nl, 'config_use_column_itd_thermodynamics'); add_default($nl, 'config_use_column_ridging'); add_default($nl, 'config_use_column_snow_tracers'); +if ($wave_comp eq 'ww3' or $wave_comp eq 'dwav') { + add_default($nl, 'config_use_column_waves', 'val'=>".true."); +} else { + add_default($nl, 'config_use_column_waves'); +} ################################## # Namelist group: column_tracers # @@ -649,7 +669,37 @@ add_default($nl, 'config_use_aerosols'); add_default($nl, 'config_use_effective_snow_density'); add_default($nl, 'config_use_snow_grain_radius'); add_default($nl, 'config_use_special_boundaries_tracers'); -add_default($nl, 'config_use_floe_size_distribution'); +if ($wave_comp eq 'ww3' or $wave_comp eq 'dwav') { + add_default($nl, 'config_use_floe_size_distribution', 'val'=>".true."); +} else { + add_default($nl, 'config_use_floe_size_distribution'); +} + +################################## +# Namelist group: wave_config # +################################## +# the frequency values below should not be modified. +# They are defined to match available WW3 configurations +if ($wave_spec eq 'sp25x36') { + add_default($nl, 'config_xfreq', 'val'=>"1.147"); + add_default($nl, 'config_freq1', 'val'=>"0.035"); +} elsif ($wave_spec eq 'sp36x36' or $wave_comp eq 'dwav') { + add_default($nl, 'config_xfreq', 'val'=>"1.10"); + add_default($nl, 'config_freq1', 'val'=>"0.035"); +} elsif ($wave_spec eq 'sp50x36') { + add_default($nl, 'config_xfreq', 'val'=>"1.07"); + add_default($nl, 'config_freq1', 'val'=>"0.035"); +} else { + add_default($nl, 'config_xfreq'); + add_default($nl, 'config_freq1'); +} +if ($wave_comp eq 'ww3' or $wave_comp eq 'dwav') { + add_default($nl, 'config_wave_spec_type','val'=>"constant"); + add_default($nl, 'config_wave_height_type','val'=>"coupled"); +} else { + add_default($nl,'config_wave_spec_type'); + add_default($nl,'config_wave_height_type'); +} ################################### # Namelist group: biogeochemistry # @@ -1254,6 +1304,7 @@ my @groups = qw(seaice_model advection column_package column_tracers + wave_config biogeochemistry shortwave snow diff --git a/components/mpas-seaice/bld/build-namelist-group-list b/components/mpas-seaice/bld/build-namelist-group-list index 619926c4010a..a0d31a3e2980 100644 --- a/components/mpas-seaice/bld/build-namelist-group-list +++ b/components/mpas-seaice/bld/build-namelist-group-list @@ -11,6 +11,7 @@ my @groups = qw(seaice_model advection column_package column_tracers + wave_config biogeochemistry shortwave snow diff --git a/components/mpas-seaice/bld/build-namelist-section b/components/mpas-seaice/bld/build-namelist-section index 467e04c7908b..f552ead1356f 100644 --- a/components/mpas-seaice/bld/build-namelist-section +++ b/components/mpas-seaice/bld/build-namelist-section @@ -68,6 +68,7 @@ add_default($nl, 'config_nCategories'); add_default($nl, 'config_nFloeCategories'); add_default($nl, 'config_nIceLayers'); add_default($nl, 'config_nSnowLayers'); +add_default($nl, 'config_nFrequencies'); ############################## # Namelist group: initialize # @@ -166,6 +167,7 @@ add_default($nl, 'config_use_column_biogeochemistry'); add_default($nl, 'config_use_column_itd_thermodynamics'); add_default($nl, 'config_use_column_ridging'); add_default($nl, 'config_use_column_snow_tracers'); +add_default($nl, 'config_use_column_waves'); ################################## # Namelist group: column_tracers # @@ -182,6 +184,15 @@ add_default($nl, 'config_use_snow_grain_radius'); add_default($nl, 'config_use_special_boundaries_tracers'); add_default($nl, 'config_use_floe_size_distribution'); +################################### +# Namelist group: wave_config # +################################### + +add_default($nl, 'config_xfreq'); +add_default($nl, 'config_freq1'); +add_default($nl, 'config_wave_spec_type'); +add_default($nl, 'config_wave_height_type'); + ################################### # Namelist group: biogeochemistry # ################################### diff --git a/components/mpas-seaice/bld/namelist_files/namelist_defaults_mpassi.xml b/components/mpas-seaice/bld/namelist_files/namelist_defaults_mpassi.xml index 6af178cafd7a..c5309751614f 100644 --- a/components/mpas-seaice/bld/namelist_files/namelist_defaults_mpassi.xml +++ b/components/mpas-seaice/bld/namelist_files/namelist_defaults_mpassi.xml @@ -70,6 +70,7 @@ 1 7 5 +0 6371229.0 @@ -210,6 +211,7 @@ false true true +false false @@ -223,6 +225,12 @@ false false + +0.0 +0.0 +'none' +'none' + false false diff --git a/components/mpas-seaice/bld/namelist_files/namelist_definition_mpassi.xml b/components/mpas-seaice/bld/namelist_files/namelist_definition_mpassi.xml index 193fab7a5e04..e77bfa1b7110 100644 --- a/components/mpas-seaice/bld/namelist_files/namelist_definition_mpassi.xml +++ b/components/mpas-seaice/bld/namelist_files/namelist_definition_mpassi.xml @@ -284,8 +284,8 @@ Default: Defined in namelist_defaults.xml category="dimensions" group="dimensions"> The number of floe size categories to use. -Valid values: Any positive integer. -Default: Defined in namelist_defaults.xml +Valid values: 0,1,12,16,24. +Default: 0 (No waves). Defined in namelist_defaults.xml + +The number of frequencies used by the wave model. +Valid values: 0,25,36,50. +Default: 0 (no waves). Defined by WW3. + @@ -829,6 +835,13 @@ Valid values: true or false Default: Defined in namelist_defaults.xml + +Use the wave coupling to allow breaking of ice floes + +Valid values: true or false +Default: Defined in namelist_defaults.xml + @@ -912,6 +925,31 @@ Valid values: true or false Default: Defined in namelist_defaults.xml + + + +Frequency multiplication factor used by the wave model. +Default: Defined in namelist_defaults.xml + + + +First frequency used by the wave model. +Default: Defined in namelist_defaults.xml + + + +The type of wave spectra input. +Default = 'none'. Defined in namelist_defaults.xml + + + +The type of wave height input. +Default = 'internal'. Defined in namelist_defaults.xml + diff --git a/components/mpas-seaice/cime_config/buildnml b/components/mpas-seaice/cime_config/buildnml index 4dc604d30b2b..7b8ea7491720 100755 --- a/components/mpas-seaice/cime_config/buildnml +++ b/components/mpas-seaice/cime_config/buildnml @@ -689,6 +689,14 @@ def buildnml(case, caseroot, compname): lines.append(' ') lines.append(' ') lines.append(' ') + if ("WW3" in compset or "DWAV%FSD" in compset or "DWAV%ZEROS" in compset): + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') lines.append('') lines.append('') lines.append('') lines.append(' ') lines.append(' ') + if ("WW3" in compset or "DWAV%FSD" in compset or "DWAV%ZEROS" in compset): + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') lines.append('') lines.append('') lines.append('') lines.append(' ') lines.append(' ') + if ("WW3" in compset or "DWAV%FSD" in compset or "DWAV%ZEROS" in compset): + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + lines.append(' ') + + if ("WW3" in compset and "DOCN" in compset): # in D-cases + lines.append(' ') + if iceberg_mode != 'none': lines.append(' ') lines.append(' ') diff --git a/components/mpas-seaice/cime_config/config_compsets.xml b/components/mpas-seaice/cime_config/config_compsets.xml index 254df09eb451..50f208a323c2 100644 --- a/components/mpas-seaice/cime_config/config_compsets.xml +++ b/components/mpas-seaice/cime_config/config_compsets.xml @@ -34,4 +34,9 @@ 2000_DATM%NYF_SLND_MPASSI%COL_DOCN%SOM_DROF%NYF_SGLC_SWAV_TEST + + DTESTM-JRA1p5-DWAV + 2000_DATM%JRA-1p5_SLND_MPASSI_DOCN%SOM_DROF%JRA-1p5_SGLC_DWAV%FSD_TEST + + diff --git a/components/mpas-seaice/driver/ice_comp_mct.F b/components/mpas-seaice/driver/ice_comp_mct.F index 0e57642c0087..12e84a107bb1 100644 --- a/components/mpas-seaice/driver/ice_comp_mct.F +++ b/components/mpas-seaice/driver/ice_comp_mct.F @@ -833,14 +833,19 @@ end subroutine xml_stream_get_attributes ! Coupling prep call MPAS_pool_get_config(domain % configs, "config_column_physics_type", tempCharConfig) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", tempLogicalConfig) + if (trim(tempCharConfig) == "icepack") then call seaice_icepack_coupling_prep(domain) + if (tempLogicalConfig) then + call MPAS_pool_get_config(domain % configs, "config_use_column_waves", tempLogicalConfig) + if (.not.tempLogicalConfig) then + call mpas_log_write('FloeSizeDistribution requires column wave breaking.', MPAS_LOG_ERR) + call mpas_log_write('Either a) turn FSD off or b) include wave component (either dwav or ww3)', MPAS_LOG_CRIT) + endif + endif endif ! config_column_physics_type - call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", tempLogicalConfig) - if (tempLogicalConfig) then - call mpas_log_write('FloeSizeDistribution coming online soon. Turn FSD off for now.', MPAS_LOG_CRIT) - endif !----------------------------------------------------------------------- ! ! send intial state to driver @@ -2087,6 +2092,8 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ ! o zaer4 -- ! o zaer5 -- ! o zaer6 -- +! o Hs -- significant wave height +! o wavespec -- wave spectrum ! !----------------------------------------------------------------------- ! @@ -2115,7 +2122,7 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ message integer :: & - i,n + i,n,iFreq real (kind=RKIND) :: & frazilMassFlux, & @@ -2129,16 +2136,20 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ aerosols, & atmosCoupling, & oceanCoupling, & + waveCoupling, & biogeochemistry - integer, pointer :: nCellsSolve + integer, pointer :: & + nCellsSolve, & + nFrequencies logical, pointer :: & config_use_aerosols, & config_use_modal_aerosols, & config_use_zaerosols, & config_use_column_biogeochemistry, & - config_couple_biogeochemistry_fields + config_couple_biogeochemistry_fields,& + config_use_column_waves character(len=strKIND), pointer :: & config_column_physics_type, & @@ -2174,7 +2185,8 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ oceanAmmoniumConcField, & oceanDMSConcField, & oceanDMSPConcField, & - oceanHumicsConcField + oceanHumicsConcField, & + significantWaveHeightField type (field2DReal), pointer :: & oceanAlgaeConcField, & @@ -2188,7 +2200,8 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ atmosBlackCarbonFluxField, & atmosDustFluxField, & atmosWetDustFluxField, & - atmosDryDustFluxField + atmosDryDustFluxField, & + waveSpectraField real (kind=RKIND), dimension(:), pointer :: & seaSurfaceTemperature, & @@ -2222,7 +2235,8 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ oceanHumicsConc, & carbonToNitrogenRatioAlgae, & carbonToNitrogenRatioDON, & - DOCPoolFractions + DOCPoolFractions, & + significantWaveHeight real (kind=RKIND), dimension(:,:), pointer :: & oceanAlgaeConc, & @@ -2236,7 +2250,8 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ atmosBlackCarbonFlux, & atmosDustFlux, & atmosWetDustFlux, & - atmosDryDustFlux + atmosDryDustFlux, & + waveSpectra !----------------------------------------------------------------------- ! @@ -2264,11 +2279,13 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ call mpas_pool_get_config(configs, "config_use_column_biogeochemistry", config_use_column_biogeochemistry) call mpas_pool_get_config(configs, "config_couple_biogeochemistry_fields", config_couple_biogeochemistry_fields) call mpas_pool_get_config(configs, "config_use_zaerosols", config_use_zaerosols) + call mpas_pool_get_config(configs, "config_use_column_waves", config_use_column_waves) call mpas_pool_get_subpool(block_ptr % structs, 'mesh', meshPool) call mpas_pool_get_subpool(block_ptr % structs, 'ocean_coupling', oceanCoupling) call mpas_pool_get_subpool(block_ptr % structs, 'atmos_coupling', atmosCoupling) - + + call mpas_pool_get_dimension(block_ptr % dimensions, "nFrequencies", nFrequencies) call mpas_pool_get_dimension(meshPool, 'nCellsSolve', nCellsSolve) call mpas_pool_get_array(oceanCoupling, 'seaSurfaceTemperature', seaSurfaceTemperature) @@ -2296,6 +2313,12 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ call mpas_pool_get_array(atmosCoupling, 'uAirVelocity', uAirVelocity) call mpas_pool_get_array(atmosCoupling, 'vAirVelocity', vAirVelocity) + if (config_use_column_waves) then + call mpas_pool_get_subpool(block_ptr % structs, 'wave_coupling', waveCoupling) + call mpas_pool_get_array(waveCoupling, 'significantWaveHeight', significantWaveHeight) + call mpas_pool_get_array(waveCoupling, 'waveSpectra', waveSpectra) + endif + if (config_use_aerosols) then call mpas_pool_get_subpool(block_ptr % structs, 'aerosols', aerosols) @@ -2343,6 +2366,15 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ seaSurfaceTiltU(i) = x2i_i(index_x2i_So_dhdx, n) seaSurfaceTiltV(i) = x2i_i(index_x2i_So_dhdy, n) + if (wav_ice_coup == 'twoway') then + if (config_use_column_waves) then + significantWaveHeight(i) = x2i_i(index_x2i_Sw_Hs, n) + do iFreq = 1,nFrequencies + waveSpectra(iFreq,i) = x2i_i(index_x2i_Sw_wavespec(iFreq), n) + enddo + endif + endif + if (trim(config_ocean_surface_type) == "free") then ! free surface (MPAS-O) ! freezingMeltingPotential(i) is the ocean energy associated with frazil formation @@ -2537,6 +2569,12 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ call mpas_pool_get_field(oceanCoupling, 'seaSurfaceTiltU', seaSurfaceTiltUField) call mpas_pool_get_field(oceanCoupling, 'seaSurfaceTiltV', seaSurfaceTiltVField) + if (config_use_column_waves) then + call mpas_pool_get_subpool(domain % blocklist % structs, 'wave_coupling', waveCoupling) + call mpas_pool_get_field(waveCoupling, 'significantWaveHeight', significantWaveHeightField) + call mpas_pool_get_field(waveCoupling, 'waveSpectra', waveSpectraField) + endif + call mpas_pool_get_field(atmosCoupling, 'airLevelHeight', airLevelHeightField) call mpas_pool_get_field(atmosCoupling, 'airPotentialTemperature', airPotentialTemperatureField) call mpas_pool_get_field(atmosCoupling, 'airTemperature', airTemperatureField) @@ -2594,6 +2632,11 @@ subroutine ice_import_mct(x2i_i, errorCode)!{{{ call mpas_dmpar_exch_halo_field(seaSurfaceTiltUField) call mpas_dmpar_exch_halo_field(seaSurfaceTiltVField) + if (config_use_column_waves) then + call mpas_dmpar_exch_halo_field(significantWaveHeightField) + call mpas_dmpar_exch_halo_field(waveSpectraField) + endif + call mpas_dmpar_exch_halo_field(airLevelHeightField) call mpas_dmpar_exch_halo_field(airPotentialTemperatureField) call mpas_dmpar_exch_halo_field(airTemperatureField) @@ -2674,7 +2717,7 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ !----------------------------------------------------------------------- integer :: & - i, n + i, n, iCategory, iFloeCategory real(kind=RKIND) :: & ailohi, & @@ -2699,10 +2742,15 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ atmosFluxes, & oceanFluxes, & icebergFluxes, & - biogeochemistry + biogeochemistry, & + floe_size_distribution, & + icestate, & + tracers integer, pointer :: & - nCellsSolve + nCellsSolve, & + nCategories, & + nFloeCategories logical, pointer :: & config_rotate_cartesian_grid, & @@ -2711,7 +2759,8 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ config_use_zaerosols, & config_couple_biogeochemistry_fields, & config_use_column_shortwave, & - config_use_data_icebergs + config_use_data_icebergs, & + config_use_floe_size_distribution real(kind=RKIND), pointer :: & sphere_radius @@ -2761,7 +2810,8 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ oceanHumicsFlux, & oceanDustIronFlux, & carbonToNitrogenRatioAlgae, & - carbonToNitrogenRatioDON + carbonToNitrogenRatioDON, & + floeSizeDiameter real (kind=RKIND), dimension(:,:), pointer :: & oceanAlgaeFlux, & @@ -2787,6 +2837,7 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ call mpas_pool_get_config(configs, "config_couple_biogeochemistry_fields", config_couple_biogeochemistry_fields) call MPAS_pool_get_config(configs, "config_use_column_shortwave", config_use_column_shortwave) call MPAS_pool_get_config(configs, "config_use_data_icebergs", config_use_data_icebergs) + call MPAS_pool_get_config(configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_subpool(block_ptr % structs, 'mesh', meshPool) call MPAS_pool_get_subpool(block_ptr % structs, "tracers_aggregate", tracersAggregate) @@ -2830,6 +2881,11 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ call MPAS_pool_get_array(oceanCoupling, 'frazilMassAdjust', frazilMassAdjust) + if (config_use_floe_size_distribution) then + call MPAS_pool_get_subpool(block_ptr % structs, "floe_size_distribution", floe_size_distribution) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeDiameter",floeSizeDiameter) + endif + call MPAS_pool_get_array(atmosFluxes, 'latentHeatFlux', latentHeatFlux) call MPAS_pool_get_array(atmosFluxes, 'sensibleHeatFlux', sensibleHeatFlux) call MPAS_pool_get_array(atmosFluxes, 'longwaveUp', longwaveUp) @@ -2929,6 +2985,12 @@ subroutine ice_export_mct(i2x_i, errorCode) !{{{ i2x_i(index_i2x_Fioi_bergh,n) = bergLatentHeatFlux(i) endif + if (wav_ice_coup == 'twoway') then + if (config_use_floe_size_distribution) then + i2x_i(index_i2x_Si_ifloe, n) = floeSizeDiameter(i) + endif + endif + if ( ailohi > 0.0_RKIND ) then !-------states-------------------- diff --git a/components/mpas-seaice/driver/mpassi_cpl_indices.F b/components/mpas-seaice/driver/mpassi_cpl_indices.F index 8646233475bb..6e84fcba8533 100644 --- a/components/mpas-seaice/driver/mpassi_cpl_indices.F +++ b/components/mpas-seaice/driver/mpassi_cpl_indices.F @@ -12,6 +12,7 @@ module mpassi_cpl_indices integer :: index_i2x_Si_ifrac ! fractional ice coverage wrt ocean integer :: index_i2x_Si_ithick ! ice thickness (m) + integer :: index_i2x_Si_ifloe ! ice floe diameter (m) integer :: index_i2x_Si_snowh ! snow height (m) integer :: index_i2x_Si_t ! temperature integer :: index_i2x_Si_bpress ! basal pressure @@ -128,14 +129,23 @@ module mpassi_cpl_indices integer :: index_x2i_So_zaer4 ! integer :: index_x2i_So_zaer5 ! integer :: index_x2i_So_zaer6 ! + + ! drv -> ice (waves) + integer :: index_x2i_Sw_Hs ! wave significant wave height + integer,dimension(:),allocatable :: index_x2i_Sw_wavespec ! wave spectrum contains subroutine mpassi_cpl_indices_set( ) + use seq_flds_mod, only : wav_nfreq, wav_ice_coup type(mct_aVect) :: i2x ! temporary type(mct_aVect) :: x2i ! temporary + integer :: i + character(len= 2) :: freqnum + character(len=64) :: name + ! Determine attribute vector indices ! create temporary attribute vectors @@ -191,6 +201,10 @@ subroutine mpassi_cpl_indices_set( ) index_i2x_Fioi_fed1 = mct_avect_indexra(i2x,'Fioi_fed1',perrWith='quiet') index_i2x_Fioi_fed2 = mct_avect_indexra(i2x,'Fioi_fed2',perrWith='quiet') index_i2x_Fioi_dust1 = mct_avect_indexra(i2x,'Fioi_dust1',perrWith='quiet') + + if (wav_ice_coup .eq. 'twoway') then + index_i2x_Si_ifloe = mct_avect_indexra(i2x,'Si_ifloe') + endif index_x2i_So_t = mct_avect_indexra(x2i,'So_t') index_x2i_So_s = mct_avect_indexra(x2i,'So_s') @@ -229,6 +243,16 @@ subroutine mpassi_cpl_indices_set( ) index_x2i_Faxa_dstwet3 = mct_avect_indexra(x2i,'Faxa_dstwet3') index_x2i_Faxa_dstwet4 = mct_avect_indexra(x2i,'Faxa_dstwet4') + if (wav_ice_coup .eq. 'twoway') then + index_x2i_Sw_Hs = mct_avect_indexra(x2i,'Sw_Hs') + allocate(index_x2i_Sw_wavespec(1:wav_nfreq)) + do i = 1,wav_nfreq + write(freqnum,'(i2.2)') i + name = 'Sw_wavespec' // freqnum + index_x2i_Sw_wavespec(i) = mct_avect_indexra(x2i,trim(name)) + enddo + endif + index_x2i_So_algae1 = mct_avect_indexra(x2i,'So_algae1',perrWith='quiet') index_x2i_So_algae2 = mct_avect_indexra(x2i,'So_algae2',perrWith='quiet') index_x2i_So_algae3 = mct_avect_indexra(x2i,'So_algae3',perrWith='quiet') diff --git a/components/mpas-seaice/src/Registry.xml b/components/mpas-seaice/src/Registry.xml index ed94bb094e88..648ea51414a9 100644 --- a/components/mpas-seaice/src/Registry.xml +++ b/components/mpas-seaice/src/Registry.xml @@ -48,6 +48,10 @@ definition="namelist:config_nFloeCategories" description="The number of floe size categories." /> + - + + @@ -769,6 +781,25 @@ /> + + + + + + + + @@ -2804,6 +2836,12 @@ units="kg m-3" packages="pkgColumnTracerEffectiveSnowDensity" description="Density of snow due to compaction by wind" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -77,6 +78,7 @@ + @@ -148,6 +150,7 @@ + @@ -218,6 +221,7 @@ + @@ -294,6 +298,8 @@ + + @@ -438,6 +444,9 @@ + + + @@ -632,6 +641,7 @@ + @@ -708,6 +718,8 @@ + + diff --git a/components/mpas-seaice/src/shared/mpas_seaice_advection_incremental_remap_tracers.F b/components/mpas-seaice/src/shared/mpas_seaice_advection_incremental_remap_tracers.F index c00b96738aa9..6fe840877c4a 100644 --- a/components/mpas-seaice/src/shared/mpas_seaice_advection_incremental_remap_tracers.F +++ b/components/mpas-seaice/src/shared/mpas_seaice_advection_incremental_remap_tracers.F @@ -195,7 +195,8 @@ subroutine seaice_add_tracers_to_linked_list(domain) pkgTracerZAerosolsActive logical, pointer :: & - config_use_level_meltponds + config_use_level_meltponds, & + config_use_floe_size_distribution type (mpas_pool_type), pointer :: configPool @@ -291,6 +292,7 @@ subroutine seaice_add_tracers_to_linked_list(domain) configPool => domain % blocklist % configs call MPAS_pool_get_config(configPool, "config_use_level_meltponds", config_use_level_meltponds) + call MPAS_pool_get_config(configPool, "config_use_floe_size_distribution", config_use_floe_size_distribution) if (pkgColumnTracerIceAgeActive) then call add_tracer_to_tracer_linked_list(tracersHead, 'iceAge', 'iceVolumeCategory') @@ -300,6 +302,10 @@ subroutine seaice_add_tracers_to_linked_list(domain) call add_tracer_to_tracer_linked_list(tracersHead, 'firstYearIceArea', 'iceAreaCategory') endif + if (config_use_floe_size_distribution) then + call add_tracer_to_tracer_linked_list(tracersHead, 'floeSizeDist', 'iceAreaCategory') + endif + if (pkgColumnTracerLevelIceActive) then call add_tracer_to_tracer_linked_list(tracersHead, 'levelIceArea', 'iceAreaCategory') call add_tracer_to_tracer_linked_list(tracersHead, 'levelIceVolume', 'iceVolumeCategory') diff --git a/components/mpas-seaice/src/shared/mpas_seaice_icepack.F b/components/mpas-seaice/src/shared/mpas_seaice_icepack.F index 335e5a5346a4..c72ea7a1252c 100644 --- a/components/mpas-seaice/src/shared/mpas_seaice_icepack.F +++ b/components/mpas-seaice/src/shared/mpas_seaice_icepack.F @@ -100,7 +100,8 @@ module seaice_icepack index_snowIceMass, & ! nt_smice index_snowLiquidMass, & ! nt_smliq index_snowGrainRadius, & ! nt_rsnw - index_snowDensity ! nt_rhos + index_snowDensity, & ! nt_rhos + index_floeSizeDist ! nt_fsd !----------------------------------------------------------------------- ! biogeochemistry @@ -255,7 +256,9 @@ subroutine seaice_init_icepack_physics_package_variables(domain, clock) config_use_column_biogeochemistry, & config_use_column_shortwave, & config_use_column_snow_tracers, & - config_use_zaerosols + config_use_zaerosols, & + config_use_floe_size_distribution, & + config_use_column_waves call MPAS_pool_get_config(domain % configs, "config_use_column_physics", config_use_column_physics) call MPAS_pool_get_config(domain % configs, "config_use_column_biogeochemistry", config_use_column_biogeochemistry) @@ -269,6 +272,14 @@ subroutine seaice_init_icepack_physics_package_variables(domain, clock) ! initialize the itd thickness classes call init_column_itd(domain) + ! initalize floe size distribution and waves for col wave breaking + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) + call MPAS_pool_get_config(domain % configs, "config_use_column_waves", config_use_column_waves) + if (config_use_floe_size_distribution .and. config_use_column_waves) then + call init_column_fsd(domain) + call init_column_waves(domain) + endif + ! initialize thermodynamic tracer profiles call init_column_thermodynamic_profiles(domain) @@ -348,6 +359,257 @@ subroutine init_column_itd(domain) end subroutine init_column_itd +!||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +! +! init_column_fsd +! +!> \brief +!> \author Erin E. Thomas, LANL +!> \date Feb 2023 +!> \details +!> +! +!----------------------------------------------------------------------- + + subroutine init_column_fsd(domain) +! + use icepack_intfc, only: & + icepack_init_fsd_bounds, & + icepack_init_fsd, & + icepack_cleanup_fsd, & + icepack_char_len_long, & + c0, & + c1 + use seaice_constants, only: & + seaicePuny + + type(domain_type), intent(inout) :: domain + + type(block_type), pointer :: block + + type(MPAS_pool_type), pointer :: & + mesh, & + tracers, & + tracers_aggregate, & + floe_size_distribution + + character(len=StrKIND), pointer :: config_initial_condition_type + logical, pointer :: config_do_restart + + !dimensions + integer, pointer :: & + nCellsSolve, & + nCategories, & + nFrequencies, & + nFloeCategories + + !variables + real(kind=RKIND), dimension(:), pointer :: & + iceAreaCell, & + floeSizeBinLower, & !ndim = nFloeCat + floeSizeBinCenter, & + floeSizeBinWidth, & + floeDistribution + + real(kind=RKIND), dimension(:,:), pointer :: & + dFloeSizeNewIce, & + dFloeSizeLateralGrowth, & + dFloeSizeLateralMelt, & + dFloeSizeWaves, & + dFloeSizeWeld, & + floeDistCat + + real(kind=RKIND), dimension(:,:,:), pointer :: & + iceAreaCategory, & + floeSizeDist !ndim = nFloeCat,nCat,NCells + + !local variables + integer :: i,iCell, iCategory, iFloeCategory + character(len=icepack_char_len_long) :: tmp_config_initial_condition_type + + call MPAS_pool_get_config(domain % configs, "config_initial_condition_type",config_initial_condition_type) + call MPAS_pool_get_config(domain % configs, "config_do_restart",config_do_restart) + + tmp_config_initial_condition_type = trim(config_initial_condition_type) + + block => domain % blocklist + do while (associated(block)) + call MPAS_pool_get_subpool(block % structs, "mesh", mesh) + call MPAS_pool_get_subpool(block % structs, "floe_size_distribution", floe_size_distribution) + call MPAS_pool_get_subpool(block % structs, "tracers", tracers) + call MPAS_pool_get_subpool(block % structs, "tracers_aggregate", tracers_aggregate) + + call MPAS_pool_get_dimension(mesh, "nCellsSolve", nCellsSolve) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) + call MPAS_pool_get_dimension(block % dimensions, "nFrequencies", nFrequencies) + call MPAS_pool_get_dimension(block % dimensions, "nCategories", nCategories) + + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinLower", floeSizeBinLower) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinCenter", floeSizeBinCenter) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinWidth", floeSizeBinWidth) + call MPAS_pool_get_array(floe_size_distribution, "floeDistribution", floeDistribution) + call MPAS_pool_get_array(floe_size_distribution, "floeDistCat", floeDistCat) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeNewIce", dFloeSizeNewIce) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeLateralGrowth", dFloeSizeLateralGrowth) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeLateralMelt", dFloeSizeLateralMelt) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeWaves", dFloeSizeWaves) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeWeld", dFloeSizeWeld) + + call MPAS_pool_get_array(tracers_aggregate, "iceAreaCell", iceAreaCell) + call MPAS_pool_get_array(tracers, "floeSizeDist", floeSizeDist, 1) + call MPAS_pool_get_array(tracers, "iceAreaCategory", iceAreaCategory, 1) + + call icepack_init_fsd_bounds( & + floe_rad_l_out=floeSizeBinLower(:), & ! fsd size lower bound in m (radius) + floe_rad_c_out=floeSizeBinCenter(:), & ! fsd size bin centre in m (radius) + floe_binwidth_out=floeSizeBinWidth(:)) ! fsd size bin width in m (radius) + + ! initalize variables for change in floe size distribution + dFloeSizeNewIce(:,:) = c0 ! change in floe size distribution due to new ice + dFloeSizeLateralGrowth(:,:) = c0 ! change in floe size distribution due to lateral growth + dFloeSizeLateralMelt(:,:) = c0 ! change in floe size distribution due to lateral melt + dFloeSizeWeld(:,:) = c0 ! change in floe size distribution due to welding + dFloeSizeWaves(:,:) = c0 ! change in floe size distribution due to wave breaking + + if (.not. config_do_restart) then + ! default: floes occupy the smallest floe size category in all thickness categories + floeDistCat(:,:) = c0 + floeDistCat(1,:) = c1 + floeSizeDist(:,:,:) = c0 + floeSizeDist(1,:,:) = c1 + + call icepack_init_fsd( & + ice_ic=tmp_config_initial_condition_type, & ! method of ice cover initialization + afsd=floeDistribution) ! floe size distribution initalization tracer + + do iCell = 1, nCellsSolve + do iCategory = 1, nCategories + do iFloeCategory = 1, nFloeCategories + if (iceAreaCell(iCell) > seaicePuny .and. iceAreaCategory(1,iCategory,iCell) > seaicePuny) then + floeDistCat(iFloeCategory,iCategory) = floeDistribution(iFloeCategory) + endif + enddo + enddo + + call icepack_cleanup_fsd(floeDistCat) ! renormalize ?? + + do iCategory = 1, nCategories + do iFloeCategory = 1, nFloeCategories + if (iceAreaCell(iCell) > seaicePuny .and. iceAreaCategory(1,iCategory,iCell) > seaicePuny) then + floeSizeDist(iFloeCategory,iCategory,iCell) = floeDistCat(iFloeCategory,iCategory) + endif + enddo + enddo + enddo !iCell + endif + + block => block % next + + end do !block + + end subroutine init_column_fsd + + + +!||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +! +! init_column_waves +! +!> \brief +!> \author Erin E. Thomas, LANL +!> \date Feb 2023 +!> \details +!> +! +!----------------------------------------------------------------------- + +subroutine init_column_waves(domain) + + use shr_wave_mod, only: shr_calc_wave_freq + + use icepack_intfc, only: & + c0, & + icepack_init_wave + + type(domain_type), intent(inout) :: domain + + type(block_type), pointer :: block + + type(MPAS_pool_type), pointer :: & + mesh, & + wave_coupling + + logical, pointer :: config_do_restart + + ! dimensions + integer, pointer :: & + nCellsSolve, & + nFrequencies + + real(kind=RKIND), pointer :: & + config_freq1, & + config_xfreq + + !variables + real(kind=RKIND), dimension(:), pointer :: & + waveFrequency, & + waveFreqBinWidth, & + significantWaveHeight + + real(kind=RKIND), dimension(:,:), pointer :: & + waveSpectra + + !local variables + integer :: iCell + integer :: i + real(kind=RKIND), dimension(:),allocatable :: & + waveFrequency_TEMP, & + waveFreqBinWidth_TEMP + + call MPAS_pool_get_config(domain % configs, "config_xfreq", config_xfreq) + call MPAS_pool_get_config(domain % configs, "config_freq1", config_freq1) + call MPAS_pool_get_config(domain % configs, "config_do_restart",config_do_restart) + + block => domain % blocklist + do while (associated(block)) + + call MPAS_pool_get_subpool(block % structs, "mesh", mesh) + call MPAS_pool_get_subpool(block % structs, "wave_coupling", wave_coupling) + + call MPAS_pool_get_dimension(mesh, "nCellsSolve", nCellsSolve) + call MPAS_pool_get_dimension(block % dimensions, "nFrequencies", nFrequencies) + + call MPAS_pool_get_array(wave_coupling, "significantWaveHeight", significantWaveHeight) + call MPAS_pool_get_array(wave_coupling, "waveSpectra", waveSpectra) + call MPAS_pool_get_array(wave_coupling, "waveFrequency", waveFrequency) + call MPAS_pool_get_array(wave_coupling, "waveFreqBinWidth", waveFreqBinWidth) + + allocate(waveFrequency_TEMP(nFrequencies+2)) + allocate(waveFreqBinWidth_TEMP(nFrequencies+2)) + + !initalize Wave Frequencies (same calculation as used in WW3) + call shr_calc_wave_freq(nFrequencies, config_freq1, config_xfreq, & + waveFrequency_TEMP, & + waveFreqBinWidth_TEMP) + + ! remove frequency 'tails' (which are only needed in WW3, not MPAS-SI) + waveFrequency = waveFrequency_TEMP(2:nFrequencies-1) + waveFreqBinWidth = waveFreqBinWidth_TEMP(2:nFrequencies-1) + + ! initialize HS and Spectra + if (.not. config_do_restart) then + significantWaveHeight(:) = c0 + waveSpectra(:,:) = c0 + endif + + deallocate(waveFrequency_TEMP) + deallocate(waveFreqBinWidth_TEMP) + + block => block % next + end do !block + + end subroutine init_column_waves + !||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ! ! init_column_thermodynamic_profiles @@ -1115,7 +1377,9 @@ subroutine seaice_icepack_dynamics_time_integration(domain, clock) logical, pointer :: & config_use_column_physics, & config_use_column_ridging, & - config_use_vertical_tracers + config_use_vertical_tracers, & + config_use_column_waves, & + config_use_floe_size_distribution type(MPAS_pool_type), pointer :: & velocitySolver @@ -1129,6 +1393,8 @@ subroutine seaice_icepack_dynamics_time_integration(domain, clock) call MPAS_pool_get_config(domain % configs, "config_use_column_ridging", config_use_column_ridging) call MPAS_pool_get_config(domain % configs, "config_use_vertical_tracers", config_use_vertical_tracers) + call MPAS_pool_get_config(domain % configs, "config_use_column_waves", config_use_column_waves) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution",config_use_floe_size_distribution) call MPAS_pool_get_subpool(domain % blocklist % structs, "velocity_solver", velocitySolver) call MPAS_pool_get_array(velocitySolver, "dynamicsTimeStep", dynamicsTimeStep) @@ -1142,6 +1408,15 @@ subroutine seaice_icepack_dynamics_time_integration(domain, clock) call column_combine_snow_ice_tracers(domain) call mpas_timer_stop("Column combine snow/ice tracers") + !----------------------------------------------------------------- + ! Wave fracturing + !----------------------------------------------------------------- + call mpas_timer_start("Column wavefracture") + if (config_use_column_waves .and. config_use_floe_size_distribution) then + call column_wavefracture(domain) + endif + call mpas_timer_stop("Column wavefracture") + !----------------------------------------------------------------- ! Ridging !----------------------------------------------------------------- @@ -1441,7 +1716,8 @@ subroutine column_vertical_thermodynamics(domain, clock) iceBodyAerosol, & snowIceMass, & snowLiquidMass, & - snowGrainRadius + snowGrainRadius, & + floeSizeDist integer, dimension(:), pointer :: & indexToCellID @@ -1539,6 +1815,7 @@ subroutine column_vertical_thermodynamics(domain, clock) call MPAS_pool_get_array(tracers, "snowIceMass", snowIceMass, 1) call MPAS_pool_get_array(tracers, "snowLiquidMass", snowLiquidMass, 1) call MPAS_pool_get_array(tracers, "snowGrainRadius", snowGrainRadius, 1) + call MPAS_pool_get_array(tracers, "floeSizeDist", floeSizeDist,1) call MPAS_pool_get_array(velocity_solver, "uVelocity", uVelocity) call MPAS_pool_get_array(velocity_solver, "vVelocity", vVelocity) @@ -1861,7 +2138,8 @@ subroutine column_vertical_thermodynamics(domain, clock) mlt_onset=meltOnset(iCell), & frz_onset=freezeOnset(iCell), & yday=dayOfYear, & - prescribed_ice=config_use_prescribed_ice) + prescribed_ice=config_use_prescribed_ice, & + afsdn=floeSizeDist(:,:,iCell)) abortFlag = icepack_warnings_aborted() call seaice_icepack_write_warnings(abortFlag) @@ -2099,8 +2377,8 @@ subroutine column_itd_thermodynamics(domain, clock) tracers_aggregate, & atmos_coupling, & ocean_coupling, & -! wave_coupling, & -! floe_size_distribution, & + wave_coupling, & + floe_size_distribution, & ocean_fluxes, & melt_growth_rates, & ponds, & @@ -2116,13 +2394,14 @@ subroutine column_itd_thermodynamics(domain, clock) logical, pointer :: & config_update_ocean_fluxes, & config_use_column_biogeochemistry, & - config_use_zaerosols + config_use_zaerosols, & + config_use_floe_size_distribution ! dimensions integer, pointer :: & nCellsSolve, & nCategories, & -! nFloeCategories, & + nFloeCategories, & nIceLayers, & nSnowLayers, & nAerosols, & @@ -2146,13 +2425,12 @@ subroutine column_itd_thermodynamics(domain, clock) oceanHeatFlux, & freezeOnset, & categoryThicknessLimits, & - frazilGrowthDiagnostic -! frazilGrowthDiagnostic, & -! WaveFrequency, & -! WaveFreqBinWidth, & -! SignificantWaveHeight,& -! FloeSizeBinCenter, & -! FloeSizeBinWidth + frazilGrowthDiagnostic, & + waveFrequency, & + waveFreqBinWidth, & ! wave frequency bin widths + significantWaveHeight,& + floeSizeBinCenter, & + floeSizeBinWidth real(kind=RKIND), dimension(:,:), pointer :: & iceAreaCategoryInitial, & @@ -2162,13 +2440,13 @@ subroutine column_itd_thermodynamics(domain, clock) oceanBioFluxes, & oceanBioConcentrations, & oceanBioConcentrationsInUse, & - initialSalinityProfile -! WaveSpectra, & -! DFloeSizeNewIce, & -! DFloeSizeLateralGrowth, & -! DFloeSizeLateralMelt, & -! DFloeSizeWaves, & -! DFloeSizeWeld + initialSalinityProfile, & + waveSpectra, & + dFloeSizeNewIce, & + dFloeSizeLateralGrowth, & + dFloeSizeLateralMelt, & + dFloeSizeWaves, & + dFloeSizeWeld real(kind=RKIND), dimension(:,:,:), pointer :: & iceAreaCategory, & @@ -2227,17 +2505,18 @@ subroutine column_itd_thermodynamics(domain, clock) call MPAS_pool_get_subpool(block % structs, "initial", initial) call MPAS_pool_get_subpool(block % structs, "diagnostics", diagnostics) call MPAS_pool_get_subpool(block % structs, "aerosols", aerosols) -! call MPAS_pool_get_subpool(block % structs, "wave_coupling", wave_coupling) -! call MPAS_pool_get_subpool(block % structs, "floe_size_distribution", floe_size_distribution) + call MPAS_pool_get_subpool(block % structs, "wave_coupling", wave_coupling) + call MPAS_pool_get_subpool(block % structs, "floe_size_distribution", floe_size_distribution) call MPAS_pool_get_config(block % configs, "config_dt", config_dt) call MPAS_pool_get_config(block % configs, "config_update_ocean_fluxes", config_update_ocean_fluxes) call MPAS_pool_get_config(block % configs, "config_use_column_biogeochemistry", config_use_column_biogeochemistry) call MPAS_pool_get_config(block % configs, "config_use_zaerosols", config_use_zaerosols) + call MPAS_pool_get_config(block % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(mesh, "nCellsSolve", nCellsSolve) call MPAS_pool_get_dimension(mesh, "nCategories", nCategories) -! call MPAS_pool_get_dimension(mesh, "nFloeCategories", nFloeCategories) + call MPAS_pool_get_dimension(mesh, "nFloeCategories", nFloeCategories) call MPAS_pool_get_dimension(mesh, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(mesh, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(mesh, "nAerosols", nAerosols) @@ -2263,18 +2542,18 @@ subroutine column_itd_thermodynamics(domain, clock) call MPAS_pool_get_array(ocean_coupling, "seaFreezingTemperature", seaFreezingTemperature) call MPAS_pool_get_array(ocean_coupling, "seaSurfaceSalinity", seaSurfaceSalinity) -! call MPAS_pool_get_array(wave_coupling, "SignificantWaveHeight", SignificantWaveHeight) -! call MPAS_pool_get_array(wave_coupling, "WaveSpectra", WaveSpectra) -! call MPAS_pool_get_array(wave_coupling, "WaveFrequency", WaveFrequency) -! call MPAS_pool_get_array(wave_coupling, "WaveFreqBinWidth", WaveFreqBinWidth) + call MPAS_pool_get_array(wave_coupling, "significantWaveHeight", significantWaveHeight) + call MPAS_pool_get_array(wave_coupling, "waveSpectra", waveSpectra) + call MPAS_pool_get_array(wave_coupling, "waveFrequency", waveFrequency) + call MPAS_pool_get_array(wave_coupling, "waveFreqBinWidth", waveFreqBinWidth) -! call MPAS_pool_get_array(floe_size_distribution, "FloeSizeBinCenter", FloeSizeBinCenter) -! call MPAS_pool_get_array(floe_size_distribution, "FloeSizeBinWidth", FloeSizeBinWidth) -! call MPAS_pool_get_array(floe_size_distribution, "DFloeSizeNewIce", DFloeSizeNewIce) -! call MPAS_pool_get_array(floe_size_distribution, "DFloeSizeLateralGrowth", DFloeSizeLateralGrowth) -! call MPAS_pool_get_array(floe_size_distribution, "DFloeSizeLateralMelt", DFloeSizeLateralMelt) -! call MPAS_pool_get_array(floe_size_distribution, "DFloeSizeWeld", DFloeSizeWeld) -! call MPAS_pool_get_array(floe_size_distribution, "DFloeSizeWaves", DFloeSizeWaves) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinCenter", floeSizeBinCenter) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinWidth", floeSizeBinWidth) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeNewIce", dFloeSizeNewIce) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeLateralGrowth", dFloeSizeLateralGrowth) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeLateralMelt", dFloeSizeLateralMelt) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeWeld", dFloeSizeWeld) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeWaves", dFloeSizeWaves) call MPAS_pool_get_array(ocean_fluxes, "oceanFreshWaterFlux", oceanFreshWaterFlux) call MPAS_pool_get_array(ocean_fluxes, "oceanSaltFlux", oceanSaltFlux) @@ -2336,10 +2615,6 @@ subroutine column_itd_thermodynamics(domain, clock) oceanBioFluxesTemp(:,iCell) = 0.0_RKIND - ! CICE calculates the Sig Wave Height from the wave spectrum as follows before step therm2: - ! we will use the Sig Wave Height from WW3, but could use this for testing without WW3 - ! if (tr_fsd) wave_sig_ht(i,j,iblk) = c4*SQRT(SUM(wave_spectrum(i,j,:,iblk)*dwavefreq(:))) - call icepack_step_therm2(& dt=config_dt, & hin_max=categoryThicknessLimits(:), & @@ -2374,23 +2649,19 @@ subroutine column_itd_thermodynamics(domain, clock) ocean_bio=oceanBioConcentrationsInUse(:,iCell), & frazil_diag=frazilGrowthDiagnostic(iCell), & frz_onset=freezeOnset(iCell), & ! optional - yday=dayOfYear) ! optional -! yday=dayOfYear, & ! optional + yday=dayOfYear, & ! optional ! fiso_ocn=, & !optional - isotope flux to ocean (kg/m^2/s) ! HDO_ocn=, & ! optional - ocean concentration of HDO (kg/kg) ! H2_16O_ocn=, & ! optional - ocean concentration of H2_16O (kg/kg) ! H2_18O_ocn=, & ! optional - ocean concentration of H2_18O (kg/kg) -! nfsd=nFloeCategories, & -! wave_sig_ht=SignificantWaveHeight(iCell), & -! wave_spectrum=WaveSpectra(:,iCell), & -! wavefreq=WaveFrequency(:), & -! dwavefreq=WaveFreqBinWidth(:), & -! d_afsd_latg=DFloeSizeLateralGrowth(:,iCell), & -! d_afsd_newi=DFloeSizeNewIce(:,iCell), & -! d_afsd_latm=DFloeSizeLateralMelt(:,iCell), & -! d_afsd_weld=DFloeSizeWeld(:,iCell), & -! floe_rad_c=FloeSizeBinCenter(:), & -! floe_binwidth=FloeSizeBinWidth(:)) + wave_sig_ht=significantWaveHeight(iCell), & + wave_spectrum=waveSpectra(:,iCell), & + wavefreq=waveFrequency(:), & + dwavefreq = waveFreqBinWidth(:), & + d_afsd_latg=dFloeSizeLateralGrowth(:,iCell), & + d_afsd_newi=dFloeSizeNewIce(:,iCell), & + d_afsd_latm=dFloeSizeLateralMelt(:,iCell), & + d_afsd_weld=dFloeSizeWeld(:,iCell)) do iBioTracers = 1, ciceTracerObject % nBioTracers oceanBioFluxes(iBioTracers,iCell) = oceanBioFluxes(iBioTracers,iCell) + oceanBioFluxesTemp(iBioTracers,iCell) @@ -3142,7 +3413,182 @@ subroutine column_radiation(domain, clock, lInitialization) call seaice_icepack_write_warnings(icepack_warnings_aborted()) end subroutine column_radiation +!||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +! +! column_wavefracture +! +!> \brief +!> \author Erin E. Thomas, LANL +!> \date January 2023 +!> \details +!> +! +!----------------------------------------------------------------------- + subroutine column_wavefracture(domain) + use icepack_intfc, only: & + icepack_step_wavefracture, & + c0, & + c2, & + icepack_char_len + + type(domain_type), intent(inout) :: domain + + type(block_type), pointer :: block + + logical, pointer :: & + config_use_column_biogeochemistry + + type(MPAS_pool_type), pointer :: & + mesh, & + velocity_solver, & + tracers, & + tracers_aggregate, & + icestate, & + floe_size_distribution, & + wave_coupling + + character(len=strKIND), pointer :: & + config_wave_spec_type + + ! dimensions + integer, pointer :: & + nCellsSolve, & + nCategories, & !number of ice thicknes categories + nFloeCategories, & ! number of floe size categories + nFrequencies ! number of wave frequency bins + + ! variables + real(kind=RKIND), pointer :: & + dynamicsTimeStep + + real(kind=RKIND), dimension(:), pointer :: & + iceAreaCell, & ! ice area fraction + iceVolumeCell, & ! ice volume per unit area + floeSizeBinLower, & ! fsd size bin lower bound in m (radius) + floeSizeBinCenter, & ! fsd size bin center in m (radius) + floeSizeDiameter, & ! representative floe diameter + waveFrequency, & ! wave frequencies + waveFreqBinWidth, & ! wave frequency bin widths + significantWaveHeight ! significant wave height + + real(kind=RKIND), dimension(:,:), pointer :: & + iceAreaCategoryInitial, & + waveSpectra, & ! ocean surface wave spectrum as a function of frequency + dFloeSizeWaves ! change in fsd due to waves + + real(kind=RKIND), dimension(:,:,:), pointer :: & + iceAreaCategory , & ! ice area fraction per category + floeSizeDist + + ! local + logical :: & + setGetPhysicsTracers, & + setGetBGCTracers + + integer :: & + iCell, & + iCategory, & + iFloeCategory + + real(kind=RKIND) :: worka, workb + + + block => domain % blocklist + do while (associated(block)) + + !MPAS_pool_get* calls + call MPAS_pool_get_config(block % configs, "config_use_column_biogeochemistry", config_use_column_biogeochemistry) + call MPAS_pool_get_config(block % configs, "config_wave_spec_type",config_wave_spec_type) + + call MPAS_pool_get_subpool(block % structs, "mesh", mesh) + call MPAS_pool_get_subpool(block % structs, "tracers", tracers) + call MPAS_pool_get_subpool(block % structs, "tracers_aggregate", tracers_aggregate) + call MPAS_pool_get_subpool(block % structs, "floe_size_distribution", floe_size_distribution) + call MPAS_pool_get_subpool(block % structs, "wave_coupling", wave_coupling) + call MPAS_pool_get_subpool(block % structs, "velocity_solver", velocity_solver) + call MPAS_pool_get_subpool(block % structs, "icestate", icestate) + + call MPAS_pool_get_dimension(mesh, "nCategories", nCategories) + call MPAS_pool_get_dimension(mesh, "nCellsSolve", nCellsSolve) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) + call MPAS_pool_get_dimension(block % dimensions, "nFrequencies", nFrequencies) + + call MPAS_pool_get_array(velocity_solver, "dynamicsTimeStep", dynamicsTimeStep) + + call MPAS_pool_get_array(tracers_aggregate, "iceAreaCell", iceAreaCell) + call MPAS_pool_get_array(tracers_aggregate, "iceVolumeCell", iceVolumeCell) + + call MPAS_pool_get_array(icestate, "iceAreaCategoryInitial", iceAreaCategoryInitial) + + call MPAS_pool_get_array(tracers, "iceAreaCategory", iceAreaCategory, 1) + call MPAS_pool_get_array(tracers, "floeSizeDist", floeSizeDist, 1) + + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinLower", floeSizeBinLower) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeBinCenter", floeSizeBinCenter) + call MPAS_pool_get_array(floe_size_distribution, "dFloeSizeWaves", dFloeSizeWaves) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeDiameter",floeSizeDiameter) + call MPAS_pool_get_array(wave_coupling, "waveSpectra", waveSpectra) + call MPAS_pool_get_array(wave_coupling, "waveFrequency", waveFrequency) + call MPAS_pool_get_array(wave_coupling, "waveFreqBinWidth", waveFreqBinWidth) + call MPAS_pool_get_array(wave_coupling, "significantWaveHeight", significantWaveHeight) + + setGetPhysicsTracers = .true. + setGetBGCTracers = config_use_column_biogeochemistry + + do iCell = 1, nCellsSolve + dFloeSizeWaves(:,iCell) = c0 + floeSizeDiameter(iCell) = c0 + + call set_cice_tracer_array_category(block, ciceTracerObject, & + tracerArrayCategory, iCell, setGetPhysicsTracers, setGetBGCTracers) + + call icepack_step_wavefracture(& + wave_spec_type = config_wave_spec_type, & + dt = dynamicsTimeStep, & + nfreq = nFrequencies, & + aice = iceAreaCell(iCell), & + vice = iceVolumeCell(iCell), & + aicen = iceAreaCategory(1,:,iCell), & + wave_spectrum = waveSpectra(:,iCell), & + wavefreq = waveFrequency(:), & + dwavefreq = waveFreqBinWidth(:), & + trcrn = tracerArrayCategory, & + d_afsd_wave = dFloeSizeWaves(:,iCell)) + + call get_cice_tracer_array_category(block, ciceTracerObject, & + tracerArrayCategory, iCell, setGetPhysicsTracers, setGetBGCTracers) + + if (iceAreaCell(iCell) > 1.0e-11_RKIND) then + worka = c0 + workb = c0 + do iCategory=1,nCategories + do iFloeCategory=1,nFloeCategories + !worka = worka + (floeSizeDist(iFloeCategory, iCategory,iCell) & + ! * floeSizeBinCenter(iFloeCategory) & + ! * iceAreaCategory(1,iCategory,iCell) ) + worka = worka + (floeSizeDist(iFloeCategory, iCategory,iCell) & + * floeSizeBinCenter(iFloeCategory) & + * iceAreaCategoryInitial(iCategory,iCell) ) + workb = workb + ( floeSizeDist(iFloeCategory, iCategory,iCell) & + * iceAreaCategoryInitial(iCategory,iCell) ) + enddo + enddo + !floeSizeDiameter(iCell) = c2*worka / iceAreaCell(iCell) + if (workb > 1.0e-11_RKIND) floeSizeDiameter(iCell) = c2*worka / workb + endif + + enddo !iCell + +! call icepack_warnings_flush(nu_diag) +! if (icepack_warnings_aborted()) call icedrv_system_abort(string=subname, & +! file=__FILE__, line=__LINE__) + + + block => block % next + enddo !do while + end subroutine column_wavefracture + !||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ! ! column_ridging @@ -5795,11 +6241,13 @@ subroutine init_column_tracer_object_tracer_number(domain, tracerObject) config_use_iron, & config_use_zaerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer, pointer :: & nIceLayers, & nSnowLayers, & + nFloeCategories, & nAerosols, & nBioLayers, & nBioLayersP3, & @@ -5824,6 +6272,7 @@ subroutine init_column_tracer_object_tracer_number(domain, tracerObject) call MPAS_pool_get_config(domain % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(domain % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(domain % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_config(domain % configs, "config_use_column_biogeochemistry", config_use_column_biogeochemistry) call MPAS_pool_get_config(domain % configs, "config_use_brine", config_use_brine) @@ -5844,6 +6293,7 @@ subroutine init_column_tracer_object_tracer_number(domain, tracerObject) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nSnowLayers", nSnowLayers) + call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nAerosols", nAerosols) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nBioLayers", nBioLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nBioLayersP3", nBioLayersP3) @@ -5906,6 +6356,11 @@ subroutine init_column_tracer_object_tracer_number(domain, tracerObject) ! aerosols if (config_use_aerosols) & tracerObject % nTracers = tracerObject % nTracers + nAerosols*4 + + !floe size distribution + if (config_use_floe_size_distribution) then + tracerObject % nTracers = tracerObject % nTracers + nFloeCategories + endif !----------------------------------------------------------------------- ! biogeochemistry @@ -6060,14 +6515,16 @@ subroutine init_column_tracer_object_child_indices(domain, tracerObject) config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer :: & nTracers integer, pointer :: & nIceLayers, & - nSnowLayers + nSnowLayers, & + nFloeCategories integer, parameter :: indexMissingValue = 0 @@ -6079,9 +6536,11 @@ subroutine init_column_tracer_object_child_indices(domain, tracerObject) call MPAS_pool_get_config(domain % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(domain % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(domain % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nSnowLayers", nSnowLayers) + call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nFloeCategories", nFloeCategories) ! ice/snow surface temperature tracerObject % index_surfaceTemperature = 1 @@ -6170,6 +6629,13 @@ subroutine init_column_tracer_object_child_indices(domain, tracerObject) tracerObject % index_aerosols = nTracers + 1 endif + ! floe size distrbution + tracerObject % index_floeSizeDist = indexMissingValue + if (config_use_floe_size_distribution) then + tracerObject % index_floeSizeDist = nTracers + 1 + nTracers = nTracers + nFloeCategories + endif + !----------------------------------------------------------------------- ! BGC indices are calculated in the column package !----------------------------------------------------------------------- @@ -6204,17 +6670,20 @@ subroutine init_column_tracer_object_parent_indices(domain, tracerObject) config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer :: & iIceLayer, & iSnowLayer, & - iAerosol + iAerosol, & + iFloeSize integer, pointer :: & nIceLayers, & nSnowLayers, & - nAerosols + nAerosols, & + nFloeCategories call MPAS_pool_get_config(domain % configs, "config_use_ice_age", config_use_ice_age) call MPAS_pool_get_config(domain % configs, "config_use_first_year_ice", config_use_first_year_ice) @@ -6224,10 +6693,12 @@ subroutine init_column_tracer_object_parent_indices(domain, tracerObject) call MPAS_pool_get_config(domain % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(domain % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(domain % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nAerosols", nAerosols) + call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nFloeCategories", nFloeCategories) ! ice/snow surface temperature tracerObject % parentIndex(tracerObject % index_surfaceTemperature) = 0 @@ -6296,6 +6767,13 @@ subroutine init_column_tracer_object_parent_indices(domain, tracerObject) tracerObject % parentIndex(tracerObject % index_aerosols + (iAerosol-1)*4 + 3) = 1 ! ice enddo ! iAerosol endif + + ! Floe size distribution + if (config_use_floe_size_distribution) then + do iFloeSize = 1, nFloeCategories + tracerObject % parentIndex(tracerObject % index_floeSizeDist + iFloeSize - 1) = 0 + enddo ! iFloeSize + endif !----------------------------------------------------------------------- ! BGC parentIndices are calculated in the column package @@ -6623,12 +7101,14 @@ subroutine set_cice_physics_tracer_array_category(block, tracerArrayCategory, iC config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer, pointer :: & nIceLayers, & nSnowLayers, & - nAerosols + nAerosols, & + nFloeCategories type(MPAS_pool_type), pointer :: & tracers @@ -6652,7 +7132,8 @@ subroutine set_cice_physics_tracer_array_category(block, tracerArrayCategory, iC snowIceMass, & snowLiquidMass, & snowGrainRadius, & - snowDensity + snowDensity, & + floeSizeDist integer :: & nTracers, & @@ -6666,10 +7147,12 @@ subroutine set_cice_physics_tracer_array_category(block, tracerArrayCategory, iC call MPAS_pool_get_config(block % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(block % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(block % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(block % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(block % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(block % dimensions, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(block % dimensions, "nAerosols", nAerosols) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_subpool(block % structs, "tracers", tracers) @@ -6692,6 +7175,7 @@ subroutine set_cice_physics_tracer_array_category(block, tracerArrayCategory, iC call MPAS_pool_get_array(tracers, "snowLiquidMass", snowLiquidMass, 1) call MPAS_pool_get_array(tracers, "snowDensity", snowDensity, 1) call MPAS_pool_get_array(tracers, "snowGrainRadius", snowGrainRadius, 1) + call MPAS_pool_get_array(tracers, "floeSizeDist", floeSizeDist, 1) nTracers = 1 @@ -6774,6 +7258,12 @@ subroutine set_cice_physics_tracer_array_category(block, tracerArrayCategory, iC enddo ! iAerosol endif + + ! floe size distribution + if (config_use_floe_size_distribution) then + tracerArrayCategory(nTracers:nTracers+nFloeCategories-1,:) = floeSizeDist(:,:,iCell) + nTracers = nTracers + nFloeCategories + endif end subroutine set_cice_physics_tracer_array_category @@ -6808,13 +7298,15 @@ subroutine get_cice_physics_tracer_array_category(block, tracerArrayCategory, iC config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer, pointer :: & nIceLayers, & nSnowLayers, & - nAerosols + nAerosols, & + nFloeCategories type(MPAS_pool_type), pointer :: & tracers @@ -6838,7 +7330,8 @@ subroutine get_cice_physics_tracer_array_category(block, tracerArrayCategory, iC snowIceMass, & snowLiquidMass, & snowGrainRadius, & - snowDensity + snowDensity, & + floeSizeDist integer :: & nTracers, & @@ -6852,10 +7345,12 @@ subroutine get_cice_physics_tracer_array_category(block, tracerArrayCategory, iC call MPAS_pool_get_config(block % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(block % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(block % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(block % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(block % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(block % dimensions, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(block % dimensions, "nAerosols", nAerosols) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_subpool(block % structs, "tracers", tracers) @@ -6878,6 +7373,7 @@ subroutine get_cice_physics_tracer_array_category(block, tracerArrayCategory, iC call MPAS_pool_get_array(tracers, "snowLiquidMass", snowLiquidMass, 1) call MPAS_pool_get_array(tracers, "snowDensity", snowDensity, 1) call MPAS_pool_get_array(tracers, "snowGrainRadius", snowGrainRadius, 1) + call MPAS_pool_get_array(tracers, "floeSizeDist", floeSizeDist, 1) nTracers = 1 @@ -6961,6 +7457,12 @@ subroutine get_cice_physics_tracer_array_category(block, tracerArrayCategory, iC enddo ! iAerosol endif + ! floe size dist + if (config_use_floe_size_distribution) then + floeSizeDist(:,:,iCell) = tracerArrayCategory(nTracers:nTracers+nFloeCategories-1,:) + nTracers = nTracers + nFloeCategories + endif + end subroutine get_cice_physics_tracer_array_category !||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| @@ -6994,12 +7496,14 @@ subroutine set_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer, pointer :: & nIceLayers, & nSnowLayers, & - nAerosols + nAerosols, & + nFloeCategories type(MPAS_pool_type), pointer :: & tracers_aggregate @@ -7025,7 +7529,8 @@ subroutine set_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) snowIceMassCell, & snowLiquidMassCell, & snowGrainRadiusCell, & - snowDensityCell + snowDensityCell, & + floeSizeDistCell integer :: & nTracers, & @@ -7039,10 +7544,12 @@ subroutine set_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) call MPAS_pool_get_config(block % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(block % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(block % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(block % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(block % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(block % dimensions, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(block % dimensions, "nAerosols", nAerosols) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_subpool(block % structs, "tracers_aggregate", tracers_aggregate) @@ -7065,6 +7572,7 @@ subroutine set_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) call MPAS_pool_get_array(tracers_aggregate, "snowLiquidMassCell", snowLiquidMassCell) call MPAS_pool_get_array(tracers_aggregate, "snowDensityCell", snowDensityCell) call MPAS_pool_get_array(tracers_aggregate, "snowGrainRadiusCell", snowGrainRadiusCell) + call MPAS_pool_get_array(tracers_aggregate, "floeSizeDistCell", floeSizeDistCell) nTracers = 1 @@ -7147,6 +7655,12 @@ subroutine set_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) enddo ! iAerosol endif + + !floe size distribution + if (config_use_floe_size_distribution) then + tracerArrayCell(nTracers:nTracers+nFloeCategories-1) = floeSizeDistCell(:,iCell) + nTracers = nTracers + nFloeCategories + endif end subroutine set_cice_physics_tracer_array_cell @@ -7181,11 +7695,13 @@ subroutine get_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) config_use_topo_meltponds, & config_use_aerosols, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution integer, pointer :: & nIceLayers, & nSnowLayers, & + nFloeCategories, & nAerosols type(MPAS_pool_type), pointer :: & @@ -7212,7 +7728,8 @@ subroutine get_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) snowIceMassCell, & snowLiquidMassCell, & snowGrainRadiusCell, & - snowDensityCell + snowDensityCell, & + floeSizeDistCell integer :: & nTracers, & @@ -7226,10 +7743,12 @@ subroutine get_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) call MPAS_pool_get_config(block % configs, "config_use_aerosols", config_use_aerosols) call MPAS_pool_get_config(block % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(block % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(block % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) call MPAS_pool_get_dimension(block % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(block % dimensions, "nSnowLayers", nSnowLayers) call MPAS_pool_get_dimension(block % dimensions, "nAerosols", nAerosols) + call MPAS_pool_get_dimension(block % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_subpool(block % structs, "tracers_aggregate", tracers_aggregate) @@ -7252,6 +7771,7 @@ subroutine get_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) call MPAS_pool_get_array(tracers_aggregate, "snowLiquidMassCell", snowLiquidMassCell) call MPAS_pool_get_array(tracers_aggregate, "snowDensityCell", snowDensityCell) call MPAS_pool_get_array(tracers_aggregate, "snowGrainRadiusCell", snowGrainRadiusCell) + call MPAS_pool_get_array(tracers_aggregate, "floeSizeDistCell", floeSizeDistCell) nTracers = 1 @@ -7323,6 +7843,12 @@ subroutine get_cice_physics_tracer_array_cell(block, tracerArrayCell, iCell) nTracers = nTracers + nSnowLayers endif + !floe size distribution + if (config_use_floe_size_distribution) then + floeSizeDistCell(:,iCell) = tracerArrayCell(nTracers:nTracers+nFloeCategories-1) + nTracers = nTracers + nFloeCategories + endif + ! aerosols if (config_use_aerosols) then do iAerosol = 1, nAerosols @@ -9863,7 +10389,8 @@ subroutine init_icepack_package_tracer_flags(domain) config_use_vertical_biochemistry, & config_use_skeletal_biochemistry, & config_use_effective_snow_density, & - config_use_snow_grain_radius + config_use_snow_grain_radius, & + config_use_floe_size_distribution logical :: & use_meltponds, & @@ -9891,6 +10418,7 @@ subroutine init_icepack_package_tracer_flags(domain) call MPAS_pool_get_config(domain % configs, "config_use_vertical_biochemistry", config_use_vertical_biochemistry) call MPAS_pool_get_config(domain % configs, "config_use_effective_snow_density", config_use_effective_snow_density) call MPAS_pool_get_config(domain % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) + call MPAS_pool_get_config(domain % configs, "config_use_floe_size_distribution", config_use_floe_size_distribution) use_nitrogen = .false. if (config_use_skeletal_biochemistry .or. config_use_vertical_biochemistry) & @@ -9905,7 +10433,7 @@ subroutine init_icepack_package_tracer_flags(domain) tr_pond_in = use_meltponds, & tr_pond_lvl_in = config_use_level_meltponds, & tr_pond_topo_in = config_use_topo_meltponds, & - !tr_fsd_in = config_use_floe_size_distribution, & + tr_fsd_in = config_use_floe_size_distribution, & tr_aero_in = config_use_aerosols, & !tr_iso_in = , & tr_brine_in = config_use_brine, & @@ -9953,6 +10481,7 @@ subroutine init_icepack_package_tracer_sizes(domain, tracerObject) nCategories, & nIceLayers, & nSnowLayers, & + nFloeCategories, & nAerosols, & nBioLayers, & nAlgae, & @@ -9966,6 +10495,7 @@ subroutine init_icepack_package_tracer_sizes(domain, tracerObject) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nCategories", nCategories) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nIceLayers", nIceLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nSnowLayers", nSnowLayers) + call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nFloeCategories", nFloeCategories) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nBioLayers", nBioLayers) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nAerosols", nAerosols) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nzAerosols", nzAerosols) @@ -9981,7 +10511,7 @@ subroutine init_icepack_package_tracer_sizes(domain, tracerObject) nilyr_in = nIceLayers, & nslyr_in = nSnowLayers, & nblyr_in = nBioLayers, & - !nfsd_in = , & + nfsd_in = nFloeCategories, & !n_iso_in = , & !n_aero_in = nAerosols, & n_algae_in = nAlgae, & @@ -10037,7 +10567,7 @@ subroutine init_icepack_package_tracer_indices(tracerObject) nt_smliq_in = tracerObject % index_snowLiquidMass, & nt_rhos_in = tracerObject % index_snowDensity, & nt_rsnw_in = tracerObject % index_snowGrainRadius, & - !nt_fsd + nt_fsd_in = tracerObject % index_floeSizeDist, & !nt_isosno_in !nt_isoice_in nt_aero_in = tracerObject % index_aerosols, & @@ -10161,6 +10691,10 @@ subroutine init_icepack_package_configs(domain) type(domain_type), intent(inout) :: & domain + + type(MPAS_pool_type), pointer :: & + wave_coupling, & + floe_size_distribution type(MPAS_pool_type), pointer :: & snow @@ -10183,7 +10717,8 @@ subroutine init_icepack_package_configs(domain) config_congelation_freezing_method, & config_sea_freezing_temperature_type, & config_skeletal_bgc_flux_type, & - config_snow_redistribution_scheme + config_snow_redistribution_scheme, & + config_wave_spec_type logical, pointer :: & config_use_snicar_ad, & @@ -10204,7 +10739,8 @@ subroutine init_icepack_package_configs(domain) config_use_snow_liquid_ponds, & config_use_snow_grain_radius, & config_use_shortwave_redistribution, & - config_use_iron_solubility_file + config_use_iron_solubility_file, & + config_use_column_waves real(kind=RKIND), pointer :: & config_min_friction_velocity, & @@ -10350,7 +10886,14 @@ subroutine init_icepack_package_configs(domain) config_boundary_layer_iteration_number, & nGrainAgingTemperature, & nGrainAgingTempGradient, & - nGrainAgingSnowDensity + nGrainAgingSnowDensity, & + config_nFrequencies + + real(kind=RKIND), dimension(:), pointer :: & + floeSizeDiameter + + real(kind=RKIND), dimension(:,:), pointer :: & + waveSpectra integer :: & config_thermodynamics_type_int, & @@ -10553,6 +11096,13 @@ subroutine init_icepack_package_configs(domain) call MPAS_pool_get_config(domain % configs, "config_use_snow_grain_radius", config_use_snow_grain_radius) call MPAS_pool_get_config(domain % configs, "config_floediam", config_floediam) call MPAS_pool_get_config(domain % configs, "config_floeshape", config_floeshape) + call MPAS_pool_get_config(domain % configs, "config_use_column_waves", config_use_column_waves) + call MPAS_pool_get_config(domain % configs, "config_wave_spec_type", config_wave_spec_type) + call MPAS_pool_get_config(domain % configs, "config_nFrequencies", config_nFrequencies) + call MPAS_pool_get_subpool(domain % blocklist % structs, "wave_coupling", wave_coupling) + call MPAS_pool_get_subpool(domain % blocklist % structs, "floe_size_distribution", floe_size_distribution) + call MPAS_pool_get_array(wave_coupling, "waveSpectra", waveSpectra) + call MPAS_pool_get_array(floe_size_distribution, "floeSizeDiameter",floeSizeDiameter) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nGrainAgingTemperature", nGrainAgingTemperature) call MPAS_pool_get_dimension(domain % blocklist % dimensions, "nGrainAgingTempGradient", nGrainAgingTempGradient) @@ -10826,9 +11376,9 @@ subroutine init_icepack_package_configs(domain) frzpnd_in = config_pond_refreezing_type, & saltflux_option_in = config_salt_flux_coupling_type, & floeshape_in = config_floeshape, & - !wave_spec_in = , & ! not yet implemented in MPAS-SI (stealth feature) - !wave_spec_type_in = , & ! not yet implemented in MPAS-SI (stealth feature) - !nfreq_in = , & ! not yet implemented in MPAS-SI (stealth feature) + wave_spec_in = config_use_column_waves, & + wave_spec_type_in = config_wave_spec_type, & + nfreq_in = config_nFrequencies, & dpscale_in = config_pond_flushing_factor, & rfracmin_in = config_min_meltwater_retained_fraction, & rfracmax_in = config_max_meltwater_retained_fraction, & diff --git a/components/ww3/bld/build-namelist b/components/ww3/bld/build-namelist index df2ac2c63186..ae89aff067fa 100755 --- a/components/ww3/bld/build-namelist +++ b/components/ww3/bld/build-namelist @@ -386,6 +386,7 @@ if ($NML_TYPE eq "ww3_grid_nml") { add_default($nl, 'plon'); add_default($nl, 'unrot'); + add_default($nl,'e3d', 'val'=>"1"); add_default($nl, 'ussp', 'val'=>"1"); add_default($nl, 'iussp', 'val'=>"6"); add_default($nl, 'stk_wn', 'val'=>"0.01,0.03,0.06,0.1,0.2,0.35"); @@ -395,7 +396,9 @@ if ($NML_TYPE eq "ww3_grid_nml") { add_default($nl, 'uostfilelocal', 'val'=>"'${DIN_LOC_ROOT}/wav/ww3/obstructions_local.${WAV_GRID}${WAV_SPEC}.rtd.in'"); add_default($nl, 'uostfileshadow', 'val'=>"'${DIN_LOC_ROOT}/wav/ww3/obstructions_shadow.${WAV_GRID}${WAV_SPEC}.rtd.in'"); - + + add_default($nl, 'ic4method', 'val'=>"8"); + add_default($nl, 'icnumerics', 'val'=>".true."); } #----------------------------------------------------------------------------------------------- @@ -420,7 +423,7 @@ if ($NML_TYPE eq "ww3_grid") { $outfile = "./ww3_grid.nml"; } if ($NML_TYPE eq "ww3_grid_nml") { - @groups = qw(unst rotd outs uost misc); + @groups = qw(unst rotd outs uost misc sic4); $outfile = "./ww3_grid_namelists.nml"; } diff --git a/components/ww3/bld/namelist_files/namelist_defaults_ww3.xml b/components/ww3/bld/namelist_files/namelist_defaults_ww3.xml index 3a887bb7c449..ad1c992b67e3 100644 --- a/components/ww3/bld/namelist_files/namelist_defaults_ww3.xml +++ b/components/ww3/bld/namelist_files/namelist_defaults_ww3.xml @@ -18,8 +18,8 @@ attributes to express the dependency. --> 0 0 -"WND HS FP DP USS" -"USS USP HS FP DP CHA Z0 USTAR2" +"WND HS FP DP USS EF" +"USS USP HS FP DP CHA Z0 USTAR2 EF" diff --git a/components/ww3/bld/namelist_files/namelist_defaults_ww3_grid_nml.xml b/components/ww3/bld/namelist_files/namelist_defaults_ww3_grid_nml.xml index 9ebe2b4e5059..a7b761cb6376 100644 --- a/components/ww3/bld/namelist_files/namelist_defaults_ww3_grid_nml.xml +++ b/components/ww3/bld/namelist_files/namelist_defaults_ww3_grid_nml.xml @@ -100,7 +100,10 @@ attributes to express the dependency. 0.70 1.50 1.50 - + + +1 + .true. @@ -203,6 +206,7 @@ attributes to express the dependency. .false. .true. 1.20 +.false. diff --git a/components/ww3/bld/namelist_files/namelist_definition_ww3_grid_nml.xml b/components/ww3/bld/namelist_files/namelist_definition_ww3_grid_nml.xml index 48984963efc1..e99b785e0205 100644 --- a/components/ww3/bld/namelist_files/namelist_definition_ww3_grid_nml.xml +++ b/components/ww3/bld/namelist_files/namelist_definition_ww3_grid_nml.xml @@ -405,7 +405,15 @@ group="sbt1" valid_values=""> - + + + + + + + diff --git a/components/ww3/cime_config/buildlib_cmake b/components/ww3/cime_config/buildlib_cmake index d67f8c94c670..cd180f432c83 100755 --- a/components/ww3/cime_config/buildlib_cmake +++ b/components/ww3/cime_config/buildlib_cmake @@ -25,6 +25,7 @@ def buildlib(bldroot, installpath, case): srcroot = case.get_value("SRCROOT") caseroot = case.get_value("CASEROOT") casebuild = case.get_value("CASEBUILD") + compset = case.get_value("COMPSET") with open(os.path.join(casebuild, "ww3conf", "Filepath"), "w") as fd: fd.write(f"{caseroot}/SourceMods/src.ww3\n") @@ -32,7 +33,10 @@ def buildlib(bldroot, installpath, case): fd.write(f"{srcroot}/components/ww3/src/WW3/model/src/SCRIP\n") fd.write(f"{srcroot}/components/ww3/src/cpl\n") - cmake_args = " -DSWITCH=E3SM" + if "MPASSI" in compset: + cmake_args = " -DSWITCH=E3SM_wavice" + else: + cmake_args = " -DSWITCH=E3SM" return cmake_args diff --git a/components/ww3/src/WW3 b/components/ww3/src/WW3 index 62e77e77d764..45079f45cbad 160000 --- a/components/ww3/src/WW3 +++ b/components/ww3/src/WW3 @@ -1 +1 @@ -Subproject commit 62e77e77d764292f9d8ad8a7d90b3e81782a2aab +Subproject commit 45079f45cbad6497fc4d5c68d81274f926873adc diff --git a/components/ww3/src/cpl/wav_comp_mct.F90 b/components/ww3/src/cpl/wav_comp_mct.F90 index 7ba2a9e5b673..f16ead5d96e9 100644 --- a/components/ww3/src/cpl/wav_comp_mct.F90 +++ b/components/ww3/src/cpl/wav_comp_mct.F90 @@ -133,11 +133,11 @@ MODULE WAV_COMP_MCT use w3gdatmd, only: dtmax, dtcfl, dtcfli, dtmin, & nx, ny, nsea, nseal, mapsf, mapfs, mapsta, mapst2, x0, y0, sx, sy, xgrd, ygrd, & w3nmod, w3setg, AnglD, & - sig, nk, zb, dmin, & + sig, nk, zb, dmin, xfr, fr1, & usspf use w3wdatmd, only: time, w3ndat, w3setw, wlv, va, ust, ice use w3adatmd, only: ussp, w3naux, w3seta, sxx, sxy, syy, fliwnd, flcold, dw, cg, wn, hs, fp0, thp0, & - charn, z0,ustar2, tauwix, tauwiy, tauox, tauoy, tauocx, tauocy + charn, z0,ustar2, tauwix, tauwiy, tauox, tauoy, tauocx, tauocy, ef use w3idatmd, only: inflags1, inflags2,w3seti, w3ninp USE W3IDATMD, ONLY: TC0, CX0, CY0, TCN, CXN, CYN, ICEP1, ICEP5, TI1, TI5 USE W3IDATMD, ONLY: TW0, WX0, WY0, DT0, TWN, WXN, WYN, DTN @@ -167,7 +167,8 @@ MODULE WAV_COMP_MCT use seq_flds_mod use ww3_cpl_indices , only : ww3_cpl_indices_set - use ww3_cpl_indices , only : index_x2w_Sa_u, index_x2w_Sa_v, index_x2w_Sa_tbot, index_x2w_Si_ifrac, index_x2w_si_ithick + use ww3_cpl_indices , only : index_x2w_Sa_u, index_x2w_Sa_v, index_x2w_Sa_tbot + use ww3_cpl_indices , only : index_x2w_Si_ifrac, index_x2w_si_ithick, index_x2w_Si_ifloe use ww3_cpl_indices , only : index_x2w_So_t, index_x2w_So_u, index_x2w_So_v, index_x2w_So_bldepth, index_x2w_So_ssh use ww3_cpl_indices , only : index_w2x_Sw_ustokes_wavenumber_1, index_w2x_Sw_vstokes_wavenumber_1, & index_w2x_Sw_ustokes_wavenumber_2, index_w2x_Sw_vstokes_wavenumber_2, & @@ -178,7 +179,8 @@ MODULE WAV_COMP_MCT index_w2x_Sw_Hs, index_w2x_Sw_Fp, index_w2x_Sw_Dp, & index_w2x_Sw_Charn, index_w2x_Sw_Ustar, index_w2x_Sw_Z0, & index_w2x_Faww_Tawx, index_w2x_Faww_Tawy, index_w2x_Fwow_Twox, & - index_w2x_Fwow_Twoy, index_w2x_Faow_Tocx, index_w2x_Faow_Tocy + index_w2x_Fwow_Twoy, index_w2x_Faow_Tocx, index_w2x_Faow_Tocy, & + index_w2x_Sw_wavespec use shr_sys_mod , only : shr_sys_flush, shr_sys_abort @@ -978,6 +980,7 @@ SUBROUTINE WAV_RUN_MCT(EClock, cdata_w, x2w_w, w2x_w) integer :: tod ! current time of day (sec) integer :: hh,mm,ss integer :: n,jsea,isea + integer :: ifreq integer :: mpi_comm integer :: gindex integer :: nu @@ -1154,10 +1157,11 @@ SUBROUTINE WAV_RUN_MCT(EClock, cdata_w, x2w_w, w2x_w) endif if (inflags1(4)) then - ICEI(IX,IY) = x2w0%rattr(index_x2w_si_ifrac,gindex) - ICEP1(IX,IY) = x2w0%rattr(index_x2w_si_ithick,gindex) - !ICEP5(IX,IY) = x2w0%rattr(index_x2w_si_ifloe,gindex) - + ICEI(IX,IY) = x2w0%rattr(index_x2w_Si_ifrac,gindex) + ICEP1(IX,IY) = x2w0%rattr(index_x2w_Si_ithick,gindex) + if (wav_ice_coup .eq. 'twoway') then + ICEP5(IX,IY) = x2w0%rattr(index_x2w_Si_ifloe,gindex) + endif endif enddo @@ -1221,6 +1225,11 @@ SUBROUTINE WAV_RUN_MCT(EClock, cdata_w, x2w_w, w2x_w) w2x_w%rattr(index_w2x_Sw_ustokes_wavenumber_6,jsea) = USSP(jsea,6) w2x_w%rattr(index_w2x_Sw_vstokes_wavenumber_6,jsea) = USSP(jsea,nk+6) endif + if (wav_ice_coup .eq. 'twoway') then + do ifreq=1,nk + w2x_w%rattr(index_w2x_Sw_wavespec(ifreq),jsea) = EF(jsea,ifreq) + enddo + endif else if (wav_ocn_coup .eq. 'twoway' .or. wav_atm_coup .eq. 'twoway') then w2x_w%rattr(index_w2x_Sw_Charn,jsea) = 0.0 @@ -1257,6 +1266,11 @@ SUBROUTINE WAV_RUN_MCT(EClock, cdata_w, x2w_w, w2x_w) w2x_w%rattr(index_w2x_Sw_ustokes_wavenumber_6,jsea) = 0.0 w2x_w%rattr(index_w2x_Sw_vstokes_wavenumber_6,jsea) = 0.0 endif + if (wav_ice_coup .eq. 'twoway') then + do ifreq=1,nk + w2x_w%rattr(index_w2x_Sw_wavespec(ifreq),jsea) = 0.0 + enddo + endif endif enddo diff --git a/components/ww3/src/cpl/ww3_cpl_indices.f90 b/components/ww3/src/cpl/ww3_cpl_indices.f90 index 7a4ac961624a..5689d2468bba 100644 --- a/components/ww3/src/cpl/ww3_cpl_indices.f90 +++ b/components/ww3/src/cpl/ww3_cpl_indices.f90 @@ -13,7 +13,7 @@ module ww3_cpl_indices integer :: index_x2w_Sa_tbot integer :: index_x2w_Si_ifrac integer :: index_x2w_Si_ithick - integer :: index_x2w_si_ifloe + integer :: index_x2w_Si_ifloe integer :: index_x2w_So_t integer :: index_x2w_So_u integer :: index_x2w_So_v @@ -47,14 +47,19 @@ module ww3_cpl_indices integer :: index_w2x_Faow_Tocx integer :: index_w2x_Faow_Tocy + integer,dimension(:),allocatable :: index_w2x_Sw_wavespec contains subroutine ww3_cpl_indices_set( ) - use seq_flds_mod, only : wav_atm_coup, wav_ocn_coup + use seq_flds_mod, only : wav_atm_coup, wav_ocn_coup, wav_ice_coup, wav_nfreq type(mct_aVect) :: w2x ! temporary type(mct_aVect) :: x2w ! temporary + integer :: i + character(len= 2) :: freqnum + character(len=64) :: name + ! Determine attribute vector indices ! create temporary attribute vectors @@ -66,7 +71,9 @@ subroutine ww3_cpl_indices_set( ) index_x2w_Sa_tbot = mct_avect_indexra(x2w,'Sa_tbot') ! Temperature at lowest level index_x2w_Si_ifrac = mct_avect_indexra(x2w,'Si_ifrac') ! Fractional sea ice coverage index_x2w_Si_ithick = mct_avect_indexra(x2w,'Si_ithick') ! Sea ice thickness - !index_x2w_Si_ifloe = mct_avect_indexra(x2w,'Si_ifloe') ! Sea ice floe size + if (wav_ice_coup .eq. 'twoway') then + index_x2w_Si_ifloe = mct_avect_indexra(x2w,'Si_ifloe') ! Sea ice floe size + endif index_x2w_So_t = mct_avect_indexra(x2w,'So_t') ! Sea surface temperature index_x2w_So_u = mct_avect_indexra(x2w,'So_u') ! Zonal sea surface water velocity index_x2w_So_v = mct_avect_indexra(x2w,'So_v') ! Meridional sea surface water velocity @@ -101,6 +108,14 @@ subroutine ww3_cpl_indices_set( ) index_w2x_Sw_ustokes_wavenumber_6 = mct_avect_indexra(w2x,'Sw_ustokes_wavenumber_6') ! partitioned Stokes drift u 6 index_w2x_Sw_vstokes_wavenumber_6 = mct_avect_indexra(w2x,'Sw_vstokes_wavenumber_6') ! partitioned Stokes drift v 6 endif + if (wav_ice_coup .eq. 'twoway') then + allocate(index_w2x_Sw_wavespec(1:wav_nfreq)) + do i = 1,wav_nfreq + write(freqnum,'(i2.2)') i + name = 'Sw_wavespec' // freqnum + index_w2x_Sw_wavespec(i) = mct_avect_indexra(w2x,trim(name)) ! full wave spectrum (fcn of frq) + enddo + endif call mct_aVect_clean(x2w) call mct_aVect_clean(w2x) diff --git a/driver-mct/cime_config/buildnml b/driver-mct/cime_config/buildnml index d7b2e91e08a3..8807d9928f5c 100755 --- a/driver-mct/cime_config/buildnml +++ b/driver-mct/cime_config/buildnml @@ -87,7 +87,7 @@ def _create_drv_namelists(case, infile, confdir, nmlgen, files): config['WAV_OCN_COUP'] = 'oneway' if case.get_value('COMP_ICE') == 'mpassi': - config['WAV_ICE_COUP'] = 'oneway' + config['WAV_ICE_COUP'] = 'twoway' elif case.get_value('COMP_WAV') == 'dwav': config['WAVSPEC'] = 'sp36x36' else: diff --git a/driver-mct/cime_config/config_component.xml b/driver-mct/cime_config/config_component.xml index 99a2bbb65e86..5ca0ee84a04c 100644 --- a/driver-mct/cime_config/config_component.xml +++ b/driver-mct/cime_config/config_component.xml @@ -2054,6 +2054,23 @@ ice2wav state mapping file decomp type + + char + idmap + run_domain + env_run.xml + wav2ice state mapping file + + + + char + X,Y + Y + run_domain + env_run.xml + wav2ice state mapping file decomp type + + char idmap diff --git a/driver-mct/cime_config/namelist_definition_drv.xml b/driver-mct/cime_config/namelist_definition_drv.xml index 6748981258c2..949861e0ab5a 100644 --- a/driver-mct/cime_config/namelist_definition_drv.xml +++ b/driver-mct/cime_config/namelist_definition_drv.xml @@ -358,6 +358,22 @@ none oneway + twoway + + + + + integer + seq_flds + seq_cplflds_inparm + + Number of wave frequencies. Set by the xml variable WAV_SPEC in env_case.xml + + + 0 + 25 + 36 + 50 @@ -5201,6 +5217,36 @@ + + char + mapping + abs + seq_maps + + wav to ice state mapping file for states + + + $WAV2ICE_SMAPNAME + + + + + char + mapping + seq_maps + + The type of mapping desired, either "source" or "destination" mapping. + X is associated with rearrangement of the source grid to the + destination grid and then local mapping. Y is associated with mapping + on the source grid and then rearrangement and sum to the destination + grid. + + + $WAV2ICE_SMAPTYPE + X + + + char(10) drv_physics diff --git a/driver-mct/main/cime_comp_mod.F90 b/driver-mct/main/cime_comp_mod.F90 index 7d1d12be9bbf..b0b4822141b3 100644 --- a/driver-mct/main/cime_comp_mod.F90 +++ b/driver-mct/main/cime_comp_mod.F90 @@ -70,7 +70,7 @@ module cime_comp_mod use seq_comm_mct, only: CPLATMID,CPLLNDID,CPLOCNID,CPLICEID,CPLGLCID,CPLROFID,CPLWAVID,CPLESPID use seq_comm_mct, only: IACID, ALLIACID, CPLALLIACID, CPLIACID use seq_comm_mct, only: num_inst_atm, num_inst_lnd, num_inst_rof - use seq_comm_mct, only: num_inst_ocn, num_inst_ice, num_inst_glc, num_inst_wav + use seq_comm_mct, only: num_inst_ocn, num_inst_ice, num_inst_glc use seq_comm_mct, only: num_inst_wav, num_inst_esp use seq_comm_mct, only: num_inst_iac use seq_comm_mct, only: num_inst_xao, num_inst_frc, num_inst_phys @@ -459,6 +459,7 @@ module cime_comp_mod logical :: glcshelf_c2_ocn ! .true. => glc ice shelf to ocn coupling on logical :: glcshelf_c2_ice ! .true. => glc ice shelf to ice coupling on logical :: wav_c2_ocn ! .true. => wav to ocn coupling on + logical :: wav_c2_ice ! .true. => wav to ice coupling on logical :: iac_c2_lnd ! .true. => iac to lnd coupling on logical :: iac_c2_atm ! .true. => iac to atm coupling on @@ -1793,6 +1794,8 @@ subroutine cime_init() glcshelf_c2_ocn = .false. glcshelf_c2_ice = .false. wav_c2_ocn = .false. + wav_c2_atm = .false. + wav_c2_ice = .false. iac_c2_atm = .false. iac_c2_lnd = .false. lnd_c2_iac = .false. @@ -1843,7 +1846,9 @@ subroutine cime_init() if (glcice_present .and. iceberg_prognostic) glc_c2_ice = .true. endif if (wav_present) then + if (atm_prognostic) wav_c2_atm = .true. if (ocn_prognostic) wav_c2_ocn = .true. + if (ice_prognostic) wav_c2_ice = .true. endif if (iac_present) then if (lnd_prognostic) iac_c2_lnd = .true. @@ -1935,6 +1940,8 @@ subroutine cime_init() write(logunit,F0L)'glcshelf_c2_ocn = ',glcshelf_c2_ocn write(logunit,F0L)'glcshelf_c2_ice = ',glcshelf_c2_ice write(logunit,F0L)'wav_c2_ocn = ',wav_c2_ocn + write(logunit,F0L)'wav_c2_atm = ',wav_c2_atm + write(logunit,F0L)'wav_c2_ice = ',wav_c2_ice write(logunit,F0L)'iac_c2_lnd = ',iac_c2_lnd write(logunit,F0L)'iac_c2_atm = ',iac_c2_atm @@ -2077,7 +2084,7 @@ subroutine cime_init() call prep_ocn_init(infodata, atm_c2_ocn, atm_c2_ice, ice_c2_ocn, rof_c2_ocn, wav_c2_ocn, glc_c2_ocn, glcshelf_c2_ocn) - call prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_c2_ice ) + call prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_c2_ice, wav_c2_ice) call prep_rof_init(infodata, lnd_c2_rof, atm_c2_rof, ocn_c2_rof) @@ -4654,6 +4661,8 @@ subroutine cime_run_ice_setup_send() call prep_ice_calc_a2x_ix(a2x_ox, timer='CPL:iceprep_atm2ice') endif + if (wav_c2_ice) call prep_ice_calc_w2x_ix(timer='CPL:iceprep_wav2ice') + call prep_ice_mrg(infodata, timer_mrg='CPL:iceprep_mrgx2i') call component_diag(infodata, ice, flow='x2c', comment= 'send ice', & diff --git a/driver-mct/main/mrg_mod.F90 b/driver-mct/main/mrg_mod.F90 index 225552a1712a..0f78d5798592 100644 --- a/driver-mct/main/mrg_mod.F90 +++ b/driver-mct/main/mrg_mod.F90 @@ -256,7 +256,7 @@ end subroutine mrg_x2a_run_mct !-------------------------------------------------------------------------- - subroutine mrg_x2i_run_mct( cdata_i, a2x_i, o2x_i, x2i_i ) + subroutine mrg_x2i_run_mct( cdata_i, a2x_i, o2x_i, w2x_i, x2i_i ) !----------------------------------------------------------------------- ! @@ -266,6 +266,7 @@ subroutine mrg_x2i_run_mct( cdata_i, a2x_i, o2x_i, x2i_i ) type(mct_aVect),intent(in) :: a2x_i type(mct_aVect),intent(in) :: o2x_i type(mct_aVect),intent(inout):: x2i_i + type(mct_aVect),intent(in) :: w2x_i ! ! Local variables ! @@ -345,6 +346,7 @@ subroutine mrg_x2i_run_mct( cdata_i, a2x_i, o2x_i, x2i_i ) call mct_aVect_copy(aVin=o2x_i, aVout=x2i_i, vector=mct_usevector) call mct_aVect_copy(aVin=a2x_i, aVout=x2i_i, vector=mct_usevector) + call mct_aVect_copy(aVin=w2x_i, aVout=x2i_i, vector=mct_usevector) ! Merge total snow and precip for ice input ! Scale total precip and runoff by flux_epbalfact diff --git a/driver-mct/main/prep_ice_mod.F90 b/driver-mct/main/prep_ice_mod.F90 index 36c478f6fc21..fe5b744c938f 100644 --- a/driver-mct/main/prep_ice_mod.F90 +++ b/driver-mct/main/prep_ice_mod.F90 @@ -6,6 +6,7 @@ module prep_ice_mod use shr_sys_mod , only: shr_sys_abort, shr_sys_flush use seq_comm_mct , only: num_inst_atm, num_inst_ocn, num_inst_glc use seq_comm_mct , only: num_inst_ice, num_inst_frc, num_inst_rof + use seq_comm_mct , only: num_inst_wav use seq_comm_mct , only: CPLID, ICEID, logunit use seq_comm_mct , only: seq_comm_getData=>seq_comm_setptrs use seq_infodata_mod, only: seq_infodata_type, seq_infodata_getdata @@ -16,7 +17,7 @@ module prep_ice_mod use mct_mod use perf_mod use component_type_mod, only: component_get_x2c_cx, component_get_c2x_cx - use component_type_mod, only: ice, atm, ocn, glc, rof + use component_type_mod, only: ice, atm, ocn, glc, rof, wav implicit none save @@ -34,16 +35,19 @@ module prep_ice_mod public :: prep_ice_calc_r2x_ix public :: prep_ice_calc_g2x_ix public :: prep_ice_shelf_calc_g2x_ix + public :: prep_ice_calc_w2x_ix public :: prep_ice_get_a2x_ix public :: prep_ice_get_o2x_ix public :: prep_ice_get_g2x_ix public :: prep_ice_get_r2x_ix + public :: prep_ice_get_w2x_ix public :: prep_ice_get_mapper_SFo2i public :: prep_ice_get_mapper_Rg2i public :: prep_ice_get_mapper_Sg2i public :: prep_ice_get_mapper_Fg2i + public :: prep_ice_get_mapper_Sw2i !-------------------------------------------------------------------------- ! Private interfaces @@ -61,12 +65,14 @@ module prep_ice_mod type(seq_map), pointer :: mapper_Sg2i type(seq_map), pointer :: mapper_Fg2i type(seq_map), pointer :: mapper_Rr2i + type(seq_map), pointer :: mapper_Sw2i ! attribute vectors type(mct_aVect), pointer :: a2x_ix(:) ! Atm export, ice grid, cpl pes - allocated in driver type(mct_aVect), pointer :: o2x_ix(:) ! Ocn export, ice grid, cpl pes - allocated in driver type(mct_aVect), pointer :: g2x_ix(:) ! Glc export, ice grid, cpl pes - allocated in driver type(mct_aVect), pointer :: r2x_ix(:) ! Rof export, ice grid, cpl pes - allocated in driver + type(mct_aVect), pointer :: w2x_ix(:) ! Wav export, ice grid, cpl pes - allocated in driver ! seq_comm_getData variables integer :: mpicom_CPLID ! MPI cpl communicator @@ -76,7 +82,8 @@ module prep_ice_mod !================================================================================================ - subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_c2_ice) + subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_c2_ice, & + wav_c2_ice) !--------------------------------------------------------------- ! Description @@ -89,19 +96,22 @@ subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_ logical, intent(in) :: glc_c2_ice ! .true. => glc to ice coupling on logical, intent(in) :: glcshelf_c2_ice ! .true. => glc ice shelf to ice coupling on logical, intent(in) :: rof_c2_ice ! .true. => rof to ice coupling on + logical, intent(in) :: wav_c2_ice ! .true. => wav to ice coupling on ! ! Local Variables integer :: lsize_i - integer :: eai, eoi, egi, eri + integer :: eai, eoi, egi, eri, ewi logical :: iamroot_CPLID ! .true. => CPLID masterproc logical :: samegrid_ig ! samegrid glc and ice logical :: samegrid_ro ! samegrid rof and ice/ocn + logical :: samegrid_iw ! samegrid ice and wav logical :: ice_present ! .true. => ice is present logical :: esmf_map_flag ! .true. => use esmf for mapping character(CL) :: ice_gnam ! ice grid character(CL) :: ocn_gnam ! ocn grid character(CL) :: glc_gnam ! glc grid character(CL) :: rof_gnam ! rof grid + character(CL) :: wav_gnam ! wav grid type(mct_avect), pointer :: i2x_ix character(*), parameter :: subname = '(prep_ice_init)' character(*), parameter :: F00 = "('"//subname//" : ', 4A )" @@ -113,6 +123,7 @@ subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_ ice_gnam=ice_gnam , & ocn_gnam=ocn_gnam , & rof_gnam=rof_gnam , & + wav_gnam=wav_gnam , & glc_gnam=glc_gnam) allocate(mapper_SFo2i) @@ -120,6 +131,7 @@ subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_ allocate(mapper_Sg2i) allocate(mapper_Fg2i) allocate(mapper_Rr2i) + allocate(mapper_Sw2i) if (ice_present) then @@ -149,11 +161,18 @@ subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_ call mct_aVect_init(r2x_ix(eri), rList=seq_flds_r2x_fields, lsize=lsize_i) call mct_aVect_zero(r2x_ix(eri)) end do + allocate(w2x_ix(num_inst_wav)) + do ewi = 1,num_inst_wav + call mct_aVect_init(w2x_ix(ewi), rList=seq_flds_w2x_fields, lsize=lsize_i) + call mct_aVect_zero(w2x_ix(ewi)) + end do samegrid_ig = .true. samegrid_ro = .true. + samegrid_iw = .true. if (trim(ice_gnam) /= trim(glc_gnam)) samegrid_ig = .false. if (trim(rof_gnam) /= trim(ocn_gnam)) samegrid_ro = .false. + if (trim(ice_gnam) /= trim(wav_gnam)) samegrid_iw = .false. if (ocn_c2_ice) then if (iamroot_CPLID) then @@ -202,6 +221,17 @@ subroutine prep_ice_init(infodata, ocn_c2_ice, glc_c2_ice, glcshelf_c2_ice, rof_ endif call shr_sys_flush(logunit) + if (wav_c2_ice) then + if (iamroot_CPLID) then + write(logunit,*) ' ' + write(logunit,F00) 'Initializing mapper_Sw2i' + end if + call seq_map_init_rcfile(mapper_Sw2i, wav(1), ice(1), & + 'seq_maps.rc','wav2ice_smapname:','wav2ice_smaptype:',samegrid_iw, & + 'mapper_Sw2i initialization', esmf_map_flag) + endif + call shr_sys_flush(logunit) + end if end subroutine prep_ice_init @@ -219,7 +249,7 @@ subroutine prep_ice_mrg(infodata, timer_mrg) character(len=*) , intent(in) :: timer_mrg ! ! Local Variables - integer :: eoi, eai, egi, eii, eri + integer :: eoi, eai, egi, eii, eri, ewi real(r8) :: flux_epbalfact ! adjusted precip factor type(mct_avect), pointer :: x2i_ix character(*), parameter :: subname = '(prep_ice_mrg)' @@ -235,11 +265,12 @@ subroutine prep_ice_mrg(infodata, timer_mrg) eoi = mod((eii-1),num_inst_ocn) + 1 eri = mod((eii-1),num_inst_rof) + 1 egi = mod((eii-1),num_inst_glc) + 1 + ewi = mod((eii-1),num_inst_wav) + 1 ! Apply correction to precipitation of requested driver namelist x2i_ix => component_get_x2c_cx(ice(eii)) ! This is actually modifying x2i_ix call prep_ice_merge(flux_epbalfact, a2x_ix(eai), o2x_ix(eoi), r2x_ix(eri), g2x_ix(egi), & - x2i_ix) + w2x_ix(ewi), x2i_ix) enddo call t_drvstopf (trim(timer_mrg)) @@ -247,8 +278,9 @@ end subroutine prep_ice_mrg !================================================================================================ - subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) + subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, w2x_i, x2i_i ) + use seq_flds_mod, only: wav_ice_coup !----------------------------------------------------------------------- ! ! Arguments @@ -257,6 +289,7 @@ subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) type(mct_aVect) , intent(in) :: o2x_i type(mct_aVect) , intent(in) :: r2x_i type(mct_aVect) , intent(in) :: g2x_i + type(mct_aVect) , intent(in) :: w2x_i type(mct_aVect) , intent(inout) :: x2i_i ! ! Local variables @@ -297,6 +330,7 @@ subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) type(mct_aVect_sharedindices),save :: o2x_sharedindices type(mct_aVect_sharedindices),save :: a2x_sharedindices type(mct_aVect_sharedindices),save :: g2x_sharedindices + type(mct_aVect_sharedindices),save :: w2x_sharedindices character(*), parameter :: subname = '(prep_ice_merge) ' !----------------------------------------------------------------------- @@ -347,6 +381,7 @@ subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) call mct_aVect_setSharedIndices(o2x_i, x2i_i, o2x_SharedIndices) call mct_aVect_setSharedIndices(a2x_i, x2i_i, a2x_SharedIndices) call mct_aVect_setSharedIndices(g2x_i, x2i_i, g2x_SharedIndices) + call mct_aVect_setSharedIndices(w2x_i, x2i_i, w2x_SharedIndices) !--- document copy operations --- do i=1,o2x_SharedIndices%shared_real%num_indices @@ -367,6 +402,12 @@ subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) field = mct_aVect_getRList2c(i1, g2x_i) mrgstr(o1) = trim(mrgstr(o1))//' = g2x%'//trim(field) enddo + do i=1,w2x_SharedIndices%shared_real%num_indices + i1=w2x_SharedIndices%shared_real%aVindices1(i) + o1=w2x_SharedIndices%shared_real%aVindices2(i) + field = mct_aVect_getRList2c(i1, w2x_i) + mrgstr(o1) = trim(mrgstr(o1))//' = w2x%'//trim(field) + enddo !--- document manual merges --- mrgstr(index_x2i_Faxa_rain) = trim(mrgstr(index_x2i_Faxa_rain))//' = '// & @@ -404,6 +445,7 @@ subroutine prep_ice_merge(flux_epbalfact, a2x_i, o2x_i, r2x_i, g2x_i, x2i_i ) call mct_aVect_copy(aVin=o2x_i, aVout=x2i_i, vector=mct_usevector, sharedIndices=o2x_SharedIndices) call mct_aVect_copy(aVin=a2x_i, aVout=x2i_i, vector=mct_usevector, sharedIndices=a2x_SharedIndices) call mct_aVect_copy(aVin=g2x_i, aVout=x2i_i, vector=mct_usevector, sharedIndices=g2x_SharedIndices) + if(wav_ice_coup == 'twoway') call mct_aVect_copy(aVin=w2x_i, aVout=x2i_i, vector=mct_usevector, sharedIndices=w2x_SharedIndices) ! Merge total snow and precip for ice input ! Scale total precip and runoff by flux_epbalfact @@ -566,6 +608,31 @@ subroutine prep_ice_calc_g2x_ix(timer) call t_drvstopf (trim(timer)) end subroutine prep_ice_calc_g2x_ix + + !================================================================================================ + + subroutine prep_ice_calc_w2x_ix(timer) + !--------------------------------------------------------------- + ! Description + ! Create w2x_ix (note that w2x_ix is a local module variable) + ! + ! Arguments + character(len=*), intent(in) :: timer + ! + ! Local Variables + integer :: ewi + type(mct_aVect), pointer :: w2x_wx + character(*), parameter :: subname = '(prep_ice_calc_w2x_ix)' + !--------------------------------------------------------------- + + call t_drvstartf (trim(timer),barrier=mpicom_CPLID) + do ewi = 1,num_inst_wav + w2x_wx => component_get_c2x_cx(wav(ewi)) + call seq_map_map(mapper_Sw2i, w2x_wx, w2x_ix(ewi), fldlist=seq_flds_w2x_states, norm=.true.) + enddo + call t_drvstopf (trim(timer)) + + end subroutine prep_ice_calc_w2x_ix !================================================================================================ @@ -615,6 +682,11 @@ function prep_ice_get_r2x_ix() prep_ice_get_r2x_ix => r2x_ix(:) end function prep_ice_get_r2x_ix + function prep_ice_get_w2x_ix() + type(mct_aVect), pointer :: prep_ice_get_w2x_ix(:) + prep_ice_get_w2x_ix => w2x_ix(:) + end function prep_ice_get_w2x_ix + function prep_ice_get_mapper_SFo2i() type(seq_map), pointer :: prep_ice_get_mapper_SFo2i prep_ice_get_mapper_SFo2i => mapper_SFo2i @@ -635,4 +707,9 @@ function prep_ice_get_mapper_Fg2i() prep_ice_get_mapper_Fg2i => mapper_Fg2i end function prep_ice_get_mapper_Fg2i + function prep_ice_get_mapper_Sw2i() + type(seq_map), pointer :: prep_ice_get_mapper_Sw2i + prep_ice_get_mapper_Sw2i => mapper_Sw2i + end function prep_ice_get_mapper_Sw2i + end module prep_ice_mod diff --git a/driver-mct/main/prep_wav_mod.F90 b/driver-mct/main/prep_wav_mod.F90 index c929c8fddacc..145685b1cf59 100644 --- a/driver-mct/main/prep_wav_mod.F90 +++ b/driver-mct/main/prep_wav_mod.F90 @@ -75,7 +75,7 @@ subroutine prep_wav_init(infodata, atm_c2_wav, ocn_c2_wav, ice_c2_wav) type(seq_infodata_type) , intent(in) :: infodata logical , intent(in) :: atm_c2_wav ! .true. => atm to wav coupling on logical , intent(in) :: ocn_c2_wav ! .true. => ocn to wav coupling on - logical , intent(in) :: ice_c2_wav ! .true. => ocn to wav coupling on + logical , intent(in) :: ice_c2_wav ! .true. => ice to wav coupling on ! ! Local Variables integer :: eai , eoi, eii diff --git a/driver-mct/shr/seq_flds_mod.F90 b/driver-mct/shr/seq_flds_mod.F90 index a4487a08e7a3..f9960c1761ec 100644 --- a/driver-mct/shr/seq_flds_mod.F90 +++ b/driver-mct/shr/seq_flds_mod.F90 @@ -150,6 +150,7 @@ module seq_flds_mod character(len=CX) :: ndep_fields ! List of nitrogen deposition fields from atm->lnd/ocn character(len=CX) :: fan_fields ! List of NH3 emission fields from lnd->atm integer :: ice_ncat ! number of sea ice thickness categories + integer :: wav_nfreq ! number of wave frequencies logical :: seq_flds_i2o_per_cat! .true. if select per ice thickness category fields are passed from ice to ocean logical :: rof_heat ! .true. if river model includes temperature @@ -417,7 +418,7 @@ subroutine seq_flds_set(nmlfile, ID, infodata) glc_nec, glc_nzoc, ice_ncat, seq_flds_i2o_per_cat, flds_bgc_oi, & nan_check_component_fields, rof_heat, atm_flux_method, atm_gustiness, & rof2ocn_nutrients, lnd_rof_two_way, ocn_rof_two_way, ocn_lnd_one_way, rof_sed, & - wav_ocn_coup, wav_atm_coup, wav_ice_coup, add_iac_to_cplstate + wav_ocn_coup, wav_atm_coup, wav_ice_coup, wav_nfreq, add_iac_to_cplstate ! user specified new fields integer, parameter :: nfldmax = 200 @@ -453,6 +454,7 @@ subroutine seq_flds_set(nmlfile, ID, infodata) glc_nec = 0 glc_nzoc = 0 ice_ncat = 1 + wav_nfreq = 0 seq_flds_i2o_per_cat = .false. nan_check_component_fields = .false. rof_heat = .false. @@ -494,6 +496,7 @@ subroutine seq_flds_set(nmlfile, ID, infodata) call shr_mpi_bcast(glc_nec , mpicom) call shr_mpi_bcast(glc_nzoc , mpicom) call shr_mpi_bcast(ice_ncat , mpicom) + call shr_mpi_bcast(wav_nfreq , mpicom) call shr_mpi_bcast(seq_flds_i2o_per_cat, mpicom) call shr_mpi_bcast(nan_check_component_fields, mpicom) call shr_mpi_bcast(rof_heat , mpicom) @@ -2233,7 +2236,7 @@ subroutine seq_flds_set(nmlfile, ID, infodata) endif !------------------------------ - ! ice<->wav only exchange + ! ice<->wav exchange !------------------------------ ! Sea ice thickness @@ -2245,6 +2248,43 @@ subroutine seq_flds_set(nmlfile, ID, infodata) attname = 'Si_ithick' call metadata_set(attname, longname, stdname, units) + ! Sea ice floe Size + if (wav_ice_coup .eq. 'twoway') then + call seq_flds_add(i2x_states,"Si_ifloe") + call seq_flds_add(x2w_states,"Si_ifloe") + longname = 'Sea ice floe size' + stdname = 'sea_ice_floe_size' + units = 'm' + attname = 'Si_ifloe' + call metadata_set(attname, longname, stdname, units) + endif + + ! Significant Wave Height + if (wav_ocn_coup .eq. 'twoway' .or. wav_ice_coup .eq. 'twoway') then + call seq_flds_add(w2x_states,'Sw_Hs') + if (wav_ice_coup .eq. 'twoway') call seq_flds_add(x2i_states,'Sw_Hs') + if (wav_ocn_coup .eq. 'twoway') call seq_flds_add(x2o_states,'Sw_Hs') + longname = 'Significant wave height' + stdname = 'significant_wave_height' + units = 'm' + attname = 'Sw_Hs' + call metadata_set(attname, longname, stdname, units) + endif + + ! Wave spectra + if (wav_ice_coup .eq. 'twoway') then + do num = 1, wav_nfreq + write(cnum,'(i2.2)') num + name = 'Sw_wavespec' // cnum + call seq_flds_add(w2x_states,trim(name)) + call seq_flds_add(x2i_states,trim(name)) + longname = 'wave power spectra for wave frequency category number' // cnum + stdname = 'wave_spectra' + units = 'm2/Hz' + attname = name + call metadata_set(attname, longname, stdname, units) + enddo + endif !----------------------------- ! lnd->rof exchange @@ -2575,14 +2615,6 @@ subroutine seq_flds_set(nmlfile, ID, infodata) ! wav->ocn and ocn->wav !----------------------------- if (wav_ocn_coup == 'twoway') then - call seq_flds_add(w2x_states,'Sw_Hs') - call seq_flds_add(x2o_states,'Sw_Hs') - longname = 'Significant wave height' - stdname = 'significant_wave_height' - units = 'm' - attname = 'Sw_Hs' - call metadata_set(attname, longname, stdname, units) - call seq_flds_add(w2x_states,'Sw_ustokes_wavenumber_1') call seq_flds_add(x2o_states,'Sw_ustokes_wavenumber_1') longname = 'Partitioned Stokes drift u component, wavenumber 1' diff --git a/externals/ekat b/externals/ekat index 66c4c3561e33..d13cf66f3543 160000 --- a/externals/ekat +++ b/externals/ekat @@ -1 +1 @@ -Subproject commit 66c4c3561e337da8bfdb9d2304318bf4ae2e97cf +Subproject commit d13cf66f3543df10445cd1fd35edac3165cc2833 diff --git a/share/pcd.yaml b/share/pcd.yaml index de85553f3bb5..fac294d58977 100644 --- a/share/pcd.yaml +++ b/share/pcd.yaml @@ -10,7 +10,7 @@ # physical_constants_dictionary: - version_number: 0.0.2 + version_number: 0.1.0 institution: E3SM Project description: Community-based dictionary for consistent physical constant sets contact: E3SM Project @@ -47,6 +47,93 @@ physical_constants_dictionary: Properties of Ordinary Water Substance for General and Scientific Use, J. Phys. Chem. Ref. Data, 31, 387-535, 2002. " + SI2019: + description: "SI brochure defining units and accepted non-SI units" + citation: " + Bureau International des Poids et Mesures (BIPM), The International + System of Units (SI), 9th edition, 2019. + " + CGPM1948_1954: + description: "CGPM resolutions linking the triple point and the 0.01 degree offset" + citation: " + 9th CGPM (1948), Resolution 3, stating that the zero of the centesimal + thermodynamic scale is 0.0100 degree below the triple point of water + (DOI: 10.59161/CGPM1948RES3E); + and 10th CGPM (1954), Resolution 3, assigning the triple point of water + the exact temperature 273.16 kelvin + (DOI: 10.59161/CGPM1954RES3E). + " + US_StdAtm_1976: + description: "U.S. Standard Atmosphere reference state values" + citation: " + NOAA, NASA, and USAF, U.S. Standard Atmosphere, 1976, + U.S. Government Printing Office, Washington, D.C., 1976. + " + CRC2018: + description: "CRC handbook tabulations for thermophysical properties" + citation: " + Rumble, J. R. (ed.), CRC Handbook of Chemistry and Physics, + 99th Edition, CRC Press, 2018. + " + Smithsonian1951: + description: "Smithsonian meteorological tables with latent-heat tabulations" + citation: " + List, R. J., Smithsonian Meteorological Tables, + Smithsonian Institution, Washington, D.C., 1951. + " + Gill_1982: + description: "Atmosphere-Ocean Dynamics textbook with canonical Boussinesq scaling" + citation: " + Gill, A. E., Atmosphere-Ocean Dynamics, + International Geophysics Series, Vol. 30, + Academic Press, 1982. + " + Levitus1982: + description: "Climatological atlas source for world-ocean salinity fields" + citation: " + Levitus, S., Climatological Atlas of the World Ocean, + NOAA Professional Paper 13, U.S. Department of Commerce, 1982. + " + IUPAC1997: + description: "IUPAC Commission on Atomic Weights and Isotopic Abundances, 1997 table" + citation: " + IUPAC Commission on Atomic Weights and Isotopic Abundances, + Atomic Weights of the Elements 1997, + Pure Appl. Chem., 71(8), 1593-1607, 1999. + doi:10.1351/pac199971081593. + " + Untersteiner1961: + description: "Arctic sea-ice mass and heat budget study" + citation: " + Untersteiner, N., On the Mass and Heat Budget of Arctic Sea Ice, + Archiv für Meteorologie, Geophysik und Bioklimatologie, Serie A, 1961. + " + MaykutUntersteiner1971: + description: "Time-dependent thermodynamic sea-ice model results" + citation: " + Maykut, G. A., and N. Untersteiner, Some results from a time-dependent + thermodynamic model of sea ice, Journal of Geophysical Research, + 76(6), 1550-1575, 1971. + " + CuffeyPaterson2010: + description: "Glaciology textbook reference for ice physical properties" + citation: " + Cuffey, K. M., and W. S. B. Paterson, The Physics of Glaciers, + 4th ed., Butterworth-Heinemann, 2010. + " + IAEA1995: + description: "IAEA reference materials for stable isotopes of light elements" + citation: " + IAEA, Reference and intercomparison materials for stable isotopes of + light elements, IAEA-TECDOC-825, Vienna, 1995. + " + Craig1957: + description: "Isotopic standards for carbon and oxygen" + citation: " + Craig, H., Isotopic standards for carbon and oxygen and correction + factors for mass-spectrometric analysis of carbon dioxide, + Geochimica et Cosmochimica Acta, 12(1-2), 133-149, 1957. + " set: - mathematical: @@ -203,7 +290,7 @@ physical_constants_dictionary: reference: NIST_CODATA2022 description: " Proportionality constant between the power emitted by - a black body and the fourth power of its temperature, + a black body and the fourth power of its temperature, according to Stefan-Boltzmann's law. " @@ -241,6 +328,40 @@ physical_constants_dictionary: Ideal gas molar volume as computed by the ideal gas law (RT/p) at Standard Temperature and Pressure (STP: 273.15K, 100 kPa). " + - chemical_molar_masses: + description: " + Reference molar masses for chemical elements and compounds used + across model components. + " + entries: + - name: carbon_molar_mass_reference + value: 0.0120107 + units: kg mol-1 + prec: double + type: strict + reference: IUPAC1997 + description: " + Reference molar mass of elemental carbon. + " + - name: oxygen_molar_mass_reference + value: 0.0159994 + units: kg mol-1 + prec: double + type: strict + reference: IUPAC1997 + description: " + Reference molar mass of elemental oxygen. + " + - name: carbon_dioxide_molar_mass_reference + value: 0.0440095 + units: kg mol-1 + prec: double + type: strict + reference: IUPAC1997 + description: " + Derived from IUPAC standard atomic weights as + carbon_molar_mass_reference + 2*oxygen_molar_mass_reference. + " - earth_physical: description: " Properties of the planet Earth @@ -338,7 +459,7 @@ physical_constants_dictionary: reference: GRS80 description: " Radius of Earth's reference sphere with same surface as ellipsoid's. - Derived constant + Derived constant. " - name: radius_of_sphere_of_same_volume value: 6371000.7900 @@ -373,8 +494,8 @@ physical_constants_dictionary: " entries: - name: water_molar_mass - value: 18.015268 - units: g mol-1 + value: 0.018015268 + units: kg mol-1 prec: double type: strict uncertainty: exact @@ -435,3 +556,300 @@ physical_constants_dictionary: Triple point density of vapor H2O. Calculated from Eq. (6.4). " + - name: pure_water_freezing_temperature_reference + value: 273.15 + units: K + prec: double + type: strict + reference: CGPM1948_1954 + description: " + Conventional 0 C reference temperature in kelvin (273.15 K), + linked historically to the water-triple-point definition and the + 0.01 degree centesimal offset. + " + - name: pure_water_density_reference + value: 1000.0 + units: kg m-3 + prec: double + type: strict + reference: Gill_1982 + description: " + Conventional reference density of pure water used in coupled + modeling; this is an approximation and not an exact + thermodynamic density at a specific state point. + " + - name: pure_water_specific_heat_capacity_reference + value: 4188.0 + units: J kg-1 K-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Conventional reference specific heat capacity of pure water. + " + - name: latent_heat_of_fusion_reference + value: 333700.0 + units: J kg-1 + prec: double + type: strict + reference: CRC2018 + description: " + Reference latent heat released during freezing or absorbed during + melting of water. + " + - name: latent_heat_of_vaporization_reference + value: 2501000.0 + units: J kg-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference latent heat required to convert liquid water to vapor. + " + - name: latent_heat_of_sublimation_reference + value: 2834700.0 + units: J kg-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Derived constant. Defined as latent_heat_of_fusion_reference + + latent_heat_of_vaporization_reference. + " + - earth_atmosphere: + description: " + Thermophysical reference constants primarily associated with + Earth's atmospheric processes, and used across coupled components + " + entries: + - name: dry_air_molar_mass_reference + value: 0.028966 + units: kg mol-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference molar mass of dry air used in atmospheric + thermodynamic parameterizations. + " + - name: water_vapor_molar_mass_reference + value: 0.018016 + units: kg mol-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference molar mass of water vapor used in atmospheric + thermodynamic parameterizations. + " + - name: dry_air_specific_gas_constant_reference + value: 287.04213968100532 + units: J kg-1 K-1 + prec: double + type: strict + reference: US_StdAtm_1976 + description: " + Derived constant. Defined as molar_gas_constant / + dry_air_molar_mass_reference. + " + - name: water_vapor_specific_gas_constant_reference + value: 461.50436378774424 + units: J kg-1 K-1 + prec: double + type: strict + reference: US_StdAtm_1976 + description: " + Derived constant. Defined as molar_gas_constant / + water_vapor_molar_mass_reference. + " + - name: dry_air_density_at_standard_temperature_and_pressure + value: 1.2923190576466714 + units: kg m-3 + prec: double + type: strict + reference: US_StdAtm_1976 + description: " + Derived constant. Defined as standard_atmosphere / + (dry_air_specific_gas_constant_reference * + pure_water_freezing_temperature). + " + - name: dry_air_specific_heat_capacity_reference + value: 1004.64 + units: J kg-1 K-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference specific heat capacity of dry air at constant pressure. + " + - name: water_vapor_specific_heat_capacity_reference + value: 1810.0 + units: J kg-1 K-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference specific heat capacity of water vapor used in + atmospheric thermodynamic parameterizations. + " + - earth_seawater: + description: " + Thermophysical reference constants associated with seawater (both + liquid and solid) on Earth + " + entries: + - name: seawater_density_reference + value: 1026.0 + units: kg m-3 + prec: double + type: strict + reference: Gill_1982 + description: " + Canonical reference-scale seawater density; model adopts + 1026 kg m-3. + " + - name: seawater_specific_heat_capacity_reference + value: 3996.0 + units: J kg-1 K-1 + prec: double + type: strict + reference: Gill_1982 + description: " + Reference seawater specific heat capacity. + " + - name: ocean_reference_salinity + value: 34.7 + units: g kg-1 + prec: double + type: strict + reference: Levitus1982 + description: " + Reference ocean salinity. + " + - name: speed_of_sound_in_seawater_reference + value: 1500.0 + units: m s-1 + prec: double + type: strict + reference: Gill_1982 + description: " + Typical reference speed of sound in seawater. + " + - name: vsmow_ratio_18o_to_16o + value: 2005.2E-6 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic ratio 18O/16O in Vienna Standard Mean Ocean Water (VSMOW). + " + - name: vsmow_ratio_17o_to_16o + value: 379.0E-6 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic ratio 17O/16O in Vienna Standard Mean Ocean Water (VSMOW). + " + - name: vsmow_fraction_16o_of_total_oxygen + value: 0.997628 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic fraction of 16O in total oxygen for Vienna Standard Mean + Ocean Water (VSMOW). + " + - name: vsmow_ratio_2h_to_1h + value: 155.76E-6 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic ratio 2H/1H in Vienna Standard Mean Ocean Water (VSMOW). + " + - name: vsmow_ratio_3h_to_1h + value: 1.85E-6 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic ratio 3H/1H in Vienna Standard Mean Ocean Water (VSMOW). + " + - name: vsmow_fraction_1h_of_total_hydrogen + value: 0.99984426 + units: none + prec: double + type: strict + reference: IAEA1995 + description: " + Isotopic fraction of 1H in total hydrogen for Vienna Standard Mean + Ocean Water (VSMOW). + " + - name: sea_ice_density_reference + value: 917.0 + units: kg m-3 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference density of sea ice. + " + - name: sea_ice_specific_heat_capacity_reference + value: 2117.27 + units: J kg-1 K-1 + prec: double + type: strict + reference: Smithsonian1951 + description: " + Reference specific heat capacity of sea ice. + " + - name: sea_ice_thermal_conductivity_reference + value: 2.1 + units: W m-1 K-1 + prec: double + type: strict + reference: MaykutUntersteiner1971 + description: " + Reference thermal conductivity of sea ice. + " + - name: sea_ice_reference_salinity + value: 4.0 + units: g kg-1 + prec: double + type: strict + reference: Untersteiner1961 + description: " + Reference sea-ice salinity. + " + - name: sea_ice_thermal_diffusivity_reference + value: 1.0863739733765951E-06 + units: m2 s-1 + prec: double + type: strict + reference: CuffeyPaterson2010 + description: " + Derived constant. Defined as + sea_ice_thermal_conductivity_reference / + (sea_ice_density_reference * sea_ice_specific_heat_capacity_reference). + " + - isotopic_standards: + description: " + Isotopic ratio and fraction standards used across Earth system components + " + entries: + - name: pee_dee_belemnite_ratio_13c_to_12c + value: 0.0112372 + units: none + prec: double + type: strict + reference: Craig1957 + description: " + Reference isotopic ratio 13C/12C for the Pee Dee Belemnite carbon + isotope standard. + " diff --git a/share/util/pcd_const.F90 b/share/util/pcd_const.F90 index ebd67388d47d..17b075349050 100644 --- a/share/util/pcd_const.F90 +++ b/share/util/pcd_const.F90 @@ -9,51 +9,91 @@ module pcd integer, parameter :: dp = selected_real_kind(12) ! pcd.yaml version and institution information - character(len=*), parameter :: pcdversion = "0.0.2" + character(len=*), parameter :: pcdversion = "0.1.0" character(len=*), parameter :: pcdinstitution = "E3SM Project" !mathematical constants - real(dp), parameter :: pi = 3.141592653589793_dp ! ASHandbook1964 - real(dp), parameter :: e = 2.718281828459045_dp ! ASHandbook1964 - real(dp), parameter :: em_gamma = 0.5772156649015329_dp ! ASHandbook1964 - real(dp), parameter :: radian = 57.29577951308232_dp ! ASHandbook1964 - real(dp), parameter :: degree = 0.017453292519943295_dp ! ASHandbook1964 - real(dp), parameter :: square_root_of_2 = 1.4142135623730951_dp ! ASHandbook1964 - real(dp), parameter :: square_root_of_3 = 1.7320508075688772_dp ! ASHandbook1964 + real(dp), parameter :: pi = 3.141592653589793_dp ! ASHandbook1964 + real(dp), parameter :: e = 2.718281828459045_dp ! ASHandbook1964 + real(dp), parameter :: em_gamma = 0.5772156649015329_dp ! ASHandbook1964 + real(dp), parameter :: radian = 57.29577951308232_dp ! [degree] ASHandbook1964 + real(dp), parameter :: degree = 0.017453292519943295_dp ! [rad] ASHandbook1964 + real(dp), parameter :: square_root_of_2 = 1.4142135623730951_dp ! [rad] ASHandbook1964 + real(dp), parameter :: square_root_of_3 = 1.7320508075688772_dp ! [rad] ASHandbook1964 !universal_physical constants - real(dp), parameter :: speed_of_light_in_vacuum = 299792458_dp ! NIST_CODATA2022 - real(dp), parameter :: newtonian_gravitation_constant = 6.6743e-11_dp ! NIST_CODATA2022 - real(dp), parameter :: standard_acceleration_of_gravity = 9.80665_dp ! NIST_CODATA2022 - real(dp), parameter :: standard_atmosphere = 101325_dp ! NIST_CODATA2022 - real(dp), parameter :: avogadro_constant = 6.02214076e+23_dp ! NIST_CODATA2022 - real(dp), parameter :: boltzmann_constant = 1.380649e-23_dp ! NIST_CODATA2022 - real(dp), parameter :: stefan_boltzmann_constant = 5.670374419e-08_dp ! NIST_CODATA2022 - real(dp), parameter :: planck_constant = 6.62607015e-34_dp ! NIST_CODATA2022 - real(dp), parameter :: molar_gas_constant = 8.314462618_dp ! NIST_CODATA2022 - real(dp), parameter :: molar_volume_of_ideal_gas = 0.02271095464_dp ! NIST_CODATA2022 + real(dp), parameter :: speed_of_light_in_vacuum = 299792458_dp ! [m s-1] NIST_CODATA2022 + real(dp), parameter :: newtonian_gravitation_constant = 6.6743e-11_dp ! [m3 kg-1 s-2] NIST_CODATA2022 + real(dp), parameter :: standard_acceleration_of_gravity = 9.80665_dp ! [m s-2] NIST_CODATA2022 + real(dp), parameter :: standard_atmosphere = 101325_dp ! [Pa] NIST_CODATA2022 + real(dp), parameter :: avogadro_constant = 6.02214076e+23_dp ! [mol-1] NIST_CODATA2022 + real(dp), parameter :: boltzmann_constant = 1.380649e-23_dp ! [J K-1] NIST_CODATA2022 + real(dp), parameter :: stefan_boltzmann_constant = 5.670374419e-08_dp ! [W m-2 K-4] NIST_CODATA2022 + real(dp), parameter :: planck_constant = 6.62607015e-34_dp ! [J Hz-1] NIST_CODATA2022 + real(dp), parameter :: molar_gas_constant = 8.314462618_dp ! [J mol-1 K-1] NIST_CODATA2022 + real(dp), parameter :: molar_volume_of_ideal_gas = 0.02271095464_dp ! [m3 mol-1] NIST_CODATA2022 + + !chemical_molar_masses constants + real(dp), parameter :: carbon_molar_mass_reference = 0.0120107_dp ! [kg mol-1] IUPAC1997 + real(dp), parameter :: oxygen_molar_mass_reference = 0.0159994_dp ! [kg mol-1] IUPAC1997 + real(dp), parameter :: carbon_dioxide_molar_mass_reference = 0.0440095_dp ! [kg mol-1] IUPAC1997 !earth_physical constants - real(dp), parameter :: geocentric_gravitational_constant = 3986005E+08_dp ! GRS80 - real(dp), parameter :: semimajor_axis = 6378137_dp ! GRS80 - real(dp), parameter :: dynamic_form_factor = 108263E-08_dp ! GRS80 - real(dp), parameter :: angular_velocity = 7292115E-11_dp ! GRS80 - real(dp), parameter :: semiminor_axis = 6356752.3141_dp ! GRS80 - real(dp), parameter :: flattening = 0.00335281068118_dp ! GRS80 - real(dp), parameter :: reciprocal_flattening = 298.257222101_dp ! GRS80 - real(dp), parameter :: mean_radius = 6371008.7714_dp ! GRS80 - real(dp), parameter :: radius_of_sphere_of_same_surface = 6371007.181_dp ! GRS80 - real(dp), parameter :: radius_of_sphere_of_same_volume = 6371000.79_dp ! GRS80 + real(dp), parameter :: geocentric_gravitational_constant = 3986005E+08_dp ! [m3 s-2] GRS80 + real(dp), parameter :: semimajor_axis = 6378137_dp ! [m] GRS80 + real(dp), parameter :: dynamic_form_factor = 108263E-08_dp ! GRS80 + real(dp), parameter :: angular_velocity = 7292115E-11_dp ! [rad s-1] GRS80 + real(dp), parameter :: semiminor_axis = 6356752.3141_dp ! [m] GRS80 + real(dp), parameter :: flattening = 0.00335281068118_dp ! GRS80 + real(dp), parameter :: reciprocal_flattening = 298.257222101_dp ! GRS80 + real(dp), parameter :: mean_radius = 6371008.7714_dp ! [m] GRS80 + real(dp), parameter :: radius_of_sphere_of_same_surface = 6371007.181_dp ! [m] GRS80 + real(dp), parameter :: radius_of_sphere_of_same_volume = 6371000.79_dp ! [m] GRS80 !solar constants - real(dp), parameter :: total_solar_irradiance = 1360.8_dp ! TIM_SORCE_2005 + real(dp), parameter :: total_solar_irradiance = 1360.8_dp ! [W m-2] TIM_SORCE_2005 !water constants - real(dp), parameter :: water_molar_mass = 18.015268_dp ! IAPWS_95 - real(dp), parameter :: water_specific_gas_constant = 0.46151805_dp ! IAPWS_95 - real(dp), parameter :: water_triple_point_temperature = 273.16_dp ! IAPWS_95 - real(dp), parameter :: water_triple_point_pressure = 611.655_dp ! IAPWS_95 - real(dp), parameter :: liquid_water_triple_point_density = 999.793_dp ! IAPWS_95 - real(dp), parameter :: vapor_water_triple_point_density = 0.00485458_dp ! IAPWS_95 + real(dp), parameter :: water_molar_mass = 0.018015268_dp ! [kg mol-1] IAPWS_95 + real(dp), parameter :: water_specific_gas_constant = 0.46151805_dp ! [kJ kg-1 K-1] IAPWS_95 + real(dp), parameter :: water_triple_point_temperature = 273.16_dp ! [K] IAPWS_95 + real(dp), parameter :: water_triple_point_pressure = 611.655_dp ! [Pa] IAPWS_95 + real(dp), parameter :: liquid_water_triple_point_density = 999.793_dp ! [kg m-3] IAPWS_95 + real(dp), parameter :: vapor_water_triple_point_density = 0.00485458_dp ! [kg m-3] IAPWS_95 + real(dp), parameter :: pure_water_freezing_temperature_reference = 273.15_dp ! [K] CGPM1948_1954 + real(dp), parameter :: pure_water_density_reference = 1000.0_dp ! [kg m-3] Gill_1982 + real(dp), parameter :: pure_water_specific_heat_capacity_reference = 4188.0_dp ! [J kg-1 K-1] Smithsonian1951 + real(dp), parameter :: latent_heat_of_fusion_reference = 333700.0_dp ! [J kg-1] CRC2018 + real(dp), parameter :: latent_heat_of_vaporization_reference = 2501000.0_dp ! [J kg-1] Smithsonian1951 + real(dp), parameter :: latent_heat_of_sublimation_reference = 2834700.0_dp ! [J kg-1] Smithsonian1951 + + !earth_atmosphere constants + real(dp), parameter :: dry_air_molar_mass_reference = 0.028966_dp ! [kg mol-1] Smithsonian1951 + real(dp), parameter :: water_vapor_molar_mass_reference = 0.018016_dp ! [kg mol-1] Smithsonian1951 + real(dp), parameter :: dry_air_specific_gas_constant_reference = 287.0421396810053_dp ! [J kg-1 K-1] US_StdAtm_1976 + real(dp), parameter :: water_vapor_specific_gas_constant_reference = 461.50436378774424_dp ! [J kg-1 K-1] US_StdAtm_1976 + real(dp), parameter :: dry_air_density_at_standard_temperature_and_pressure = 1.2923190576466714_dp ! [kg m-3] US_StdAtm_1976 + real(dp), parameter :: dry_air_specific_heat_capacity_reference = 1004.64_dp ! [J kg-1 K-1] Smithsonian1951 + real(dp), parameter :: water_vapor_specific_heat_capacity_reference = 1810.0_dp ! [J kg-1 K-1] Smithsonian1951 + + !earth_seawater constants + real(dp), parameter :: seawater_density_reference = 1026.0_dp ! [kg m-3] Gill_1982 + real(dp), parameter :: seawater_specific_heat_capacity_reference = 3996.0_dp ! [J kg-1 K-1] Gill_1982 + real(dp), parameter :: ocean_reference_salinity = 34.7_dp ! [g kg-1] Levitus1982 + real(dp), parameter :: speed_of_sound_in_seawater_reference = 1500.0_dp ! [m s-1] Gill_1982 + real(dp), parameter :: vsmow_ratio_18o_to_16o = 0.0020052_dp ! IAEA1995 + real(dp), parameter :: vsmow_ratio_17o_to_16o = 0.000379_dp ! IAEA1995 + real(dp), parameter :: vsmow_fraction_16o_of_total_oxygen = 0.997628_dp ! IAEA1995 + real(dp), parameter :: vsmow_ratio_2h_to_1h = 0.00015576_dp ! IAEA1995 + real(dp), parameter :: vsmow_ratio_3h_to_1h = 1.85e-06_dp ! IAEA1995 + real(dp), parameter :: vsmow_fraction_1h_of_total_hydrogen = 0.99984426_dp ! IAEA1995 + real(dp), parameter :: sea_ice_density_reference = 917.0_dp ! [kg m-3] Smithsonian1951 + real(dp), parameter :: sea_ice_specific_heat_capacity_reference = 2117.27_dp ! [J kg-1 K-1] Smithsonian1951 + real(dp), parameter :: sea_ice_thermal_conductivity_reference = 2.1_dp ! [W m-1 K-1] MaykutUntersteiner1971 + real(dp), parameter :: sea_ice_reference_salinity = 4.0_dp ! [g kg-1] Untersteiner1961 + real(dp), parameter :: sea_ice_thermal_diffusivity_reference = 1.086373973376595e-06_dp ! [m2 s-1] CuffeyPaterson2010 + + !isotopic_standards constants + real(dp), parameter :: pee_dee_belemnite_ratio_13c_to_12c = 0.0112372_dp ! Craig1957 end module pcd diff --git a/share/util/shr_wave_mod.F90 b/share/util/shr_wave_mod.F90 new file mode 100644 index 000000000000..be64f11179cf --- /dev/null +++ b/share/util/shr_wave_mod.F90 @@ -0,0 +1,35 @@ +MODULE shr_wave_mod + + use shr_kind_mod, only : R8 => shr_kind_r8 + + implicit none + public :: shr_calc_wave_freq + +contains + subroutine shr_calc_wave_freq(nfr,freq1,xfreq,freq,dfreq) + !This subroutine calculates the wave frequencies as done in WW3 + + integer, intent(in) :: nfr ! number of frequency bin to use (or wave numbers) + real(R8), intent(in) :: freq1 ! first freq + real(R8), intent(in) :: xfreq ! freq multiplication factor + + real(R8), dimension(:), intent(out) :: freq ! FREQUENCIES + real(R8), dimension(:), intent(out) :: dfreq !Frequency bandwidths + + !local vars + integer :: ik + real(R8) :: sigma + real(R8) :: sxfr + + sigma = freq1 / xfreq**2 + sxfr = 0.5 * (xfreq-1./xfreq) + + DO ik=1, nfr+2 + sigma = sigma * xfreq + freq(ik) = sigma + dfreq(ik) = sigma * sxfr + ENDDO + + end subroutine shr_calc_wave_freq + +END MODULE shr_wave_mod diff --git a/share/util_cxx/pcd_const.h b/share/util_cxx/pcd_const.h index 1a2297f96604..477a3cf11f72 100644 --- a/share/util_cxx/pcd_const.h +++ b/share/util_cxx/pcd_const.h @@ -10,52 +10,92 @@ namespace pcd { // pcd.yaml version and institution information -const std::string pcdversion = "0.0.2"; +const std::string pcdversion = "0.1.0"; const std::string pcdinstitution = "E3SM Project"; // mathematical constants -constexpr double pi = 3.141592653589793; // ASHandbook1964 -constexpr double e = 2.718281828459045; // ASHandbook1964 -constexpr double em_gamma = 0.5772156649015329; // ASHandbook1964 -constexpr double radian = 57.29577951308232; // ASHandbook1964 -constexpr double degree = 0.017453292519943295; // ASHandbook1964 -constexpr double square_root_of_2 = 1.4142135623730951; // ASHandbook1964 -constexpr double square_root_of_3 = 1.7320508075688772; // ASHandbook1964 +constexpr double pi = 3.141592653589793; // ASHandbook1964 +constexpr double e = 2.718281828459045; // ASHandbook1964 +constexpr double em_gamma = 0.5772156649015329; // ASHandbook1964 +constexpr double radian = 57.29577951308232; // [degree] ASHandbook1964 +constexpr double degree = 0.017453292519943295; // [rad] ASHandbook1964 +constexpr double square_root_of_2 = 1.4142135623730951; // [rad] ASHandbook1964 +constexpr double square_root_of_3 = 1.7320508075688772; // [rad] ASHandbook1964 // universal_physical constants -constexpr double speed_of_light_in_vacuum = 299792458; // NIST_CODATA2022 -constexpr double newtonian_gravitation_constant = 6.6743e-11; // NIST_CODATA2022 -constexpr double standard_acceleration_of_gravity = 9.80665; // NIST_CODATA2022 -constexpr double standard_atmosphere = 101325; // NIST_CODATA2022 -constexpr double avogadro_constant = 6.02214076e+23; // NIST_CODATA2022 -constexpr double boltzmann_constant = 1.380649e-23; // NIST_CODATA2022 -constexpr double stefan_boltzmann_constant = 5.670374419e-08; // NIST_CODATA2022 -constexpr double planck_constant = 6.62607015e-34; // NIST_CODATA2022 -constexpr double molar_gas_constant = 8.314462618; // NIST_CODATA2022 -constexpr double molar_volume_of_ideal_gas = 0.02271095464; // NIST_CODATA2022 +constexpr double speed_of_light_in_vacuum = 299792458; // [m s-1] NIST_CODATA2022 +constexpr double newtonian_gravitation_constant = 6.6743e-11; // [m3 kg-1 s-2] NIST_CODATA2022 +constexpr double standard_acceleration_of_gravity = 9.80665; // [m s-2] NIST_CODATA2022 +constexpr double standard_atmosphere = 101325; // [Pa] NIST_CODATA2022 +constexpr double avogadro_constant = 6.02214076e+23; // [mol-1] NIST_CODATA2022 +constexpr double boltzmann_constant = 1.380649e-23; // [J K-1] NIST_CODATA2022 +constexpr double stefan_boltzmann_constant = 5.670374419e-08; // [W m-2 K-4] NIST_CODATA2022 +constexpr double planck_constant = 6.62607015e-34; // [J Hz-1] NIST_CODATA2022 +constexpr double molar_gas_constant = 8.314462618; // [J mol-1 K-1] NIST_CODATA2022 +constexpr double molar_volume_of_ideal_gas = 0.02271095464; // [m3 mol-1] NIST_CODATA2022 + +// chemical_molar_masses constants +constexpr double carbon_molar_mass_reference = 0.0120107; // [kg mol-1] IUPAC1997 +constexpr double oxygen_molar_mass_reference = 0.0159994; // [kg mol-1] IUPAC1997 +constexpr double carbon_dioxide_molar_mass_reference = 0.0440095; // [kg mol-1] IUPAC1997 // earth_physical constants -constexpr double geocentric_gravitational_constant = 3986005E+08; // GRS80 -constexpr double semimajor_axis = 6378137; // GRS80 -constexpr double dynamic_form_factor = 108263E-08; // GRS80 -constexpr double angular_velocity = 7292115E-11; // GRS80 -constexpr double semiminor_axis = 6356752.3141; // GRS80 -constexpr double flattening = 0.00335281068118; // GRS80 -constexpr double reciprocal_flattening = 298.257222101; // GRS80 -constexpr double mean_radius = 6371008.7714; // GRS80 -constexpr double radius_of_sphere_of_same_surface = 6371007.181; // GRS80 -constexpr double radius_of_sphere_of_same_volume = 6371000.79; // GRS80 +constexpr double geocentric_gravitational_constant = 3986005E+08; // [m3 s-2] GRS80 +constexpr double semimajor_axis = 6378137; // [m] GRS80 +constexpr double dynamic_form_factor = 108263E-08; // GRS80 +constexpr double angular_velocity = 7292115E-11; // [rad s-1] GRS80 +constexpr double semiminor_axis = 6356752.3141; // [m] GRS80 +constexpr double flattening = 0.00335281068118; // GRS80 +constexpr double reciprocal_flattening = 298.257222101; // GRS80 +constexpr double mean_radius = 6371008.7714; // [m] GRS80 +constexpr double radius_of_sphere_of_same_surface = 6371007.181; // [m] GRS80 +constexpr double radius_of_sphere_of_same_volume = 6371000.79; // [m] GRS80 // solar constants -constexpr double total_solar_irradiance = 1360.8; // TIM_SORCE_2005 +constexpr double total_solar_irradiance = 1360.8; // [W m-2] TIM_SORCE_2005 // water constants -constexpr double water_molar_mass = 18.015268; // IAPWS_95 -constexpr double water_specific_gas_constant = 0.46151805; // IAPWS_95 -constexpr double water_triple_point_temperature = 273.16; // IAPWS_95 -constexpr double water_triple_point_pressure = 611.655; // IAPWS_95 -constexpr double liquid_water_triple_point_density = 999.793; // IAPWS_95 -constexpr double vapor_water_triple_point_density = 0.00485458; // IAPWS_95 +constexpr double water_molar_mass = 0.018015268; // [kg mol-1] IAPWS_95 +constexpr double water_specific_gas_constant = 0.46151805; // [kJ kg-1 K-1] IAPWS_95 +constexpr double water_triple_point_temperature = 273.16; // [K] IAPWS_95 +constexpr double water_triple_point_pressure = 611.655; // [Pa] IAPWS_95 +constexpr double liquid_water_triple_point_density = 999.793; // [kg m-3] IAPWS_95 +constexpr double vapor_water_triple_point_density = 0.00485458; // [kg m-3] IAPWS_95 +constexpr double pure_water_freezing_temperature_reference = 273.15; // [K] CGPM1948_1954 +constexpr double pure_water_density_reference = 1000.0; // [kg m-3] Gill_1982 +constexpr double pure_water_specific_heat_capacity_reference = 4188.0; // [J kg-1 K-1] Smithsonian1951 +constexpr double latent_heat_of_fusion_reference = 333700.0; // [J kg-1] CRC2018 +constexpr double latent_heat_of_vaporization_reference = 2501000.0; // [J kg-1] Smithsonian1951 +constexpr double latent_heat_of_sublimation_reference = 2834700.0; // [J kg-1] Smithsonian1951 + +// earth_atmosphere constants +constexpr double dry_air_molar_mass_reference = 0.028966; // [kg mol-1] Smithsonian1951 +constexpr double water_vapor_molar_mass_reference = 0.018016; // [kg mol-1] Smithsonian1951 +constexpr double dry_air_specific_gas_constant_reference = 287.0421396810053; // [J kg-1 K-1] US_StdAtm_1976 +constexpr double water_vapor_specific_gas_constant_reference = 461.50436378774424; // [J kg-1 K-1] US_StdAtm_1976 +constexpr double dry_air_density_at_standard_temperature_and_pressure = 1.2923190576466714; // [kg m-3] US_StdAtm_1976 +constexpr double dry_air_specific_heat_capacity_reference = 1004.64; // [J kg-1 K-1] Smithsonian1951 +constexpr double water_vapor_specific_heat_capacity_reference = 1810.0; // [J kg-1 K-1] Smithsonian1951 + +// earth_seawater constants +constexpr double seawater_density_reference = 1026.0; // [kg m-3] Gill_1982 +constexpr double seawater_specific_heat_capacity_reference = 3996.0; // [J kg-1 K-1] Gill_1982 +constexpr double ocean_reference_salinity = 34.7; // [g kg-1] Levitus1982 +constexpr double speed_of_sound_in_seawater_reference = 1500.0; // [m s-1] Gill_1982 +constexpr double vsmow_ratio_18o_to_16o = 0.0020052; // IAEA1995 +constexpr double vsmow_ratio_17o_to_16o = 0.000379; // IAEA1995 +constexpr double vsmow_fraction_16o_of_total_oxygen = 0.997628; // IAEA1995 +constexpr double vsmow_ratio_2h_to_1h = 0.00015576; // IAEA1995 +constexpr double vsmow_ratio_3h_to_1h = 1.85e-06; // IAEA1995 +constexpr double vsmow_fraction_1h_of_total_hydrogen = 0.99984426; // IAEA1995 +constexpr double sea_ice_density_reference = 917.0; // [kg m-3] Smithsonian1951 +constexpr double sea_ice_specific_heat_capacity_reference = 2117.27; // [J kg-1 K-1] Smithsonian1951 +constexpr double sea_ice_thermal_conductivity_reference = 2.1; // [W m-1 K-1] MaykutUntersteiner1971 +constexpr double sea_ice_reference_salinity = 4.0; // [g kg-1] Untersteiner1961 +constexpr double sea_ice_thermal_diffusivity_reference = 1.086373973376595e-06; // [m2 s-1] CuffeyPaterson2010 + +// isotopic_standards constants +constexpr double pee_dee_belemnite_ratio_13c_to_12c = 0.0112372; // Craig1957 } // namespace pcd #endif // PHYSICAL_CONSTANTS_DICTIONARY