Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d6e357e
Add shr_wtracers_check_tracer_ratios
billsacks Dec 26, 2025
00c3ad9
Add unit tests of shr_wtracers_check_tracer_ratios
billsacks Dec 29, 2025
780def8
Merge remote-tracking branch 'billsacks/water_tracers' into water_tra…
billsacks Dec 29, 2025
6132245
In the init-for-testing, set is_maintask to always true
billsacks Dec 29, 2025
13c4c52
Merge remote-tracking branch 'billsacks/water_tracers' into water_tra…
billsacks Dec 29, 2025
9d288e7
Revert "In the init-for-testing, set is_maintask to always true"
billsacks Dec 30, 2025
51c5e0f
Merge remote-tracking branch 'origin/main' into water_tracers_part2
billsacks Jan 2, 2026
efdbd47
Fix formatting of a comment
billsacks Jan 24, 2026
5d6dd22
Introduce shr_wtracers_get_bulk_fieldname subroutine
billsacks Jan 24, 2026
fa78e0a
Add a version of shr_wtracers_check_tracer_ratios for 2-d arrays
billsacks Feb 10, 2026
4952bdc
Change dimension order convention for rank-3 tracer fields
billsacks Feb 26, 2026
1af04f1
Merge branch 'main' into water_tracers_part2
billsacks Mar 3, 2026
b234928
Simplify conditional for tracer consistency check
billsacks Mar 5, 2026
cd98202
Only print water tracer information if shr_log_level > 0
billsacks Mar 31, 2026
fa24658
Add unit tests of NaN handling in water tracer checks
billsacks Apr 17, 2026
c8b712b
Add more info to shr_aborts from shr_wtracers_mod
billsacks May 13, 2026
d4c56a0
Add "only" clause to "use shr_wtracers_mod" in unit tests
billsacks May 14, 2026
c322156
CXX seems like overkill for msg variables; use CX to save some memory
billsacks May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ endif()

# Among other things, this handles the genf90 generation
add_subdirectory(src)
add_subdirectory(src/water_isotopes)

file(GLOB FSOURCES "src/*.F90" "src/water_isotopes/*.F90" "RandNum/src/*.F90" "RandNum/src/*/*.F90")
file(GLOB CSOURCES "src/*.c" "RandNum/src/*/*.c")
Expand All @@ -110,6 +111,7 @@ target_include_directories(csm_share PRIVATE ${CMAKE_BINARY_DIR})
if(UNITTESTS)
# need to turn the warning check off for pfunit
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -Wno-error ${CMAKE_Fortran_COMPILER_DIRECTIVE} -I${CMAKE_BINARY_DIR}/unittests/shr_assert_test/mod/assert/ ")
add_subdirectory(${CMAKE_SOURCE_DIR}/unit_test_stubs/util csm_share_stubs)
add_subdirectory(${CMAKE_SOURCE_DIR}/unit_test_stubs/util csm_share_stubs_util)
add_subdirectory(${CMAKE_SOURCE_DIR}/unit_test_stubs/gptl csm_share_stubs_gptl)
add_subdirectory(${CMAKE_SOURCE_DIR}/test/unit ${CMAKE_BINARY_DIR}/unittests)
endif()
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ list(APPEND share_sources
shr_mpi_mod.F90
shr_pio_mod.F90
shr_wv_sat_mod.F90
m_MergeSorts.F90)
m_MergeSorts.F90
nuopc_shr_methods.F90)

sourcelist_to_parent(share_sources)

4 changes: 4 additions & 0 deletions src/water_isotopes/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
list(APPEND share_sources
shr_wtracers_mod.F90)

sourcelist_to_parent(share_sources)
298 changes: 290 additions & 8 deletions src/water_isotopes/shr_wtracers_mod.F90

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ add_subdirectory(dynamic_vector)

add_subdirectory(shr_vmath_test)

add_subdirectory(shr_wtracers_test)

add_subdirectory(shr_wv_sat_test)

add_subdirectory(shr_precip_test)

add_subdirectory(shr_cal_test)
add_subdirectory(shr_cal_test)
29 changes: 29 additions & 0 deletions test/unit/shr_wtracers_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
set (pf_sources
test_shr_wtracers.pf
)

