diff --git a/.gitmodules b/.gitmodules index 71aab550ff..73b0434577 100644 --- a/.gitmodules +++ b/.gitmodules @@ -27,8 +27,11 @@ # [submodule "fates"] path = src/fates -url = https://github.com/NGEET/fates -fxtag = sci.1.87.2_api.41.0.0 +#url = https://github.com/NGEET/fates +url = https://github.com/ekluzek/fates +#fxtag = sci.1.87.2_api.41.0.0 +#fxtag = add_file_to_cmake_unittest_list +fxtag = fa4016d1b59ffdb509a7833da113a88872a8a56c fxrequired = AlwaysRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NGEET/fates diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index 9562cca9bd..c88af453b5 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -5315,6 +5315,7 @@ sub write_output_files { push @groups, "clm_canopy_inparm"; push @groups, "prigentroughness"; push @groups, "zendersoilerod"; + push @groups, "for_testing_options"; if (remove_leading_and_trailing_quotes($nl->get_value('snow_cover_fraction_method')) eq 'SwensonLawrence2012') { push @groups, "scf_swenson_lawrence_2012_inparm"; } diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 69a243bd27..2b5b4f2e99 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1259,12 +1259,38 @@ Whether to use subgrid fluxes for snow Whether snow on the vegetation canopy affects the radiation/albedo calculations + + + + + + +For testing whether to bypass the rest of the initialization after the self test driver is run + + + +For testing whether to bypass most of the run phase other than the clock advance + + + +Whether to exit early after the initialization self tests are run. This is typically only used in automated tests. + + + group="for_testing_options" > Whether to run some tests of ncdio_pio as part of the model run. This is typically only used in automated tests. + +Whether to run some tests of decompInit (to get the gridcell to MPI task decomposition) as part of the model run. This is +typically only used in automated tests. + + If true, allocate memory for and use a second crop grain pool. This is diff --git a/cime_config/buildlib b/cime_config/buildlib index a4b853924e..3ce5080dc4 100755 --- a/cime_config/buildlib +++ b/cime_config/buildlib @@ -135,6 +135,7 @@ def _main_func(): os.path.join(lnd_root, "src", "dyn_subgrid"), os.path.join(lnd_root, "src", "init_interp"), os.path.join(lnd_root, "src", "self_tests"), + os.path.join(lnd_root, "src", "unit_test_shr"), os.path.join(lnd_root, "src", "fates"), os.path.join(lnd_root, "src", "fates", "main"), os.path.join(lnd_root, "src", "fates", "biogeophys"), diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index f8f05bf5f1..34c3729b18 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -381,13 +381,6 @@ - - - FAIL - #3316 - - - diff --git a/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm index c2a2d14793..573df5c02e 100644 --- a/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm @@ -1,3 +1,6 @@ +! Exit early and bypass the run phase +for_testing_exit_after_self_tests = .true. + ! Turn off history, restarts, and output hist_empty_htapes = .true. use_noio = .true. diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands new file mode 100755 index 0000000000..9383f70de0 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands @@ -0,0 +1,9 @@ +#!/bin/bash +./xmlchange CLM_FORCE_COLDSTART="on" + +# We use this testmod in a _Ln1 test; this requires forcing the ROF coupling frequency to every time step +./xmlchange ROF_NCPL=48 + +# Restarts aren't allowed for these tests, and turn off CPL history +./xmlchange REST_OPTION="never" +./xmlchange HIST_OPTION="never" diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm index 9e8e0fcd04..c1ac6a7174 100644 --- a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm @@ -1,5 +1,15 @@ +! Bypass as much of the init phase as can be done +! Bypassing the run phase already was inherited from the for_testing_fastsetup_bypassrun testmod +for_testing_bypass_init = .true. + +! Turn on some of the self tests for_testing_run_ncdiopio_tests = .true. +for_testing_run_decomp_init_tests = .true. ! Turn off history, restarts, and output hist_empty_htapes = .true. use_noio = .true. +for_testing_run_decomp_init_tests = .true. + +! Exit initialization phase after the self tests +for_testing_bypass_init = .true. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2682775ca5..ada073cba8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,41 +59,29 @@ add_subdirectory(${CLM_ROOT}/src/main clm_main) add_subdirectory(${CLM_ROOT}/src/init_interp clm_init_interp) add_subdirectory(${CLM_ROOT}/src/self_tests clm_self_tests) +# Add FATES source directories +add_subdirectory(${CLM_ROOT}/src/fates/main fates_main) +add_subdirectory(${CLM_ROOT}/src/fates/biogeochem fates_biogeochem) +add_subdirectory(${CLM_ROOT}/src/fates/biogeophys fates_biogeophys) +add_subdirectory(${CLM_ROOT}/src/fates/parteh fates_parteh) +add_subdirectory(${CLM_ROOT}/src/fates/fire fates_fire) +add_subdirectory(${CLM_ROOT}/src/fates/radiation fates_radiation) + # Add general unit test directories (stubbed out files, etc.) add_subdirectory(unit_test_stubs) add_subdirectory(unit_test_shr) -# Remove some things from share_sources -# -# TODO: this should be moved into a general-purpose function in Sourcelist_utils. -# Then each removal could be replaced with a single call, like: -# remove_source_file(${share_sources} "shr_mpi_mod.F90") -foreach (sourcefile ${share_sources}) - # Remove shr_mpi_mod from share_sources. - # This is needed because we want to use the mock shr_mpi_mod in place of the real one - string(REGEX MATCH "shr_mpi_mod.F90" match_found ${sourcefile}) - if(match_found) - list(REMOVE_ITEM share_sources ${sourcefile}) - endif() - - # Remove shr_pio_mod from share_sources. This is needed to avoid an explicit dependency - # on PIO. This removal is needed on some systems but not on others: the unit test build - # works without this removal on a Mac with a pre-built PIO library, but failed (with - # error message, "Cannot open module file 'pio.mod'") on a Mac without a pre-built PIO - # (where ESMF was built with its internal PIO). - string(REGEX MATCH "shr_pio_mod.F90" match_found ${sourcefile}) - if(match_found) - list(REMOVE_ITEM share_sources ${sourcefile}) - endif() -endforeach() - # Build libraries containing stuff needed for the unit tests. # Eventually, these add_library calls should probably be distributed into the correct location, rather than being in this top-level CMakeLists.txt file. add_library(csm_share ${share_sources} ${drv_sources_needed}) declare_generated_dependencies(csm_share "${share_genf90_sources}") add_library(clm ${clm_sources}) +add_library(fates ${fates_sources}) declare_generated_dependencies(clm "${clm_genf90_sources}") -add_dependencies(clm csm_share esmf) +# Add explicit dependencies to lower level libraries, for each library +add_dependencies(csm_share esmf) +add_dependencies(clm fates csm_share esmf) +add_dependencies(fates csm_share esmf) # We need to look for header files here, in order to pick up shr_assert.h include_directories(${CLM_ROOT}/share/include) diff --git a/src/cpl/nuopc/lnd_comp_nuopc.F90 b/src/cpl/nuopc/lnd_comp_nuopc.F90 index 3db987f2fa..3e0fc3c2f7 100644 --- a/src/cpl/nuopc/lnd_comp_nuopc.F90 +++ b/src/cpl/nuopc/lnd_comp_nuopc.F90 @@ -49,6 +49,7 @@ module lnd_comp_nuopc use lnd_import_export , only : advertise_fields, realize_fields, import_fields, export_fields use lnd_comp_shr , only : mesh, model_meshfile, model_clock use perf_mod , only : t_startf, t_stopf, t_barrierf + use SelfTestDriver , only : for_testing_exit_after_self_tests implicit none private ! except @@ -351,6 +352,8 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) use lnd_set_decomp_and_domain , only : lnd_set_decomp_and_domain_from_readmesh use lnd_set_decomp_and_domain , only : lnd_set_mesh_for_single_column use lnd_set_decomp_and_domain , only : lnd_set_decomp_and_domain_for_single_column + use SelfTestDriver , only : for_testing_bypass_init_after_self_tests, & + for_testing_exit_after_self_tests ! input/output variables type(ESMF_GridComp) :: gcomp @@ -500,6 +503,12 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) else single_column = .false. end if + !if ( for_testing_exit_after_self_tests) then + ! ******************* + ! *** RETURN HERE *** + ! ******************* + !RETURN + !end if !---------------------------------------------------------------------------- ! Reset shr logging to my log file @@ -676,14 +685,19 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) call t_startf('clm_init2') call initialize2(ni, nj, currtime) call t_stopf('clm_init2') + if (for_testing_exit_after_self_tests) then + RETURN + end if !-------------------------------- ! Create land export state !-------------------------------- + if ( .not. for_testing_bypass_init_after_self_tests() ) then call get_proc_bounds(bounds) call export_fields(gcomp, bounds, glc_present, rof_prognostic, & water_inst%waterlnd2atmbulk_inst, lnd2atm_inst, lnd2glc_inst, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + end if ! Set scalars in export state call State_SetScalar(dble(ldomain%ni), flds_scalar_index_nx, exportState, & @@ -731,6 +745,7 @@ subroutine ModelAdvance(gcomp, rc) use clm_instMod , only : water_inst, atm2lnd_inst, glc2lnd_inst, lnd2atm_inst, lnd2glc_inst use decompMod , only : bounds_type, get_proc_bounds use clm_driver , only : clm_drv + use SelfTestDriver, only : for_testing_bypass_init_after_self_tests ! input/output variables type(ESMF_GridComp) :: gcomp @@ -786,6 +801,9 @@ subroutine ModelAdvance(gcomp, rc) if (single_column .and. .not. scol_valid) then RETURN end if + !if (for_testing_exit_after_self_tests) then + ! RETURN + !end if !$ call omp_set_num_threads(nthrds) @@ -818,16 +836,20 @@ subroutine ModelAdvance(gcomp, rc) flds_scalar_index_nextsw_cday, nextsw_cday, & flds_scalar_name, flds_scalar_num, rc) - ! Get proc bounds - call get_proc_bounds(bounds) - !-------------------------------- ! Unpack import state !-------------------------------- + if ( .not. for_testing_bypass_init_after_self_tests() ) then + ! Get proc bounds for both import and export + call get_proc_bounds(bounds) + + call t_startf ('lc_lnd_import') call import_fields( gcomp, bounds, glc_present, rof_prognostic, & atm2lnd_inst, glc2lnd_inst, water_inst%wateratm2lndbulk_inst, rc ) if (ChkErr(rc,__LINE__,u_FILE_u)) return + call t_stopf ('lc_lnd_import') + end if !-------------------------------- ! Run model @@ -917,9 +939,13 @@ subroutine ModelAdvance(gcomp, rc) ! Pack export state !-------------------------------- + if ( .not. for_testing_bypass_init_after_self_tests() ) then + call t_startf ('lc_lnd_export') call export_fields(gcomp, bounds, glc_present, rof_prognostic, & water_inst%waterlnd2atmbulk_inst, lnd2atm_inst, lnd2glc_inst, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + call t_stopf ('lc_lnd_export') + end if !-------------------------------- ! Advance ctsm time step @@ -1009,6 +1035,7 @@ subroutine ModelSetRunClock(gcomp, rc) rc = ESMF_SUCCESS call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO) if (.not. scol_valid) return + !if (for_testing_exit_after_self_tests) return ! query the Component for its clocks call NUOPC_ModelGet(gcomp, driverClock=dclock, modelClock=mclock, rc=rc) @@ -1320,6 +1347,9 @@ subroutine CheckImport(gcomp, rc) if (single_column .and. .not. scol_valid) then RETURN end if + !if (for_testing_exit_after_self_tests) then + !RETURN + !end if ! The remander of this should be equivalent to the NUOPC internal routine ! from NUOPC_ModeBase.F90 diff --git a/src/fates b/src/fates index c1dfc21c50..fa4016d1b5 160000 --- a/src/fates +++ b/src/fates @@ -1 +1 @@ -Subproject commit c1dfc21c505b5c8b29d4592b7d4a5e058239f6fb +Subproject commit fa4016d1b59ffdb509a7833da113a88872a8a56c diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index fc324efeb9..3884e0ba36 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -19,6 +19,7 @@ list(APPEND clm_sources clm_varsur.F90 column_varcon.F90 decompMod.F90 + decompInitMod.F90 filterColMod.F90 FireMethodType.F90 glc2lndMod.F90 @@ -30,6 +31,7 @@ list(APPEND clm_sources ncdio_utils.F90 organicFileMod.F90 paramUtilMod.F90 + subgridMod.F90 subgridAveMod.F90 subgridWeightsMod.F90 surfrdUtilsMod.F90 diff --git a/src/main/clm_driver.F90 b/src/main/clm_driver.F90 index 3a47a7eed3..279154f52c 100644 --- a/src/main/clm_driver.F90 +++ b/src/main/clm_driver.F90 @@ -85,6 +85,7 @@ module clm_driver use clm_instMod use SoilMoistureStreamMod , only : PrescribedSoilMoistureInterp, PrescribedSoilMoistureAdvance use SoilBiogeochemDecompCascadeConType , only : no_soil_decomp, decomp_method + use SelfTestDriver , only : for_testing_bypass_run_except_clock_advance ! ! !PUBLIC TYPES: implicit none @@ -165,6 +166,7 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro ! CalcIrrigationNeeded. Simply declaring this variable makes the ICE go away. real(r8), allocatable :: dummy1_to_make_pgi_happy(:) !----------------------------------------------------------------------- + if ( for_testing_bypass_run_except_clock_advance() ) return ! Determine processor bounds and clumps for this processor @@ -1576,6 +1578,8 @@ subroutine clm_drv_init(bounds, & integer :: fp, fc ! filter indices !----------------------------------------------------------------------- + if ( for_testing_bypass_run_except_clock_advance() ) return + associate( & snl => col%snl , & ! Input: [integer (:) ] number of snow layers @@ -1657,6 +1661,7 @@ subroutine clm_drv_patch2col (bounds, & ! !LOCAL VARIABLES: integer :: c,fc ! indices ! ----------------------------------------------------------------- + if ( for_testing_bypass_run_except_clock_advance() ) return ! Note: lake points are excluded from many of the following ! averages. For some fields, this is because the field doesn't @@ -1752,6 +1757,8 @@ subroutine write_diagnostic (bounds, nstep, lnd2atm_inst) integer :: status(MPI_STATUS_SIZE) ! mpi status !------------------------------------------------------------------------ + if ( for_testing_bypass_run_except_clock_advance() ) return + call get_proc_global(ng=numg) if (masterproc) then diff --git a/src/main/clm_initializeMod.F90 b/src/main/clm_initializeMod.F90 index 1c26b55cfd..0ffa7737a8 100644 --- a/src/main/clm_initializeMod.F90 +++ b/src/main/clm_initializeMod.F90 @@ -30,7 +30,7 @@ module clm_initializeMod use CLMFatesInterfaceMod , only : CLMFatesGlobals1,CLMFatesGlobals2 use CLMFatesInterfaceMod , only : CLMFatesTimesteps use dynSubgridControlMod , only : dynSubgridControl_init, get_reset_dynbal_baselines - use SelfTestDriver , only : self_test_driver + use SelfTestDriver , only : self_test_driver, for_testing_bypass_init_after_self_tests use SoilMoistureStreamMod , only : PrescribedSoilMoistureInit use clm_instMod ! @@ -67,6 +67,7 @@ subroutine initialize1(dtime) use SoilBiogeochemDecompCascadeConType , only : decomp_cascade_par_init use CropReprPoolsMod , only: crop_repr_pools_init use HillslopeHydrologyMod, only: hillslope_properties_init + use SelfTestDriver , only: self_test_readnml ! ! !ARGUMENTS integer, intent(in) :: dtime ! model time step (seconds) @@ -102,6 +103,8 @@ subroutine initialize1(dtime) call surfrd_get_num_patches(fsurdat, actual_maxsoil_patches, actual_numpft, actual_numcft) call surfrd_get_nlevurb(fsurdat, actual_nlevurb) + call self_test_readnml( NLFilename ) + ! If fates is on, we override actual_maxsoil_patches. FATES dictates the ! number of patches per column. We still use numcft from the surface ! file though... @@ -182,6 +185,7 @@ subroutine initialize2(ni,nj, currtime) use FATESFireFactoryMod , only : scalar_lightning use dynFATESLandUseChangeMod , only : dynFatesLandUseInit use HillslopeHydrologyMod , only : InitHillslope + use SelfTestDriver , only : for_testing_bypass_init_after_self_tests ! ! !ARGUMENTS integer, intent(in) :: ni, nj ! global grid sizes @@ -333,6 +337,7 @@ subroutine initialize2(ni,nj, currtime) ! Run any requested self-tests call self_test_driver(bounds_proc) + if ( .not. for_testing_bypass_init_after_self_tests() )then ! Deallocate surface grid dynamic memory for variables that aren't needed elsewhere. ! Some things are kept until the end of initialize2; urban_valid is kept through the ! end of the run for error checking, pct_urban_max is kept through the end of the run @@ -349,8 +354,9 @@ subroutine initialize2(ni,nj, currtime) allocate(nutrient_competition_method, & source=create_nutrient_competition_method(bounds_proc)) call readParameters(photosyns_inst) - + end if ! End of bypass + ! Self test skipping should still do the time manager initialization ! Initialize time manager if (nsrest == nsrStartup) then call timemgr_init() @@ -376,6 +382,7 @@ subroutine initialize2(ni,nj, currtime) call t_stopf('clm_init2_part2') call t_startf('clm_init2_part3') + if ( .not. for_testing_bypass_init_after_self_tests() )then ! Initialize Balance checking (after time-manager) call BalanceCheckInit() @@ -423,7 +430,9 @@ subroutine initialize2(ni,nj, currtime) call SnowAge_init( ) ! SNICAR aging parameters: ! Print history field info to standard out - call hist_printflds() + if ( .not. use_noio )then + call hist_printflds() + end if ! Initializate dynamic subgrid weights (for prescribed transient Patches, CNDV ! and/or dynamic landunits); note that these will be overwritten in a restart run @@ -508,6 +517,7 @@ subroutine initialize2(ni,nj, currtime) if (nsrest == nsrContinue ) then call htapes_fieldlist() end if + end if ! End of bypass ! Read restart/initial info is_cold_start = .false. @@ -601,6 +611,8 @@ subroutine initialize2(ni,nj, currtime) call t_stopf('clm_init2_init_interp') end if + if ( .not. for_testing_bypass_init_after_self_tests() )then + ! If requested, reset dynbal baselines ! This needs to happen after reading the restart file (including after reading the ! interpolated restart file, if applicable). @@ -759,6 +771,7 @@ subroutine initialize2(ni,nj, currtime) water_inst%waterdiagnosticbulk_inst, canopystate_inst, & soilstate_inst, soilbiogeochem_carbonflux_inst) end if + end if ! end of bypass ! topo_glc_mec was allocated in initialize1, but needed to be kept around through ! initialize2 because it is used to initialize other variables; now it can be deallocated diff --git a/src/main/controlMod.F90 b/src/main/controlMod.F90 index 089503dc8b..4a956e33b2 100644 --- a/src/main/controlMod.F90 +++ b/src/main/controlMod.F90 @@ -210,7 +210,7 @@ subroutine control_init(dtime) snow_thermal_cond_method, snow_thermal_cond_glc_method, & snow_thermal_cond_lake_method, snow_cover_fraction_method, & irrigate, run_zero_weight_urban, all_active, & - crop_fsat_equals_zero, for_testing_run_ncdiopio_tests, & + crop_fsat_equals_zero, & for_testing_use_second_grain_pool, for_testing_use_repr_structure_pool, & for_testing_no_crop_seed_replenishment, & z0param_method, use_z0m_snowmelt @@ -766,9 +766,6 @@ subroutine control_spmd() ! Crop saturated excess runoff call mpi_bcast(crop_fsat_equals_zero, 1, MPI_LOGICAL, 0, mpicom, ier) - ! Whether to run tests of ncdio_pio - call mpi_bcast(for_testing_run_ncdiopio_tests, 1, MPI_LOGICAL, 0, mpicom, ier) - ! Various flags used for testing infrastructure for having multiple crop reproductive pools call mpi_bcast(for_testing_use_second_grain_pool, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast(for_testing_use_repr_structure_pool, 1, MPI_LOGICAL, 0, mpicom, ier) diff --git a/src/self_tests/Assertions.F90.in b/src/self_tests/Assertions.F90.in index 2a4c8cccc6..4a86929a8a 100644 --- a/src/self_tests/Assertions.F90.in +++ b/src/self_tests/Assertions.F90.in @@ -17,6 +17,12 @@ module Assertions public :: assert_equal interface assert_equal + !TYPE double,int,logical + module procedure assert_equal_0d_{TYPE} + + !TYPE text + module procedure assert_equal_0d_{TYPE} + !TYPE double,int,logical module procedure assert_equal_1d_{TYPE} @@ -30,6 +36,8 @@ module Assertions interface vals_are_equal !TYPE double,int,logical module procedure vals_are_equal_{TYPE} + !TYPE text + module procedure vals_are_equal_{TYPE} end interface vals_are_equal contains @@ -75,6 +83,60 @@ contains end subroutine assert_equal_1d_{TYPE} + !----------------------------------------------------------------------- + !TYPE double,int,logical + subroutine assert_equal_0d_{TYPE}(expected, actual, msg, abs_tol) + ! + ! !DESCRIPTION: + ! Assert scalar values are equal + ! + ! !ARGUMENTS: + {VTYPE}, intent(in) :: expected + {VTYPE}, intent(in) :: actual + character(len=*), intent(in) :: msg + + ! absolute tolerance; if not specified, require exact equality; ignored for logicals + real(r8), intent(in), optional :: abs_tol + ! + ! !LOCAL VARIABLES: + integer :: i + + character(len=*), parameter :: subname = 'assert_equal_0d_{TYPE}' + !----------------------------------------------------------------------- + + if (.not. vals_are_equal(actual, expected, abs_tol)) then + write(iulog,*) 'ERROR in assert_equal: ', msg + write(iulog,*) 'Actual : ', actual + write(iulog,*) 'Expected: ', expected + call endrun('ERROR in assert_equal') + end if + + end subroutine assert_equal_0d_{TYPE} + + !----------------------------------------------------------------------- + !TYPE text + subroutine assert_equal_0d_{TYPE}(expected, actual, msg) + ! + ! !DESCRIPTION: + ! Assert scalar values are equal + ! + ! !ARGUMENTS: + {VTYPE}, intent(in) :: expected + {VTYPE}, intent(in) :: actual + character(len=*), intent(in) :: msg + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + if (.not. vals_are_equal(actual, expected)) then + write(iulog,*) 'ERROR in assert_equal: ', msg + write(iulog,*) 'Actual : ', actual + write(iulog,*) 'Expected: ', expected + call endrun('ERROR in assert_equal') + end if + + end subroutine assert_equal_0d_{TYPE} + !----------------------------------------------------------------------- !TYPE double,int,logical subroutine assert_equal_2d_{TYPE}(expected, actual, msg, abs_tol) @@ -198,4 +260,23 @@ contains end function vals_are_equal_{TYPE} + !----------------------------------------------------------------------- + !TYPE text + function vals_are_equal_{TYPE}(actual, expected) result(vals_equal) + ! + ! !DESCRIPTION: + ! Returns true if actual is the same as expected, false otherwise + ! + ! !ARGUMENTS: + logical :: vals_equal ! function result + {VTYPE}, intent(in) :: actual + {VTYPE}, intent(in) :: expected + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + vals_equal = actual == expected + + end function vals_are_equal_{TYPE} + end module Assertions diff --git a/src/self_tests/CMakeLists.txt b/src/self_tests/CMakeLists.txt index 3a454aab2b..fc78041c6b 100644 --- a/src/self_tests/CMakeLists.txt +++ b/src/self_tests/CMakeLists.txt @@ -3,6 +3,9 @@ set(genf90_files Assertions.F90.in +) +list(APPEND clm_sources + TestDecompInit.F90 ) process_genf90_source_list("${genf90_files}" ${CMAKE_CURRENT_BINARY_DIR} clm_genf90_sources) diff --git a/src/self_tests/SelfTestDriver.F90 b/src/self_tests/SelfTestDriver.F90 index d109a27827..eb391c5ced 100644 --- a/src/self_tests/SelfTestDriver.F90 +++ b/src/self_tests/SelfTestDriver.F90 @@ -6,9 +6,10 @@ module SelfTestDriver ! ! See the README file in this directory for a high-level overview of these self-tests. - use clm_varctl, only : for_testing_run_ncdiopio_tests use decompMod, only : bounds_type use TestNcdioPio, only : test_ncdio_pio + use abortutils, only : endrun + use clm_varctl, only : iulog implicit none private @@ -16,7 +17,17 @@ module SelfTestDriver ! Public routines - public :: self_test_driver + public :: self_test_driver ! Run the self-tests asked for + public :: self_test_readnml ! Read in the general self testing options for overall code flow + public :: for_testing_bypass_init_after_self_tests ! For testing bypass the rest of the initialization after the self test driver was run + public :: for_testing_bypass_run_except_clock_advance ! For testing bypass most of the run phase other than the clock advance + + ! Private module data + logical :: for_testing_bypass_init ! For testing bypass the initialization phase after the self-test driver + logical :: for_testing_bypass_run ! For testing bypass most of the run phase except the time advance + logical :: for_testing_run_ncdiopio_tests ! true => run tests of ncdio_pio + logical :: for_testing_run_decomp_init_tests ! true => run tests of decompInit + logical, public :: for_testing_exit_after_self_tests ! true => exit after running self tests character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -32,18 +43,144 @@ subroutine self_test_driver(bounds) ! This subroutine should be called all the time, but each set of self tests is only ! run if the appropriate flag is set. ! + ! !USES: + use decompMod, only : bounds_type + use TestNcdioPio, only : test_ncdio_pio + use ESMF, only : ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_Finalize + use shr_sys_mod, only : shr_sys_flush + use spmdMod, only : masterproc ! !ARGUMENTS: type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: character(len=*), parameter :: subname = 'self_test_driver' + integer :: ntests = 0 !----------------------------------------------------------------------- + ! Just return if no tests are asked for... + if (.not. for_testing_run_ncdiopio_tests .and .not. for_testing_run_decomp_init_tests) then + RETURN + end if + + if ( masterproc ) then + write(iulog,*) '-------------------------------' + write(iulog,*) '----- Starting self tests -----' + write(iulog,*) '-------------------------------' + call shr_sys_flush(iulog) + end if if (for_testing_run_ncdiopio_tests) then + ntests = ntests + 1 call test_ncdio_pio(bounds) end if + if (for_testing_run_decomp_init_tests) then + ntests = ntests + 1 + call test_decomp_init() + end if + if ( masterproc ) then + write(iulog,*) '-------------------------------' + write(iulog,*) '----- Ending self tests -------' + write(iulog,*) '-------------------------------' + call shr_sys_flush(iulog) + end if + if (for_testing_exit_after_self_tests) then + ! Print out some messaging if we are exiting after self tests. + if ( masterproc ) then + if ( ntests == 0 )then + write(iulog,*) 'WARNING: You are exiting after self tests were run -- but no self tests were run.' + else + write(iulog,*) 'Exiting after running ', ntests, ' suite of self tests.' + end if + call shr_sys_flush(iulog) + end if + end if end subroutine self_test_driver + !----------------------------------------------------------------------- + subroutine self_test_readnml(NLFileName) + ! + ! !DESCRIPTION: + ! Namelist read for the self-test driver. This includes bypass options + ! that will be used in other parts of the code to bypass bits of the code + ! for testing purposes. + ! + ! !USES: + use shr_nl_mod , only : shr_nl_find_group_name + use spmdMod, only : masterproc, mpicom + use shr_mpi_mod, only : shr_mpi_bcast + use clm_varctl, only : iulog + ! + ! !ARGUMENTS: + character(len=*), intent(in) :: NLFilename ! Namelist filename + ! + ! !LOCAL VARIABLES: + integer :: ierr ! error code + integer :: unitn ! unit for namelist file + + ! Namelist name: this has to be matched with the name in the read stqatement + character(len=*), parameter :: nmlname = 'for_testing_options' + !----------------------------------------------------------------------- + + namelist /for_testing_options/ for_testing_bypass_init, for_testing_bypass_run, & + for_testing_run_ncdiopio_tests, for_testing_run_decomp_init_tests, & + for_testing_exit_after_self_tests + + ! Initialize options to default values, in case they are not specified in + ! the namelist + + if (masterproc) then + write(iulog,*) 'Read in '//nmlname//' namelist' + open(newunit=unitn, status='old', file=NLFilename) + call shr_nl_find_group_name(unitn, nmlname, status=ierr) + if (ierr == 0) then + read(unit=unitn, nml=for_testing_options, iostat=ierr) + if (ierr /= 0) then + call endrun(msg="ERROR reading "//nmlname//"namelist", file=sourcefile, line=__LINE__) + end if + else + call endrun(msg="ERROR finding "//nmlname//"namelist", file=sourcefile, line=__LINE__) + end if + close(unitn) + end if + + call shr_mpi_bcast (for_testing_bypass_init, mpicom) + call shr_mpi_bcast (for_testing_bypass_run, mpicom) + call shr_mpi_bcast(for_testing_run_ncdiopio_tests, mpicom) + call shr_mpi_bcast(for_testing_run_decomp_init_tests, mpicom) + call shr_mpi_bcast(for_testing_exit_after_self_tests, mpicom) + + if (masterproc) then + write(iulog,*) ' ' + write(iulog,*) nmlname//' settings:' + write(iulog,nml=for_testing_options) + write(iulog,*) ' ' + end if + + end subroutine self_test_readnml + + !----------------------------------------------------------------------- + + logical function for_testing_bypass_init_after_self_tests() + ! Determine if should exit initialization early after having run the self tests + if ( for_testing_bypass_init ) then + for_testing_bypass_init_after_self_tests = .true. + else + for_testing_bypass_init_after_self_tests = .false. + end if + end function for_testing_bypass_init_after_self_tests + + !----------------------------------------------------------------------- + + logical function for_testing_bypass_run_except_clock_advance() + ! Determine if should skip most of the run phase other than the clock advance + if ( for_testing_bypass_init ) then + for_testing_bypass_run_except_clock_advance = .true. + else + for_testing_bypass_run_except_clock_advance = .false. + end if + end function for_testing_bypass_run_except_clock_advance + + !----------------------------------------------------------------------- + end module SelfTestDriver diff --git a/src/self_tests/TestDecompInit.F90 b/src/self_tests/TestDecompInit.F90 new file mode 100644 index 0000000000..80cd089067 --- /dev/null +++ b/src/self_tests/TestDecompInit.F90 @@ -0,0 +1,264 @@ +module TestDecompInit + + ! ------------------------------------------------------------------------ + ! !DESCRIPTION: + ! This module contains tests of decomp_init + +#include "shr_assert.h" + use shr_kind_mod, only : r8 => shr_kind_r8, CX => shr_kind_cx + use Assertions, only : assert_equal + use clm_varctl, only : iulog + use abortutils, only : endrun + use spmdMod, only : masterproc, npes, iam + use decompInitMod, only : decompInit_lnd, clump_pproc, decompInit_clumps + use decompMod + use glcBehaviorMod, only : glc_behavior_type + + implicit none + private + save + + ! Public routines + + public :: test_decomp_init + public :: setup + public :: clean + + ! Module data used in various tests + + ! Make the size of the test grid 384 so that it can be divided by 128 or 48 + ! for the number of tasks per node on Derecho or Izumi. + integer, public, parameter :: ni = 16, nj = 24 + integer, public :: amask(ni*nj) + + integer :: default_npes + integer :: default_clump_pproc + + type(glc_behavior_type), target, public :: glc_behavior + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + +contains + + !----------------------------------------------------------------------- + subroutine test_decomp_init() + ! + ! !DESCRIPTION: + ! Drive tests of decomp_init + ! + ! NOTE(wjs, 2020-10-15) Currently, endrun is called when any test assertion fails. I + ! thought about changing this so that, instead, a counter is incremented for each + ! failure, then at the end of the testing (in the higher-level self-test driver), + ! endrun is called if this counter is greater than 0. The benefit of this is that we'd + ! see all test failures, not just the first failure. To do that, we'd need to change + ! the assertions here to increment a counter rather than aborting. However, I'm not + ! spending the time to make this change for now because (1) I'm not sure how much + ! value we'd get from it; (2) even if we made that change, it's still very possible + ! for test code to abort for reasons other than assertions, if something goes wrong + ! inside decomp_init or pio; and (3) some tests here are dependent on earlier tests (for + ! example, the reads depend on the writes having worked), so a failure in an early + ! phase could really muck things up for later testing phases. Migrating to a + ! pFUnit-based unit test would solve this problem, since each pFUnit test is + ! independent, though would prevent us from being able to have dependent tests the + ! way we do here (where reads depend on earlier writes), for better or for worse. + ! + ! !USERS: + use decompInitMod, only : decompInit_clumps, decompInit_glcp + use domainMod, only : ldomain + ! !ARGUMENTS: + ! + ! !LOCAL VARIABLES: + integer, allocatable :: model_amask(:) + !----------------------------------------------------------------------- + + default_npes = npes + default_clump_pproc = clump_pproc + call write_to_log('start_test_decomp_init') + + call write_to_log('test_check_nclumps') + call test_check_nclumps() + call write_to_log('test_decompInit_lnd_check_sizes') + call test_decompInit_lnd_check_sizes() + call write_to_log('test_decompInit_clump_gcell_info_correct') + call test_decompInit_clump_gcell_info_correct() + + ! + ! THIS DESCRIBES WHAT WOULD NEED TO BE DONE TO CONTINUE A SIMULATION AFTER THIS: + ! SINCE THE CODE EXITS AFTER THE SELF TESTS THIS IS NOT NEEDED: + ! Call the decompInit initialization series a last time so that decompMod data can still be used + ! + !allocate( model_amask(ldomain%ni*ldomain%nj) ) + !model_amask(:) = 1 + !call decompInit_lnd( ldomain%ni, ldomain%nj, model_amask ) + !call decompInit_clumps(ldomain%ni, ldomain%nj, glc_behavior) + !call decompInit_glcp(ldomain%ni, ldomain%nj, glc_behavior) + !deallocate( model_amask ) + ! END DESCRIBE: + + end subroutine test_decomp_init + + !----------------------------------------------------------------------- + subroutine setup() + use clm_varctl, only : nsegspc + + clump_pproc = default_clump_pproc + nsegspc = 20 + npes = default_npes + amask(:) = 1 ! Set all to land + + end subroutine setup + + !----------------------------------------------------------------------- + subroutine test_decompInit_lnd_check_sizes() + use decompMod, only : get_proc_bounds + type(bounds_type) :: bounds + + integer :: expected_endg, expected_numg + + call setup() + expected_numg = ni*nj + if ( expected_numg < npes )then + call endrun( msg="npes is too large for this test", file=sourcefile, line=__LINE__ ) + end if + if ( modulo( expected_numg, npes ) /= 0 )then + call endrun( msg="npes does not evenly divide into numg so this test will not work", file=sourcefile, line=__LINE__ ) + end if + expected_endg = ni*nj / npes + amask(:) = 1 ! Set all to land + call decompInit_lnd( ni, nj, amask ) + call get_proc_bounds(bounds) + call assert_equal( bounds%begg, 1, msg='begg is not as expected' ) + call assert_equal( bounds%endg, expected_endg, msg='endg is not as expected' ) + call clean() + end subroutine test_decompInit_lnd_check_sizes + + !----------------------------------------------------------------------- + subroutine test_check_nclumps() + integer :: expected_nclumps + + call setup() + expected_nclumps = npes / clump_pproc + call assert_equal(expected=expected_nclumps, actual=nclumps, & + msg='nclumps are not as expected') + call clean() + end subroutine test_check_nclumps + +!----------------------------------------------------------------------- + subroutine test_decompMod_get_clump_bounds_correct() + ! Some testing for get_clump_bounds + use decompMod, only : get_clump_bounds, bounds_type + use unittestSimpleSubgridSetupsMod, only : setup_ncells_single_veg_patch + use unittestSubgridMod, only : unittest_subgrid_teardown + use pftconMod, only : noveg + type(bounds_type) :: bounds + integer :: expected_begg, expected_endg, expected_numg, gcell_per_task + integer :: iclump + + call setup() + ! Now setup a singple grid that's just the full test with every point a single baresoil patch + call setup_ncells_single_veg_patch( ncells=ni*nj, pft_type=noveg ) + clump_pproc = 1 ! Ensure we are just doing this for one clump per proc for now + expected_numg = ni*nj + if ( expected_numg < npes )then + call endrun( msg="npes is too large for this test", file=sourcefile, line=__LINE__ ) + end if + if ( modulo( expected_numg, npes ) /= 0 )then + call endrun( msg="npes does not evenly divide into numg so this test will not work", file=sourcefile, line=__LINE__ ) + end if + gcell_per_task = expected_numg / npes + expected_begg = gcell_per_task * iam + 1 + expected_endg = expected_begg + gcell_per_task + amask(:) = 1 ! Set all to land + call decompInit_lnd( ni, nj, amask ) + call decompInit_clumps( ni, nj, glc_behavior ) + iclump = 1 ! Clump is just 1 since there's only one clump per task + call get_clump_bounds(iclump, bounds) + call assert_equal( bounds%begg, expected_begg, msg='begg is not as expected' ) + call assert_equal( bounds%endg, expected_endg, msg='endg is not as expected' ) + ! Other subgrtid level information will be the same -- since there's only one landunit, column, and patch per gridcell + call assert_equal( bounds%begl, expected_begg, msg='begl is not as expected' ) + call assert_equal( bounds%endl, expected_endg, msg='endl is not as expected' ) + call assert_equal( bounds%begc, expected_begg, msg='begc is not as expected' ) + call assert_equal( bounds%endc, expected_endg, msg='endc is not as expected' ) + call assert_equal( bounds%begp, expected_begg, msg='begp is not as expected' ) + call assert_equal( bounds%endp, expected_endg, msg='endp is not as expected' ) + call unittest_subgrid_teardown( ) + call clean() + end subroutine test_decompMod_get_clump_bounds_correct + + !----------------------------------------------------------------------- + subroutine test_decompInit_clump_gcell_info_correct() + ! Some testing for get_clump_bounds + use decompMod, only : clumps + use decompMod, only : get_proc_bounds + type(bounds_type) :: bounds + integer :: expected_gcells, iclump, g, beg_global_index, gcell_per_task + integer :: expected_begg, expected_endg, lc + + call setup() + expected_gcells = ni*nj + if ( expected_gcells < npes )then + call endrun( msg="npes is too large for this test", file=sourcefile, line=__LINE__ ) + end if + if ( modulo( expected_gcells, npes ) /= 0 )then + call endrun( msg="npes does not evenly divide into gcell so this test will not work", file=sourcefile, line=__LINE__ ) + end if + gcell_per_task = expected_gcells / npes + expected_begg = gcell_per_task * iam + 1 + expected_endg = expected_begg + gcell_per_task + amask(:) = 1 ! Set all to land + call decompInit_lnd( ni, nj, amask ) + ! When clump_pproc is one clumps will be the same as PE + if ( clump_pproc == 1 ) then + call assert_equal( nclumps, npes, msg='nclumps should match number of processors when clump_pproc is 1' ) + else + call assert_equal( nclumps/clump_pproc, npes, msg='nclumps divided by clump_pproc should match number of processors when clump_pproc > 1' ) + end if + ! Just test over the local clumps + do lc = 1, clump_pproc + iclump = procinfo%cid(lc) + call assert_equal( clumps(iclump)%owner, iam, msg='clumps owner is not correct' ) + call assert_equal( clumps(iclump)%ncells, gcell_per_task, msg='clumps ncells is not correct' ) + end do + call clean() + end subroutine test_decompInit_clump_gcell_info_correct + + !----------------------------------------------------------------------- + subroutine write_to_log(msg) + ! + ! !DESCRIPTION: + ! Write a message to the log file, just from the masterproc + ! + use shr_sys_mod, only : shr_sys_flush + ! !ARGUMENTS: + character(len=*), intent(in) :: msg + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'write_to_log' + !----------------------------------------------------------------------- + + if (masterproc) then + write(iulog,'(a)') msg + call shr_sys_flush(iulog) ! Flush the I/O buffers always + end if + + end subroutine write_to_log + + !----------------------------------------------------------------------- + subroutine clean + ! + ! !DESCRIPTION: + ! Do end-of-testing cleanup after each test + ! + ! !ARGUMENTS: + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + call decompmod_clean() + + end subroutine clean + + +end module TestDecompInit diff --git a/src/self_tests/test/CMakeLists.txt b/src/self_tests/test/CMakeLists.txt index d616310bcd..5e58bc6043 100644 --- a/src/self_tests/test/CMakeLists.txt +++ b/src/self_tests/test/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(assertions_test) +add_subdirectory(decomp_init_error_test) diff --git a/src/self_tests/test/decomp_init_error_test/CMakeLists.txt b/src/self_tests/test/decomp_init_error_test/CMakeLists.txt new file mode 100644 index 0000000000..caedcd9c61 --- /dev/null +++ b/src/self_tests/test/decomp_init_error_test/CMakeLists.txt @@ -0,0 +1,8 @@ +set (pfunit_sources + test_decomp_init_errors.pf) + +add_pfunit_ctest(test_decomp_init_error + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf fates + EXTRA_FINALIZE unittest_finalize_esmf + EXTRA_USE unittestInitializeAndFinalize) diff --git a/src/self_tests/test/decomp_init_error_test/test_decomp_init_errors.pf b/src/self_tests/test/decomp_init_error_test/test_decomp_init_errors.pf new file mode 100644 index 0000000000..54be8e327c --- /dev/null +++ b/src/self_tests/test/decomp_init_error_test/test_decomp_init_errors.pf @@ -0,0 +1,81 @@ +module test_decomp_init_errors + + ! Tests of decompInitMod error handling + + use funit + use shr_kind_mod, only : r8 => shr_kind_r8, CX => shr_kind_cx + use unittestUtils, only : endrun_msg + use clm_varctl, only : iulog + use spmdMod, only : masterproc, npes, iam + use decompInitMod, only : decompInit_lnd, clump_pproc + use TestDecompInit, only : ni, nj, amask + + implicit none + + @TestCase + type, extends(TestCase) :: TestDecompInitErrors + contains + procedure :: setUp + procedure :: tearDown + end type TestDecompInitErrors + +contains + + subroutine setUp(this) + use TestDecompInit, only : DecompInitTestSetup => setup + class(TestDecompInitErrors), intent(inout) :: this + + call DecompInitTestSetup() + + end subroutine setUp + + subroutine tearDown(this) + use TestDecompInit, only : clean + class(TestDecompInitErrors), intent(inout) :: this + + call clean() + + end subroutine tearDown + + @Test + subroutine test_decompInit_lnd_abort_on_bad_clump_pproc(this) + class(TestDecompInitErrors), intent(inout) :: this + character(len=:), allocatable :: expected_msg + + clump_pproc = 0 + call decompInit_lnd( ni, nj, amask ) + expected_msg = endrun_msg( & + 'clump_pproc must be greater than 0') + @assertExceptionRaised(expected_msg) + + end subroutine test_decompInit_lnd_abort_on_bad_clump_pproc + + @Test + subroutine test_decompInit_lnd_abort_when_npes_too_large(this) + class(TestDecompInitErrors), intent(inout) :: this + character(len=:), allocatable :: expected_msg + + npes = ni*nj + 1 + + amask(:) = 1 ! Set all to land + call decompInit_lnd( ni, nj, amask ) + expected_msg = endrun_msg( & + 'Number of processes exceeds number of land grid cells') + @assertExceptionRaised(expected_msg) + end subroutine test_decompInit_lnd_abort_when_npes_too_large + + @Test + subroutine test_decompInit_lnd_abort_on_too_small_nsegspc(this) + use clm_varctl, only : nsegspc + class(TestDecompInitErrors), intent(inout) :: this + character(len=:), allocatable :: expected_msg + + amask(:) = 1 ! Set all to land + nsegspc = 0 + call decompInit_lnd( ni, nj, amask ) + expected_msg = endrun_msg( & + 'Number of segments per clump (nsegspc) is less than 1 and can NOT be') + @assertExceptionRaised(expected_msg) + end subroutine test_decompInit_lnd_abort_on_too_small_nsegspc + +end module test_decomp_init_errors diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 04ad683517..9038b6dbca 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -22,6 +22,7 @@ list(APPEND clm_sources SparseMatrixMultiplyMod.F90 IssueFixedMetadataHandler.F90 NumericsMod.F90 + spmdMod.F90 ) sourcelist_to_parent(clm_sources)