diff --git a/.github/actions/test-all-eamxx/action.yml b/.github/actions/test-all-eamxx/action.yml
index dc738bd3567a..8157131b2005 100644
--- a/.github/actions/test-all-eamxx/action.yml
+++ b/.github/actions/test-all-eamxx/action.yml
@@ -118,6 +118,6 @@ runs:
with:
name: log-files-${{ inputs.build_type }}-${{ inputs.machine }}
path: |
- components/eamxx/ctest-build/**/Testing/Temporary/Last*.log
components/eamxx/ctest-build/**/ctest_resource_file.json
components/eamxx/ctest-build/**/CMakeCache.txt
+ components/eamxx/ctest-build/**/Testing/**
diff --git a/cime b/cime
index ed580a83616a..4e686d2f1c57 160000
--- a/cime
+++ b/cime
@@ -1 +1 @@
-Subproject commit ed580a83616ac400b9e1f42ba275eab457988110
+Subproject commit 4e686d2f1c57996d5a38d09e132c4010c68fc93c
diff --git a/cime_config/machines/cmake_macros/aurora_oneapi-ifxgpu.cmake b/cime_config/machines/cmake_macros/aurora_oneapi-ifxgpu.cmake
index 06f23b478c61..5c3bdc42bdf1 100644
--- a/cime_config/machines/cmake_macros/aurora_oneapi-ifxgpu.cmake
+++ b/cime_config/machines/cmake_macros/aurora_oneapi-ifxgpu.cmake
@@ -4,7 +4,7 @@ if (compile_threaded)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fiopenmp -fopenmp-targets=spir64")
endif()
-string(APPEND KOKKOS_OPTIONS " -DCMAKE_CXX_STANDARD=17 -DKokkos_ENABLE_SERIAL=On -DKokkos_ARCH_INTEL_PVC=On -DKokkos_ENABLE_SYCL=On -DKokkos_ENABLE_EXPLICIT_INSTANTIATION=Off -DCMAKE_POSITION_INDEPENDENT_CODE=ON")
+string(APPEND KOKKOS_OPTIONS " -DKokkos_ENABLE_SERIAL=On -DKokkos_ARCH_INTEL_PVC=On -DKokkos_ENABLE_SYCL=On -DKokkos_ENABLE_EXPLICIT_INSTANTIATION=Off -DCMAKE_POSITION_INDEPENDENT_CODE=ON")
string(APPEND SYCL_FLAGS " -fsycl -fsycl-targets=spir64_gen -mlong-double-64 ")
string(APPEND OMEGA_SYCL_EXE_LINKER_FLAGS " -Xsycl-target-backend \"-device pvc\" ")
diff --git a/cime_config/machines/cmake_macros/frontier_craycray-mphipcc.cmake b/cime_config/machines/cmake_macros/frontier_craycray-mphipcc.cmake
index a464bda80d9f..3bd4ce4c40a6 100644
--- a/cime_config/machines/cmake_macros/frontier_craycray-mphipcc.cmake
+++ b/cime_config/machines/cmake_macros/frontier_craycray-mphipcc.cmake
@@ -17,8 +17,6 @@ if (compile_threaded)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fopenmp")
endif()
-string(APPEND CMAKE_CXX_FLAGS " -std=c++17")
-
string(APPEND CMAKE_Fortran_FLAGS " -hipa0 -hzero -f free")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -L$ENV{CRAY_MPICH_ROOTDIR}/gtl/lib -lmpi_gtl_hsa")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -L$ENV{ROCM_PATH}/lib -lamdhip64")
diff --git a/cime_config/machines/cmake_macros/frontier_craycray.cmake b/cime_config/machines/cmake_macros/frontier_craycray.cmake
index 3d8990f2d2e4..cf008431c88c 100644
--- a/cime_config/machines/cmake_macros/frontier_craycray.cmake
+++ b/cime_config/machines/cmake_macros/frontier_craycray.cmake
@@ -17,8 +17,6 @@ if (compile_threaded)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fopenmp")
endif()
-string(APPEND CMAKE_CXX_FLAGS " -std=c++17")
-
string(APPEND CMAKE_Fortran_FLAGS " -hipa0 -hzero -f free")
# Crusher: this resolves a crash in mct in docn init
diff --git a/cime_config/machines/cmake_macros/gnugpu.cmake b/cime_config/machines/cmake_macros/gnugpu.cmake
index 3313140dc6a7..d0a6c2d07659 100644
--- a/cime_config/machines/cmake_macros/gnugpu.cmake
+++ b/cime_config/machines/cmake_macros/gnugpu.cmake
@@ -1,5 +1,14 @@
-string(APPEND CMAKE_C_FLAGS " -mcmodel=medium")
-string(APPEND CMAKE_Fortran_FLAGS " -mcmodel=medium -fconvert=big-endian -ffree-line-length-none -ffixed-line-length-none")
+if (CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64")
+ string(APPEND CMAKE_C_FLAGS " -mcmodel=small")
+ string(APPEND CMAKE_Fortran_FLAGS " -mcmodel=small")
+elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
+ string(APPEND CMAKE_C_FLAGS " -mcmodel=large")
+ string(APPEND CMAKE_Fortran_FLAGS " -mcmodel=large")
+else()
+ string(APPEND CMAKE_C_FLAGS " -mcmodel=medium")
+ string(APPEND CMAKE_Fortran_FLAGS " -mcmodel=medium")
+endif()
+string(APPEND CMAKE_Fortran_FLAGS " -fconvert=big-endian -ffree-line-length-none -ffixed-line-length-none")
if (CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL 10)
string(APPEND CMAKE_Fortran_FLAGS " -fallow-argument-mismatch")
endif()
diff --git a/cime_config/machines/cmake_macros/vista-gg_gnu.cmake b/cime_config/machines/cmake_macros/vista-gg_gnu.cmake
new file mode 100644
index 000000000000..e673272f9823
--- /dev/null
+++ b/cime_config/machines/cmake_macros/vista-gg_gnu.cmake
@@ -0,0 +1,13 @@
+string(APPEND CPPDEFS " -DLINUX")
+if (COMP_NAME STREQUAL gptl)
+ string(APPEND CPPDEFS " -DBIT64 -DHAVE_SLASHPROC -DHAVE_COMM_F2C -DHAVE_TIMES -DHAVE_GETTIMEOFDAY -DHAVE_MPI")
+endif()
+string(APPEND CMAKE_C_FLAGS_RELEASE " -O2 -g")
+string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -O2 -g")
+
+set(MPICC "mpicc")
+set(MPICXX "mpicxx")
+set(MPIFC "mpif90")
+set(SCC "gcc")
+set(SCXX "g++")
+set(SFC "gfortran")
diff --git a/cime_config/machines/cmake_macros/vista-gg_nvidia.cmake b/cime_config/machines/cmake_macros/vista-gg_nvidia.cmake
new file mode 100644
index 000000000000..fb24721f1640
--- /dev/null
+++ b/cime_config/machines/cmake_macros/vista-gg_nvidia.cmake
@@ -0,0 +1,20 @@
+string(APPEND CPPDEFS " -DLINUX")
+if (COMP_NAME STREQUAL gptl)
+ string(APPEND CPPDEFS " -DBIT64 -DHAVE_SLASHPROC -DHAVE_COMM_F2C -DHAVE_TIMES -DHAVE_GETTIMEOFDAY -DHAVE_MPI")
+endif()
+string(APPEND CMAKE_C_FLAGS_RELEASE " -O2")
+string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -O2")
+string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -g")
+
+set(HOMME_QUAD_PREC FALSE CACHE BOOL "") # nvidia does not seem to support QUAD
+
+if (compile_threaded)
+ string(APPEND KOKKOS_OPTIONS " -DKokkos_ENABLE_OPENMP=Off") # work-around for nvidia as kokkos is not passing "-mp" for threaded build
+endif()
+
+set(MPICC "mpicc")
+set(MPICXX "mpicxx")
+set(MPIFC "mpif90")
+set(SCC "gcc")
+set(SCXX "g++")
+set(SFC "gfortran")
diff --git a/cime_config/machines/cmake_macros/vista-gh_gnu.cmake b/cime_config/machines/cmake_macros/vista-gh_gnu.cmake
new file mode 100644
index 000000000000..0fcf1205df51
--- /dev/null
+++ b/cime_config/machines/cmake_macros/vista-gh_gnu.cmake
@@ -0,0 +1,15 @@
+string(APPEND CPPDEFS " -DLINUX")
+if (COMP_NAME STREQUAL gptl)
+ string(APPEND CPPDEFS " -DBIT64 -DHAVE_SLASHPROC -DHAVE_COMM_F2C -DHAVE_TIMES -DHAVE_GETTIMEOFDAY -DHAVE_MPI")
+endif()
+string(APPEND CMAKE_C_FLAGS_RELEASE " -O2 -g")
+string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -O2 -g")
+
+string(APPEND CMAKE_EXE_LINKER_FLAGS " -L/opt/apps/gcc/14.2.0/lib64 -lstdc++") # workaround for pnetcdf thinking it needs gcc14 abi
+
+set(MPICC "mpicc")
+set(MPICXX "mpicxx")
+set(MPIFC "mpif90")
+set(SCC "gcc")
+set(SCXX "g++")
+set(SFC "gfortran")
diff --git a/cime_config/machines/cmake_macros/vista-gh_gnugpu.cmake b/cime_config/machines/cmake_macros/vista-gh_gnugpu.cmake
new file mode 100644
index 000000000000..fca3f27da811
--- /dev/null
+++ b/cime_config/machines/cmake_macros/vista-gh_gnugpu.cmake
@@ -0,0 +1,21 @@
+set(USE_CUDA "TRUE")
+string(APPEND CPPDEFS " -DGPU")
+string(APPEND CPPDEFS " -DLINUX")
+if (COMP_NAME STREQUAL gptl)
+ string(APPEND CPPDEFS " -DBIT64 -DHAVE_SLASHPROC -DHAVE_COMM_F2C -DHAVE_TIMES -DHAVE_GETTIMEOFDAY -DHAVE_MPI")
+ # -DHAVE_NANOTIME -- cant use this as the assembly instructions that wont work on ARM
+endif()
+string(APPEND CMAKE_CUDA_FLAGS " -ccbin CC -O2 -arch sm_90 --use_fast_math")
+string(APPEND KOKKOS_OPTIONS " -DKokkos_ARCH_HOPPER90=On -DKokkos_ENABLE_CUDA=On -DKokkos_ENABLE_CUDA_LAMBDA=On -DKokkos_ENABLE_SERIAL=ON -DKokkos_ENABLE_OPENMP=Off -DKokkos_ENABLE_IMPL_CUDA_MALLOC_ASYNC=Off")
+set(CMAKE_CUDA_ARCHITECTURES "90")
+
+string(APPEND CMAKE_C_FLAGS_RELEASE " -O2")
+string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -O2")
+string(APPEND CMAKE_EXE_LINKER_FLAGS " -L/opt/apps/gcc/14.2.0/lib64 -lstdc++") # workaround for pnetcdf thinking it needs gcc14 abi
+
+set(MPICC "mpicc")
+set(MPICXX "mpicxx") # Needs MPICH_CXX to use nvcc $SHELL{which nvcc}
+set(MPIFC "mpif90")
+set(SCC "gcc")
+set(SCXX "nvcc")
+set(SFC "gfortran")
diff --git a/cime_config/machines/config_batch.xml b/cime_config/machines/config_batch.xml
index 75c60a438b06..f884ca88450a 100644
--- a/cime_config/machines/config_batch.xml
+++ b/cime_config/machines/config_batch.xml
@@ -618,6 +618,27 @@
+
+
+ -n {{ total_tasks }}
+ --partition=gh
+
+
+ gh-dev
+ gh
+
+
+
+
+
+ -n {{ total_tasks }}
+ --partition=gg
+
+
+ gg
+
+
+
qsub
diff --git a/cime_config/machines/config_machines.xml b/cime_config/machines/config_machines.xml
index b66ca9214cc8..6713fd5cd454 100644
--- a/cime_config/machines/config_machines.xml
+++ b/cime_config/machines/config_machines.xml
@@ -515,9 +515,7 @@
MPI_Bcast
$ENV{CRAY_NETCDF_HDF5PARALLEL_PREFIX}
$ENV{CRAY_PARALLEL_NETCDF_PREFIX}
-
-
- $ENV{CRAY_LD_LIBRARY_PATH}:$ENV{LD_LIBRARY_PATH}
+ $ENV{CRAY_LD_LIBRARY_PATH}:$ENV{LD_LIBRARY_PATH}
128M
@@ -572,6 +570,229 @@
+
+ vista at TACC https://docs.tacc.utexas.edu/hpc/vista gh (grace-hopper) GH200 Superchip -- Grace CPU (72 cores) and 1 NVIDIA H100 GPU
+ .*vista.*
+ Linux
+ gnugpu,gnu,nvidiagpu,nvidia
+ openmpi
+ CDA24017
+ $ENV{WORK}
+ e3sm
+ $ENV{WORK}/e3sm_scratch/vista-gh
+ $ENV{WORK}/www/$ENV{USER}
+ $ENV{WORK}/inputdata
+ $ENV{WORK}/inputdata/atm/datm7
+ $CIME_OUTPUT_ROOT/archive/$CASE
+ $ENV{WORK}/baselines/$COMPILER
+ $ENV{WORK}/tools/cprnc/cprnc
+ 4
+ e3sm_developer
+ 2
+ slurm
+ e3sm
+ 72
+ 144
+ 144
+ 1
+ 144
+ 144
+ FALSE
+
+ ibrun
+
+ --label
+ -n {{ total_tasks }}
+
+
+
+ /opt/apps/lmod/lmod/init/perl
+ /opt/apps/lmod/lmod/init/python
+ /opt/apps/lmod/lmod/init/sh
+ /opt/apps/lmod/lmod/init/csh
+ /opt/apps/lmod/lmod/libexec/lmod perl
+ /opt/apps/lmod/lmod/libexec/lmod python
+ module -q
+ module -q
+
+
+ netcdf
+ pnetcdf
+ hdf5
+ gcc
+ nvidia
+ cuda
+ perftools-base
+ perftools
+ darshan
+
+
+
+ gcc/13.2.0
+ openmpi/5.0.5
+
+
+
+ nvidia/24.7
+ openmpi/5.0.8
+
+
+
+ gcc/13.2.0
+ cuda/12.6
+ openmpi/5.0.5
+
+
+
+ cuda/12.6
+ openmpi/5.0.8
+
+
+
+ ucx/1.19.1
+ netcdf/4.9.2
+ pnetcdf/1.13.0
+ cmake
+
+
+
+ $CIME_OUTPUT_ROOT/$CASE/run
+ $CIME_OUTPUT_ROOT/$CASE/bld
+ 0.1
+ 0.20
+
+
+ 128M
+ spread
+ threads
+ FALSE
+ kdreg2
+ $ENV{TACC_NETCDF_DIR}
+ $ENV{TACC_PNETCDF_DIR}
+ /opt/apps/gcc/14.2.0/lib64:$ENV{LD_LIBRARY_PATH}
+
+
+
+ 144
+
+
+ 144
+
+
+ 1
+ 1
+ $SHELL{which nvcc}
+
+
+ 1
+ 1
+ $SHELL{which nvcc}
+
+
+ -1
+
+
+
+
+ vista at TACC https://docs.tacc.utexas.edu/hpc/vista (gg -- grace-grace) CPU-only: NVIDIA Grace CPU Superchip (72x2 or 144 cores)
+ .*vista.*
+ Linux
+ gnu,nvidia
+ openmpi
+ CDA24017
+ $ENV{WORK}
+ e3sm
+ $ENV{WORK}/e3sm_scratch/vista-gg
+ $ENV{WORK}/www/$ENV{USER}
+ $ENV{WORK}/inputdata
+ $ENV{WORK}/inputdata/atm/datm7
+ $CIME_OUTPUT_ROOT/archive/$CASE
+ $ENV{WORK}/baselines/$COMPILER
+ $ENV{WORK}/tools/cprnc/cprnc
+ 4
+ e3sm_developer
+ 2
+ slurm
+ e3sm
+ 288
+ 144
+ FALSE
+
+ ibrun
+
+ --label
+ -n {{ total_tasks }}
+
+
+
+ /opt/apps/lmod/lmod/init/perl
+ /opt/apps/lmod/lmod/init/python
+ /opt/apps/lmod/lmod/init/sh
+ /opt/apps/lmod/lmod/init/csh
+ /opt/apps/lmod/lmod/libexec/lmod perl
+ /opt/apps/lmod/lmod/libexec/lmod python
+ module -q
+ module -q
+
+
+ netcdf
+ pnetcdf
+ hdf5
+ gcc
+ nvidia
+ cuda
+ perftools-base
+ perftools
+ darshan
+
+
+
+ gcc
+ gcc/15.1.0
+ openmpi/5.0.5
+
+
+
+ nvidia/25.5
+ openmpi/5.0.8
+
+
+
+ ucx/1.19.1
+ netcdf/4.9.2
+ pnetcdf/1.13.0
+ cmake
+
+
+
+ $CIME_OUTPUT_ROOT/$CASE/run
+ $CIME_OUTPUT_ROOT/$CASE/bld
+ 0.1
+ 0.20
+
+
+ 128M
+ spread
+ threads
+ FALSE
+ kdreg2
+ $ENV{TACC_NETCDF_DIR}
+ $ENV{TACC_PNETCDF_DIR}
+ 1
+ 1
+
+
+
+ 144
+
+
+ 144
+
+
+ -1
+
+
+
Muller CPU-only nodes on internal NERSC machine, similar to pm-cpu (very small)
$ENV{NERSC_HOST}:muller
@@ -2416,7 +2637,9 @@
$ENV{SEMS_NETCDF_ROOT}/include
$ENV{SEMS_NETCDF_ROOT}/lib
64M
-
+ $SHELL{if [ -z "$MOAB_ROOT" ]; then echo /projects/ccsm/moab; else echo "$MOAB_ROOT"; fi}
+ $SHELL{dirname $(dirname $(which h5dump))}
+
$ENV{SEMS_NETCDF_ROOT}
diff --git a/cime_config/tests.py b/cime_config/tests.py
index 38ebba26568d..d578409eaae6 100644
--- a/cime_config/tests.py
+++ b/cime_config/tests.py
@@ -340,7 +340,7 @@
},
"e3sm_developer" : {
- "inherit" : ("e3sm_land_developer", "e3sm_atm_developer", "e3sm_ice_developer", "e3sm_cryo_developer"),
+ "inherit" : ("e3sm_land_developer", "e3sm_atm_developer", "e3sm_ice_developer", "e3sm_cryo_developer", "e3sm_gcam_developer"),
"time" : "0:45:00",
"tests" : (
"ERS.ne4pg2_oQU480_rx1.A",
@@ -396,7 +396,7 @@
"e3sm_extra_coverage" : {
"inherit" : ("e3sm_atm_extra_coverage", "e3sm_ocnice_extra_coverage"),
"tests" : (
- "SMS_Vmct_D_Ln3.TL319_EC30to60E2r2_wQU225EC30to60E2r2.GMPAS-JRA1p5-WW3.ww3-jra_1958",
+ "SMS_Vmct_D_Ln3.TL319_IcoswISC30E3r5_wQU225Icos30E3r5.GMPAS-JRA1p5-WW3.ww3-jra_1958",
)
},
@@ -406,6 +406,7 @@
"tests" : (
"SMS_Ld3.ne120pg2_r025_RRSwISC6to18E3r5.WCYCL1850NS.eam-cosplite",
"SMS.T62_SOwISC12to30E3r3.GMPAS-IAF",
+ "SMS_Ld3.ne30pg2_r05_SOwISC12to30E3r3.CRYO1850-CMIP7",
)
},
@@ -435,6 +436,7 @@
"SMS_Ld1.ne30pg2_r05_IcoswISC30E3r5.WCYCLSSP370.allactive-wcprodssp",
"SMS_Ld1.ne30pg2_r05_IcoswISC30E3r5.WCYCLSSP585.allactive-wcprodssp",
"SMS_Ld1_P512.northamericax4v1pg2_r025_IcoswISC30E3r5.WCYCL1850.allactive-wcprodrrm_1850",
+ "SMS_Vmct_D_Ld1.TL319_IcoswISC30E3r5_wQU225Icos30E3r5.GMPAS-JRA1p5-WW3.ww3-jra_1958",
"SMS_D_Ld1.ne30pg2_r05_IcoswISC30E3r5.CRYO1850",
"SMS_D_Ld1.ne30pg2_r05_IcoswISC30E3r5.CRYO1850-CMIP7",
)
@@ -1140,8 +1142,17 @@
"e3sm_gcam_developer" : {
"time" : "1:00:00",
"tests" : (
- "SMS.ne30pg2_f09_oEC60to30v3.SSP245_ZATM_BGC",
- "ERS.ne30pg2_f09_oEC60to30v3.SSP245_ZATM_BGC",
+ "SMS_Vmct.ne30pg2_f09_oEC60to30v3.SSP245_ZATM_BGC",
+ "ERS_Vmct.ne30pg2_f09_oEC60to30v3.SSP245_ZATM_BGC",
)
},
+ "e3sm_test_bless" : {
+ "time" : "10:00",
+ "tests" : (
+ "TESTRUNDIFF_P1.f19_g16.A",
+ "TESTRUNDIFF_P2.f19_g16.A",
+ "TESTRUNDIFF_P4.f19_g16.A",
+ "TESTRUNDIFF_P8.f19_g16.A",
+ )
+ },
}
diff --git a/components/cmake/build_model.cmake b/components/cmake/build_model.cmake
index b3a076a8216a..18d5ab728bdd 100644
--- a/components/cmake/build_model.cmake
+++ b/components/cmake/build_model.cmake
@@ -263,6 +263,13 @@ macro(build_model COMP_CLASS COMP_NAME)
add_executable(${TARGET_NAME})
target_sources(${TARGET_NAME} PRIVATE ${REAL_SOURCES})
+ # driver-mct/main sources (e.g. cime_comp_mod.F90) use netcdf directly, but
+ # the component libraries only link netcdf PRIVATEly (via csm_share), so
+ # its usage requirements (e.g. include dirs for netcdf.mod) do not
+ # propagate up to this exe target. Find/link it explicitly here too.
+ find_package(NETCDF REQUIRED)
+ target_link_libraries(${TARGET_NAME} netcdf)
+
foreach(ITEM IN LISTS COMP_CLASSES)
if (NOT ITEM STREQUAL "cpl")
target_link_libraries(${TARGET_NAME} ${ITEM})
diff --git a/components/eam/bld/namelist_files/namelist_definition.xml b/components/eam/bld/namelist_files/namelist_definition.xml
index 258c3ef00e27..c5da065afb33 100644
--- a/components/eam/bld/namelist_files/namelist_definition.xml
+++ b/components/eam/bld/namelist_files/namelist_definition.xml
@@ -6794,6 +6794,11 @@ Bottom of sponge layer in hPa.
Default: 0 (use default value based on reference pressure at model top).
+
+Default: Set by build-namelist.
+
+
Default: Set by build-namelist.
diff --git a/components/eam/bld/namelist_files/use_cases/SSP245_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml b/components/eam/bld/namelist_files/use_cases/SSP245_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
index db1c6f3c9dcd..50d42f36bd78 100644
--- a/components/eam/bld/namelist_files/use_cases/SSP245_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
+++ b/components/eam/bld/namelist_files/use_cases/SSP245_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
@@ -16,9 +16,6 @@
1.e-5
-
-1
-
INTERP_MISSING_MONTHS
atm/cam/chem/trop_mozart_aero/emis/CMIP6_SSP245_ne30/emissions-cmip6_ssp245_e3sm_NO2_aircraft_vertical_2015-2100_1.9x2.5_c20240219.nc
diff --git a/components/eam/bld/namelist_files/use_cases/SSP370_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml b/components/eam/bld/namelist_files/use_cases/SSP370_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
index 34b63ceade1f..78835dce3237 100755
--- a/components/eam/bld/namelist_files/use_cases/SSP370_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
+++ b/components/eam/bld/namelist_files/use_cases/SSP370_eam_CMIP6_chemUCI-Linoz-mam5-vbs_EHC.xml
@@ -20,9 +20,6 @@
1.e-5
-
-1
-
INTERP_MISSING_MONTHS
atm/cam/chem/trop_mozart_aero/emis/CMIP6_SSP370_ne30/emissions-cmip6_ssp370_e3sm_NO2_aircraft_vertical_2015-2100_1.9x2.5_c20240208.nc
diff --git a/components/eam/src/dynamics/se/gravity_waves_sources.F90 b/components/eam/src/dynamics/se/gravity_waves_sources.F90
index c6fcc76dc1fe..f4ae2cc9c157 100644
--- a/components/eam/src/dynamics/se/gravity_waves_sources.F90
+++ b/components/eam/src/dynamics/se/gravity_waves_sources.F90
@@ -242,7 +242,7 @@ subroutine compute_frontogenesis( frontgf, frontga, tl, &
do k = 1,nlev
! latlon -> cartesian - Summing along the third dimension is a sum over components for each point
do component=1,3
- dum_cart(:,:,component,k)=sum( elem(ie)%vec_sphere2cart(:,:,component,:) * elem(ie)%state%v(:,:,:,k,tl) ,3)
+ dum_cart(:,:,component,k)=sum( elem(ie)%vec_sphere2cart(:,:,component,1:2) * elem(ie)%state%v(:,:,:,k,tl) ,3)
end do
end do
diff --git a/components/eam/src/physics/cam/cflx.F90 b/components/eam/src/physics/cam/cflx.F90
index 86f6e398679f..f4484cc3a5e8 100644
--- a/components/eam/src/physics/cam/cflx.F90
+++ b/components/eam/src/physics/cam/cflx.F90
@@ -12,7 +12,7 @@ module cflx
contains
- subroutine cflx_tend (state, cam_in, ztodt, ptend)
+ subroutine cflx_tend (state, cam_in, ztodt, ptend, skip_co2, co2_only)
use physics_types, only: physics_state, physics_ptend, &
physics_ptend_init, &
@@ -20,7 +20,7 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
use physconst, only: gravit
use ppgrid, only: pver, pcols
use constituents, only: pcnst, cnst_get_ind, cnst_type
- use co2_cycle, only: co2_cycle_set_cnst_type
+ use co2_cycle, only: co2_cycle_set_cnst_type, co2_transport, c_i
use camsrfexch, only: cam_in_t
implicit none
@@ -30,6 +30,8 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
type(physics_state), intent(inout) :: state ! Physics state variables
type(cam_in_t), intent(in) :: cam_in ! contains surface fluxes of constituents
real(r8), intent(in) :: ztodt ! 2 delta-t [ s ]
+ logical, intent(in), optional :: skip_co2 ! if .true., skip CO2 tracers (apply all others)
+ logical, intent(in), optional :: co2_only ! if .true., apply CO2 tracers only
! Output Auguments
@@ -42,15 +44,31 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
real(r8) :: tmp1(pcols)
real(r8) :: rztodt ! 1./ztodt
- integer :: m
+ integer :: m, k
logical :: lq(pcnst)
+ logical :: l_skip_co2, l_co2_only
+ logical :: co2_mask(pcnst) ! .true. for CO2 tracer indices
character(len=3), dimension(pcnst) :: cnst_type_loc ! local override option for constituents cnst_type
ncol = state%ncol
+ ! Process optional arguments
+ l_skip_co2 = .false.
+ if (present(skip_co2)) l_skip_co2 = skip_co2
+ l_co2_only = .false.
+ if (present(co2_only)) l_co2_only = co2_only
+
+ ! Build a mask identifying CO2 tracer indices
+ co2_mask(:) = .false.
+ if (co2_transport()) then
+ do k = 1, size(c_i)
+ if (c_i(k) >= 1 .and. c_i(k) <= pcnst) co2_mask(c_i(k)) = .true.
+ end do
+ end if
+
!-------------------------------------------------------
! Assume 'wet' mixing ratios in surface diffusion code.
! don't convert co2 tracers to wet mixing ratios
@@ -60,11 +78,27 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
call set_dry_to_wet(state, cnst_type_loc)
!-------------------------------------------------------
- ! Initialize ptend
-
- lq(:) = .TRUE.
+ ! Initialize ptend with appropriate tracer mask
+ ! skip_co2=.true.: apply all tracers except CO2 (used by tphysbc when cflx_cpl_opt==2)
+ ! co2_only=.true.: apply CO2 tracers only (used by tphysac when cflx_cpl_opt==2)
+ ! default (both .false.): apply all tracers
+
+ if (l_co2_only) then
+ lq(:) = co2_mask(:)
+ else
+ lq(:) = .TRUE.
+ if (l_skip_co2) lq(:) = lq(:) .and. (.not. co2_mask(:))
+ end if
call physics_ptend_init(ptend, state%psetcols, 'clubb_srf', lq=lq)
+ ! If no tracers are selected, ptend%q is not allocated; exit cleanly.
+ if (.not. any(lq)) then
+ cnst_type_loc(:) = cnst_type(:)
+ call co2_cycle_set_cnst_type(cnst_type_loc, 'wet')
+ call set_wet_to_dry(state, cnst_type_loc)
+ return
+ end if
+
!-------------------------------------------------------
! Calculate tracer mixing ratio tendencies from cflx
@@ -73,6 +107,7 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
tmp1(:ncol) = ztodt * gravit * state%rpdel(:ncol,pver)
do m = 2, pcnst
+ if (.not. lq(m)) cycle
ptend%q(:ncol,pver,m) = ptend%q(:ncol,pver,m) + tmp1(:ncol) * cam_in%cflx(:ncol,m)
enddo
@@ -80,6 +115,7 @@ subroutine cflx_tend (state, cam_in, ztodt, ptend)
! Convert tendencies of dry constituents to dry basis.
do m = 1,pcnst
+ if (.not. lq(m)) cycle
if (cnst_type(m).eq.'dry') then
ptend%q(:ncol,:pver,m) = ptend%q(:ncol,:pver,m)*state%pdel(:ncol,:pver)/state%pdeldry(:ncol,:pver)
endif
diff --git a/components/eam/src/physics/cam/co2_diagnostics.F90 b/components/eam/src/physics/cam/co2_diagnostics.F90
index 81381e9c8d15..2288fe61805f 100644
--- a/components/eam/src/physics/cam/co2_diagnostics.F90
+++ b/components/eam/src/physics/cam/co2_diagnostics.F90
@@ -24,8 +24,8 @@ module co2_diagnostics
use cam_logfile , only: iulog
use spmd_utils , only: masterproc
use cam_abortutils , only: endrun
-use time_manager , only: is_first_step, is_last_step, get_prev_date, &
- get_curr_date, is_end_curr_month
+use time_manager , only: is_first_step, is_first_restart_step, is_last_step, &
+ get_prev_date, get_curr_date, is_end_curr_month
implicit none
private
@@ -280,7 +280,7 @@ subroutine get_carbon_sfc_fluxes(state, cam_in, dtime)
end do
end if
- if ( .not. is_first_step() ) then
+ if ( .not. is_first_step() .or. is_first_restart_step() ) then
do i = 1, ncol
state%c_iflx_sfc(i) = state%c_iflx_sfc(i) + (sfc_flux(i) * dtime)
state%c_iflx_ocn(i) = state%c_iflx_ocn(i) + (sfc_flux_ocn(i) * dtime)
@@ -365,7 +365,7 @@ subroutine get_carbon_air_fluxes(state, pbuf, dtime, pbuf_name)
end do
end if
- if ( .not. is_first_step() ) then
+ if ( .not. is_first_step() .or. is_first_restart_step() ) then
do i = 1, ncol
state%c_iflx_air(i) = state%c_iflx_air(i) + (fossil_flux(i) * dtime)
state%c_mflx_air(i) = state%c_mflx_air(i) + (fossil_flux(i) * dtime)
diff --git a/components/eam/src/physics/cam/physpkg.F90 b/components/eam/src/physics/cam/physpkg.F90
index d31659bb0fa5..57ac1f31c891 100644
--- a/components/eam/src/physics/cam/physpkg.F90
+++ b/components/eam/src/physics/cam/physpkg.F90
@@ -1494,18 +1494,6 @@ subroutine phys_run2(phys_state, ztodt, phys_tend, pbuf2d, cam_out, &
if ( is_end_curr_month() ) then
phys_state(c)%tc_mnst(:ncol) = phys_state(c)%tc_curr(:ncol)
end if
- ! upon restart with cflx_cpl_opt=2, these need to be re-zeroed
- ! because get_carbon_sfc_fluxes has not been called yet,
- ! and co2_diags_read_fields has been called to fill them with old values
- ! there may still be an issue with the timestep-level values, but don't
- ! zero them yet so that they can be diagnosed
- call phys_getopts( cflx_cpl_opt_out = cflx_cpl_opt)
- if ( is_first_restart_step() .and. is_start_curr_month() .and. cflx_cpl_opt == 2) then
- phys_state(c)%c_mflx_sfc(:ncol) = 0._r8
- phys_state(c)%c_mflx_ocn(:ncol) = 0._r8
- phys_state(c)%c_mflx_sff(:ncol) = 0._r8
- phys_state(c)%c_mflx_lnd(:ncol) = 0._r8
- end if
end do
call co2_diags_store_fields(phys_state, pbuf2d)
end if
@@ -1915,9 +1903,14 @@ subroutine tphysac (ztodt, cam_in, &
if (cflx_cpl_opt==1) then
call cflx_tend( state, cam_in, ztodt, ptend)
- call physics_update(state, ptend, ztodt, tend)
- !!!! todo: delete !!!
- if (masterproc) write(iulog,*) 'cflx-log: surface flux tendencies applied in tphysac after clubb_surface'
+ call physics_update(state, ptend, ztodt, tend)
+ if (masterproc) write(iulog,*) 'cflx-log: surface flux tendencies applied in tphysac after clubb_surface'
+ elseif (cflx_cpl_opt==2) then
+ ! Apply CO2 tracer fluxes here in tphysac; non-CO2 fluxes were already applied
+ ! in tphysbc (see GH #8201). This avoids duplicate CO2 additions during init.
+ call cflx_tend( state, cam_in, ztodt, ptend, co2_only=.true.)
+ call physics_update(state, ptend, ztodt, tend)
+ if (masterproc) write(iulog,*) 'cflx-log: CO2 surface flux tendencies applied in tphysac after clubb_surface'
end if
call cnd_diag_checkpoint( diag, 'CFLXAPP', state, pbuf, cam_in, cam_out )
@@ -1948,12 +1941,10 @@ subroutine tphysac (ztodt, cam_in, &
end if ! l_vdiff
endif
- ! collect surface carbon fluxes, but only if they have been updated by cflx_tend above, (cflx_cpl_opt==1)
- ! or by vertical_diffusion_tend above (l_vdiff==.true.) or if they are not being updated in tphysbc (cflx_cpl_opt /= 2)
- ! otherwise, this function is called by tphysbc after cflx_tend() and update_physics()
- if (cflx_cpl_opt /= 2) then
- call get_carbon_sfc_fluxes(state, cam_in, ztodt)
- endif
+ ! collect surface carbon fluxes after cflx_tend (or vertical_diffusion_tend) has been applied.
+ ! For cflx_cpl_opt==2, CO2 fluxes are now applied above in tphysac (not tphysbc), so
+ ! get_carbon_sfc_fluxes is always called here (see GH #8201).
+ call get_carbon_sfc_fluxes(state, cam_in, ztodt)
if (l_rayleigh) then
!===================================================
@@ -2317,7 +2308,6 @@ subroutine tphysbc (ztodt, &
use debug_info, only: get_debug_chunk, get_debug_macmiciter
use lnd_infodata, only: precip_downscaling_method
use cflx, only: cflx_tend
- use co2_diagnostics, only: get_carbon_sfc_fluxes
implicit none
@@ -2867,12 +2857,13 @@ subroutine tphysbc (ztodt, &
! on tracers for which cam_in%cflx(:,m) is zero at this point.
!if ( do_clubb_sgs .and. (cflx_cpl_opt==2) ) then
- if ( cflx_cpl_opt==2 ) then
- call cflx_tend( state, cam_in, ztodt, ptend)
+ if ( (do_clubb_sgs .or. do_shoc_sgs) .and. (cflx_cpl_opt==2) ) then
+ ! Apply surface fluxes for all tracers EXCEPT CO2; CO2 is applied in tphysac
+ ! to avoid redundant additions during the multi-call init sequence (see GH #8201)
+ call cflx_tend( state, cam_in, ztodt, ptend, skip_co2=.true.)
call physics_update(state, ptend, ztodt, tend)
- call get_carbon_sfc_fluxes(state, cam_in, ztodt)
! for examining surface cflx update timing - aldivi
- if (masterproc) write(iulog,*) 'cflx-log: surface flux tendencies applied in tphysbc.'
+ if (masterproc) write(iulog,*) 'cflx-log: surface flux tendencies (non-CO2) applied in tphysbc.'
end if
!========================================================================================
diff --git a/components/eam/src/physics/cam/tropopause.F90 b/components/eam/src/physics/cam/tropopause.F90
index 96df8e3f43ec..46c3f43ef854 100644
--- a/components/eam/src/physics/cam/tropopause.F90
+++ b/components/eam/src/physics/cam/tropopause.F90
@@ -94,6 +94,12 @@ module tropopause
real(r8) :: cnst_faktor ! = -gravit/rair
real(r8) :: cnst_ka1 ! = cnst_kap - 1._r8
+ ! If pressure values in all model layers are higher than the threshold specified below,
+ ! do not attempt to locate the tropopause. The value used here is somewhat
+ ! arbitrary and is taken from subroutine tropopause_twmo.
+
+ real(r8),parameter :: ptop_thresh = 45000._r8 ! unit: Pa
+
!================================================================================================
contains
!================================================================================================
@@ -1557,6 +1563,11 @@ subroutine tropopause_output(pstate)
lchnk = pstate%lchnk
ncol = pstate%ncol
+ ! Skip the rest of the subroutine if pressure values in all model layers are
+ ! higher than ptop_thresh. This is unlikely in typical global simulations but
+ ! can happen in idealized tests.
+ if (minval(pstate%pmid(:ncol,:)).gt.ptop_thresh) return
+
! Find the tropopause using the default algorithm backed by the climatology.
call tropopause_find(pstate, tropLev, tropP=tropP, tropT=tropT, tropZ=tropZ)
@@ -1665,6 +1676,11 @@ subroutine tropopause_e90_3d_output(pstate)
if (e90_ndx < 0) return
+ ! Skip the rest of the subroutine if pressure values in all model layers are
+ ! higher than ptop_thresh. This is unlikely in typical global simulations but
+ ! can happen in idealized tests.
+ if (minval(pstate%pmid(:ncol,:)).gt.ptop_thresh) return
+
! Find the tropopause
call tropopause_e90_3d(pstate, tropLevB, tropLevU, tropFlag, tropFlagInt, tropP=tropP, tropT=tropT, tropZ=tropZ)
diff --git a/components/eam/src/physics/rrtmg/radiation.F90 b/components/eam/src/physics/rrtmg/radiation.F90
index 8c16b694d777..d3a3120fc6b7 100644
--- a/components/eam/src/physics/rrtmg/radiation.F90
+++ b/components/eam/src/physics/rrtmg/radiation.F90
@@ -19,7 +19,7 @@ module radiation
use ppgrid, only: pcols, pver, pverp, begchunk, endchunk
use physics_types, only: physics_state, physics_ptend
use physconst, only: cappa
-use time_manager, only: get_nstep, is_first_restart_step
+use time_manager, only: get_nstep, is_first_restart_step, is_first_step
use cam_abortutils, only: endrun
use error_messages, only: handle_err
use cam_control_mod, only: lambm0, obliqr, mvelpp, eccen
@@ -1145,6 +1145,28 @@ subroutine radiation_tend(state,ptend, pbuf, &
dosw = radiation_do('sw') ! do shortwave heating calc this timestep?
dolw = radiation_do('lw') ! do longwave heating calc this timestep?
+ ! In sensitivity experiments with iradsw = 0, dosw is always .false.;
+ ! consequently, the "if (dosw) then" blocks later in this subroutine are skipped.
+ ! With a debug build, arrays like qrs, fsnt, and fsns may be left
+ ! in an uninitialized state and subsequently cause floating-point exception.
+ ! Here, we initialize these arrays with zero at the first timestep to avoid trouble.
+
+ if ( (.not.dosw) .and. is_first_step() ) then
+ qrs(1:ncol,1:pver) = 0._r8
+ fsnt(1:ncol) = 0._r8
+ fsns(1:ncol) = 0._r8
+ end if
+
+ ! Similarly, initialize qrl, flnt, and flns with 0 for experiments that have iradlw = 0.
+
+ if ( (.not.dolw) .and. is_first_step() ) then
+ qrl(1:ncol,1:pver) = 0._r8
+ flnt(1:ncol) = 0._r8
+ flns(1:ncol) = 0._r8
+ end if
+
+ !-----------
+
if (dosw .or. dolw) then
! construct an RRTMG state object
diff --git a/components/eam/src/utils/hycoef.F90 b/components/eam/src/utils/hycoef.F90
index 7fb4317fa741..821c543a3f3a 100644
--- a/components/eam/src/utils/hycoef.F90
+++ b/components/eam/src/utils/hycoef.F90
@@ -301,6 +301,9 @@ subroutine hycoef_read(File)
ierr = pio_get_var(File, hyam_desc, hyam)
ierr = pio_get_var(File, hybm_desc, hybm)
+ ! Make sure hybi(1) is zero, in order to be consistent with pressure calculations in the SE dycore.
+ if (hybi(1) .ne. 0._r8) call endrun(routine//':ERROR: hybi(1) is non-zero.')
+
#if ( defined OFFLINE_DYN )
! make sure top interface is non zero for fv dycore
if (hyai(1) .eq. 0._r8) then
diff --git a/components/eamxx/CMakeLists.txt b/components/eamxx/CMakeLists.txt
index 0ca08c437807..5a39e19dafdd 100644
--- a/components/eamxx/CMakeLists.txt
+++ b/components/eamxx/CMakeLists.txt
@@ -51,11 +51,6 @@ if (SCREAM_CIME_BUILD)
${CMAKE_CURRENT_SOURCE_DIR}/cmake/cime)
endif ()
-if (NOT CMAKE_CXX_STANDARD)
- # Default to C++17 in EAMxx
- set(CMAKE_CXX_STANDARD 17)
-endif()
-
if (NOT SCREAM_CIME_BUILD)
project(SCREAM CXX C Fortran)
@@ -462,6 +457,12 @@ endif()
# Configure all tpls and subfolders #
####################################################################
+# Set compiler-specific flags. Do it BEFORE ekat, so ekat gets the flags too
+include(EkatSetCompilerFlags)
+ResetFlags()
+SetCommonFlags()
+SetProfilingFlags(PROFILER ${EKAT_PROFILING_TOOL} COVERAGE ${EKAT_ENABLE_COVERAGE})
+
# We will use the sharedlib one in CIME builds
if (NOT SCREAM_CIME_BUILD)
set (EKAT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../externals/ekat)
@@ -500,12 +501,6 @@ if (NOT SCREAM_CIME_BUILD)
add_subdirectory(${EKAT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/externals/ekat)
endif()
-# Set compiler-specific flags
-include(EkatSetCompilerFlags)
-ResetFlags()
-SetCommonFlags()
-SetProfilingFlags(PROFILER ${EKAT_PROFILING_TOOL} COVERAGE ${EKAT_ENABLE_COVERAGE})
-
include(EkatMpiUtils)
# We should avoid cxx bindings in mpi; they are already deprecated,
# and can cause headaches at link time, cause they require -lmpi_cxx
diff --git a/components/eamxx/cime_config/namelist_defaults_eamxx.xml b/components/eamxx/cime_config/namelist_defaults_eamxx.xml
index 29c61a6260fe..3e206a3b8b67 100644
--- a/components/eamxx/cime_config/namelist_defaults_eamxx.xml
+++ b/components/eamxx/cime_config/namelist_defaults_eamxx.xml
@@ -296,6 +296,16 @@ be lost if SCREAM_HACK_XML is not enabled.
0.0
+
+
+ 0
+
+
+
+
+ 0
+
+
false
@@ -435,26 +445,26 @@ be lost if SCREAM_HACK_XML is not enabled.
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/DMSflux.2010.ne30pg2_conserv.POPmonthlyClimFromACES4BGC_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so2_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_bc_a4_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a1_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a2_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a4_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_pom_a4_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a1_surf_ne30pg2_2010_clim_c20240816.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a2_surf_ne30pg2_2010_clim_c20240816.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/DMSflux.2010.ne30pg2_conserv.POPmonthlyClimFromACES4BGC_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so2_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_bc_a4_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a1_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a2_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_num_a4_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_pom_a4_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a1_surf_ne30pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne30pg2/surface/cmip6_mam4_so4_a2_surf_ne30pg2_2010_clim_c20260730.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/DMSflux.2010.ne4pg2_conserv.POPmonthlyClimFromACES4BGC_c20240814.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so2_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_bc_a4_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a1_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a2_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a4_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_pom_a4_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a1_surf_ne4pg2_2010_clim_c20240815.nc
- ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a2_surf_ne4pg2_2010_clim_c20240815.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/DMSflux.2010.ne4pg2_conserv.POPmonthlyClimFromACES4BGC_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so2_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_bc_a4_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a1_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a2_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_num_a4_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_pom_a4_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a1_surf_ne4pg2_2010_clim_c20260730.nc
+ ${DIN_LOC_ROOT}/atm/scream/mam4xx/emissions/ne4pg2/surface/cmip6_mam4_so4_a2_surf_ne4pg2_2010_clim_c20260730.nc
1.0
@@ -559,6 +569,8 @@ be lost if SCREAM_HACK_XML is not enabled.
${DIN_LOC_ROOT}/atm/scream/init/spc_from_v3LRamip_2010_clim_ne30pg2_c20260206.nc
${DIN_LOC_ROOT}/atm/scream/init/spc_from_v3LRamip_2010_clim_ne4pg2_c20260211.nc
+ o3
+
yearly_periodic
@@ -915,6 +927,7 @@ be lost if SCREAM_HACK_XML is not enabled.
6
2
3.0
+ 0.0
1
1
1
diff --git a/components/eamxx/cmake/machine-files/vista.cmake b/components/eamxx/cmake/machine-files/vista.cmake
new file mode 100644
index 000000000000..e37504d5de04
--- /dev/null
+++ b/components/eamxx/cmake/machine-files/vista.cmake
@@ -0,0 +1,37 @@
+include(${CMAKE_CURRENT_LIST_DIR}/common.cmake)
+common_setup()
+
+#message(STATUS "vista PROJECT_NAME=${PROJECT_NAME} USE_CUDA=${USE_CUDA} KOKKOS_ENABLE_CUDA=${KOKKOS_ENABLE_CUDA}")
+if ("${PROJECT_NAME}" STREQUAL "E3SM")
+ if (USE_CUDA)
+ include (${EKAT_MACH_FILES_PATH}/kokkos/nvidia-h100.cmake) # H100=Hopper=Ampere90=Hopper90 Kokkos_ARCH_HOPPER90
+ include (${EKAT_MACH_FILES_PATH}/kokkos/cuda.cmake)
+ else()
+ include (${EKAT_MACH_FILES_PATH}/kokkos/nvidia-grace.cmake) # KOKKOS_ARCH_ARMV9_GRACE
+ include (${EKAT_MACH_FILES_PATH}/kokkos/openmp.cmake)
+ #include (${EKAT_MACH_FILES_PATH}/kokkos/serial.cmake)
+ endif()
+else()
+ include (${EKAT_MACH_FILES_PATH}/kokkos/nvidia-h100.cmake)
+ include (${EKAT_MACH_FILES_PATH}/kokkos/cuda.cmake)
+endif()
+
+include (${EKAT_MACH_FILES_PATH}/mpi/srun.cmake) # should be changed to use ibrun
+
+set(EKAT_MPI_EXTRA_ARGS "${EKAT_MPI_EXTRA_ARGS} --gpus-per-task=1" CACHE STRING "" FORCE)
+
+#option(Kokkos_ARCH_AMPERE90 "" ON)
+set(CMAKE_CXX_FLAGS "-DTHRUST_IGNORE_CUB_VERSION_CHECK" CACHE STRING "" FORCE)
+
+#set(CMAKE_CUDA_FLAGS "-allow-unsupported-compiler" CACHE STRING "" FORCE) #ndk try for gcc14
+
+#message(STATUS "vista CMAKE_CXX_COMPILER_ID=${CMAKE_CXX_COMPILER_ID} CMAKE_Fortran_COMPILER_VERSION=${CMAKE_Fortran_COMPILER_VERSION}")
+if ("${PROJECT_NAME}" STREQUAL "E3SM")
+ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ if (CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL 10)
+ set(CMAKE_Fortran_FLAGS "-fallow-argument-mismatch" CACHE STRING "" FORCE) # only works with gnu v10 and above
+ endif()
+ endif()
+else()
+ set(CMAKE_Fortran_FLAGS "-fallow-argument-mismatch" CACHE STRING "" FORCE) # only works with gnu v10 and above
+endif()
diff --git a/components/eamxx/src/control/atmosphere_driver.cpp b/components/eamxx/src/control/atmosphere_driver.cpp
index 516b8d9b03f4..79cb73268974 100644
--- a/components/eamxx/src/control/atmosphere_driver.cpp
+++ b/components/eamxx/src/control/atmosphere_driver.cpp
@@ -312,6 +312,10 @@ void AtmosphereDriver::create_grids()
setup_shoc_tms_links();
}
+ if (m_atm_process_group->has_process("shoc")) {
+ setup_shoc_3d_turbulence_link();
+ }
+
// IOP object needs the grids_manager to have been created, but is then needed in set_grids()
// implementation of some processes, so setup here.
const bool enable_iop =
@@ -518,6 +522,21 @@ void AtmosphereDriver::setup_shoc_tms_links ()
shoc_process->get_params().set("apply_tms", true);
}
+void AtmosphereDriver::setup_shoc_3d_turbulence_link ()
+{
+ EKAT_REQUIRE_MSG(m_atm_process_group->has_process("shoc"),
+ "Error! Attempting to setup 3D turbulence link for "
+ "SHOC, but SHOC is not defined.\n");
+
+ if (m_atm_process_group->has_process("homme")) {
+ auto homme_process = m_atm_process_group->get_process_nonconst("homme");
+ const bool do_3d_turbulence = homme_process->get_params().get("do_3d_turbulence", false);
+
+ auto shoc_process = m_atm_process_group->get_process_nonconst("shoc");
+ shoc_process->get_params().set("do_3d_turbulence_shoc", do_3d_turbulence);
+ }
+}
+
void AtmosphereDriver::add_additional_column_data_to_property_checks () {
// Get list of additional data fields from driver_options parameters.
// If no fields given, return.
@@ -609,33 +628,37 @@ void AtmosphereDriver::create_fields()
}
// Now go through the input fields/groups to the atm proc group,
- // and mark them as part of the RESTART group.
+ // and mark them as part of the RESTART/STARTUP/TOPOGRAPHY groups.
// Skip fields in the ACCUMULATED group, since those are reset to 0
// at the beginning of each atm step, so there is no need to read
// them from the IC or restart file.
- for (const auto& f : m_atm_process_group->get_fields_in()) {
+ auto is_topography_field = [] (const std::string& name) {
+ return name=="phis" or name=="sgh" or name=="sgh30";
+ };
+
+ auto set_groups = [&](const Field& f) {
const auto& fid = f.get_header().get_identifier();
const auto& fgroups = f.get_header().get_tracking().get_groups_names();
if (not ekat::contains(fgroups, "ACCUMULATED")) {
m_field_mgr->add_to_group(fid, "RESTART");
- }
- }
- for (const auto& g : m_atm_process_group->get_groups_in()) {
- if (g.m_monolithic_field) {
- const auto& mf = *g.m_monolithic_field;
- const auto& mfgroups = mf.get_header().get_tracking().get_groups_names();
- if (not ekat::contains(mfgroups, "ACCUMULATED")) {
- m_field_mgr->add_to_group(mf.get_header().get_identifier(), "RESTART");
- }
- } else {
- for (const auto& fn : g.m_info->m_fields_names) {
- auto field = m_field_mgr->get_field(fn, g.grid_name());
- const auto& fgroups = field.get_header().get_tracking().get_groups_names();
- if (not ekat::contains(fgroups, "ACCUMULATED")) {
- m_field_mgr->add_to_group(fn, g.grid_name(), "RESTART");
- }
+ m_field_mgr->add_to_group(fid, "STARTUP");
+ if (is_topography_field(fid.name())) {
+ m_field_mgr->add_to_group(fid, "TOPOGRAPHY");
}
}
+ };
+
+ // Process input fields
+ for (const auto& f : m_atm_process_group->get_fields_in())
+ set_groups(f);
+
+ // Process input groups
+ for (const auto& g : m_atm_process_group->get_groups_in()) {
+ if (g.m_monolithic_field)
+ set_groups(*g.m_monolithic_field);
+ else
+ for (const auto& it : g.m_individual_fields)
+ set_groups(*it.second);
}
auto& driver_options_pl = m_atm_params.sublist("driver_options");
diff --git a/components/eamxx/src/control/atmosphere_driver.hpp b/components/eamxx/src/control/atmosphere_driver.hpp
index c3d6735d7abb..fcc83ab43645 100644
--- a/components/eamxx/src/control/atmosphere_driver.hpp
+++ b/components/eamxx/src/control/atmosphere_driver.hpp
@@ -105,6 +105,9 @@ class AtmosphereDriver
// tms' surface drag coefficient.
void setup_shoc_tms_links();
+ // Propagate HOMME's parsed 3D turbulence flag to SHOC's internal runtime option.
+ void setup_shoc_3d_turbulence_link();
+
// Add column data to all pre/postcondition property checks
// for use in output.
void add_additional_column_data_to_property_checks ();
diff --git a/components/eamxx/src/dynamics/homme/CMakeLists.txt b/components/eamxx/src/dynamics/homme/CMakeLists.txt
index 59750c48e278..b7c68de22783 100644
--- a/components/eamxx/src/dynamics/homme/CMakeLists.txt
+++ b/components/eamxx/src/dynamics/homme/CMakeLists.txt
@@ -150,6 +150,7 @@ macro (CreateDynamicsLib HOMME_TARGET NP PLEV QSIZE)
${SCREAM_DYNAMICS_SRC_DIR}/eamxx_homme_rayleigh_friction.cpp
${SCREAM_DYNAMICS_SRC_DIR}/physics_dynamics_remapper.cpp
${SCREAM_DYNAMICS_SRC_DIR}/homme_grids_manager.cpp
+ ${SCREAM_DYNAMICS_SRC_DIR}/eamxx_homme_3d_turbulence_strain.cpp
${SCREAM_DYNAMICS_SRC_DIR}/interface/homme_context_mod.F90
${SCREAM_DYNAMICS_SRC_DIR}/interface/homme_driver_mod.F90
${SCREAM_DYNAMICS_SRC_DIR}/interface/homme_grid_mod.F90
diff --git a/components/eamxx/src/dynamics/homme/eamxx_homme_3d_turbulence_strain.cpp b/components/eamxx/src/dynamics/homme/eamxx_homme_3d_turbulence_strain.cpp
new file mode 100644
index 000000000000..97d8f9144e2e
--- /dev/null
+++ b/components/eamxx/src/dynamics/homme/eamxx_homme_3d_turbulence_strain.cpp
@@ -0,0 +1,249 @@
+#include "eamxx_homme_process_interface.hpp"
+
+// HOMMEXX includes
+#include "Context.hpp"
+#include "ElementsGeometry.hpp"
+#include "ElementsState.hpp"
+#include "ReferenceElement.hpp"
+#include "TimeLevel.hpp"
+#include "Types.hpp"
+#include "utilities/ViewUtils.hpp"
+
+// Scream includes
+#include "dynamics/homme/homme_dimensions.hpp"
+#include "share/util/eamxx_column_ops.hpp"
+
+// EKAT includes
+#include
+
+namespace scream
+{
+
+namespace {
+
+// Project a local velocity vector (u,v,w) into one Cartesian component.
+template
+KOKKOS_INLINE_FUNCTION
+Real local_to_cart_component(
+ const BasisViewType& basis_sph2cart,
+ const Real u,
+ const Real v,
+ const Real w)
+{
+ return basis_sph2cart(0) * u
+ + basis_sph2cart(1) * v
+ + basis_sph2cart(2) * w;
+}
+
+} // anonymous namespace
+
+void HommeDynamics::compute_horizontal_derivs_of_car_velocity ()
+{
+ using namespace Homme;
+
+ constexpr int NGP = HOMMEXX_NP;
+
+ const auto& c = Context::singleton();
+ const auto& state = c.get();
+ const auto& geom = c.get();
+ const auto& ref_fe = c.get();
+ const auto& tl = c.get();
+
+ const int nelem = m_dyn_grid->get_num_local_dofs() / (NGP*NGP);
+ const int n0 = tl.n0;
+ const auto& grad_Ux_field = m_helper_fields.at("grad_Ux_dyn");
+ const auto& grad_Ux_layout = grad_Ux_field.get_header().get_identifier().get_layout();
+ const int nlev_scalar = grad_Ux_layout.dims().back();
+
+ const auto w_int_dyn = state.m_w_i;
+
+ auto grad_Ux_dyn = m_helper_fields.at("grad_Ux_dyn").template get_view();
+ auto grad_Uy_dyn = m_helper_fields.at("grad_Uy_dyn").template get_view();
+ auto grad_Uz_dyn = m_helper_fields.at("grad_Uz_dyn").template get_view();
+
+ const auto dvv = ref_fe.get_deriv();
+ const auto dinv = geom.m_dinv;
+ const auto vec_sph2cart = geom.m_vec_sph2cart;
+ const Real scale_factor_inv = 1.0 / geom.m_scale_factor;
+
+ using TeamPolicy = Kokkos::TeamPolicy;
+ using MemberType = typename TeamPolicy::member_type;
+ const int ncols = nelem*NGP*NGP;
+ const TeamPolicy policy(ncols, Kokkos::AUTO());
+ const auto dsdx_Ux_all = m_dsdx_Ux_all;
+ const auto dsdy_Ux_all = m_dsdy_Ux_all;
+ const auto dsdx_Uy_all = m_dsdx_Uy_all;
+ const auto dsdy_Uy_all = m_dsdy_Uy_all;
+ const auto dsdx_Uz_all = m_dsdx_Uz_all;
+ const auto dsdy_Uz_all = m_dsdy_Uz_all;
+
+ Kokkos::parallel_for(
+ "compute_horizontal_derivs_of_car_velocity",
+ policy,
+ KOKKOS_LAMBDA (const MemberType& team) {
+
+ const int ie = team.league_rank() / (NGP*NGP);
+ const int igp = (team.league_rank() / NGP) % NGP;
+ const int jgp = team.league_rank() % NGP;
+ const int icol = team.league_rank();
+
+ // Grab the scratch storage associated with this (ie,igp,jgp) column.
+ const auto dsdx_Ux = Kokkos::subview(dsdx_Ux_all, icol, Kokkos::ALL());
+ const auto dsdy_Ux = Kokkos::subview(dsdy_Ux_all, icol, Kokkos::ALL());
+ const auto dsdx_Uy = Kokkos::subview(dsdx_Uy_all, icol, Kokkos::ALL());
+ const auto dsdy_Uy = Kokkos::subview(dsdy_Uy_all, icol, Kokkos::ALL());
+ const auto dsdx_Uz = Kokkos::subview(dsdx_Uz_all, icol, Kokkos::ALL());
+ const auto dsdy_Uz = Kokkos::subview(dsdy_Uz_all, icol, Kokkos::ALL());
+
+ // Accumulate reference-element derivatives in the two local horizontal directions.
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_scalar), [&] (const int ilev) {
+ dsdx_Ux(ilev) = 0;
+ dsdy_Ux(ilev) = 0;
+ dsdx_Uy(ilev) = 0;
+ dsdy_Uy(ilev) = 0;
+ dsdx_Uz(ilev) = 0;
+ dsdy_Uz(ilev) = 0;
+ });
+ team.team_barrier();
+
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_scalar), [&] (const int ilev) {
+ Real dsdx_ux = 0;
+ Real dsdy_ux = 0;
+ Real dsdx_uy = 0;
+ Real dsdy_uy = 0;
+ Real dsdx_uz = 0;
+ Real dsdy_uz = 0;
+
+ for (int kgp = 0; kgp < NGP; ++kgp) {
+ // The horizontal stencil uses interface w, so average the two
+ // adjacent interface values onto midpoint levels on the fly.
+ const auto w_row_i = Kokkos::subview(w_int_dyn, ie, n0, igp, kgp, Kokkos::ALL());
+ const auto w_col_i = Kokkos::subview(w_int_dyn, ie, n0, kgp, jgp, Kokkos::ALL());
+ const auto w_row_i_real = Homme::viewAsReal(w_row_i);
+ const auto w_col_i_real = Homme::viewAsReal(w_col_i);
+
+ const auto row_x = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 0, igp, kgp);
+ const auto row_y = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 1, igp, kgp);
+ const auto row_z = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 2, igp, kgp);
+ const auto col_x = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 0, kgp, jgp);
+ const auto col_y = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 1, kgp, jgp);
+ const auto col_z = Kokkos::subview(vec_sph2cart, ie, Kokkos::ALL(), 2, kgp, jgp);
+ const auto u_row_view =
+ Homme::viewAsReal(Kokkos::subview(state.m_v, ie, n0, 0, igp, kgp, Kokkos::ALL()));
+ const auto v_row_view =
+ Homme::viewAsReal(Kokkos::subview(state.m_v, ie, n0, 1, igp, kgp, Kokkos::ALL()));
+ const auto u_col_view =
+ Homme::viewAsReal(Kokkos::subview(state.m_v, ie, n0, 0, kgp, jgp, Kokkos::ALL()));
+ const auto v_col_view =
+ Homme::viewAsReal(Kokkos::subview(state.m_v, ie, n0, 1, kgp, jgp, Kokkos::ALL()));
+
+ const Real u_row = u_row_view(ilev);
+ const Real v_row = v_row_view(ilev);
+ const Real w_row = 0.5 * (w_row_i_real(ilev) + w_row_i_real(ilev + 1));
+
+ const Real u_col = u_col_view(ilev);
+ const Real v_col = v_col_view(ilev);
+ const Real w_col = 0.5 * (w_col_i_real(ilev) + w_col_i_real(ilev + 1));
+
+ dsdx_ux += dvv(jgp,kgp) * local_to_cart_component(row_x, u_row, v_row, w_row);
+ dsdy_ux += dvv(igp,kgp) * local_to_cart_component(col_x, u_col, v_col, w_col);
+
+ dsdx_uy += dvv(jgp,kgp) * local_to_cart_component(row_y, u_row, v_row, w_row);
+ dsdy_uy += dvv(igp,kgp) * local_to_cart_component(col_y, u_col, v_col, w_col);
+
+ dsdx_uz += dvv(jgp,kgp) * local_to_cart_component(row_z, u_row, v_row, w_row);
+ dsdy_uz += dvv(igp,kgp) * local_to_cart_component(col_z, u_col, v_col, w_col);
+ }
+
+ dsdx_Ux(ilev) = dsdx_ux;
+ dsdy_Ux(ilev) = dsdy_ux;
+ dsdx_Uy(ilev) = dsdx_uy;
+ dsdy_Uy(ilev) = dsdy_uy;
+ dsdx_Uz(ilev) = dsdx_uz;
+ dsdy_Uz(ilev) = dsdy_uz;
+ });
+ team.team_barrier();
+
+ // Convert the reference-element derivatives into physical horizontal
+ // gradients using the inverse metric tensor on this curved element.
+ const auto dinv_ij = Kokkos::subview(dinv, ie, Kokkos::ALL(), Kokkos::ALL(), igp, jgp);
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_scalar), [&] (const int ilev) {
+ grad_Ux_dyn(ie,0,igp,jgp,ilev) = (dinv_ij(0,0) * dsdx_Ux(ilev) + dinv_ij(0,1) * dsdy_Ux(ilev)) * scale_factor_inv;
+ grad_Uy_dyn(ie,0,igp,jgp,ilev) = (dinv_ij(0,0) * dsdx_Uy(ilev) + dinv_ij(0,1) * dsdy_Uy(ilev)) * scale_factor_inv;
+ grad_Uz_dyn(ie,0,igp,jgp,ilev) = (dinv_ij(0,0) * dsdx_Uz(ilev) + dinv_ij(0,1) * dsdy_Uz(ilev)) * scale_factor_inv;
+
+ grad_Ux_dyn(ie,1,igp,jgp,ilev) = (dinv_ij(1,0) * dsdx_Ux(ilev) + dinv_ij(1,1) * dsdy_Ux(ilev)) * scale_factor_inv;
+ grad_Uy_dyn(ie,1,igp,jgp,ilev) = (dinv_ij(1,0) * dsdx_Uy(ilev) + dinv_ij(1,1) * dsdy_Uy(ilev)) * scale_factor_inv;
+ grad_Uz_dyn(ie,1,igp,jgp,ilev) = (dinv_ij(1,0) * dsdx_Uz(ilev) + dinv_ij(1,1) * dsdy_Uz(ilev)) * scale_factor_inv;
+ });
+ });
+
+ Kokkos::fence();
+}
+
+void HommeDynamics::compute_local_strain_components3d ()
+{
+ using namespace Homme;
+
+ constexpr int NGP = HOMMEXX_NP;
+
+ const auto& c = Context::singleton();
+ const auto& geom = c.get();
+
+ const int nelem = m_dyn_grid->get_num_local_dofs() / (NGP*NGP);
+
+ auto grad_Ux_dyn = m_helper_fields.at("grad_Ux_dyn").template get_view();
+ auto grad_Uy_dyn = m_helper_fields.at("grad_Uy_dyn").template get_view();
+ auto grad_Uz_dyn = m_helper_fields.at("grad_Uz_dyn").template get_view();
+
+ auto& shear_components_field = m_helper_fields.at("shear_strain3d_components_dyn");
+ const auto& shear_components_layout = shear_components_field.get_header().get_identifier().get_layout();
+ auto shear_components_dyn = shear_components_field.template get_view();
+
+ const auto vec_sph2cart = geom.m_vec_sph2cart;
+
+ const int nlev_scalar = shear_components_layout.dims().back();
+
+ using Policy = Kokkos::MDRangePolicy>;
+ const Policy policy({0, 0, 0, 0}, {nelem, NGP, NGP, nlev_scalar});
+
+ Kokkos::parallel_for(
+ "compute_local_strain_components3d",
+ policy,
+ KOKKOS_LAMBDA (const int ie, const int igp, const int jgp, const int ilev) {
+
+ // The stored gradients are Cartesian components differentiated along the
+ // two local horizontal directions. Project them back into the local basis
+ // so SHOC receives the six local shear-tensor components it expects.
+ const Real gx0 = grad_Ux_dyn(ie,0,igp,jgp,ilev);
+ const Real gy0 = grad_Uy_dyn(ie,0,igp,jgp,ilev);
+ const Real gz0 = grad_Uz_dyn(ie,0,igp,jgp,ilev);
+
+ const Real gx1 = grad_Ux_dyn(ie,1,igp,jgp,ilev);
+ const Real gy1 = grad_Uy_dyn(ie,1,igp,jgp,ilev);
+ const Real gz1 = grad_Uz_dyn(ie,1,igp,jgp,ilev);
+
+ const Real b0_0 = vec_sph2cart(ie, 0, 0, igp, jgp);
+ const Real b0_1 = vec_sph2cart(ie, 0, 1, igp, jgp);
+ const Real b0_2 = vec_sph2cart(ie, 0, 2, igp, jgp);
+
+ const Real b1_0 = vec_sph2cart(ie, 1, 0, igp, jgp);
+ const Real b1_1 = vec_sph2cart(ie, 1, 1, igp, jgp);
+ const Real b1_2 = vec_sph2cart(ie, 1, 2, igp, jgp);
+
+ const Real b2_0 = vec_sph2cart(ie, 2, 0, igp, jgp);
+ const Real b2_1 = vec_sph2cart(ie, 2, 1, igp, jgp);
+ const Real b2_2 = vec_sph2cart(ie, 2, 2, igp, jgp);
+
+ shear_components_dyn(ie,0,igp,jgp,ilev) = b0_0 * gx0 + b0_1 * gy0 + b0_2 * gz0;
+ shear_components_dyn(ie,1,igp,jgp,ilev) = b0_0 * gx1 + b0_1 * gy1 + b0_2 * gz1;
+ shear_components_dyn(ie,2,igp,jgp,ilev) = b1_0 * gx0 + b1_1 * gy0 + b1_2 * gz0;
+ shear_components_dyn(ie,3,igp,jgp,ilev) = b1_0 * gx1 + b1_1 * gy1 + b1_2 * gz1;
+ shear_components_dyn(ie,4,igp,jgp,ilev) = b2_0 * gx0 + b2_1 * gy0 + b2_2 * gz0;
+ shear_components_dyn(ie,5,igp,jgp,ilev) = b2_0 * gx1 + b2_1 * gy1 + b2_2 * gz1;
+ });
+
+ Kokkos::fence();
+}
+
+} // namespace scream
diff --git a/components/eamxx/src/dynamics/homme/eamxx_homme_fv_phys.cpp b/components/eamxx/src/dynamics/homme/eamxx_homme_fv_phys.cpp
index a27f966889a8..8f21a676dea2 100644
--- a/components/eamxx/src/dynamics/homme/eamxx_homme_fv_phys.cpp
+++ b/components/eamxx/src/dynamics/homme/eamxx_homme_fv_phys.cpp
@@ -139,6 +139,11 @@ void HommeDynamics::fv_phys_dyn_to_fv_phys (const util::TimeStamp& ts, const boo
auto f = get_field_out(n,pgn);
f.get_header().get_tracking().update_time_stamp(ts);
}
+ const auto& params = Homme::Context::singleton().get();
+ if (params.do_3d_turbulence) {
+ auto f = get_field_out("tke_shear_strain3d_components",pgn);
+ f.get_header().get_tracking().update_time_stamp(ts);
+ }
auto Q = get_group_out("tracers",pgn).m_monolithic_field;
Q->get_header().get_tracking().update_time_stamp(ts);
}
@@ -196,7 +201,21 @@ void HommeDynamics::remap_dyn_to_fv_phys (GllFvRemapTmp* t) const {
get_field_out("pseudo_density", gn).get_view().data(),
nelem, npg, nlev);
- gfr.run_dyn_to_fv_phys(time_idx, ps, phis, T, omega, uv, q, &dp);
+ const auto& params = c.get();
+ if (params.do_3d_turbulence) {
+ const auto strain3d_components_gll = Homme::GllFvRemap::CPhys3T(
+ m_helper_fields.at("shear_strain3d_components_dyn").get_view().data(),
+ nelem, NGP*NGP, 6, nlev);
+ const auto strain3d_components_fv = Homme::GllFvRemap::Phys3T(
+ get_field_out("tke_shear_strain3d_components", gn).get_view().data(),
+ nelem, npg, 6, nlev);
+ gfr.run_dyn_to_fv_phys(time_idx, ps, phis, T, omega,
+ &strain3d_components_gll, &strain3d_components_fv,
+ uv, q, &dp);
+ } else {
+ gfr.run_dyn_to_fv_phys(time_idx, ps, phis, T, omega,
+ nullptr, nullptr, uv, q, &dp);
+ }
Kokkos::fence();
}
@@ -216,10 +235,6 @@ void HommeDynamics::remap_fv_phys_to_dyn () const {
const auto uv_ndim = m_helper_fields.at("FM_phys").get_view().extent_int(1);
assert(uv_ndim == 2);
- // SGS Eddy diffusivities on FV phys grid
- const auto Km_phys = get_field_in("eddy_diff_mom",gn).get_view();
- const auto Kh_phys = get_field_in("eddy_diff_heat",gn).get_view();
-
const auto T = Homme::GllFvRemap::CPhys2T(
m_helper_fields.at("FT_phys").get_view().data(),
nelem, npg, nlev);
@@ -230,10 +245,17 @@ void HommeDynamics::remap_fv_phys_to_dyn () const {
get_group_in("tracers", gn).m_monolithic_field->get_view().data(),
nelem, npg, nq, nlev);
- const auto Km = Homme::GllFvRemap::CPhys2T(Km_phys.data(), nelem, npg, nlev);
- const auto Kh = Homme::GllFvRemap::CPhys2T(Kh_phys.data(), nelem, npg, nlev);
+ const auto& params = c.get();
+ if (params.do_3d_turbulence) {
+ const auto Km_phys = get_field_in("eddy_diff_mom",gn).get_view();
+ const auto Kh_phys = get_field_in("eddy_diff_heat",gn).get_view();
+ const auto Km = Homme::GllFvRemap::CPhys2T(Km_phys.data(), nelem, npg, nlev);
+ const auto Kh = Homme::GllFvRemap::CPhys2T(Kh_phys.data(), nelem, npg, nlev);
+ gfr.run_fv_phys_to_dyn(time_idx, T, uv, q, &Km, &Kh);
+ } else {
+ gfr.run_fv_phys_to_dyn(time_idx, T, uv, q);
+ }
- gfr.run_fv_phys_to_dyn(time_idx, T, uv, q, Km, Kh);
Kokkos::fence();
gfr.run_fv_phys_to_dyn_dss();
Kokkos::fence();
diff --git a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp
index 4884fc5154c4..cca2e0b58f9f 100644
--- a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp
+++ b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.cpp
@@ -176,8 +176,12 @@ void HommeDynamics::create_requests ()
add_field("p_dry_int", pg_scalar3d_int, Pa, pgn,N);
add_field("p_dry_mid", pg_scalar3d_mid, Pa, pgn,N);
add_field("omega", pg_scalar3d_mid, Pa/s, pgn,N);
- add_field("eddy_diff_heat", pg_scalar3d_mid, m2/s, pgn,N);
- add_field("eddy_diff_mom", pg_scalar3d_mid, m2/s, pgn,N);
+ if (params.do_3d_turbulence) {
+ add_field("eddy_diff_heat", pg_scalar3d_mid, m2/s, pgn,N);
+ add_field("eddy_diff_mom", pg_scalar3d_mid, m2/s, pgn,N);
+ auto pg_shear_components_mid = m_phys_grid->get_3d_vector_layout(LEV,6);
+ add_field("tke_shear_strain3d_components", pg_shear_components_mid, 1/s, pgn,N);
+ }
add_tracer("qv", m_phys_grid, kg/kg, N);
add_group("tracers",pgn,N, MonolithicAlloc::Required);
@@ -212,8 +216,14 @@ void HommeDynamics::create_requests ()
create_helper_field("phis_dyn", {EL, GP,GP}, {nelem, NP,NP }, dgn);
create_helper_field("omega_dyn", {EL, GP,GP,LEV}, {nelem, NP,NP,nlev_mid}, dgn);
create_helper_field("Qdp_dyn", {EL,TL,CMP,GP,GP,LEV}, {nelem,QTL,HOMMEXX_QSIZE_D,NP,NP,nlev_mid},dgn);
- create_helper_field("Km_dyn", {EL, GP,GP,LEV}, {nelem, NP,NP,nlev_mid}, dgn);
- create_helper_field("Kh_dyn", {EL, GP,GP,LEV}, {nelem, NP,NP,nlev_mid}, dgn);
+ if (params.do_3d_turbulence) {
+ create_helper_field("Km_dyn", {EL, GP,GP,LEV}, {nelem, NP,NP,nlev_mid}, dgn);
+ create_helper_field("Kh_dyn", {EL, GP,GP,LEV}, {nelem, NP,NP,nlev_mid}, dgn);
+ create_helper_field("grad_Ux_dyn", {EL,CMP, GP,GP,LEV}, {nelem,2, NP,NP,nlev_mid}, dgn);
+ create_helper_field("grad_Uy_dyn", {EL,CMP, GP,GP,LEV}, {nelem,2, NP,NP,nlev_mid}, dgn);
+ create_helper_field("grad_Uz_dyn", {EL,CMP, GP,GP,LEV}, {nelem,2, NP,NP,nlev_mid}, dgn);
+ create_helper_field("shear_strain3d_components_dyn", {EL,CMP,GP,GP,LEV}, {nelem,6,NP,NP,nlev_mid}, dgn);
+ }
// For BFB restart, we need to read in the state on the dyn grid. The state above has NTL time slices,
// but only one is really needed for restart. Therefore, we create "dynamic" subfields for
@@ -272,6 +282,8 @@ void HommeDynamics::create_requests ()
size_t HommeDynamics::requested_buffer_size_in_bytes() const
{
using namespace Homme;
+ constexpr int num_turb3d_scratch_buffers = 8;
+ constexpr int np2 = HOMMEXX_NP*HOMMEXX_NP;
auto& c = Context::singleton();
auto& params = c.get();
@@ -314,7 +326,13 @@ size_t HommeDynamics::requested_buffer_size_in_bytes() const
}
fv_phys_requested_buffer_size_in_bytes();
- return fbm.allocated_size()*sizeof(Real);
+ size_t requested_bytes = fbm.allocated_size()*sizeof(Real);
+ if (params.do_3d_turbulence) {
+ const size_t ncols = num_elems*np2;
+ requested_bytes += num_turb3d_scratch_buffers*sizeof(Real)*ncols*NUM_PHYSICAL_LEV;
+ }
+
+ return requested_bytes;
}
void HommeDynamics::init_buffers(const ATMBufferManager &buffer_manager)
@@ -325,14 +343,36 @@ void HommeDynamics::init_buffers(const ATMBufferManager &buffer_manager)
using namespace Homme;
auto& c = Context::singleton();
auto& fbm = c.get();
+ const auto& params = c.get();
// Reset Homme buffer to use AD buffer memory.
// Internally, homme will actually initialize its own buffers.
EKAT_REQUIRE(buffer_manager.allocated_bytes()%sizeof(Real)==0); // Sanity check
+ const int fbm_size = fbm.allocated_size();
Real* mem = reinterpret_cast(buffer_manager.get_memory());
- fbm.allocate(mem, buffer_manager.allocated_bytes()/sizeof(Real));
- mem += fbm.allocated_size();
+ fbm.allocate(mem, fbm_size);
+ mem += fbm_size;
+
+ if (params.do_3d_turbulence) {
+ constexpr int np2 = HOMMEXX_NP*HOMMEXX_NP;
+ const int ncols = c.get().num_elems()*np2;
+ const int scratch_col_size = ncols*NUM_PHYSICAL_LEV;
+
+ auto assign_scratch = [&](HommeDynamics::fixed_view_2d_phys& view) {
+ view = HommeDynamics::fixed_view_2d_phys(mem, ncols);
+ mem += scratch_col_size;
+ };
+
+ assign_scratch(m_w_mid_row_all);
+ assign_scratch(m_w_mid_col_all);
+ assign_scratch(m_dsdx_Ux_all);
+ assign_scratch(m_dsdy_Ux_all);
+ assign_scratch(m_dsdx_Uy_all);
+ assign_scratch(m_dsdy_Uy_all);
+ assign_scratch(m_dsdx_Uz_all);
+ assign_scratch(m_dsdy_Uz_all);
+ }
size_t used_mem = (mem - buffer_manager.get_memory())*sizeof(Real);
EKAT_REQUIRE_MSG(used_mem==requested_buffer_size_in_bytes(),
@@ -352,6 +392,15 @@ void HommeDynamics::initialize_impl (const RunType run_type)
const auto& c = Homme::Context::singleton();
const auto& params = c.get();
+ // The first fv_phys D->P remap during initialization happens before the
+ // dycore has computed these diagnostic components, so start from a benign
+ // value. Homme overwrites them after each dynamics step when 3D turbulence is
+ // enabled.
+ if (params.do_3d_turbulence) {
+ m_helper_fields.at("shear_strain3d_components_dyn").deep_copy(0);
+ get_field_out("tke_shear_strain3d_components").deep_copy(0);
+ }
+
// Complete Homme prim_init1_xyz sequence
prim_complete_init1_phase_f90 ();
@@ -426,9 +475,14 @@ void HommeDynamics::initialize_impl (const RunType run_type)
m_d2p_remapper->register_field(m_helper_fields.at("Q_dyn"),*get_group_out("Q",pgn).m_monolithic_field);
m_d2p_remapper->register_field(m_helper_fields.at("omega_dyn"), get_field_out("omega"));
- // Remap SHOC eddy diffusivities from physics grid to dynamics grid
- m_p2d_remapper->register_field(get_field_in("eddy_diff_mom",pgn),m_helper_fields.at("Km_dyn"));
- m_p2d_remapper->register_field(get_field_in("eddy_diff_heat",pgn),m_helper_fields.at("Kh_dyn"));
+ if (params.do_3d_turbulence) {
+ // Remap SHOC eddy diffusivities from physics grid to dynamics grid.
+ m_p2d_remapper->register_field(get_field_in("eddy_diff_mom",pgn),m_helper_fields.at("Km_dyn"));
+ m_p2d_remapper->register_field(get_field_in("eddy_diff_heat",pgn),m_helper_fields.at("Kh_dyn"));
+
+ // Remap horizontal/local strain tensor components from dynamics to physics grid.
+ m_d2p_remapper->register_field(m_helper_fields.at("shear_strain3d_components_dyn"), get_field_out("tke_shear_strain3d_components"));
+ }
m_p2d_remapper->registration_ends();
m_d2p_remapper->registration_ends();
@@ -523,6 +577,14 @@ void HommeDynamics::run_impl (const double dt)
prim_run_f90(/* nsplit_iteration = */ subiter+1);
}
+ // This is where we will compute the strain term needed for Shear Production of TKE
+ if (params.do_3d_turbulence){
+ compute_horizontal_derivs_of_car_velocity();
+ compute_local_strain_components3d();
+ } else if (params.do_3d_turbulence) {
+ m_helper_fields.at("shear_strain3d_components_dyn").deep_copy(0.0);
+ }
+
// Update nstep in the restart extra data, so it can be written to restart if needed.
const auto& tl = c.get();
std::any_cast(*m_restart_extra_data["homme_nsteps"]) = tl.nstep;
@@ -943,15 +1005,17 @@ void HommeDynamics::init_homme_views () {
// by EAMxx, so we set FM(3)=0 right away
m_helper_fields.at("FM_dyn").get_component(2).deep_copy(0);
- // SGS Eddy diffusivity for momentum
- auto Km_in = m_helper_fields.at("Km_dyn").template get_view();
- using turb_type_mom = std::remove_reference::type;
- derived.m_turb_diff_mom = turb_type_mom(Km_in.data(), nelem);
+ if (params.do_3d_turbulence) {
+ // SGS Eddy diffusivity for momentum
+ auto Km_in = m_helper_fields.at("Km_dyn").template get_view();
+ using turb_type_mom = std::remove_reference::type;
+ derived.m_turb_diff_mom = turb_type_mom(Km_in.data(), nelem);
- // SGS Eddy diffusivity for heat
- auto Kh_in = m_helper_fields.at("Kh_dyn").template get_view();
- using turb_type_heat = std::remove_reference::type;
- derived.m_turb_diff_heat = turb_type_heat(Kh_in.data(), nelem);
+ // SGS Eddy diffusivity for heat
+ auto Kh_in = m_helper_fields.at("Kh_dyn").template get_view();
+ using turb_type_heat = std::remove_reference::type;
+ derived.m_turb_diff_heat = turb_type_heat(Kh_in.data(), nelem);
+ }
}
diff --git a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.hpp b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.hpp
index a1534d6f8ac6..d73799912831 100644
--- a/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.hpp
+++ b/components/eamxx/src/dynamics/homme/eamxx_homme_process_interface.hpp
@@ -3,6 +3,7 @@
#include "share/atm_process/atmosphere_process.hpp"
#include "share/remap/abstract_remapper.hpp"
+#include "dynamics/homme/homme_dimensions.hpp"
#include
#include
@@ -36,6 +37,8 @@ class HommeDynamics : public AtmosphereProcess
using uview_1d = ekat::Unmanaged>;
template
using uview_2d = ekat::Unmanaged>;
+ using fixed_view_2d_phys = Kokkos::View::array_layout,
+ DefaultDevice, Kokkos::MemoryTraits>;
public:
@@ -79,6 +82,9 @@ class HommeDynamics : public AtmosphereProcess
void initialize_impl (const RunType run_type);
+ void compute_horizontal_derivs_of_car_velocity ();
+ void compute_local_strain_components3d ();
+
// fv_phys refers to the horizontal finite volume (FV) grid for column
// parameterizations nested inside the horizontal element grid. The grid names
// are "physics_pgn", where N in practice is 2. The name of each routine is
@@ -157,6 +163,16 @@ class HommeDynamics : public AtmosphereProcess
Real m_raytau0; // Approximate value of decay time at model top (days)
// if set to 0, no rayleigh friction is applied
+ // Scratch reused by the 3D turbulence strain kernels when that feature is active.
+ fixed_view_2d_phys m_w_mid_row_all;
+ fixed_view_2d_phys m_w_mid_col_all;
+ fixed_view_2d_phys m_dsdx_Ux_all;
+ fixed_view_2d_phys m_dsdy_Ux_all;
+ fixed_view_2d_phys m_dsdx_Uy_all;
+ fixed_view_2d_phys m_dsdy_Uy_all;
+ fixed_view_2d_phys m_dsdx_Uz_all;
+ fixed_view_2d_phys m_dsdy_Uz_all;
+
int m_bfb_hash_nstep;
};
diff --git a/components/eamxx/src/dynamics/homme/interface/homme_context_mod.F90 b/components/eamxx/src/dynamics/homme/interface/homme_context_mod.F90
index 441172723148..e49822fd5d1e 100644
--- a/components/eamxx/src/dynamics/homme/interface/homme_context_mod.F90
+++ b/components/eamxx/src/dynamics/homme/interface/homme_context_mod.F90
@@ -63,7 +63,7 @@ subroutine set_homme_log_file_name_f90(c_str) bind(c)
!
character(len=256), pointer :: full_name
character(len=256) :: path, fname
- integer :: len, slash, ierr
+ integer :: len, slash
call c_f_pointer(c_str,full_name)
len = index(full_name, C_NULL_CHAR) -1
@@ -79,22 +79,20 @@ subroutine set_homme_log_file_name_f90(c_str) bind(c)
homme_log_fname = trim(path)//"homme_"//fname
- iulog = shr_file_getunit()
+ ! Only the root rank opens/owns the homme log file (following the same
+ ! approach used by EAM in atm_comp_mct.F90/atm_comp_esmf.F90). All other
+ ! ranks simply keep iulog at its module default (stdout), rather than
+ ! all ranks independently opening the same shared file, which is racy
+ ! and can lead to corrupted/interleaved writes or spurious I/O errors.
if (masterproc) then
- ! Create the homme log file on root rank...
+ iulog = shr_file_getunit()
open (unit=iulog,file=trim(homme_log_fname),status='REPLACE', &
action='WRITE', access='SEQUENTIAL', position="append")
write(iulog,*) " ---- HOMME LOG FILE ----"
flush(iulog)
- endif
- call mpi_barrier(par%comm,ierr)
- if (.not. masterproc) then
- ! ... and open it on all other ranks
- open (unit=iulog,file=trim(homme_log_fname),status='OLD', &
- action='WRITE', access='SEQUENTIAL', position="append")
- endif
- homme_log_set = .true.
+ homme_log_set = .true.
+ endif
endif
end subroutine set_homme_log_file_name_f90
diff --git a/components/eamxx/src/dynamics/homme/interface/homme_driver_mod.F90 b/components/eamxx/src/dynamics/homme/interface/homme_driver_mod.F90
index 809ce948f66f..9cf334442a6b 100644
--- a/components/eamxx/src/dynamics/homme/interface/homme_driver_mod.F90
+++ b/components/eamxx/src/dynamics/homme/interface/homme_driver_mod.F90
@@ -183,7 +183,8 @@ end subroutine prim_copy_cxx_to_f90
subroutine prim_init_model_f90 () bind(c)
use prim_driver_mod, only: prim_init_ref_states_views, &
prim_init_diags_views, prim_init_kokkos_functors, &
- prim_init_state_views
+ prim_init_state_views, prim_init_tensorvisc, &
+ prim_init_tensorvisc2
use prim_state_mod, only: prim_printstate
use model_init_mod, only: model_init2
use global_norms_mod, only: dss_hvtensor, print_cfl
@@ -210,6 +211,15 @@ subroutine prim_init_model_f90 () bind(c)
! Apply dss and bilinear projection to tensor coefficients
call dss_hvtensor(elem,hybrid,1,nelemd)
+ ! Update the C++ tensorVisc view with dss_hvtensor's result (the other,
+ ! constant, geometry views were already sent to C++ earlier, in
+ ! prim_complete_init1_phase_f90 -> prim_init_grid_views).
+ call prim_init_tensorvisc (elem)
+
+ ! Same as above, but for tensorVisc_2 (the sponge-layer tensor
+ ! coefficient), which dss_hvtensor also updates.
+ call prim_init_tensorvisc2 (elem)
+
! Print advective and viscious CFL estimates
call print_cfl(elem,hybrid,1,nelemd)
diff --git a/components/eamxx/src/dynamics/homme/interface/homme_params_mod.F90 b/components/eamxx/src/dynamics/homme/interface/homme_params_mod.F90
index 9a3df59b6161..8afa3c8722ae 100644
--- a/components/eamxx/src/dynamics/homme/interface/homme_params_mod.F90
+++ b/components/eamxx/src/dynamics/homme/interface/homme_params_mod.F90
@@ -112,7 +112,7 @@ function get_homme_int_param_f90 (param_name_c) result(param_value) bind(c)
end function get_homme_int_param_f90
function get_homme_real_param_f90 (param_name_c) result(param_value) bind(c)
- use control_mod, only: nu, nu_div, nu_p, nu_q, nu_s, hypervis_scaling
+ use control_mod, only: nu, nu_div, nu_p, nu_q, nu_s, hypervis_scaling, laplace_scaling
use time_mod, only: tstep
!
! Input(s)
@@ -140,6 +140,8 @@ function get_homme_real_param_f90 (param_name_c) result(param_value) bind(c)
param_value = nu_s
case("hypervis_scaling")
param_value = hypervis_scaling
+ case("laplace_scaling")
+ param_value = laplace_scaling
case("dt")
param_value = tstep
case default
@@ -150,7 +152,7 @@ function get_homme_real_param_f90 (param_name_c) result(param_value) bind(c)
end function get_homme_real_param_f90
function get_homme_bool_param_f90 (param_name_c) result(param_value) bind(c)
- use control_mod, only: moisture
+ use control_mod, only: moisture, do_3d_turbulence
!
! Input(s)
!
@@ -171,6 +173,8 @@ function get_homme_bool_param_f90 (param_name_c) result(param_value) bind(c)
else
param_value = .true.
endif
+ case("do_3d_turbulence")
+ param_value = do_3d_turbulence
case default
call abortmp ("[get_homme_bool_param_f90] Error! Unrecognized parameter name.")
param_value = .false.
diff --git a/components/eamxx/src/physics/CMakeLists.txt b/components/eamxx/src/physics/CMakeLists.txt
index 296b0b8b50bc..e8d282f1bd6d 100644
--- a/components/eamxx/src/physics/CMakeLists.txt
+++ b/components/eamxx/src/physics/CMakeLists.txt
@@ -20,3 +20,4 @@ if (SCREAM_ENABLE_MAM)
add_subdirectory(mam)
endif()
add_subdirectory(gw)
+add_subdirectory(specialized_tracers)
diff --git a/components/eamxx/src/physics/mam/CMakeLists.txt b/components/eamxx/src/physics/mam/CMakeLists.txt
index 0ea5c1b7d05a..d23a7f5d0a4b 100644
--- a/components/eamxx/src/physics/mam/CMakeLists.txt
+++ b/components/eamxx/src/physics/mam/CMakeLists.txt
@@ -8,9 +8,9 @@ endif()
# configure and build mam4xx (C++ port of MAM4)
if (SCREAM_DOUBLE_PRECISION)
- set(MAM4XX_PRECISION "double" CACHE BOOL "Enable double precision for mam4xx")
+ set(MAM4XX_PRECISION "double" CACHE STRING "Enable double precision for mam4xx")
else()
- set(MAM4XX_PRECISION "single" CACHE BOOL "Enable single precision for mam4xx")
+ set(MAM4XX_PRECISION "single" CACHE STRING "Enable single precision for mam4xx")
endif()
set(MAM4XX_ENABLE_GPU ${EAMXX_ENABLE_GPU} CACHE BOOL "Enable mam4xx GPU configuration" FORCE)
set(MAM4XX_ENABLE_TESTS OFF CACHE BOOL "Disable mam4xx tests within E3SM" FORCE)
@@ -37,11 +37,11 @@ add_library(mam
target_compile_definitions(mam PUBLIC EAMXX_HAS_MAM)
target_link_libraries(mam PUBLIC eamxx_physics_share csm_share scream_share mam4xx)
-#if (NOT SCREAM_LIB_ONLY)
-# add_subdirectory(tests)
-#endif()
-
if (TARGET eamxx_physics)
# Add this library to eamxx_physics
target_link_libraries(eamxx_physics INTERFACE mam)
endif()
+
+if (NOT SCREAM_LIB_ONLY)
+ add_subdirectory(tests)
+endif()
diff --git a/components/eamxx/src/physics/mam/eamxx_mam_aci_functions.hpp b/components/eamxx/src/physics/mam/eamxx_mam_aci_functions.hpp
index ca89de44144b..0d8cff061336 100644
--- a/components/eamxx/src/physics/mam/eamxx_mam_aci_functions.hpp
+++ b/components/eamxx/src/physics/mam/eamxx_mam_aci_functions.hpp
@@ -271,29 +271,14 @@ void call_function_dropmixnuc(
//---------------------------------------------------------------------------
// ## Initialize the ndrop class.
//---------------------------------------------------------------------------
- const int ntot_amode = mam_coupling::num_aero_modes();
- const int maxd_aspectype = mam4::ndrop::maxd_aspectype;
- const int nspec_max = mam4::ndrop::nspec_max;
- int nspec_amode[ntot_amode] = {};
- int lspectype_amode[maxd_aspectype][ntot_amode] = {};
- int lmassptr_amode[maxd_aspectype][ntot_amode] = {};
- int numptr_amode[ntot_amode] = {};
- int mam_idx[ntot_amode][nspec_max] = {};
- int mam_cnst_idx[ntot_amode][nspec_max] = {};
-
- Real specdens_amode[maxd_aspectype] = {};
- Real spechygro[maxd_aspectype] = {};
+ const int ntot_amode = mam_coupling::num_aero_modes();
Real exp45logsig[ntot_amode] = {}, alogsig[ntot_amode] = {},
num2vol_ratio_min_nmodes[ntot_amode] = {},
num2vol_ratio_max_nmodes[ntot_amode] = {};
- Real aten = 0;
- mam4::ndrop::get_e3sm_parameters(nspec_amode, lspectype_amode, lmassptr_amode,
- numptr_amode, specdens_amode, spechygro,
- mam_idx, mam_cnst_idx);
+ Real aten = 0;
mam4::ndrop::ndrop_init(exp45logsig, alogsig, aten, num2vol_ratio_min_nmodes,
num2vol_ratio_max_nmodes);
//---------------------------------------------------------------------------
- //---------------------------------------------------------------------------
const bool local_enable_aero_vertical_mix = enable_aero_vertical_mix;
Kokkos::parallel_for(
"MAMAci::run_impl::call_function_dropmixnuc", team_policy,
@@ -372,10 +357,8 @@ void call_function_dropmixnuc(
// in zm[kk] - zm[kk+1], for pver zm[kk-1] - zm[kk]
ekat::subview(zm, icol), ekat::subview(state_q_work_loc, icol),
ekat::subview(nc, icol), ekat::subview(kvh_int, icol), // kvh[kk+1]
- ekat::subview(cloud_frac, icol), lspectype_amode, specdens_amode,
- spechygro, lmassptr_amode, num2vol_ratio_min_nmodes,
- num2vol_ratio_max_nmodes, numptr_amode, nspec_amode, exp45logsig,
- alogsig, aten, mam_idx, mam_cnst_idx,
+ ekat::subview(cloud_frac, icol), num2vol_ratio_min_nmodes,
+ num2vol_ratio_max_nmodes, exp45logsig, alogsig, aten,
local_enable_aero_vertical_mix, ekat::subview(qcld, icol), // out
ekat::subview(wsub, icol), // in
ekat::subview(cloud_frac_prev, icol), // in
diff --git a/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.cpp b/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.cpp
index 2ae04f5586f7..90b8a4fc387c 100644
--- a/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.cpp
+++ b/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.cpp
@@ -210,15 +210,9 @@ void MAMSrfOnlineEmiss::create_requests() {
srf_emiss_species_.push_back(so4_a2);
//--------------------------------------------------------------------
- // Init data structures to read and interpolate
+ // Register sector fields in FM for surface emissions.
+ // DataInterpolation is set up in initialize_impl.
//--------------------------------------------------------------------
- for(srf_emiss_ &ispec_srf : srf_emiss_species_) {
- srfEmissFunc::init_srf_emiss_objects(
- ncol_, grid_, ispec_srf.data_file, ispec_srf.sectors, srf_map_file,
- // output
- ispec_srf.horizInterp_, ispec_srf.data_start_, ispec_srf.data_end_,
- ispec_srf.data_out_, ispec_srf.dataReader_);
- } // srf emissions file read init
// -------------------------------------------------------------
// Setup to enable reading soil erodibility file
@@ -316,33 +310,45 @@ void MAMSrfOnlineEmiss::initialize_impl(const RunType run_type) {
// Output fields
// ---------------------------------------------------------------
// Constituent fluxes of species in [kg/m2/s]
- constituent_fluxes_ = get_field_out("constituent_fluxes").get_view();
-
- // ---------------------------------------------------------------
- // Allocate memory for local and work arrays
- // ---------------------------------------------------------------
-
- // Work array to store fluxes after unit conversions to kg/m2/s
- fluxes_in_mks_units_ = view_1d("fluxes_in_mks_units", ncol_);
-
- // Current month ( 0-based)
- const int curr_month = start_of_step_ts().get_month() - 1;
-
- // Load the first month into data_end.
-
- // Note: At the first time step, the data will be moved into data_beg,
- // and data_end will be reloaded from file with the new month.
+ constituent_fluxes_ = get_field_out("constituent_fluxes");
//--------------------------------------------------------------------
- // Update surface emissions from file
+ // Setup data interpolation for surface emissions.
//--------------------------------------------------------------------
- for(srf_emiss_ &ispec_srf : srf_emiss_species_) {
- srfEmissFunc::update_srfEmiss_data_from_file(
- ispec_srf.dataReader_, start_of_step_ts(), curr_month,
- ispec_srf.scale_factor, *ispec_srf.horizInterp_,
- ispec_srf.data_end_); // output
+ {
+ using namespace ekat::units;
+ using namespace ShortFieldTagsNames;
+ const FieldLayout scalar2d = grid_->get_2d_scalar_layout();
+ const auto srf_map_file = m_params.get("srf_remap_file", "");
+ const auto srf_time_interp = DataInterpolation::Linear;
+ for(srf_emiss_ &ispec_srf : srf_emiss_species_) {
+ std::vector srf_fields;
+ srf_fields.reserve(ispec_srf.sectors.size());
+ for(const auto §or_name : ispec_srf.sectors) {
+ Field field(FieldIdentifier(sector_name, scalar2d, none, grid_->name()));
+ field.allocate_view();
+ srf_fields.push_back(field);
+ }
+ ispec_srf.emiss_sector_fields_ = srf_fields;
+
+ ispec_srf.data_interp_ = std::make_shared(grid_, srf_fields);
+ ispec_srf.data_interp_->set_logger(m_atm_logger);
+ ispec_srf.data_interp_->setup_periodic_time_database(
+ {ispec_srf.data_file});
+ ispec_srf.data_interp_->create_horiz_remappers(
+ srf_map_file == "none" ? "" : srf_map_file);
+
+ DataInterpolation::VertRemapData remap_data;
+ remap_data.vr_type = DataInterpolation::None;
+ ispec_srf.data_interp_->create_vert_remapper(remap_data);
+
+ ispec_srf.data_interp_->init_time_interpolation(start_of_step_ts(), srf_time_interp);
+ }
}
+ // Current month ( 0-based)
+ const int curr_month = start_of_step_ts().get_month() - 1;
+
//-----------------------------------------------------------------
// Read Soil erodibility data
//-----------------------------------------------------------------
@@ -386,7 +392,7 @@ void MAMSrfOnlineEmiss::run_impl(const double dt) {
Kokkos::fence();
// Constituent fluxes [kg/m^2/s]
- auto constituent_fluxes = this->constituent_fluxes_;
+ auto constituent_fluxes = constituent_fluxes_.get_view();
// Zero out constituent fluxes only for gasses and aerosols
init_fluxes(ncol_, // in
@@ -456,18 +462,7 @@ void MAMSrfOnlineEmiss::run_impl(const double dt) {
//--------------------------------------------------------------------
for(srf_emiss_ &ispec_srf : srf_emiss_species_) {
- // Update TimeState, note the addition of dt
- ispec_srf.timeState_.t_now = ts.frac_of_year_in_days();
-
- // Update time state and if the month has changed, update the data.
- srfEmissFunc::update_srfEmiss_timestate(
- ispec_srf.dataReader_, ts, *ispec_srf.horizInterp_, ispec_srf.scale_factor,
- // output
- ispec_srf.timeState_, ispec_srf.data_start_, ispec_srf.data_end_);
-
- // Call the main srfEmiss routine to get interpolated aerosol forcings.
- srfEmissFunc::srfEmiss_main(ispec_srf.timeState_, ispec_srf.data_start_,
- ispec_srf.data_end_, ispec_srf.data_out_);
+ ispec_srf.data_interp_->run(ts);
//--------------------------------------------------------------------
// Modify units to MKS units (from molecules/cm2/s to kg/m2/s)
@@ -476,18 +471,17 @@ void MAMSrfOnlineEmiss::run_impl(const double dt) {
// constituent_fluxes_)
const int species_index = spcIndex_in_pcnst_.at(ispec_srf.species_name);
+ auto constituent_fluxes_ispe_srf = constituent_fluxes_.get_component(species_index);
// modify units from molecules/cm2/s to kg/m2/s
- auto fluxes_in_mks_units = this->fluxes_in_mks_units_;
- const Real mfactor =
- amufac * mam4::gas_chemistry::adv_mass[species_index - offset_];
- const view_1d ispec_outdata0 =
- ekat::subview(ispec_srf.data_out_.emiss_sectors, 0);
- // Parallel loop over all the columns to update units
- Kokkos::parallel_for(
- "srf_emis_fluxes", ncol_, KOKKOS_LAMBDA(int icol) {
- fluxes_in_mks_units(icol) = ispec_outdata0(icol) * mfactor;
- constituent_fluxes(icol, species_index) = fluxes_in_mks_units(icol);
- });
+ constituent_fluxes_ispe_srf.deep_copy(0.0);
+
+ for(const auto §or_field : ispec_srf.emiss_sector_fields_) {
+ constituent_fluxes_ispe_srf.update(sector_field, 1, 1);
+ }
+
+ const Real mfactor = amufac * ispec_srf.scale_factor *
+ mam4::gas_chemistry::adv_mass[species_index - offset_];
+ constituent_fluxes_ispe_srf.scale(mfactor);
} // for loop for species
Kokkos::fence();
} // run_impl ends
diff --git a/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.hpp b/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.hpp
index 1f2115bbfaf7..3bce8d01b59e 100644
--- a/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.hpp
+++ b/components/eamxx/src/physics/mam/eamxx_mam_srf_and_online_emissions_process_interface.hpp
@@ -2,10 +2,10 @@
#define EAMXX_MAM_SRF_ONLINE_EMISS_HPP
#include "share/remap/abstract_remapper.hpp"
+#include "share/algorithm/eamxx_data_interpolation.hpp"
// For MAM4 aerosol configuration
#include
-#include
// For reading marine organics file
#include
@@ -42,15 +42,12 @@ class MAMSrfOnlineEmiss final : public MAMGenericInterface {
const_view_2d dust_fluxes_;
// Constituent fluxes of species in [kg/m2/s]
- view_2d constituent_fluxes_;
+ Field constituent_fluxes_;
// Runtime scale factors for online emissions from namelist.
Real dust_emis_scale_factor;
Real seasalt_emis_scale_factor;
- // Work array to store fluxes after unit conversions to kg/m2/s
- view_1d fluxes_in_mks_units_;
-
// Unified atomic mass unit used for unit conversion (BAD constant)
static constexpr Real amufac = 1.65979e-23; // 1.e4* kg / amu
@@ -61,7 +58,6 @@ class MAMSrfOnlineEmiss final : public MAMGenericInterface {
public:
// For reading surface emissions and marine organics file
- using srfEmissFunc = mam_coupling::srfEmissFunctions;
using marineOrganicsFunc =
marine_organics::marineOrganicsFunctions;
@@ -153,12 +149,9 @@ class MAMSrfOnlineEmiss final : public MAMGenericInterface {
// Species-specific scale factor
Real scale_factor = 1.0;
- // Data structure for reading interpolation
- std::shared_ptr horizInterp_;
- std::shared_ptr dataReader_;
- srfEmissFunc::srfEmissTimeState timeState_;
- srfEmissFunc::srfEmissInput data_start_, data_end_;
- srfEmissFunc::srfEmissOutput data_out_;
+ // Data interpolation object and local output fields for each file sector.
+ std::shared_ptr data_interp_;
+ std::vector emiss_sector_fields_;
};
// A vector for carrying emissions for all the species
diff --git a/components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp b/components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp
index 101761d363b9..e9a24aca37b2 100644
--- a/components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp
+++ b/components/eamxx/src/physics/mam/readfiles/photo_table_utils.cpp
@@ -6,6 +6,8 @@ namespace scream::impl {
using mam4::mo_photo::phtcnt;
using HostView1D = mam4::DeviceType::view_1d::host_mirror_type;
+using HostView5D = mam4::DeviceType::view::host_mirror_type;
+using HostView3D = mam4::DeviceType::view::host_mirror_type;
using HostViewInt1D = mam4::DeviceType::view_1d::host_mirror_type;
//-------------------------------------------------------------------------
@@ -19,29 +21,33 @@ std::vector populate_etfphot_from_e3sm_case() {
// We obtained these values from an e3sm simulations.
// We should only use this function on Host.
std::vector etfphot_data = {
- 7.5691227E+11, 8.6525905E+11, 1.0355749E+12, 1.1846288E+12, 2.1524405E+12,
- 3.2362584E+12, 3.7289849E+12, 4.4204330E+12, 4.6835350E+12, 6.1217728E+12,
- 4.5575051E+12, 5.3491446E+12, 4.7016063E+12, 5.4281722E+12, 4.5023968E+12,
- 6.8931981E+12, 6.2012647E+12, 6.1430771E+12, 5.7820385E+12, 7.6770646E+12,
- 1.3966509E+13, 1.2105348E+13, 2.8588980E+13, 3.2160821E+13, 2.4978066E+13,
- 2.7825401E+13, 2.3276451E+13, 3.6343684E+13, 6.1787886E+13, 7.8009914E+13,
- 7.6440824E+13, 7.6291458E+13, 9.4645085E+13, 1.0124628E+14, 1.0354111E+14,
- 1.0999650E+14, 1.0889946E+14, 1.1381912E+14, 1.3490042E+14, 1.5941519E+14,
- 1.4983265E+14, 1.5184267E+14, 1.5991420E+14, 1.6976697E+14, 1.8771840E+14,
- 1.6434367E+14, 1.8371960E+14, 2.1966369E+14, 1.9617879E+14, 2.2399700E+14,
- 1.8429912E+14, 2.0129736E+14, 2.0541588E+14, 2.4334962E+14, 3.5077122E+14,
- 3.4517894E+14, 3.5749668E+14, 3.6624304E+14, 3.4975113E+14, 3.5566025E+14,
- 4.2825273E+14, 4.8406375E+14, 4.9511159E+14, 5.2695368E+14, 5.2401611E+14,
- 5.0877746E+14, 4.8780853E+14};
+ 0.75691227453241626E+012, 0.86525904597344678E+012, 0.10355748678445210E+013, 0.11846288453143215E+013, 0.21524405047611838E+013,
+ 0.32362583636383438E+013, 0.37289849127086353E+013, 0.44204330229059023E+013, 0.46835350139683008E+013, 0.61217728454045146E+013,
+ 0.45575051094967529E+013, 0.53491446243876533E+013, 0.47016062694342764E+013, 0.54281722298247529E+013, 0.45023968313414365E+013,
+ 0.68931981401230361E+013, 0.62012647462481055E+013, 0.61430770669364131E+013, 0.57820384729408037E+013, 0.76770646262530391E+013,
+ 0.13966508541416857E+014, 0.12105347510143980E+014, 0.28588979654418141E+014, 0.32160820948665508E+014, 0.24978065543030500E+014,
+ 0.27825400776036188E+014, 0.23276451219415352E+014, 0.36343683716296695E+014, 0.61787885646314477E+014, 0.78009914475741344E+014,
+ 0.76440824240882500E+014, 0.76291457600771391E+014, 0.94645085080390984E+014, 0.10124627769922270E+015, 0.10354111421691689E+015,
+ 0.10999649606948711E+015, 0.10889946060495367E+015, 0.11381912455165878E+015, 0.13490042469475880E+015, 0.15941519351184984E+015,
+ 0.14983265369952531E+015, 0.15184267258496494E+015, 0.15991419729740088E+015, 0.16976696691694741E+015, 0.18771840486614825E+015,
+ 0.16434366552645634E+015, 0.18371960453616509E+015, 0.21966368981040753E+015, 0.19617878628663241E+015, 0.22399700059898819E+015,
+ 0.18429911731380941E+015, 0.20129735694980109E+015, 0.20541588491339825E+015, 0.24334961879677731E+015, 0.35077121778312700E+015,
+ 0.34517894220011569E+015, 0.35749668154179594E+015, 0.36624304237331069E+015, 0.34975112547690056E+015, 0.35566025203681831E+015,
+ 0.42825273260963562E+015, 0.48406375456076200E+015, 0.49511158653410975E+015, 0.52695367706176038E+015, 0.52401610578239200E+015,
+ 0.50877746346978994E+015, 0.48780852943692825E+015};
return etfphot_data;
}
// This version uses eamxx_scorpio_interface to read netcdf files.
mam4::mo_photo::PhotoTableData read_photo_table(
- const std::string &rsf_file, const std::string &xs_long_file) {
- // set up the lng_indexer and pht_alias_mult_1 views based on our
- // (hardwired) chemical mechanism
- HostViewInt1D lng_indexer_h("lng_indexer", phtcnt);
+ const std::string &rsf_file, const std::string &xs_long_file,
+ const std::vector &rxt_names, const int numj,
+ const HostViewInt1D &lng_indexer_h) {
+
+ EKAT_REQUIRE_MSG(numj > 0, "Error: read_photo_table requires numj > 0.\n");
+ EKAT_REQUIRE_MSG(lng_indexer_h.extent_int(0) == phtcnt,
+ "Error: read_photo_table requires lng_indexer_h sized by phtcnt.\n");
+
int nw, nump, numsza, numcolo3, numalb, nt, np_xs; // table dimensions
scorpio::register_file(rsf_file, scorpio::Read);
@@ -56,15 +62,13 @@ mam4::mo_photo::PhotoTableData read_photo_table(
nw = scorpio::get_dimlen(xs_long_file, "numwl");
np_xs = scorpio::get_dimlen(xs_long_file, "numprs");
- // FIXME: hard-coded for only one photo reaction.
- std::string rxt_names[1] = {"jh2o2"};
- int numj = 1;
- lng_indexer_h(0) = 0;
// allocate the photolysis table
auto table = mam4::mo_photo::create_photo_table_data(
nw, nt, np_xs, numj, nump, numsza, numcolo3, numalb);
// allocate host views for table data
+ HostView5D l_rsf_tab_h("rsf_tab_h",numalb,numcolo3,numsza,nump,nw);
+ HostView3D l_xsqy_h("xsqy_h",np_xs,nt,nw);
auto rsf_tab_h = Kokkos::create_mirror_view(table.rsf_tab);
auto xsqy_h = Kokkos::create_mirror_view(table.xsqy);
auto sza_h = Kokkos::create_mirror_view(table.sza);
@@ -72,8 +76,8 @@ mam4::mo_photo::PhotoTableData read_photo_table(
auto press_h = Kokkos::create_mirror_view(table.press);
auto colo3_h = Kokkos::create_mirror_view(table.colo3);
auto o3rat_h = Kokkos::create_mirror_view(table.o3rat);
- // auto etfphot_h = Kokkos::create_mirror_view(table.etfphot);
auto prs_h = Kokkos::create_mirror_view(table.prs);
+
// read file data into our host views
scorpio::read_var(rsf_file, "pm", press_h.data());
@@ -81,17 +85,19 @@ mam4::mo_photo::PhotoTableData read_photo_table(
scorpio::read_var(rsf_file, "alb", alb_h.data());
scorpio::read_var(rsf_file, "colo3fact", o3rat_h.data());
scorpio::read_var(rsf_file, "colo3", colo3_h.data());
- // it produces an error.
- scorpio::read_var(rsf_file, "RSF", rsf_tab_h.data());
+ scorpio::read_var(rsf_file, "RSF", l_rsf_tab_h.data());
scorpio::read_var(xs_long_file, "pressure", prs_h.data());
// read xsqy data (using lng_indexer_h for the first index)
- // FIXME: hard-coded for only one photo reaction.
- for(int m = 0; m < phtcnt; ++m) {
- auto xsqy_ndx_h = ekat::subview(xsqy_h, m);
- scorpio::read_var(xs_long_file, rxt_names[m], xsqy_h.data());
+ using policy_t3 = Kokkos::MDRangePolicy, Kokkos::DefaultHostExecutionSpace>;
+ for(int m = 0; m < numj; ++m) {
+ scorpio::read_var(xs_long_file, rxt_names[m], l_xsqy_h.data());
+ Kokkos::parallel_for("xsqy_h",
+ policy_t3({0, 0, 0}, {xsqy_h.extent(1), xsqy_h.extent(2), xsqy_h.extent(3)}),
+ [&](const int i, const int j, const int k) {
+ xsqy_h(m, i, j, k) = l_xsqy_h(k,j,i);
+ });
}
-
// populate etfphot by rebinning solar data
HostView1D wc_h("wc", nw), wlintv_h("wlintv", nw), we_h("we", nw + 1);
@@ -106,6 +112,16 @@ mam4::mo_photo::PhotoTableData read_photo_table(
auto etfphot_data = populate_etfphot_from_e3sm_case();
auto etfphot_h = HostView1D((Real *)etfphot_data.data(), nw);
+ using policy_t = Kokkos::MDRangePolicy, Kokkos::DefaultHostExecutionSpace>;
+
+ Kokkos::parallel_for("scale_rsf_tab",
+ policy_t({0, 0, 0, 0}, {rsf_tab_h.extent(1), rsf_tab_h.extent(2), rsf_tab_h.extent(3), rsf_tab_h.extent(4)}),
+ [&](const int l, const int i, const int j, const int k) {
+ for (int w = 0; w < nw; ++w) {
+ rsf_tab_h(w,l, i, j, k) = l_rsf_tab_h(k,j,i,l,w)*wlintv_h(w);
+ }
+ });
+
scorpio::release_file(rsf_file);
scorpio::release_file(xs_long_file);
@@ -148,4 +164,15 @@ mam4::mo_photo::PhotoTableData read_photo_table(
return table;
}
+// MAM4xx E3SM v2 photolysis table reader.
+mam4::mo_photo::PhotoTableData read_photo_table(
+ const std::string &rsf_file, const std::string &xs_long_file) {
+
+ HostViewInt1D lng_indexer_h("lng_indexer", phtcnt);
+ std::vector rxt_names = {"jh2o2"};
+ int numj = 1;
+ lng_indexer_h(0) = 0;
+ return read_photo_table(rsf_file, xs_long_file, rxt_names, numj, lng_indexer_h);
+}
+
} // namespace scream::impl
diff --git a/components/eamxx/src/physics/mam/tests/CMakeLists.txt b/components/eamxx/src/physics/mam/tests/CMakeLists.txt
new file mode 100644
index 000000000000..66f4da18ba4a
--- /dev/null
+++ b/components/eamxx/src/physics/mam/tests/CMakeLists.txt
@@ -0,0 +1,17 @@
+include(ScreamUtils)
+
+if (NOT SCREAM_ONLY_GENERATE_BASELINES)
+ CreateUnitTest(mam_photo_table_test
+ SOURCES mam_photo_table_test.cpp
+ LIBS mam eamxx_scorpio_interface eamxx_io ekat::YamlParser
+ LABELS "mam;physics"
+ MPI_RANKS 1
+ THREADS 1
+ )
+ target_compile_definitions(mam_photo_table_test PRIVATE
+ SCREAM_DATA_DIR="${SCREAM_DATA_DIR}"
+ )
+ # Ensure test input files are present in the data dir
+ GetInputFile(scream/mam4xx/photolysis/table_photo_input_ts_355.yaml)
+ GetInputFile(scream/mam4xx/photolysis/jlong_input_ts_355.yaml)
+endif()
diff --git a/components/eamxx/src/physics/mam/tests/mam_photo_table_test.cpp b/components/eamxx/src/physics/mam/tests/mam_photo_table_test.cpp
new file mode 100644
index 000000000000..56ffb6f0bffe
--- /dev/null
+++ b/components/eamxx/src/physics/mam/tests/mam_photo_table_test.cpp
@@ -0,0 +1,401 @@
+#include
+
+#include
+
+#include
+#include
+
+#include "share/core/eamxx_types.hpp"
+
+#include
+#include
+#include
+
+#include "share/scorpio_interface/eamxx_scorpio_interface.hpp"
+
+namespace scream {
+namespace impl {
+
+mam4::mo_photo::PhotoTableData read_photo_table(
+ const std::string& rsf_file, const std::string& xs_long_file);
+
+} // namespace impl
+} // namespace scream
+
+namespace mam_photo_table {
+
+using Real = scream::Real;
+using HostSpace = Kokkos::HostSpace;
+
+using Device = scream::DefaultDevice;
+using ExecSpace = Device::execution_space;
+using KT = ekat::KokkosTypes;
+using view_1d = typename KT::template view_1d;
+using view_2d = typename KT::template view_2d;
+using view_3d = typename KT::template view_3d;
+using TeamPolicy = Kokkos::TeamPolicy;
+using MemberType = TeamPolicy::member_type;
+
+inline bool nearly_equal(const Real a, const Real b,
+ const Real rtol = 1e-8,
+ const Real atol = 1e-14) {
+ return std::abs(a - b) <= atol + rtol * std::abs(b);
+}
+
+// Read a double-valued sequence from a ParameterList and convert to Real.
+std::vector get_real_vec(const ekat::ParameterList& pl,
+ const std::string& key) {
+ const auto& dv = pl.get>(key);
+ return std::vector(dv.begin(), dv.end());
+}
+
+template struct PrecisionTolerance;
+
+template <> struct PrecisionTolerance {
+ static constexpr float relative_tol = 1e-5f; // Single precision tolerance
+ static constexpr float absolute_tol = 1e-8f; // Single precision tolerance
+};
+
+template <> struct PrecisionTolerance {
+ static constexpr double relative_tol = 1e-8; // Double precision tolerance
+ static constexpr double absolute_tol = 1e-12; // Double precision tolerance
+};
+
+} // namespace mam_photo_table
+
+using namespace mam_photo_table;
+
+TEST_CASE("mam_photo_table_yaml_reference_regression",
+ "[mam4][photo][kokkos]") {
+ if constexpr (mam4::nlev != 72) return;
+ using namespace scream;
+
+ ekat::Comm comm(MPI_COMM_WORLD);
+ struct ScorpioGuard {
+ explicit ScorpioGuard(const ekat::Comm& comm) : comm_(comm) {
+ scorpio::init_subsystem(comm_);
+ }
+ ~ScorpioGuard() {
+ scorpio::finalize_subsystem();
+ }
+ const ekat::Comm& comm_;
+ } scorpio_guard(comm);
+
+ const std::string rsf_file =
+ std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc";
+ const std::string xs_long_file =
+ std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc";
+ const std::string input_yaml_file = std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/jlong_input_ts_355.yaml";
+
+ const auto photo_table = scream::impl::read_photo_table(rsf_file, xs_long_file);
+ const auto root = ekat::parse_yaml_file(input_yaml_file);
+ REQUIRE(root.isSublist("input"));
+ REQUIRE(root.sublist("input").isSublist("fixed"));
+ const auto& fixed = root.sublist("input").sublist("fixed");
+
+ REQUIRE(photo_table.nw > 0);
+ REQUIRE(photo_table.numj == 1);
+
+ auto sza_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.sza);
+ auto del_sza_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.del_sza);
+ auto alb_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.alb);
+ auto del_alb_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.del_alb);
+ auto colo3_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.colo3);
+ auto o3rat_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.o3rat);
+ auto del_o3rat_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.del_o3rat);
+ auto press_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.press);
+ auto prs_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.prs);
+ auto dprs_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.dprs);
+ auto rsf_tab_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.rsf_tab);
+ auto xsqy_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.xsqy);
+ auto etfphot_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.etfphot);
+ auto lng_indexer_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.lng_indexer);
+ auto pht_alias_mult_h =
+ Kokkos::create_mirror_view_and_copy(HostSpace(), photo_table.pht_alias_mult_1);
+
+ const auto nw_ref = fixed.get>("nw")[0];
+ const auto numj_ref = fixed.get>("numj")[0];
+ const auto shape_ref = fixed.get>("shape_of_rsf_tab");
+ REQUIRE(shape_ref.size() == 5);
+ const int nw_shape = shape_ref[0];
+ const int nump_shape = shape_ref[1];
+ const int numsza_shape = shape_ref[2];
+ const int numcolo3_shape = shape_ref[3];
+ const int numalb_shape = shape_ref[4];
+
+ const auto sza_ref = get_real_vec(fixed, "sza");
+ const auto del_sza_ref = get_real_vec(fixed, "del_sza");
+ const auto alb_ref = get_real_vec(fixed, "alb");
+ const auto del_alb_ref = get_real_vec(fixed, "del_alb");
+ const auto colo3_ref = get_real_vec(fixed, "colo3");
+ const auto o3rat_ref = get_real_vec(fixed, "o3rat");
+ const auto del_o3rat_ref = get_real_vec(fixed, "del_o3rat");
+ const auto press_ref = fixed.isParameter("press") ?
+ get_real_vec(fixed, "press") :
+ get_real_vec(fixed, "pm");
+ REQUIRE(!press_ref.empty());
+ const auto etfphot_ref = get_real_vec(fixed, "etfphot");
+ const auto prs_ref = get_real_vec(fixed, "prs");
+ const auto dprs_ref = get_real_vec(fixed, "dprs");
+ const auto rsf_tab_2d = get_real_vec(fixed, "rsf_tab_2d");
+ const auto xsqy_2d = get_real_vec(fixed, "xsqy_2d");
+
+ SECTION("dimensions_match_expected_shapes") {
+ REQUIRE(photo_table.nw == nw_ref);
+ REQUIRE(photo_table.numj == numj_ref);
+ REQUIRE(photo_table.nw == nw_shape);
+ REQUIRE(photo_table.nump == nump_shape);
+ REQUIRE(photo_table.numsza == numsza_shape);
+ REQUIRE(photo_table.numcolo3 == numcolo3_shape);
+ REQUIRE(photo_table.numalb == numalb_shape);
+
+ REQUIRE(photo_table.sza.extent_int(0) == photo_table.numsza);
+ REQUIRE(photo_table.alb.extent_int(0) == photo_table.numalb);
+ REQUIRE(photo_table.colo3.extent_int(0) == photo_table.nump);
+ REQUIRE(photo_table.o3rat.extent_int(0) == photo_table.numcolo3);
+ REQUIRE(photo_table.prs.extent_int(0) == photo_table.np_xs);
+ REQUIRE(photo_table.lng_indexer.extent_int(0) == mam4::mo_photo::phtcnt);
+ REQUIRE(photo_table.pht_alias_mult_1.extent_int(0) == mam4::mo_photo::phtcnt);
+ }
+
+ SECTION("1d_tables_match_yaml_reference") {
+ for (int i = 0; i < photo_table.numsza; ++i) {
+ REQUIRE(nearly_equal(sza_h(i), sza_ref[i]));
+ }
+ for (int i = 0; i < photo_table.numalb; ++i) {
+ REQUIRE(nearly_equal(alb_h(i), alb_ref[i]));
+ }
+ for (int i = 0; i < photo_table.nump; ++i) {
+ REQUIRE(nearly_equal(colo3_h(i), colo3_ref[i]));
+ }
+ for (int i = 0; i < photo_table.numcolo3; ++i) {
+ REQUIRE(nearly_equal(o3rat_h(i), o3rat_ref[i]));
+ }
+ for (int i = 0; i < photo_table.nump; ++i) {
+ REQUIRE(nearly_equal(press_h(i), press_ref[i]));
+ }
+ for (int i = 0; i < photo_table.np_xs; ++i) {
+ REQUIRE(nearly_equal(prs_h(i), prs_ref[i]));
+ }
+ for (int i = 0; i < photo_table.numsza - 1; ++i) {
+ REQUIRE(nearly_equal(del_sza_h(i), del_sza_ref[i]));
+ }
+ for (int i = 0; i < photo_table.numalb - 1; ++i) {
+ REQUIRE(nearly_equal(del_alb_h(i), del_alb_ref[i]));
+ }
+ for (int i = 0; i < photo_table.numcolo3 - 1; ++i) {
+ REQUIRE(nearly_equal(del_o3rat_h(i), del_o3rat_ref[i]));
+ }
+ for (int i = 0; i < photo_table.np_xs - 1; ++i) {
+ REQUIRE(nearly_equal(dprs_h(i), dprs_ref[i]));
+ }
+ }
+
+ SECTION("rsf_corner_slice_matches_reference") {
+ const int nw = photo_table.nw;
+ const int nump = photo_table.nump;
+ int count = 0;
+ for (int d2 = 0; d2 < nump; ++d2) {
+ for (int d1 = 0; d1 < nw; ++d1) {
+ const auto computed = rsf_tab_h(d1, d2, 0, 0, 0);;
+ const auto expected = rsf_tab_2d[count];
+ count++;
+ INFO("rsf_tab mismatch at (i=" << d1 << ", j=" << d2
+ << "), computed=" << computed << ", expected=" << expected);
+ REQUIRE(nearly_equal(computed, expected,1e-6));
+ }
+ }
+ }
+
+ SECTION("xsqy_table_matches_yaml_reference") {
+ REQUIRE(xsqy_2d.size() ==
+ static_cast(photo_table.numj) *
+ static_cast(photo_table.nw));
+ int count = 0;
+ for (int w = 0; w < photo_table.nw; ++w) {
+ REQUIRE(nearly_equal(xsqy_h(0, w, 0, 0), xsqy_2d[count], 0, 0));
+ ++count;
+ }
+ }
+
+ SECTION("indexing_and_alias_arrays_are_initialized") {
+ REQUIRE(lng_indexer_h(0) == 0);
+ for (int i = 0; i < mam4::mo_photo::phtcnt; ++i) {
+ REQUIRE(nearly_equal(pht_alias_mult_h(i), 1.0));
+ }
+ }
+
+ SECTION("etfphot_is_finite") {
+ REQUIRE(static_cast(etfphot_ref.size()) == photo_table.nw);
+ for (int i = 0; i < photo_table.nw; ++i) {
+ REQUIRE(nearly_equal(etfphot_h(i), etfphot_ref[i]));
+ REQUIRE(std::isfinite(etfphot_h(i)));
+ }
+ }
+
+}
+
+TEST_CASE("mam_photo_table_kernel_single_column_nlev72_regression",
+ "[mam4][photo][kokkos]") {
+ if constexpr (mam4::nlev != 72) return;
+ constexpr int ncol = 1;
+ constexpr int nlev = mam4::nlev;
+ constexpr int nref = 1;
+ using namespace scream;
+
+ ekat::Comm comm(MPI_COMM_WORLD);
+ struct ScorpioGuard {
+ explicit ScorpioGuard(const ekat::Comm& comm) : comm_(comm) {
+ scorpio::init_subsystem(comm_);
+ }
+ ~ScorpioGuard() { scorpio::finalize_subsystem(); }
+ const ekat::Comm& comm_;
+ } scorpio_guard(comm);
+
+ const std::string rsf_file =
+ std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/RSF_GT200nm_v3.0_c080811.nc";
+ const std::string xs_long_file =
+ std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/temp_prs_GT200nm_JPL10_c130206.nc";
+ const std::string input_yaml_file = std::string(SCREAM_DATA_DIR) + "/mam4xx/photolysis/table_photo_input_ts_355.yaml";
+
+ const auto root = ekat::parse_yaml_file(input_yaml_file);
+ REQUIRE(root.isSublist("input"));
+ REQUIRE(root.sublist("input").isSublist("fixed"));
+ const auto& fixed = root.sublist("input").sublist("fixed");
+
+ const auto photo_table = scream::impl::read_photo_table(rsf_file, xs_long_file);
+ const int work_len = mam4::mo_photo::get_photo_table_work_len(photo_table);
+ const int npht = mam4::mo_photo::phtcnt;
+
+ REQUIRE(work_len > 0);
+ REQUIRE(npht >= nref);
+
+ // Allocate device views.
+ view_2d work_photo_table("work_photo_table", ncol, work_len);
+ view_2d pmid("pmid", ncol, nlev);
+ view_2d pdel("pdel", ncol, nlev);
+ view_2d temper("temper", ncol, nlev);
+ view_2d o3col("o3col", ncol, nlev);
+ view_1d zen_angle("zen_angle", ncol);
+ view_1d srf_alb("srf_alb", ncol);
+ view_2d qc("qc", ncol, nlev);
+ view_2d cld("cld", ncol, nlev);
+ view_3d photo("photo", ncol, nlev, npht);
+
+ Kokkos::deep_copy(work_photo_table, 0.0);
+ Kokkos::deep_copy(photo, 0.0);
+
+ // Read atmospheric-state reference data from ParameterList.
+ const auto pmid_vals = get_real_vec(fixed, "pmid");
+ const auto pdel_vals = get_real_vec(fixed, "pdel");
+ const auto temper_vals = get_real_vec(fixed, "temper");
+ const auto o3col_vals = get_real_vec(fixed, "col_dens_1");
+ const auto lwc_vals = get_real_vec(fixed, "lwc");
+ const auto cloud_vals = get_real_vec(fixed, "clouds");
+ const auto zen_vals = get_real_vec(fixed, "zen_angle");
+ const auto alb_vals = get_real_vec(fixed, "srf_alb");
+ const auto esfact_vals = get_real_vec(fixed, "esfact");
+ const auto photo_ref = get_real_vec(fixed, "photos");
+
+ REQUIRE(pmid_vals.size() >= static_cast(nlev));
+ REQUIRE(pdel_vals.size() >= static_cast(nlev));
+ REQUIRE(temper_vals.size() >= static_cast(nlev));
+ REQUIRE(o3col_vals.size() >= static_cast(nlev));
+ REQUIRE(lwc_vals.size() >= static_cast(nlev));
+ REQUIRE(cloud_vals.size() >= static_cast(nlev));
+ REQUIRE(zen_vals.size() >= 1);
+ REQUIRE(alb_vals.size() >= 1);
+ REQUIRE(esfact_vals.size() >= 1);
+
+ const Real zen_val = zen_vals[0];
+ const Real alb_val = alb_vals[0];
+ const Real esfact = esfact_vals[0];
+
+ // Fill host mirrors and copy to device.
+ auto pmid_h = Kokkos::create_mirror_view(pmid);
+ auto pdel_h = Kokkos::create_mirror_view(pdel);
+ auto temper_h = Kokkos::create_mirror_view(temper);
+ auto o3col_h = Kokkos::create_mirror_view(o3col);
+ auto zen_h = Kokkos::create_mirror_view(zen_angle);
+ auto alb_h = Kokkos::create_mirror_view(srf_alb);
+ auto qc_h = Kokkos::create_mirror_view(qc);
+ auto cld_h = Kokkos::create_mirror_view(cld);
+
+ for (int k = 0; k < nlev; ++k) {
+ pmid_h(0, k) = pmid_vals[k];
+ pdel_h(0, k) = pdel_vals[k];
+ temper_h(0, k) = temper_vals[k];
+ o3col_h(0, k) = o3col_vals[k];
+ qc_h(0, k) = lwc_vals[k];
+ cld_h(0, k) = cloud_vals[k];
+ }
+ zen_h(0) = zen_val;
+ alb_h(0) = alb_val;
+
+ Kokkos::deep_copy(pmid, pmid_h);
+ Kokkos::deep_copy(pdel, pdel_h);
+ Kokkos::deep_copy(temper, temper_h);
+ Kokkos::deep_copy(o3col, o3col_h);
+ Kokkos::deep_copy(zen_angle, zen_h);
+ Kokkos::deep_copy(srf_alb, alb_h);
+ Kokkos::deep_copy(qc, qc_h);
+ Kokkos::deep_copy(cld, cld_h);
+
+ // Launch one-column photolysis kernel.
+ TeamPolicy policy(ncol, Kokkos::AUTO());
+ Kokkos::parallel_for(
+ "unit_test_table_photo_nlev72", policy,
+ KOKKOS_LAMBDA(const MemberType& team) {
+ const int icol = team.league_rank();
+
+ const auto work_icol = ekat::subview(work_photo_table, icol);
+ mam4::mo_photo::PhotoTableWorkArrays photo_work_arrays;
+ mam4::mo_photo::set_photo_table_work_arrays(photo_table, work_icol,
+ photo_work_arrays);
+ team.team_barrier();
+
+ mam4::mo_photo::table_photo(
+ team,
+ ekat::subview(photo, icol),
+ ekat::subview(pmid, icol),
+ ekat::subview(pdel, icol),
+ ekat::subview(temper, icol),
+ ekat::subview(o3col, icol),
+ zen_angle(icol), srf_alb(icol),
+ ekat::subview(qc, icol),
+ ekat::subview(cld, icol),
+ esfact, photo_table, photo_work_arrays);
+ });
+ Kokkos::fence();
+
+ auto photo_h = Kokkos::create_mirror_view_and_copy(HostSpace(), photo);
+
+ SECTION("all_outputs_are_finite") {
+ for (int k = 0; k < nlev; ++k) {
+ for (int j = 0; j < nref; ++j) {
+ INFO("Non-finite output at k=" << k << ", j=" << j
+ << ", value=" << photo_h(0, k, j));
+ REQUIRE(std::isfinite(photo_h(0, k, j)));
+ }
+ }
+ }
+
+ SECTION("compare_against_reference_when_available") {
+ REQUIRE(photo_ref.size() == static_cast(nlev * nref));
+ constexpr Real relative_tol = PrecisionTolerance::relative_tol;
+ constexpr Real absolute_tol = PrecisionTolerance::absolute_tol;
+
+ int count = 0;
+ for (int d2 = 0; d2 < nref; ++d2) {
+ for (int d1 = 0; d1 < nlev; ++d1) {
+ const auto computed = photo_h(0, d1, d2);
+ const auto expected = photo_ref[count];
+ count++;
+ INFO("Reference mismatch at d1=" << d1 << ", d2=" << d2
+ << ", computed=" << computed
+ << ", expected=" << expected);
+ REQUIRE(nearly_equal(computed, expected, relative_tol, absolute_tol));
+ }
+ }
+ }
+}
diff --git a/components/eamxx/src/physics/register_physics.hpp b/components/eamxx/src/physics/register_physics.hpp
index 75b88bd73022..2cd884232227 100644
--- a/components/eamxx/src/physics/register_physics.hpp
+++ b/components/eamxx/src/physics/register_physics.hpp
@@ -53,6 +53,12 @@
#ifdef EAMXX_HAS_CLD_FRAC_NET
#include "physics/cld_fraction/cld_frac_net/eamxx_cld_frac_net_process_interface.hpp"
#endif
+#ifdef EAMXX_HAS_WATER_TRACERS
+#include "physics/specialized_tracers/water_tracers/eamxx_water_tracers_process_interface.hpp"
+#endif
+#ifdef EAMXX_HAS_WATER_ISOTOPES
+#include "physics/specialized_tracers/water_isotopes/eamxx_water_isotopes_process_interface.hpp"
+#endif
namespace scream {
@@ -106,6 +112,12 @@ inline void register_physics () {
#ifdef EAMXX_HAS_CLD_FRAC_NET
proc_factory.register_product("cld_frac_net",&create_atmosphere_process);
#endif
+#ifdef EAMXX_HAS_WATER_TRACERS
+ proc_factory.register_product("water_tracers",&create_atmosphere_process);
+#endif
+#ifdef EAMXX_HAS_WATER_ISOTOPES
+ proc_factory.register_product("water_isotopes",&create_atmosphere_process);
+#endif
// If no physics was enabled, silence compile warning about unused var
(void) proc_factory;
diff --git a/components/eamxx/src/physics/shoc/CMakeLists.txt b/components/eamxx/src/physics/shoc/CMakeLists.txt
index 68bcbf595a86..d7ed3926e83a 100644
--- a/components/eamxx/src/physics/shoc/CMakeLists.txt
+++ b/components/eamxx/src/physics/shoc/CMakeLists.txt
@@ -24,6 +24,7 @@ if (NOT EAMXX_ENABLE_GPU)
eti/shoc_compute_shoc_mix_shoc_length.cpp
eti/shoc_compute_shoc_vapor.cpp
eti/shoc_compute_shoc_temperature.cpp
+ eti/shoc_compute_shear_strain3d.cpp
eti/shoc_compute_shr_prod.cpp
eti/shoc_compute_tmpi.cpp
eti/shoc_diag_obklen.cpp
diff --git a/components/eamxx/src/physics/shoc/disp/shoc_tke_disp.cpp b/components/eamxx/src/physics/shoc/disp/shoc_tke_disp.cpp
index f8d730068e50..fb56def8e94b 100644
--- a/components/eamxx/src/physics/shoc/disp/shoc_tke_disp.cpp
+++ b/components/eamxx/src/physics/shoc/disp/shoc_tke_disp.cpp
@@ -20,7 +20,10 @@ ::shoc_tke_disp(
const Scalar& Ckh,
const Scalar& Ckm,
const bool& shoc_1p5tke,
+ const bool& do_3d_turb,
const view_2d& wthv_sec,
+ const view_3d& shear_strain3d_components,
+ const view_2d& shear_strain3d,
const view_2d& shoc_mix,
const view_2d& dz_zi,
const view_2d& dz_zt,
@@ -28,6 +31,7 @@ ::shoc_tke_disp(
const view_2d& tabs,
const view_2d& u_wind,
const view_2d& v_wind,
+ const view_2d& w_field,
const view_2d& brunt,
const view_2d& zt_grid,
const view_2d& zi_grid,
@@ -47,11 +51,17 @@ ::shoc_tke_disp(
const Int i = team.league_rank();
auto workspace = workspace_mgr.get_workspace(team);
+ uview_2d shear_strain3d_components_s;
+ if (do_3d_turb) {
+ shear_strain3d_components_s = ekat::subview(shear_strain3d_components, i);
+ }
shoc_tke(team, nlev, nlevi, dtime,
lambda_low, lambda_high, lambda_slope, lambda_thresh,
- Ckh, Ckm, shoc_1p5tke,
+ Ckh, Ckm, shoc_1p5tke, do_3d_turb,
ekat::subview(wthv_sec, i),
+ shear_strain3d_components_s,
+ ekat::subview(shear_strain3d, i),
ekat::subview(shoc_mix, i),
ekat::subview(dz_zi, i),
ekat::subview(dz_zt, i),
@@ -59,6 +69,7 @@ ::shoc_tke_disp(
ekat::subview(tabs, i),
ekat::subview(u_wind, i),
ekat::subview(v_wind, i),
+ ekat::subview(w_field, i),
ekat::subview(brunt, i),
ekat::subview(zt_grid, i),
ekat::subview(zi_grid, i),
diff --git a/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.cpp b/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.cpp
index dd57640bb728..52188b3748bb 100644
--- a/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.cpp
+++ b/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.cpp
@@ -54,6 +54,8 @@ void SHOCMacrophysics::create_requests()
const auto m2 = pow(m,2);
const auto s2 = pow(s,2);
+ const auto nondim = none;
+ const bool do_3d_turb = m_params.get("do_3d_turbulence_shoc", false);
// These variables are needed by the interface, but not actually passed to shoc_main.
add_field("omega", scalar3d_mid, Pa/s, grid_name, ps);
@@ -74,6 +76,11 @@ void SHOCMacrophysics::create_requests()
add_field("p_int", scalar3d_int, Pa, grid_name, ps);
add_field("pseudo_density", scalar3d_mid, Pa, grid_name, ps);
add_field("phis", scalar2d , m2/s2, grid_name);
+ if (do_3d_turb) {
+ const auto vector3d_mid_6 = m_grid->get_3d_vector_layout(LEV,6);
+ add_field("tke_shear_strain3d_components", vector3d_mid_6,nondim/s, grid_name, ps);
+ add_field("tke_shear_strain3d", scalar3d_mid,nondim/s2, grid_name, ps);
+ }
// Input/Output variables
add_field("horiz_winds", vector3d_mid, m/s, grid_name, ps);
@@ -169,7 +176,7 @@ size_t SHOCMacrophysics::requested_buffer_size_in_bytes() const
const auto policy = TPF::get_default_team_policy(m_num_cols, nlev_packs);
const int n_wind_slots = ekat::npack(2)*Pack::n;
const int n_trac_slots = ekat::npack(m_num_tracers+3)*Pack::n;
- const size_t wsm_request= WSM::get_total_bytes_needed(nlevi_packs, 14+(2*n_wind_slots+n_trac_slots), policy);
+ const size_t wsm_request= WSM::get_total_bytes_needed(nlevi_packs, 20+(2*n_wind_slots+n_trac_slots), policy);
return interface_request + wsm_request;
}
@@ -205,6 +212,8 @@ void SHOCMacrophysics::init_buffers(const ATMBufferManager &buffer_manager)
const int nlev_packs = ekat::npack(m_num_levs);
const int nlevi_packs = ekat::npack(m_num_levs+1);
const int num_tracer_packs = ekat::npack(m_num_tracers);
+ m_dummy_shear_strain3d = view_2d("dummy_shear_strain3d", m_num_cols, nlev_packs);
+ Kokkos::deep_copy(m_dummy_shear_strain3d, 0);
m_buffer.pref_mid = decltype(m_buffer.pref_mid)(s_mem, nlev_packs);
s_mem += m_buffer.pref_mid.size();
@@ -249,7 +258,7 @@ void SHOCMacrophysics::init_buffers(const ATMBufferManager &buffer_manager)
const auto policy = TPF::get_default_team_policy(m_num_cols, nlev_packs);
const int n_wind_slots = ekat::npack(2)*Pack::n;
const int n_trac_slots = ekat::npack(m_num_tracers+3)*Pack::n;
- const int wsm_size = WSM::get_total_bytes_needed(nlevi_packs, 14+(2*n_wind_slots+n_trac_slots), policy)/sizeof(Pack);
+ const int wsm_size = WSM::get_total_bytes_needed(nlevi_packs, 20+(2*n_wind_slots+n_trac_slots), policy)/sizeof(Pack);
s_mem += wsm_size;
size_t used_mem = (reinterpret_cast(s_mem) - buffer_manager.get_memory())*sizeof(Real);
@@ -275,6 +284,7 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
runtime_options.Ckh = m_params.get("coeff_kh");
runtime_options.Ckm = m_params.get("coeff_km");
runtime_options.shoc_1p5tke = m_params.get("shoc_1p5tke");
+ runtime_options.do_3d_turb = m_params.get("do_3d_turbulence_shoc", false);
runtime_options.extra_diags = m_params.get("extra_shoc_diags");
// Initialize all of the structures that are passed to shoc_main in run_impl.
// Note: Some variables in the structures are not stored in the field manager. For these
@@ -287,6 +297,14 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
const auto& surf_sens_flux = get_field_in("surf_sens_flux").get_view();
const auto& surf_evap = get_field_in("surf_evap").get_view();
const auto& surf_mom_flux = get_field_in("surf_mom_flux").get_view();
+ const auto shear_strain3d =
+ runtime_options.do_3d_turb
+ ? get_field_out("tke_shear_strain3d").get_view()
+ : view_2d(m_dummy_shear_strain3d);
+ view_3d_const shear_strain3d_components;
+ if (runtime_options.do_3d_turb) {
+ shear_strain3d_components = get_field_in("tke_shear_strain3d_components").get_view();
+ }
const auto& qtracers = get_group_out("turbulence_advected_tracers").m_monolithic_field->get_strided_view();
const auto& qc = get_field_out("qc").get_view();
const auto& qv = get_field_out("qv").get_view();
@@ -335,6 +353,7 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
if (run_type==RunType::Initial){
Kokkos::deep_copy(sgs_buoy_flux,0.0);
Kokkos::deep_copy(tk,0.0);
+ Kokkos::deep_copy(shear_strain3d,0.0);
Kokkos::deep_copy(tke,0.0004);
Kokkos::deep_copy(tke_copy,0.0004);
Kokkos::deep_copy(cldfrac_liq,0.0);
@@ -346,7 +365,7 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
shoc_preprocess.set_variables(m_num_cols,m_num_levs,z_surf,
T_mid,p_mid,p_int,pseudo_density,omega,phis,surf_sens_flux,surf_evap,
- surf_mom_flux,qtracers,qv,qc,qc_copy,tke,tke_copy,z_mid,z_int,
+ surf_mom_flux,qtracers,qv,shear_strain3d_components,shear_strain3d,qc,qc_copy,tke,tke_copy,z_mid,z_int,
dse,rrho,rrho_i,thv,dz,zt_grid,zi_grid,wpthlp_sfc,wprtp_sfc,upwp_sfc,vpwp_sfc,
wtracer_sfc,wm_zt,inv_exner,thlm,qw, cldfrac_liq, cldfrac_liq_prev,
upwp_sfc_pert, vpwp_sfc_pert, um_pert, vm_pert,
@@ -369,6 +388,8 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
input.wtracer_sfc = shoc_preprocess.wtracer_sfc;
input.inv_exner = shoc_preprocess.inv_exner;
input.phis = phis;
+ input.shear_strain3d_components = shear_strain3d_components;
+ input.shear_strain3d = shear_strain3d;
// Input/Output Variables
input_output.host_dse = shoc_preprocess.shoc_s;
@@ -471,7 +492,7 @@ void SHOCMacrophysics::initialize_impl (const RunType run_type)
const int n_wind_slots = ekat::npack(2)*Pack::n;
const int n_trac_slots = ekat::npack(m_num_tracers+3)*Pack::n;
const auto default_policy = TPF::get_default_team_policy(m_num_cols, nlev_packs);
- workspace_mgr.setup(m_buffer.wsm_data, nlevi_packs, 14+(2*n_wind_slots+n_trac_slots), default_policy);
+ workspace_mgr.setup(m_buffer.wsm_data, nlevi_packs, 20+(2*n_wind_slots+n_trac_slots), default_policy);
// Calculate pref_mid, and use that to calculate
// maximum number of levels in pbl from surface
diff --git a/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.hpp b/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.hpp
index a6f86eb52bc7..b97df6023dad 100644
--- a/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.hpp
+++ b/components/eamxx/src/physics/shoc/eamxx_shoc_process_interface.hpp
@@ -194,6 +194,8 @@ class SHOCMacrophysics : public scream::AtmosphereProcess
sview_2d_const surf_mom_flux;
view_3d_strided qtracers;
view_2d qv;
+ view_3d_const shear_strain3d_components;
+ view_2d shear_strain3d;
view_2d_const qc;
view_2d qc_copy;
view_2d z_mid;
@@ -232,7 +234,8 @@ class SHOCMacrophysics : public scream::AtmosphereProcess
const view_1d_const& phis_, const view_1d_const& surf_sens_flux_, const view_1d_const& surf_evap_,
const sview_2d_const& surf_mom_flux_,
const view_3d_strided& qtracers_,
- const view_2d& qv_, const view_2d_const& qc_, const view_2d& qc_copy_,
+ const view_2d& qv_, const view_3d_const& shear_strain3d_components_,
+ const view_2d& shear_strain3d_, const view_2d_const& qc_, const view_2d& qc_copy_,
const view_2d& tke_, const view_2d& tke_copy_,
const view_2d& z_mid_, const view_2d& z_int_,
const view_2d& dse_, const view_2d& rrho_, const view_2d& rrho_i_,
@@ -258,6 +261,8 @@ class SHOCMacrophysics : public scream::AtmosphereProcess
surf_evap = surf_evap_;
surf_mom_flux = surf_mom_flux_;
qv = qv_;
+ shear_strain3d_components = shear_strain3d_components_;
+ shear_strain3d = shear_strain3d_;
// OUT
qtracers = qtracers_;
qc = qc_;
@@ -555,6 +560,7 @@ class SHOCMacrophysics : public scream::AtmosphereProcess
// Struct which contains local variables
Buffer m_buffer;
+ view_2d m_dummy_shear_strain3d;
// Store the structures for each argument to shoc_main;
SHF::SHOCInput input;
diff --git a/components/eamxx/src/physics/shoc/eti/shoc_compute_shear_strain3d.cpp b/components/eamxx/src/physics/shoc/eti/shoc_compute_shear_strain3d.cpp
new file mode 100644
index 000000000000..2d40eadafed1
--- /dev/null
+++ b/components/eamxx/src/physics/shoc/eti/shoc_compute_shear_strain3d.cpp
@@ -0,0 +1,14 @@
+#include "shoc_compute_shear_strain3d_impl.hpp"
+
+namespace scream {
+namespace shoc {
+
+/*
+ * Explicit instantiation for doing compute_shear_strain3d on Reals using the
+ * default device.
+ */
+
+template struct Functions;
+
+} // namespace shoc
+} // namespace scream
diff --git a/components/eamxx/src/physics/shoc/impl/shoc_adv_sgs_tke_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_adv_sgs_tke_impl.hpp
index d98933000736..9ecc0220f0bc 100644
--- a/components/eamxx/src/physics/shoc/impl/shoc_adv_sgs_tke_impl.hpp
+++ b/components/eamxx/src/physics/shoc/impl/shoc_adv_sgs_tke_impl.hpp
@@ -19,11 +19,13 @@ ::adv_sgs_tke(
const Int& nlev,
const Real& dtime,
const bool& shoc_1p5tke,
+ const bool& do_3d_turb,
const uview_1d& shoc_mix,
const uview_1d& wthv_sec,
const uview_1d& sterm_zt,
const uview_1d& tk,
const uview_1d& brunt,
+ const uview_1d& shear_strain3d,
const uview_1d& tke,
const uview_1d& a_diss)
{
@@ -33,7 +35,6 @@ ::adv_sgs_tke(
static constexpr Scalar basetemp = C::basetemp;
static constexpr Scalar mintke = scream::shoc::Constants::mintke;
static constexpr Scalar maxtke = scream::shoc::Constants::maxtke;
- Pack a_prod_bu;
//declare some constants
static constexpr Scalar Cs = 0.15;
@@ -45,6 +46,8 @@ ::adv_sgs_tke(
const Int nlev_pack = ekat::npack(nlev);
Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_pack), [&] (const Int& k) {
+ Pack a_prod_bu;
+ Pack a_prod_sh;
// Compute buoyant production term
if (shoc_1p5tke){
@@ -59,7 +62,12 @@ ::adv_sgs_tke(
tke(k) = ekat::max(0,tke(k));
// Shear production term, use diffusivity from previous timestep
- const Pack a_prod_sh = tk(k)*sterm_zt(k);
+ if (do_3d_turb){
+ a_prod_sh = Ck*tk(k)*shear_strain3d(k);
+ }
+ else{
+ a_prod_sh = tk(k)*sterm_zt(k);
+ }
// Dissipation term
a_diss(k)=Cee/shoc_mix(k)*ekat::pow(tke(k),sp(1.5));
diff --git a/components/eamxx/src/physics/shoc/impl/shoc_assemble_shoc_shear_strain3d_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_assemble_shoc_shear_strain3d_impl.hpp
new file mode 100644
index 000000000000..4cf0d249650a
--- /dev/null
+++ b/components/eamxx/src/physics/shoc/impl/shoc_assemble_shoc_shear_strain3d_impl.hpp
@@ -0,0 +1,56 @@
+#ifndef SHOC_ASSEMBLE_SHOC_SHEAR_STRAIN3D_IMPL_HPP
+#define SHOC_ASSEMBLE_SHOC_SHEAR_STRAIN3D_IMPL_HPP
+
+#include "shoc_functions.hpp"
+
+namespace scream {
+namespace shoc {
+
+template
+KOKKOS_FUNCTION
+void Functions::assemble_shoc_shear_strain3d(
+ const MemberType& team,
+ const Int& nlev,
+ const uview_2d& shear_strain3d_components,
+ const uview_1d& du_dz_m,
+ const uview_1d& dv_dz_m,
+ const uview_1d& dw_dz_m,
+ const uview_1d& shear_strain3d)
+{
+ const Int nlev_pack = ekat::npack(nlev);
+
+ // Assemble the full local velocity-gradient tensor from dycore horizontal
+ // components and SHOC-computed vertical components, then form the symmetric strain invariant.
+ team.team_barrier();
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_pack), [&] (const Int& k) {
+ constexpr Scalar one_half = Scalar(0.5);
+ constexpr Scalar two = Scalar(2.0);
+
+ const Pack A00 = shear_strain3d_components(0,k);
+ const Pack A01 = shear_strain3d_components(1,k);
+ const Pack A10 = shear_strain3d_components(2,k);
+ const Pack A11 = shear_strain3d_components(3,k);
+ const Pack A20 = shear_strain3d_components(4,k);
+ const Pack A21 = shear_strain3d_components(5,k);
+
+ const Pack A02 = du_dz_m(k);
+ const Pack A12 = dv_dz_m(k);
+ const Pack A22 = dw_dz_m(k);
+
+ const Pack S00 = A00;
+ const Pack S11 = A11;
+ const Pack S22 = A22;
+ const Pack S01 = one_half * (A01 + A10);
+ const Pack S02 = one_half * (A02 + A20);
+ const Pack S12 = one_half * (A12 + A21);
+
+ shear_strain3d(k) =
+ two * (S00*S00 + S11*S11 + S22*S22
+ + two*S01*S01 + two*S02*S02 + two*S12*S12);
+ });
+}
+
+} // namespace shoc
+} // namespace scream
+
+#endif
diff --git a/components/eamxx/src/physics/shoc/impl/shoc_compute_shear_strain3d_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_compute_shear_strain3d_impl.hpp
new file mode 100644
index 000000000000..b950e3fff1c8
--- /dev/null
+++ b/components/eamxx/src/physics/shoc/impl/shoc_compute_shear_strain3d_impl.hpp
@@ -0,0 +1,63 @@
+#ifndef SHOC_COMPUTE_SHEAR_STRAIN3D_IMPL_HPP
+#define SHOC_COMPUTE_SHEAR_STRAIN3D_IMPL_HPP
+
+#include "shoc_assemble_shoc_shear_strain3d_impl.hpp"
+#include "shoc_compute_vertical_shear_terms_impl.hpp"
+
+namespace scream {
+namespace shoc {
+
+#ifdef SCREAM_SHOC_SMALL_KERNELS
+template
+void Functions::compute_shear_strain3d_disp(
+ const Int& shcol,
+ const Int& nlev,
+ const Int& nlevi,
+ const view_3d& shear_strain3d_components,
+ const view_2d& dz_zi,
+ const view_2d& u_wind,
+ const view_2d& v_wind,
+ const view_2d& w_field,
+ const view_2d& zt_grid,
+ const view_2d& zi_grid,
+ const WorkspaceMgr& workspace_mgr,
+ const view_2d& shear_strain3d)
+{
+ using ExeSpace = typename KT::ExeSpace;
+ using TPF = ekat::TeamPolicyFactory;
+
+ const auto nlev_packs = ekat::npack(nlev);
+ const auto policy = TPF::get_default_team_policy(shcol, nlev_packs);
+ Kokkos::parallel_for(policy, KOKKOS_LAMBDA(const MemberType& team) {
+ const Int i = team.league_rank();
+ auto workspace = workspace_mgr.get_workspace(team);
+ uview_1d du_dz_m, dv_dz_m, dw_dz_m;
+ workspace.template take_many_contiguous_unsafe<3>(
+ {"du_dz_m", "dv_dz_m", "dw_dz_m"},
+ {&du_dz_m, &dv_dz_m, &dw_dz_m});
+
+ compute_vertical_shear_terms(team, nlev, nlevi,
+ Kokkos::subview(dz_zi, i, Kokkos::ALL()),
+ Kokkos::subview(u_wind, i, Kokkos::ALL()),
+ Kokkos::subview(v_wind, i, Kokkos::ALL()),
+ Kokkos::subview(w_field, i, Kokkos::ALL()),
+ Kokkos::subview(zt_grid, i, Kokkos::ALL()),
+ Kokkos::subview(zi_grid, i, Kokkos::ALL()),
+ workspace,
+ du_dz_m, dv_dz_m, dw_dz_m);
+
+ assemble_shoc_shear_strain3d(team, nlev,
+ Kokkos::subview(shear_strain3d_components, i, Kokkos::ALL(), Kokkos::ALL()),
+ du_dz_m, dv_dz_m, dw_dz_m,
+ Kokkos::subview(shear_strain3d, i, Kokkos::ALL()));
+
+ workspace.template release_many_contiguous<3>(
+ {&du_dz_m, &dv_dz_m, &dw_dz_m});
+ });
+}
+#endif
+
+} // namespace shoc
+} // namespace scream
+
+#endif
diff --git a/components/eamxx/src/physics/shoc/impl/shoc_compute_vertical_shear_terms_impl.hpp b/components/eamxx/src/physics/shoc/impl/shoc_compute_vertical_shear_terms_impl.hpp
new file mode 100644
index 000000000000..8b16651b82e1
--- /dev/null
+++ b/components/eamxx/src/physics/shoc/impl/shoc_compute_vertical_shear_terms_impl.hpp
@@ -0,0 +1,101 @@
+#ifndef SHOC_COMPUTE_VERTICAL_SHEAR_TERMS_IMPL_HPP
+#define SHOC_COMPUTE_VERTICAL_SHEAR_TERMS_IMPL_HPP
+
+#include "shoc_functions.hpp"
+
+namespace scream {
+namespace shoc {
+
+template
+KOKKOS_FUNCTION
+void Functions::compute_vertical_shear_terms(
+ const MemberType& team,
+ const Int& nlev,
+ const Int& nlevi,
+ const uview_1d& dz_zi,
+ const uview_1d& u_wind,
+ const uview_1d& v_wind,
+ const uview_1d& w_field,
+ const uview_1d& zt_grid,
+ const uview_1d& zi_grid,
+ const Workspace& workspace,
+ const uview_1d& du_dz_m,
+ const uview_1d& dv_dz_m,
+ const uview_1d& dw_dz_m)
+{
+ // Compute the SHOC-column vertical gradients on interfaces, then
+ // interpolate them back to midpoint levels.
+ uview_1d du_dz_i, dv_dz_i, dw_dz_i;
+ workspace.template take_many_contiguous_unsafe<3>(
+ {"du_dz_i", "dv_dz_i", "dw_dz_i"},
+ {&du_dz_i, &dv_dz_i, &dw_dz_i});
+
+ const Int nlev_pack = ekat::npack(nlev);
+ const Int nlevi_pack = ekat::npack(nlevi);
+
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlevi_pack), [&] (const Int& k) {
+ du_dz_i(k) = 0;
+ dv_dz_i(k) = 0;
+ dw_dz_i(k) = 0;
+ });
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_pack), [&] (const Int& k) {
+ du_dz_m(k) = 0;
+ dv_dz_m(k) = 0;
+ dw_dz_m(k) = 0;
+ });
+ team.team_barrier();
+
+ const auto s_u_wind = scalarize(u_wind);
+ const auto s_v_wind = scalarize(v_wind);
+ const auto s_w_field = scalarize(w_field);
+
+ // Form the vertical gradients on the interface grid first so they are
+ // consistent with SHOC's native staggered-grid treatment of shear production.
+ Kokkos::parallel_for(Kokkos::TeamVectorRange(team, nlev_pack), [&] (const Int& k) {
+ auto range_pack = ekat::range(k*Pack::n);
+ const auto active_range = range_pack > 0 && range_pack < nlev;
+
+ if (active_range.any()) {
+ const Pack inv_dz = 1 / dz_zi(k);
+
+ auto range_pack_safe = range_pack;
+ range_pack_safe.set(range_pack < 1, 1);
+
+ Pack u_grid, u_up_grid;
+ Pack v_grid, v_up_grid;
+ Pack w_grid, w_up_grid;
+ ekat::index_and_shift<-1>(s_u_wind, range_pack_safe, u_grid, u_up_grid);
+ ekat::index_and_shift<-1>(s_v_wind, range_pack_safe, v_grid, v_up_grid);
+ ekat::index_and_shift<-1>(s_w_field, range_pack_safe, w_grid, w_up_grid);
+
+ du_dz_i(k).set(active_range, inv_dz*(u_up_grid - u_grid));
+ dv_dz_i(k).set(active_range, inv_dz*(v_up_grid - v_grid));
+ dw_dz_i(k).set(active_range, inv_dz*(w_up_grid - w_grid));
+ }
+ });
+
+ auto s_du_dz_i = scalarize(du_dz_i);
+ auto s_dv_dz_i = scalarize(dv_dz_i);
+ auto s_dw_dz_i = scalarize(dw_dz_i);
+ s_du_dz_i(0) = 0;
+ s_dv_dz_i(0) = 0;
+ s_dw_dz_i(0) = 0;
+ s_du_dz_i(nlevi-1) = 0;
+ s_dv_dz_i(nlevi-1) = 0;
+ s_dw_dz_i(nlevi-1) = 0;
+
+ // Interpolate the interface-grid vertical gradients back to midpoint levels,
+ // where SHOC carries thermodynamic variables and TKE.
+ team.team_barrier();
+ linear_interp(team, zi_grid, zt_grid, du_dz_i, du_dz_m, nlevi, nlev, 0);
+ linear_interp(team, zi_grid, zt_grid, dv_dz_i, dv_dz_m, nlevi, nlev, 0);
+ linear_interp(team, zi_grid, zt_grid, dw_dz_i, dw_dz_m, nlevi, nlev, 0);
+
+ workspace.template release_many_contiguous<3>(
+ {&du_dz_i, &dv_dz_i, &dw_dz_i});
+}
+
+} // namespace shoc
+} // namespace scream
+
+#endif
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 54ea8e231af9..cda67cdb3878 100644
--- a/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp
+++ b/components/eamxx/src/physics/shoc/impl/shoc_main_impl.hpp
@@ -89,6 +89,7 @@ void Functions::shoc_main_internal(
const Scalar& Ckh,
const Scalar& Ckm,
const bool& shoc_1p5tke,
+ const bool& do_3d_turb,
const bool& extra_diags,
// Input Variables
const Scalar& dx,
@@ -107,6 +108,8 @@ void Functions::shoc_main_internal(
const uview_1d& wtracer_sfc,
const uview_1d& inv_exner,
const Scalar& phis,
+ const uview_2d& shear_strain3d_components,
+ const uview_1d& shear_strain3d,
// Workspace/Local Variables
const Workspace& workspace,
// Input/Output Variables
@@ -228,9 +231,11 @@ void Functions::shoc_main_internal(
shoc_tke(team,nlev,nlevi,dtime, // Input
lambda_low,lambda_high,lambda_slope, // Runtime options
lambda_thresh,Ckh,Ckm,shoc_1p5tke, // Runtime options
- wthv_sec, // Input
+ do_3d_turb, // Runtime options
+ wthv_sec,shear_strain3d_components, // Input
+ shear_strain3d, // Input/Output
shoc_mix,dz_zi,dz_zt,pres,shoc_tabs, // Input
- u_wind,v_wind,brunt,zt_grid, // Input
+ u_wind,v_wind,w_field,brunt,zt_grid, // Input
zi_grid,pblh, // Input
workspace, // Workspace
tke,tk,tkh, // Input/Output
@@ -356,6 +361,7 @@ void Functions::shoc_main_internal(
const Scalar& Ckh,
const Scalar& Ckm,
const bool& shoc_1p5tke,
+ const bool& do_3d_turb,
const bool& extra_diags,
// Input Variables
const view_1d& dx,
@@ -374,6 +380,8 @@ void Functions::shoc_main_internal(
const view_2d& wtracer_sfc,
const view_2d& inv_exner,
const view_1d& phis,
+ const view_3d& shear_strain3d_components,
+ const view_2d& shear_strain3d,
// Workspace Manager
WorkspaceMgr& workspace_mgr,
// Input/Output Variables
@@ -496,13 +504,14 @@ void Functions::shoc_main_internal(
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
- wthv_sec, // Input
+ lambda_low,lambda_high,lambda_slope, // Runtime options
+ lambda_thresh,Ckh,Ckm,shoc_1p5tke, // Runtime options
+ do_3d_turb, // Runtime options
+ wthv_sec,shear_strain3d_components, // Input
+ shear_strain3d, // Input/Output
shoc_mix,dz_zi,dz_zt,pres,shoc_tabs, // Input
- u_wind,v_wind,brunt,zt_grid, // Input
+ u_wind,v_wind,w_field,brunt,zt_grid, // Input
zi_grid,pblh, // Input
workspace_mgr, // Workspace mgr
tke,tk,tkh, // Input/Output
@@ -636,6 +645,7 @@ Int Functions::shoc_main(
const Scalar Ckm = shoc_runtime.Ckm;
const bool shoc_1p5tke = shoc_runtime.shoc_1p5tke;
const bool extra_diags = shoc_runtime.extra_diags;
+ const bool do_3d_turb = shoc_runtime.do_3d_turb;
#ifndef SCREAM_SHOC_SMALL_KERNELS
using ExeSpace = typename KT::ExeSpace;
@@ -671,6 +681,12 @@ Int Functions::shoc_main(
const auto w_field_s = ekat::subview(shoc_input.w_field, i);
const auto wtracer_sfc_s = ekat::subview(shoc_input.wtracer_sfc, i);
const auto inv_exner_s = ekat::subview(shoc_input.inv_exner, i);
+ uview_2d shear_strain3d_components_s;
+ if (do_3d_turb) {
+ shear_strain3d_components_s =
+ Kokkos::subview(shoc_input.shear_strain3d_components, i, Kokkos::ALL(), Kokkos::ALL());
+ }
+ const auto shear_strain3d_s = ekat::subview(shoc_input.shear_strain3d, i);
const auto host_dse_s = ekat::subview(shoc_input_output.host_dse, i);
const auto tke_s = ekat::subview(shoc_input_output.tke, i);
const auto thetal_s = ekat::subview(shoc_input_output.thetal, i);
@@ -707,11 +723,13 @@ Int Functions::shoc_main(
shoc_main_internal(team, nlev, nlevi, npbl, nadv, num_qtracers, dtime,
lambda_low, lambda_high, lambda_slope, lambda_thresh, // Runtime options
thl2tune, qw2tune, qwthl2tune, w2tune, length_fac, // Runtime options
- c_diag_3rd_mom, Ckh, Ckm, shoc_1p5tke, extra_diags, // Runtime options
+ c_diag_3rd_mom, Ckh, Ckm, shoc_1p5tke, // Runtime options
+ do_3d_turb, extra_diags, // Runtime options
dx_s, dy_s, zt_grid_s, zi_grid_s, // Input
pres_s, presi_s, pdel_s, thv_s, w_field_s, // Input
wthl_sfc_s, wqw_sfc_s, uw_sfc_s, vw_sfc_s, // Input
wtracer_sfc_s, inv_exner_s, phis_s, // Input
+ shear_strain3d_components_s, shear_strain3d_s, // Input/Output
workspace, // Workspace
host_dse_s, tke_s, thetal_s, qw_s, u_wind_s, v_wind_s, // Input/Output
wthv_sec_s, qtracers_s, tk_s, shoc_cldfrac_s, // Input/Output
@@ -735,11 +753,12 @@ Int Functions::shoc_main(
shoc_main_internal(shcol, nlev, nlevi, npbl, nadv, num_qtracers, dtime,
lambda_low, lambda_high, lambda_slope, lambda_thresh, // Runtime options
thl2tune, qw2tune, qwthl2tune, w2tune, length_fac, // Runtime options
- c_diag_3rd_mom, Ckh, Ckm, shoc_1p5tke, extra_diags, // Runtime options
+ c_diag_3rd_mom, Ckh, Ckm, shoc_1p5tke, do_3d_turb, extra_diags, // Runtime options
shoc_input.dx, shoc_input.dy, shoc_input.zt_grid, shoc_input.zi_grid, // Input
shoc_input.pres, shoc_input.presi, shoc_input.pdel, shoc_input.thv, shoc_input.w_field, // Input
shoc_input.wthl_sfc, shoc_input.wqw_sfc, shoc_input.uw_sfc, shoc_input.vw_sfc, // Input
- shoc_input.wtracer_sfc, shoc_input.inv_exner, shoc_input.phis, // Input
+ shoc_input.wtracer_sfc, shoc_input.inv_exner, shoc_input.phis,
+ shoc_input.shear_strain3d_components, shoc_input.shear_strain3d, // Input/Output
workspace_mgr, // Workspace Manager
shoc_input_output.host_dse, shoc_input_output.tke, shoc_input_output.thetal, shoc_input_output.qw, u_wind_s, v_wind_s, // Input/Output
shoc_input_output.wthv_sec, shoc_input_output.qtracers, shoc_input_output.tk, shoc_input_output.shoc_cldfrac, // Input/Output
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 9f5fa5f8b7ab..159265cef0d8 100644
--- a/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp
+++ b/components/eamxx/src/physics/shoc/impl/shoc_tke_impl.hpp
@@ -31,7 +31,10 @@ void Functions::shoc_tke(
const Scalar& Ckh,
const Scalar& Ckm,
const bool& shoc_1p5tke,
+ const bool& do_3d_turb,
const uview_1d& wthv_sec,
+ const uview_2d& shear_strain3d_components,
+ const uview_1d& shear_strain3d,
const uview_1d& shoc_mix,
const uview_1d& dz_zi,
const uview_1d& dz_zt,
@@ -39,6 +42,7 @@ void Functions::shoc_tke(
const uview_1d& tabs,
const uview_1d& u_wind,
const uview_1d& v_wind,
+ const uview_1d