set(sources_needed
shr_wtracers_mod.F90
shr_assert_mod.F90
shr_infnan_mod.F90
shr_kind_mod.F90
shr_log_mod.F90
shr_strconvert_mod.F90
shr_string_mod.F90
shr_sys_mod.nompi_abortthrows.F90
shr_abort_mod.abortthrows.F90
shr_timer_mod.F90
nuopc_shr_methods.F90
gptl.F90)

extract_sources("${sources_needed}" "${share_sources}" test_sources)

add_pfunit_ctest(shr_wtracers
TEST_SOURCES "${pf_sources}"
OTHER_SOURCES "${test_sources}")

declare_generated_dependencies(shr_wtracers "${share_genf90_sources}")

target_link_libraries(shr_wtracers esmf)
# The following adds all dependencies of ESMF, including PIO, NetCDF, etc.:
target_link_libraries(shr_wtracers ESMF::ESMF)
292 changes: 292 additions & 0 deletions test/unit/shr_wtracers_test/test_shr_wtracers.pf
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
module test_shr_wtracers

! Tests of shr_wtracers_mod

use funit

use shr_wtracers_mod, only : shr_wtracers_initialized, shr_wtracers_finalize
use shr_wtracers_mod, only : shr_wtracers_init_directly_for_testing
use shr_wtracers_mod, only : shr_wtracers_check_tracer_ratios
use shr_wtracers_mod, only : WATER_SPECIES_NAME_BULK

use shr_kind_mod, only : r8=>SHR_KIND_R8
use shr_infnan_mod, only : assignment(=), nan => shr_infnan_nan
use ESMF, only : ESMF_SUCCESS

implicit none

@TestCase
type, extends(TestCase) :: TestShrWtracers
contains
procedure :: setUp
procedure :: tearDown
end type TestShrWtracers

contains

subroutine setUp(this)
class(TestShrWtracers), intent(inout) :: this
end subroutine setUp
Comment thread
nusbaume marked this conversation as resolved.

subroutine tearDown(this)
class(TestShrWtracers), intent(inout) :: this

integer :: rc

if (shr_wtracers_initialized()) then
call shr_wtracers_finalize(rc)
end if
end subroutine tearDown

! ------------------------------------------------------------------------
! Tests of shr_wtracers_check_tracer_ratios
! ------------------------------------------------------------------------

@Test
subroutine test_shr_wtracers_check_tracer_ratios_both0(this)
! The tracer ratio test should pass when both tracer and bulk are 0
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = [2._r8, 2._r8, 2._r8], &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = 0._r8
tracers(:,:) = 0._r8

! The test passes if the following call runs successfully without aborting
call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
end subroutine test_shr_wtracers_check_tracer_ratios_both0

@Test
subroutine test_shr_wtracers_check_tracer_ratios_correct(this)
! The tracer ratio test should pass when the tracer ratios are correct
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do

! The test passes if the following call runs successfully without aborting
call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
end subroutine test_shr_wtracers_check_tracer_ratios_correct

@Test
subroutine test_shr_wtracers_check_tracer_ratios_differ(this)
! The tracer ratio test should fail when some tracer ratio differs from expected
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do
tracers(2,3) = tracers(2,3) * 1.1_r8

call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
@assertExceptionRaised("ABORTED: shr_wtracers_check_tracer_ratios ERROR: tracer does not agree with bulk water for variable 'test', tracer 'tracer2', at index 3")
end subroutine test_shr_wtracers_check_tracer_ratios_differ

@Test
subroutine test_shr_wtracers_check_tracer_ratios_tracer0(this)
! The tracer ratio test should fail when some tracer value is zero despite non-zero bulk
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do
tracers(2,3) = 0._r8

call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
@assertExceptionRaised("ABORTED: shr_wtracers_check_tracer_ratios ERROR: tracer does not agree with bulk water for variable 'test', tracer 'tracer2', at index 3")
end subroutine test_shr_wtracers_check_tracer_ratios_tracer0

@Test
subroutine test_shr_wtracers_check_tracer_ratios_bulk0(this)
! The tracer ratio test should fail when some bulk value is zero despite non-zero tracer
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do
bulk(3) = 0._r8

call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
@assertExceptionRaised("ABORTED: shr_wtracers_check_tracer_ratios ERROR: tracer does not agree with bulk water for variable 'test', tracer 'tracer1', at index 3")
end subroutine test_shr_wtracers_check_tracer_ratios_bulk0

@Test
subroutine test_shr_wtracers_check_tracer_ratios_bothnan(this)
! The tracer ratio test should pass when both tracer and bulk are NaN
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do
bulk(3) = nan
tracers(:,3) = nan

! The test passes if the following call runs successfully without aborting
call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
end subroutine test_shr_wtracers_check_tracer_ratios_bothnan

@Test
subroutine test_shr_wtracers_check_tracer_ratios_tracernan(this)
! The tracer ratio test should fail when some tracer value is NaN despite non-NaN bulk
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(4)
real(r8) :: tracers(3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(:) = [1._r8, 2._r8, 3._r8, 4._r8]
do i = 1, 3
tracers(i,:) = ratios(i) * bulk(:)
end do
tracers(2,3) = nan

call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
@assertExceptionRaised("ABORTED: shr_wtracers_check_tracer_ratios ERROR: tracer does not agree with bulk water for variable 'test', tracer 'tracer2', at index 3")
end subroutine test_shr_wtracers_check_tracer_ratios_tracernan

! ------------------------------------------------------------------------
! Tests of shr_wtracers_check_tracer_ratios with 2-d bulk arrays
! ------------------------------------------------------------------------

@Test
subroutine test_shr_wtracers_check_tracer_ratios_2d_correct(this)
! The tracer ratio test should pass when the tracer ratios are correct (2-d bulk)
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(2, 4)
real(r8) :: tracers(2, 3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i, j

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(1,:) = [1._r8, 2._r8, 3._r8, 4._r8]
bulk(2,:) = [5._r8, 6._r8, 7._r8, 8._r8]
do i = 1, 3
do j = 1, 2
tracers(j,i,:) = ratios(i) * bulk(j,:)
end do
end do

! The test passes if the following call runs successfully without aborting
call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
end subroutine test_shr_wtracers_check_tracer_ratios_2d_correct

@Test
subroutine test_shr_wtracers_check_tracer_ratios_2d_differ(this)
! The tracer ratio test should fail when some tracer ratio differs from expected (2-d bulk)
class(TestShrWtracers), intent(inout) :: this
integer :: rc
real(r8) :: bulk(2, 4)
real(r8) :: tracers(2, 3, 4)
real(r8), parameter :: ratios(3) = [2._r8, 3._r8, 4._r8]
integer :: i, j

call shr_wtracers_init_directly_for_testing( &
water_tracer_names = ["tracer1", "tracer2", "tracer3"], &
water_tracer_species = [WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK, WATER_SPECIES_NAME_BULK], &
water_tracer_initial_ratios = ratios, &
rc = rc)
@assertEqual(ESMF_SUCCESS, rc)

bulk(1,:) = [1._r8, 2._r8, 3._r8, 4._r8]
bulk(2,:) = [5._r8, 6._r8, 7._r8, 8._r8]
do i = 1, 3
do j = 1, 2
tracers(j,i,:) = ratios(i) * bulk(j,:)
end do
end do
tracers(2,2,3) = tracers(2,2,3) * 1.1_r8

call shr_wtracers_check_tracer_ratios(tracers, bulk, "test")
@assertExceptionRaised("ABORTED: shr_wtracers_check_tracer_ratios ERROR: tracer does not agree with bulk water for variable 'test', tracer 'tracer2', at index 3")
end subroutine test_shr_wtracers_check_tracer_ratios_2d_differ
Comment thread
nusbaume marked this conversation as resolved.

end module test_shr_wtracers
6 changes: 6 additions & 0 deletions unit_test_stubs/gptl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# In the real build, gptl would be a separate library. Here, for simplicity, we add the
# stub version of gptl to the share library.
list(APPEND share_sources
gptl.F90)

sourcelist_to_parent(share_sources)
3 changes: 3 additions & 0 deletions unit_test_stubs/gptl/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This directory contains stubs of the gptl timing library. In real builds, we typically
link against a pre-built gptl. Here, for simplicity, we facilitate including the necessary
stubs in the share library so we can avoid linking against an actual gptl.
12 changes: 12 additions & 0 deletions unit_test_stubs/gptl/gptl.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
! Stubs of the gptl timing library

! Note that in the real gptl, these are defined in C. Here we define them in Fortran for
! simplicity, but keep them outside of a module for consistency with their usage (via an
! "external" statement rather than a "use" statement).

function GPTLprint_memusage(msg) result(ierr)
character(len=*), intent(in) :: msg
integer :: ierr

ierr = 0
end function GPTLprint_memusage
Loading