From 6f47f21cae6d40c2fb9e26a18b73b7c892cbe685 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 13:49:16 +0100 Subject: [PATCH 01/17] Changed OASIS3 install location to ${OASIS_ROOT}/install --- .github/build.oasis3-mct.ubuntu22.04 | 2 +- .github/workflows/CI.yml | 32 +++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/build.oasis3-mct.ubuntu22.04 b/.github/build.oasis3-mct.ubuntu22.04 index 8c7319f64d..14256a4fed 100644 --- a/.github/build.oasis3-mct.ubuntu22.04 +++ b/.github/build.oasis3-mct.ubuntu22.04 @@ -21,7 +21,7 @@ BUILD_DIR = $(OASIS_ROOT)/util/make_dir # # # ARCHDIR : directory created when compiling -ARCHDIR = $(HOME)/.local +ARCHDIR = $(OASIS_ROOT)/install # # MPI library ((see the file /etc/modulefiles/mpi/openmpi-x86_64) MPIDIR = /usr/lib/x86_64-linux-gnu/openmpi diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fbacc61f54..94a8c2ccb0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -38,6 +38,8 @@ jobs: FC: mpifort BUILD_DIR: bld INSTALL_DIR: install + OASIS_TAG: tsmp-patches-v0.1 + PFUNIT_TAG: v4.10.0 CMAKE_BUILD_PARALLEL_LEVEL: 4 steps: @@ -52,34 +54,40 @@ jobs: run: sudo apt-get install gfortran openmpi-bin libopenmpi-dev - if: matrix.config.use_oasis == 'True' - name: Cache OASIS3-MCT - uses: actions/cache@v4 - id: cache-deps - env: - cache-name: cache-eCLM-dependencies + name: Restore cached OASIS3-MCT ${{ env.OASIS_TAG }} + uses: actions/cache/restore@v4 + id: cache-oasis-restore with: - path: "~/.local" - key: cache-${{ matrix.config.name }} + path: ${{ github.workspace }}/oasis3-mct/install + key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} - - if: matrix.config.use_oasis == 'True' && steps.cache-deps.outputs.cache-hit != 'true' - name: Install OASIS3-MCT + - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' + name: Install OASIS3-MCT ${{ env.OASIS_TAG }} + working-directory: ${{ github.workspace }} run: | - git clone https://icg4geo.icg.kfa-juelich.de/ExternalReposPublic/oasis3-mct.git + git clone -b $OASIS_TAG https://icg4geo.icg.kfa-juelich.de/ExternalReposPublic/oasis3-mct.git cd oasis3-mct export OASIS_ROOT=$(pwd) echo "OASIS_ROOT=${OASIS_ROOT}" - echo "DEPENDENCIES_DIR=${DEPENDENCIES_DIR}" cd util/make_dir echo "include ${GITHUB_WORKSPACE}/.github/build.oasis3-mct.ubuntu22.04" > make.inc cat make.inc make realclean static-libs -f TopMakefileOasis3 + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_ROOT}/install" >> $GITHUB_ENV + + - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' + name: Cache OASIS3-MCT ${{ env.OASIS_TAG }} + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/oasis3-mct/install + key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} - name: Configure eCLM run: | + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" cmake -S src -B $BUILD_DIR \ -DCMAKE_BUILD_TYPE="RELEASE" \ -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ - -DCMAKE_PREFIX_PATH="$HOME/.local" \ -DCMAKE_C_COMPILER=$CC \ -DCMAKE_Fortran_COMPILER=$FC \ -DUSE_OASIS=${{ matrix.config.use_oasis }} \ From f6651cb74cd5d824fe93dfd62259a5ad9d4c5bd7 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 14:12:16 +0100 Subject: [PATCH 02/17] CI: Build pFUnit --- .github/workflows/CI.yml | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 94a8c2ccb0..e10442c52b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -53,6 +53,9 @@ jobs: - name: Download MPI Fortran compiler run: sudo apt-get install gfortran openmpi-bin libopenmpi-dev + # + # OASIS3-MCT + # - if: matrix.config.use_oasis == 'True' name: Restore cached OASIS3-MCT ${{ env.OASIS_TAG }} uses: actions/cache/restore@v4 @@ -68,10 +71,8 @@ jobs: git clone -b $OASIS_TAG https://icg4geo.icg.kfa-juelich.de/ExternalReposPublic/oasis3-mct.git cd oasis3-mct export OASIS_ROOT=$(pwd) - echo "OASIS_ROOT=${OASIS_ROOT}" cd util/make_dir echo "include ${GITHUB_WORKSPACE}/.github/build.oasis3-mct.ubuntu22.04" > make.inc - cat make.inc make realclean static-libs -f TopMakefileOasis3 echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_ROOT}/install" >> $GITHUB_ENV @@ -82,6 +83,37 @@ jobs: path: ${{ github.workspace }}/oasis3-mct/install key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} + # + # pFUnit + # + - name: Restore cached pFUnit ${{ env.PFUNIT_TAG }} + uses: actions/cache/restore@v4 + id: cache-pFUnit-restore + with: + path: ${{ github.workspace }}/pFUnit/install + key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} + + - if: steps.cache-pFUnit-restore.outputs.cache-hit != 'true' + name: Install pFUnit ${{ env.PFUNIT_TAG }} + working-directory: ${{ github.workspace }} + run: | + git clone -b ${PFUNIT_TAG} --recursive https://github.com/Goddard-Fortran-Ecosystem/pFUnit.git + cd pFUnit + cmake -S . -B bld -DCMAKE_INSTALL_PREFIX=install + cmake --build bld + cmake --install bld + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:$(realpath install)" >> $GITHUB_ENV + + - if: steps.cache-pFUnit-restore.outputs.cache-hit != 'true' + name: Cache pFUnit ${{ env.PFUNIT_TAG }} + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/pFUnit/install + key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} + + # + # eCLM + # - name: Configure eCLM run: | echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" From 2827eca5ca3c8592e9797d48036c9f5badbc951f Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 15:33:52 +0100 Subject: [PATCH 03/17] Loaded pFUnit library via ENABLE_TESTS option --- .github/workflows/CI.yml | 3 ++- src/CMakeLists.txt | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e10442c52b..44ad7df3d3 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -125,7 +125,8 @@ jobs: -DUSE_OASIS=${{ matrix.config.use_oasis }} \ -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ - -DUSE_PDAF=${{ matrix.config.use_pdaf }} + -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ + -DENABLE_TESTS="True" - name: Build eCLM run: cmake --build $BUILD_DIR diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c2eb13fe2c..9702fe666b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,14 @@ add_subdirectory(datm) add_subdirectory(mosart) add_subdirectory(eclm) +option(ENABLE_TESTS "Enable unit tests." OFF) +if (ENABLE_TESTS) + find_package(PFUNIT REQUIRED) + enable_testing() + message(STATUS "Found pFUnit v${PFUNIT_VERSION}") + message(STATUS "Unit tests enabled.") +endif() + # Make sure that the required libraries are always found # independent from LD_LIBRARY_PATH and the install location. # https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath From fbe2d56dbdf9f63572e4dfcdde16d37eef6a1c2a Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 15:42:27 +0100 Subject: [PATCH 04/17] CI: Fixed missing CMAKE_PREFIX_PATH --- .github/workflows/CI.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 44ad7df3d3..1cdd95ae98 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,7 +39,9 @@ jobs: BUILD_DIR: bld INSTALL_DIR: install OASIS_TAG: tsmp-patches-v0.1 + OASIS_INSTALL_PREFIX: ${{ github.workspace }}/oasis3-mct/install PFUNIT_TAG: v4.10.0 + PFUNIT_INSTALL_PREFIX: ${{ github.workspace }}/pFUnit/install CMAKE_BUILD_PARALLEL_LEVEL: 4 steps: @@ -61,7 +63,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-oasis-restore with: - path: ${{ github.workspace }}/oasis3-mct/install + path: ${{ env.OASIS_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' @@ -74,13 +76,12 @@ jobs: cd util/make_dir echo "include ${GITHUB_WORKSPACE}/.github/build.oasis3-mct.ubuntu22.04" > make.inc make realclean static-libs -f TopMakefileOasis3 - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_ROOT}/install" >> $GITHUB_ENV - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' name: Cache OASIS3-MCT ${{ env.OASIS_TAG }} uses: actions/cache/save@v4 with: - path: ${{ github.workspace }}/oasis3-mct/install + path: ${{ env.OASIS_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} # @@ -90,7 +91,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-pFUnit-restore with: - path: ${{ github.workspace }}/pFUnit/install + path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} - if: steps.cache-pFUnit-restore.outputs.cache-hit != 'true' @@ -102,13 +103,12 @@ jobs: cmake -S . -B bld -DCMAKE_INSTALL_PREFIX=install cmake --build bld cmake --install bld - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:$(realpath install)" >> $GITHUB_ENV - if: steps.cache-pFUnit-restore.outputs.cache-hit != 'true' name: Cache pFUnit ${{ env.PFUNIT_TAG }} uses: actions/cache/save@v4 with: - path: ${{ github.workspace }}/pFUnit/install + path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} # @@ -116,16 +116,16 @@ jobs: # - name: Configure eCLM run: | - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" - cmake -S src -B $BUILD_DIR \ - -DCMAKE_BUILD_TYPE="RELEASE" \ - -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_Fortran_COMPILER=$FC \ - -DUSE_OASIS=${{ matrix.config.use_oasis }} \ - -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ - -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ - -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ + cmake -S src -B $BUILD_DIR \ + -DCMAKE_BUILD_TYPE="RELEASE" \ + -DCMAKE_PREFIX_PATH="${OASIS_INSTALL_PREFIX}:${PFUNIT_INSTALL_PREFIX}" \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_C_COMPILER=$CC \ + -DCMAKE_Fortran_COMPILER=$FC \ + -DUSE_OASIS=${{ matrix.config.use_oasis }} \ + -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ + -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ + -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ -DENABLE_TESTS="True" - name: Build eCLM From 255084dc73bbfbe2ac345b55077718dec7d4b3c8 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 15:45:44 +0100 Subject: [PATCH 05/17] CI: Check OASIS and PFUNIT install prefixes --- .github/workflows/CI.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1cdd95ae98..fc909128f9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -111,6 +111,15 @@ jobs: path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} + - name: Check OASIS_INSTALL_PREFIX + run: | + echo "OASIS_INSTALL_PREFIX=${OASIS_INSTALL_PREFIX}" + tree ${OASIS_INSTALL_PREFIX} + + - name: Check PFUNIT_INSTALL_PREFIX + run: | + echo "PFUNIT_INSTALL_PREFIX=${PFUNIT_INSTALL_PREFIX}" + tree ${PFUNIT_INSTALL_PREFIX} # # eCLM # From 6eb8878c8c52228bcf17fed41fbf6f5bed10f3b3 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 15:47:33 +0100 Subject: [PATCH 06/17] CI: Only check OASIS_INSTALL_PREFIX when use_oasis=true --- .github/workflows/CI.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fc909128f9..76ae9da31f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -111,7 +111,8 @@ jobs: path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} - - name: Check OASIS_INSTALL_PREFIX + - if: matrix.config.use_oasis == 'True' + name: Check OASIS_INSTALL_PREFIX run: | echo "OASIS_INSTALL_PREFIX=${OASIS_INSTALL_PREFIX}" tree ${OASIS_INSTALL_PREFIX} From db5f41b9df480dc9f04b7c90044602fca0b15ebe Mon Sep 17 00:00:00 2001 From: kvrigor Date: Fri, 1 Nov 2024 16:01:28 +0100 Subject: [PATCH 07/17] CI: Added OASIS and pFUNIT to CMAKE_PREFIX_PATH on separate CI steps --- .github/workflows/CI.yml | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 76ae9da31f..5378d835df 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -84,6 +84,12 @@ jobs: path: ${{ env.OASIS_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} + - if: matrix.config.use_oasis == 'True' + name: Add OASIS to CMAKE_PREFIX_PATH + run: | + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" + tree -FUCh --du --filelimit 20 -L 2 ${OASIS_INSTALL_PREFIX} # # pFUnit # @@ -111,31 +117,26 @@ jobs: path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} - - if: matrix.config.use_oasis == 'True' - name: Check OASIS_INSTALL_PREFIX + - name: Add pFUnit to CMAKE_PREFIX_PATH run: | - echo "OASIS_INSTALL_PREFIX=${OASIS_INSTALL_PREFIX}" - tree ${OASIS_INSTALL_PREFIX} + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${PFUNIT_INSTALL_PREFIX}" >> $GITHUB_ENV + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" + tree -FUCh --du --filelimit 20 -L 2 ${PFUNIT_INSTALL_PREFIX} - - name: Check PFUNIT_INSTALL_PREFIX - run: | - echo "PFUNIT_INSTALL_PREFIX=${PFUNIT_INSTALL_PREFIX}" - tree ${PFUNIT_INSTALL_PREFIX} # # eCLM # - name: Configure eCLM run: | - cmake -S src -B $BUILD_DIR \ - -DCMAKE_BUILD_TYPE="RELEASE" \ - -DCMAKE_PREFIX_PATH="${OASIS_INSTALL_PREFIX}:${PFUNIT_INSTALL_PREFIX}" \ - -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_Fortran_COMPILER=$FC \ - -DUSE_OASIS=${{ matrix.config.use_oasis }} \ - -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ - -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ - -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ + cmake -S src -B $BUILD_DIR \ + -DCMAKE_BUILD_TYPE="RELEASE" \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_C_COMPILER=$CC \ + -DCMAKE_Fortran_COMPILER=$FC \ + -DUSE_OASIS=${{ matrix.config.use_oasis }} \ + -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ + -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ + -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ -DENABLE_TESTS="True" - name: Build eCLM From b325e444f43656c62e3670df83656c138421a27e Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 10:11:33 +0200 Subject: [PATCH 08/17] Merged all commits starting from Dec. 2024 --- .github/workflows/CI.yml | 56 ++- .github/workflows/docs.yml | 44 +- CONTRIBUTORS.txt | 9 + LICENSE | 8 +- cmake/FindLAPACK.cmake | 464 ------------------ cmake/SetBuildOptions.cmake | 9 +- docs/INDEX.md | 4 +- docs/Makefile | 6 +- docs/_config.yml | 2 +- docs/_toc.yml | 102 ++-- docs/users_guide/building_eCLM/JSC/README.md | 3 - .../JSC/prerequisites_JSC_users.md | 11 - .../building_eCLM/JSC/setting_up_eCLM.md | 154 ------ docs/users_guide/building_eCLM/Levante.md | 5 - docs/users_guide/building_eCLM/README.md | 61 --- docs/users_guide/building_eCLM/Ubuntu.md | 36 -- docs/users_guide/case_examples/README.md | 2 +- docs/users_guide/installation/README.md | 13 + .../installation/source_installation.md | 37 ++ .../introduction.md | 0 .../introduction_to_eCLM/README.md | 5 - .../how_to_use_this_document.md | 16 - .../introduction_to_eCLM/prerequisites.md | 19 - .../case_customization.md | 14 +- docs/users_guide/running_eCLM/README.md | 7 - .../running_eCLM/basic_commands.md | 32 -- src/clm5/biogeochem/ch4Mod.F90 | 9 +- src/clm5/biogeophys/BalanceCheckMod.F90 | 35 +- src/clm5/biogeophys/SoilHydrologyMod.F90 | 26 +- src/clm5/biogeophys/WaterfluxType.F90 | 2 +- src/clm5/main/lnd2atmMod.F90 | 23 +- src/eclm/cime_comp_mod.F90 | 6 +- src/externals/CMakeLists.txt | 1 + 33 files changed, 263 insertions(+), 958 deletions(-) create mode 100644 CONTRIBUTORS.txt delete mode 100644 cmake/FindLAPACK.cmake delete mode 100644 docs/users_guide/building_eCLM/JSC/README.md delete mode 100644 docs/users_guide/building_eCLM/JSC/prerequisites_JSC_users.md delete mode 100644 docs/users_guide/building_eCLM/JSC/setting_up_eCLM.md delete mode 100644 docs/users_guide/building_eCLM/Levante.md delete mode 100644 docs/users_guide/building_eCLM/README.md delete mode 100644 docs/users_guide/building_eCLM/Ubuntu.md create mode 100644 docs/users_guide/installation/README.md create mode 100644 docs/users_guide/installation/source_installation.md rename docs/users_guide/{introduction_to_eCLM => introduction}/introduction.md (100%) delete mode 100644 docs/users_guide/introduction_to_eCLM/README.md delete mode 100644 docs/users_guide/introduction_to_eCLM/how_to_use_this_document.md delete mode 100644 docs/users_guide/introduction_to_eCLM/prerequisites.md rename docs/users_guide/{running_eCLM => running_cases}/case_customization.md (86%) delete mode 100644 docs/users_guide/running_eCLM/README.md delete mode 100644 docs/users_guide/running_eCLM/basic_commands.md diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5378d835df..8e4f80a24d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,7 +1,12 @@ name: eCLM CI Test -# Controls when the action will run. -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: eclm_build_job: @@ -13,6 +18,15 @@ jobs: config: - { name: "eCLM-Standalone", + build_type: "RELEASE", + use_oasis: "False", + coup_oas_icon: "False", + coup_oas_pfl: "False", + use_pdaf: "False" + } + - { + name: "eCLM-Standalone_DEBUG", + build_type: "DEBUG", use_oasis: "False", coup_oas_icon: "False", coup_oas_pfl: "False", @@ -20,6 +34,15 @@ jobs: } - { name: "eCLM-ParFlow-ICON", + build_type: "RELEASE", + use_oasis: "True", + coup_oas_icon: "True", + coup_oas_pfl: "True", + use_pdaf: "False" + } + - { + name: "eCLM-ParFlow-ICON_DEBUG", + build_type: "DEBUG", use_oasis: "True", coup_oas_icon: "True", coup_oas_pfl: "True", @@ -27,6 +50,15 @@ jobs: } - { name: "eCLM-PDAF", + build_type: "RELEASE", + use_oasis: "False", + coup_oas_icon: "False", + coup_oas_pfl: "False", + use_pdaf: "True" + } + - { + name: "eCLM-PDAF_DEBUG", + build_type: "DEBUG", use_oasis: "False", coup_oas_icon: "False", coup_oas_pfl: "False", @@ -128,16 +160,16 @@ jobs: # - name: Configure eCLM run: | - cmake -S src -B $BUILD_DIR \ - -DCMAKE_BUILD_TYPE="RELEASE" \ - -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_Fortran_COMPILER=$FC \ - -DUSE_OASIS=${{ matrix.config.use_oasis }} \ - -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ - -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ - -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ - -DENABLE_TESTS="True" + cmake -S src -B $BUILD_DIR \ + -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ + -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \ + -DCMAKE_PREFIX_PATH="$HOME/.local" \ + -DCMAKE_C_COMPILER=$CC \ + -DCMAKE_Fortran_COMPILER=$FC \ + -DUSE_OASIS=${{ matrix.config.use_oasis }} \ + -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ + -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ + -DUSE_PDAF=${{ matrix.config.use_pdaf }} - name: Build eCLM run: cmake --build $BUILD_DIR diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b6d93aa64d..f05e4a36b4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,7 +4,9 @@ on: push: branches: - master - - 'docs-**' + pull_request: + branches: + - master env: BASE_URL: /${{ github.event.repository.name }} @@ -16,24 +18,18 @@ concurrency: cancel-in-progress: false jobs: - deploy-docs: - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - permissions: - pages: write - id-token: write + build-docs: + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 cache: 'pip' - - name: Install dependencies + - name: Install pip packages run: pip install -r ${GITHUB_WORKSPACE}/docs/requirements.txt - name: Build eCLM doc homepage @@ -47,10 +43,26 @@ jobs: make src-browser - name: Upload documentation artifacts - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: "docs/_build/html" + name: eCLM_docs - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 + deploy-docs: + if: github.event_name != 'pull_request' + needs: build-docs + runs-on: ubuntu-24.04 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + permissions: + pages: write + id-token: write + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + with: + artifact_name: eCLM_docs \ No newline at end of file diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 0000000000..5607ee5e3d --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,9 @@ +Stefan KOLLET +Paul RIGOR +Stefan POLL +Johannes KELLER +Marco VAN HULTEN +Ana GONZALEZ-NICOLAS +Daniel CAVIEDES-VOULLIÈME +Olga DOMBROWSKI + diff --git a/LICENSE b/LICENSE index a3ce186962..1ae8a06718 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ -Copyright (c) 2020-2021, Paul Rigor, Stefan Kollet +Copyright (c) 2020-2025, Centre for High-Performance Scientific Computing in Terrestrial Systems (HPSC TerrSys) Copyright (c) 2005-2018, University Corporation for Atmospheric Research (UCAR) All rights reserved. -Developed by: Paul Rigor, Stefan Kollet - https://www.fz-juelich.de/ibg/ibg-3/EN/Home/home_node.html +Developed by: Centre for High-Performance Scientific Computing in Terrestrial Systems (HPSC TerrSys) + https://www.hpsc-terrsys.de/en University Corporation for Atmospheric Research - National Center for Atmospheric Research https://www2.cesm.ucar.edu/working-groups/sewg @@ -20,7 +20,7 @@ the Software is furnished to do so, subject to the following conditions: - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. - - Neither the names of [Name of Development Group, UCAR], + - Neither the names of [UCAR, HPSC TerrSys], nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. diff --git a/cmake/FindLAPACK.cmake b/cmake/FindLAPACK.cmake deleted file mode 100644 index 4eaee1f83e..0000000000 --- a/cmake/FindLAPACK.cmake +++ /dev/null @@ -1,464 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: - -FindLapack ----------- - -* Michael Hirsch, Ph.D. www.scivision.dev -* David Eklund - -Let Michael know if there are more MKL / Lapack / compiler combination you want. -Refer to https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor - -Finds LAPACK libraries for C / C++ / Fortran. -Works with Netlib Lapack / LapackE, Atlas and Intel MKL. -Intel MKL relies on having environment variable MKLROOT set, typically by sourcing -mklvars.sh beforehand. - -Why not the FindLapack.cmake built into CMake? It has a lot of old code for -infrequently used Lapack libraries and is unreliable for me. - -Tested on Linux, MacOS and Windows with: -* GCC / Gfortran -* Clang / Flang -* PGI (pgcc, pgfortran) -* Intel (icc, ifort) - - -Parameters -^^^^^^^^^^ - -COMPONENTS default to Netlib LAPACK / LapackE, otherwise: - -``MKL`` - Intel MKL for MSVC, ICL, ICC, GCC and PGCC -- sequential by default, or add TBB or MPI as well -``OpenMP`` - Intel MPI with OpenMP threading addition to MKL -``TBB`` - Intel MPI + TBB for MKL -``MKL64`` - MKL only: 64-bit integers (default is 32-bit integers) - -``LAPACKE`` - Netlib LapackE for C / C++ -``Netlib`` - Netlib Lapack for Fortran -``OpenBLAS`` - OpenBLAS Lapack for Fortran - -``LAPACK95`` - get Lapack95 interfaces for MKL or Netlib (must also specify one of MKL, Netlib) - - -Result Variables -^^^^^^^^^^^^^^^^ - -``LAPACK_FOUND`` - Lapack libraries were found -``LAPACK__FOUND`` - LAPACK specified was found -``LAPACK_LIBRARIES`` - Lapack library files (including BLAS -``LAPACK_INCLUDE_DIRS`` - Lapack include directories (for C/C++) - - -References -^^^^^^^^^^ - -* Pkg-Config and MKL: https://software.intel.com/en-us/articles/intel-math-kernel-library-intel-mkl-and-pkg-config-tool -* MKL for Windows: https://software.intel.com/en-us/mkl-windows-developer-guide-static-libraries-in-the-lib-intel64-win-directory -* MKL Windows directories: https://software.intel.com/en-us/mkl-windows-developer-guide-high-level-directory-structure -* Atlas http://math-atlas.sourceforge.net/errata.html#LINK -* MKL LAPACKE (C, C++): https://software.intel.com/en-us/mkl-linux-developer-guide-calling-lapack-blas-and-cblas-routines-from-c-c-language-environments -#]=======================================================================] - -# clear to avoid endless appending on subsequent calls -set(LAPACK_LIBRARY) -set(LAPACK_INCLUDE_DIR) - -# ===== functions ========== - -function(atlas_libs) - -find_library(ATLAS_LIB - NAMES atlas - NAMES_PER_DIR - PATH_SUFFIXES atlas) - -pkg_check_modules(pc_atlas_lapack lapack-atlas QUIET) - -find_library(LAPACK_ATLAS - NAMES ptlapack lapack_atlas lapack - NAMES_PER_DIR - PATH_SUFFIXES atlas - HINTS ${pc_atlas_lapack_LIBRARY_DIRS} ${pc_atlas_lapack_LIBDIR}) - -pkg_check_modules(pc_atlas_blas blas-atlas QUIET) - -find_library(BLAS_LIBRARY - NAMES ptf77blas f77blas blas - NAMES_PER_DIR - PATH_SUFFIXES atlas - HINTS ${pc_atlas_blas_LIBRARY_DIRS} ${pc_atlas_blas_LIBDIR}) -# === C === -find_library(BLAS_C_ATLAS - NAMES ptcblas cblas - NAMES_PER_DIR - PATH_SUFFIXES atlas - HINTS ${pc_atlas_blas_LIBRARY_DIRS} ${pc_atlas_blas_LIBDIR}) - -find_path(LAPACK_INCLUDE_DIR - NAMES cblas-atlas.h cblas.h clapack.h - HINTS ${pc_atlas_blas_INCLUDE_DIRS} ${pc_atlas_blas_LIBDIR}) - -#=========== -if(LAPACK_ATLAS AND BLAS_C_ATLAS AND BLAS_LIBRARY AND ATLAS_LIB) - set(LAPACK_Atlas_FOUND true PARENT_SCOPE) - set(LAPACK_LIBRARY ${LAPACK_ATLAS} ${BLAS_C_ATLAS} ${BLAS_LIBRARY} ${ATLAS_LIB}) - if(NOT WIN32) - find_package(Threads) # not required--for example Flang - list(APPEND LAPACK_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) - endif() -endif() - -set(LAPACK_LIBRARY ${LAPACK_LIBRARY} PARENT_SCOPE) -set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} PARENT_SCOPE) - -endfunction(atlas_libs) - -#======================= - -function(netlib_libs) - -if(LAPACK95 IN_LIST LAPACK_FIND_COMPONENTS) - find_path(LAPACK95_INCLUDE_DIR - NAMES f95_lapack.mod - PATH_SUFFIXES include - PATHS ${LAPACK95_ROOT}) - - find_library(LAPACK95_LIBRARY - NAMES lapack95 - NAMES_PER_DIR - PATH_SUFFIXES lib - PATHS ${LAPACK95_ROOT}) - - if(LAPACK95_LIBRARY AND LAPACK95_INCLUDE_DIR) - set(LAPACK_INCLUDE_DIR ${LAPACK95_INCLUDE_DIR}) - set(LAPACK_LIBRARY ${LAPACK95_LIBRARY}) - else() - return() - endif() -endif(LAPACK95 IN_LIST LAPACK_FIND_COMPONENTS) - -set(_lapack_hints) -if(CMAKE_Fortran_COMPILER_ID STREQUAL PGI) - get_filename_component(_pgi_path ${CMAKE_Fortran_COMPILER} DIRECTORY) - set(_lapack_hints ${_pgi_path}/../) -endif() - -pkg_check_modules(pc_lapack lapack-netlib QUIET) -if(NOT pc_lapack_FOUND) - pkg_check_modules(pc_lapack lapack QUIET) # Netlib on Cygwin, Homebrew and others -endif() -find_library(LAPACK_LIB - NAMES lapack - NAMES_PER_DIR - PATHS /usr/local/opt # homebrew - HINTS ${_lapack_hints} ${pc_lapack_LIBRARY_DIRS} ${pc_lapack_LIBDIR} - PATH_SUFFIXES lib lapack lapack/lib) -if(LAPACK_LIB) - list(APPEND LAPACK_LIBRARY ${LAPACK_LIB}) -else() - return() -endif() - -if(LAPACKE IN_LIST LAPACK_FIND_COMPONENTS) - pkg_check_modules(pc_lapacke lapacke QUIET) - find_library(LAPACKE_LIBRARY - NAMES lapacke - NAMES_PER_DIR - PATHS /usr/local/opt - HINTS ${pc_lapacke_LIBRARY_DIRS} ${pc_lapacke_LIBDIR} - PATH_SUFFIXES lapack lapack/lib) - - # lapack/include for Homebrew - find_path(LAPACKE_INCLUDE_DIR - NAMES lapacke.h - PATHS /usr/local/opt - HINTS ${pc_lapacke_INCLUDE_DIRS} ${pc_lapacke_LIBDIR} - PATH_SUFFIXES lapack lapack/include) - - if(LAPACKE_LIBRARY AND LAPACKE_INCLUDE_DIR) - set(LAPACK_LAPACKE_FOUND true PARENT_SCOPE) - list(APPEND LAPACK_INCLUDE_DIR ${LAPACKE_INCLUDE_DIR}) - list(APPEND LAPACK_LIBRARY ${LAPACKE_LIBRARY}) - else() - message(WARNING "Trouble finding LAPACKE: - include: ${LAPACKE_INCLUDE_DIR} - libs: ${LAPACKE_LIBRARY}") - return() - endif() - - mark_as_advanced(LAPACKE_LIBRARY LAPACKE_INCLUDE_DIR) -endif(LAPACKE IN_LIST LAPACK_FIND_COMPONENTS) - -pkg_check_modules(pc_blas blas-netlib QUIET) -if(NOT pc_blas_FOUND) - pkg_check_modules(pc_blas blas QUIET) # Netlib on Cygwin and others -endif() -find_library(BLAS_LIBRARY - NAMES refblas blas - NAMES_PER_DIR - PATHS /usr/local/opt - HINTS ${_lapack_hints} ${pc_blas_LIBRARY_DIRS} ${pc_blas_LIBDIR} - PATH_SUFFIXES lib lapack lapack/lib blas) - -if(BLAS_LIBRARY) - list(APPEND LAPACK_LIBRARY ${BLAS_LIBRARY}) - set(LAPACK_Netlib_FOUND true PARENT_SCOPE) -else() - return() -endif() - -if(NOT WIN32) - list(APPEND LAPACK_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) -endif() - -if(LAPACK95_LIBRARY) - set(LAPACK_LAPACK95_FOUND true PARENT_SCOPE) -endif() - -set(LAPACK_LIBRARY ${LAPACK_LIBRARY} PARENT_SCOPE) -set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} PARENT_SCOPE) - -endfunction(netlib_libs) - -#=============================== -function(openblas_libs) - -pkg_check_modules(pc_lapack lapack-openblas QUIET) -find_library(LAPACK_LIBRARY - NAMES lapack - NAMES_PER_DIR - HINTS ${pc_lapack_LIBRARY_DIRS} ${pc_lapack_LIBDIR} - PATH_SUFFIXES openblas) - - -pkg_check_modules(pc_blas blas-openblas QUIET) -find_library(BLAS_LIBRARY - NAMES openblas blas - NAMES_PER_DIR - HINTS ${pc_blas_LIBRARY_DIRS} ${pc_blas_LIBDIR} - PATH_SUFFIXES openblas) - -find_path(LAPACK_INCLUDE_DIR - NAMES cblas-openblas.h cblas.h f77blas.h openblas_config.h - HINTS ${pc_lapack_INCLUDE_DIRS}) - -if(LAPACK_LIBRARY AND BLAS_LIBRARY) - list(APPEND LAPACK_LIBRARY ${BLAS_LIBRARY}) - set(LAPACK_OpenBLAS_FOUND true PARENT_SCOPE) -else() - message(WARNING "Trouble finding OpenBLAS: - include: ${LAPACK_INCLUDE_DIR} - libs: ${LAPACK_LIBRARY} ${BLAS_LIBRARY}") - return() -endif() - -if(NOT WIN32) - find_package(Threads) # not required--for example Flang - list(APPEND LAPACK_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) -endif() - -set(LAPACK_LIBRARY ${LAPACK_LIBRARY} PARENT_SCOPE) -set(LAPACK_INCLUDE_DIR ${LAPACK_INCLUDE_DIR} PARENT_SCOPE) - -endfunction(openblas_libs) - -#=============================== - -function(find_mkl_libs) -# https://software.intel.com/en-us/articles/intel-mkl-link-line-advisor - -set(_mkl_libs ${ARGV}) -if((UNIX AND NOT APPLE) AND CMAKE_Fortran_COMPILER_ID STREQUAL GNU) - list(INSERT _mkl_libs 0 mkl_gf_${_mkl_bitflag}lp64) -else() - list(INSERT _mkl_libs 0 mkl_intel_${_mkl_bitflag}lp64) -endif() - -# Note: Don't remove items from PATH_SUFFIXES unless you're extensively testing, -# each path is there for a specific reason! - -foreach(s ${_mkl_libs}) - find_library(LAPACK_${s}_LIBRARY - NAMES ${s} - NAMES_PER_DIR - PATHS ${MKLROOT} ENV TBBROOT - PATH_SUFFIXES - lib lib/intel64 lib/intel64_win - lib/intel64/gcc4.7 ../tbb/lib/intel64/gcc4.7 - lib/intel64/vc_mt ../tbb/lib/intel64/vc_mt - ../compiler/lib/intel64 - HINTS ${pc_mkl_LIBRARY_DIRS} ${pc_mkl_LIBDIR} - NO_DEFAULT_PATH) - - if(NOT LAPACK_${s}_LIBRARY) - message(STATUS "MKL component not found: " ${s}) - return() - endif() - - list(APPEND LAPACK_LIB ${LAPACK_${s}_LIBRARY}) -endforeach() - -if(NOT BUILD_SHARED_LIBS AND (UNIX AND NOT APPLE)) - set(LAPACK_LIB -Wl,--start-group ${LAPACK_LIB} -Wl,--end-group) -endif() - -set(BLAS_LIBRARY PARENT_SCOPE) -set(LAPACK_LIBRARY ${LAPACK_LIB} PARENT_SCOPE) -set(LAPACK_INCLUDE_DIR - ${MKLROOT}/include - ${MKLROOT}/include/intel64/${_mkl_bitflag}lp64 - PARENT_SCOPE) - # ${pc_mkl_INCLUDE_DIRS} has garbage on Windows - -endfunction(find_mkl_libs) - -# ========== main program - -if(NOT (OpenBLAS IN_LIST LAPACK_FIND_COMPONENTS - OR Netlib IN_LIST LAPACK_FIND_COMPONENTS - OR Atlas IN_LIST LAPACK_FIND_COMPONENTS - OR MKL IN_LIST LAPACK_FIND_COMPONENTS)) - if(DEFINED ENV{MKLROOT}) - list(APPEND LAPACK_FIND_COMPONENTS MKL) - else() - list(APPEND LAPACK_FIND_COMPONENTS Netlib) - endif() -endif() - -find_package(PkgConfig QUIET) - -# ==== generic MKL variables ==== - -if(MKL IN_LIST LAPACK_FIND_COMPONENTS) - # we have to sanitize MKLROOT if it has Windows backslashes (\) otherwise it will break at build time - # double-quotes are necessary per CMake to_cmake_path docs. - file(TO_CMAKE_PATH "$ENV{MKLROOT}" MKLROOT) - - list(APPEND CMAKE_PREFIX_PATH ${MKLROOT}/bin/pkgconfig) - - if(NOT WIN32) - find_package(Threads) - endif() - - if(BUILD_SHARED_LIBS) - set(_mkltype dynamic) - else() - set(_mkltype static) - endif() - - if(MKL64 IN_LIST LAPACK_FIND_COMPONENTS) - set(_mkl_bitflag i) - else() - set(_mkl_bitflag) - endif() - - unset(_mkl_libs) - if(LAPACK95 IN_LIST LAPACK_FIND_COMPONENTS) - list(APPEND _mkl_libs mkl_blas95_${_mkl_bitflag}lp64 mkl_lapack95_${_mkl_bitflag}lp64) - endif() - - unset(_tbb) - if(TBB IN_LIST LAPACK_FIND_COMPONENTS) - list(APPEND _mkl_libs mkl_tbb_thread mkl_core) - set(_tbb tbb stdc++) - if(WIN32) - list(APPEND _mkl_libs tbb.lib) - endif() - elseif(OpenMP IN_LIST LAPACK_FIND_COMPONENTS) - pkg_check_modules(pc_mkl mkl-${_mkltype}-${_mkl_bitflag}lp64-iomp QUIET) - - set(_mp iomp5) - if(WIN32) - set(_mp libiomp5md) # "lib" is indeed necessary, even on CMake 3.14.0 - endif() - list(APPEND _mkl_libs mkl_intel_thread mkl_core ${_mp}) - else() - pkg_check_modules(pc_mkl mkl-${_mkltype}-${_mkl_bitflag}lp64-seq QUIET) - list(APPEND _mkl_libs mkl_sequential mkl_core) - endif() - - find_mkl_libs(${_mkl_libs}) - - if(LAPACK_LIBRARY) - - if(NOT WIN32) - list(APPEND LAPACK_LIBRARY ${_tbb} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} m) - endif() - - set(LAPACK_MKL_FOUND true) - - if(MKL64 IN_LIST LAPACK_FIND_COMPONENTS) - set(LAPACK_MKL64_FOUND true) - endif() - - if(LAPACK95 IN_LIST LAPACK_FIND_COMPONENTS) - set(LAPACK_LAPACK95_FOUND true) - endif() - - if(OpenMP IN_LIST LAPACK_FIND_COMPONENTS) - set(LAPACK_OpenMP_FOUND true) - endif() - - if(TBB IN_LIST LAPACK_FIND_COMPONENTS) - set(LAPACK_TBB_FOUND true) - endif() - endif() - -elseif(Atlas IN_LIST LAPACK_FIND_COMPONENTS) - - atlas_libs() - -elseif(Netlib IN_LIST LAPACK_FIND_COMPONENTS) - - netlib_libs() - -elseif(OpenBLAS IN_LIST LAPACK_FIND_COMPONENTS) - - openblas_libs() - -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LAPACK - REQUIRED_VARS LAPACK_LIBRARY - HANDLE_COMPONENTS) - -set(BLAS_LIBRARIES ${BLAS_LIBRARY}) -set(LAPACK_LIBRARIES ${LAPACK_LIBRARY}) -set(LAPACK_INCLUDE_DIRS ${LAPACK_INCLUDE_DIR}) - -if(LAPACK_FOUND) -# need if _FOUND guard to allow project to autobuild; can't overwrite imported target even if bad - if(NOT TARGET BLAS::BLAS) - add_library(BLAS::BLAS INTERFACE IMPORTED) - set_target_properties(BLAS::BLAS PROPERTIES - INTERFACE_LINK_LIBRARIES "${BLAS_LIBRARY}" - ) - endif() - - if(NOT TARGET LAPACK::LAPACK) - add_library(LAPACK::LAPACK INTERFACE IMPORTED) - set_target_properties(LAPACK::LAPACK PROPERTIES - INTERFACE_LINK_LIBRARIES "${LAPACK_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LAPACK_INCLUDE_DIR}" - ) - endif() -endif() - -mark_as_advanced(LAPACK_LIBRARY LAPACK_INCLUDE_DIR) diff --git a/cmake/SetBuildOptions.cmake b/cmake/SetBuildOptions.cmake index 00f29b761a..c6b932369c 100644 --- a/cmake/SetBuildOptions.cmake +++ b/cmake/SetBuildOptions.cmake @@ -35,7 +35,14 @@ elseif(COMPILER STREQUAL "Intel" OR COMPILER STREQUAL "IntelLLVM") set(CMAKE_C_FLAGS_DEBUG "-O0 -g") set(CMAKE_C_FLAGS_RELEASE "-O2 -debug minimal") set(CMAKE_Fortran_FLAGS "-free -qno-opt-dynamic-align -ftz -traceback -convert big_endian -assume byterecl -assume realloc_lhs -fp-model source -qopenmp") - set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -check uninit -check bounds -check pointers -fpe0 -check noarg_temp_created") + if(USE_PDAF) + # PDAF does not pass all checks from "-check all" + # TODO: Resolve the PDAF/non-PDAF difference by either adapting + # the compile options or debugging the source code + set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -fpe0") #-check all + else() + set(CMAKE_Fortran_FLAGS_DEBUG "-O0 -g -fpe0 -check all") + endif() set(CMAKE_Fortran_FLAGS_RELEASE "-O2 -debug minimal") else() message(FATAL_ERROR "COMPILER='${COMPILER}' is not supported.") diff --git a/docs/INDEX.md b/docs/INDEX.md index 7da1a37179..76c7c62cff 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -4,5 +4,5 @@ **Welcome!** You are viewing the first version of the documentation for eCLM. This is a living document, which means it will be continuously updated and improved. Please check back regularly for the latest information and updates. ``` -```{tableofcontents} -``` \ No newline at end of file +eCLM is based on version 5.0 of the Community Land Model ([CLM5](https://www.cesm.ucar.edu/models/clm)) with simplified infrastructure for build and namelist generation. The build system is handled entirely by Cmake and namelists are generated through a small set of Python scripts. Similar to CLM5, eCLM is forced with meteorological data and uses numerous input streams on soil properties, land cover and land use, as well as complex parameter sets on crop phenology, and plant hydraulics for simulations. + diff --git a/docs/Makefile b/docs/Makefile index 7c40135a30..58de1c0645 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,15 +1,15 @@ BUILD_DIR = ./_build SRC_DIR = ../src -all: docs src-browser - docs: jupyter-book build -W -n --keep-going . src-browser: ford -d $(SRC_DIR) -o $(BUILD_DIR)/html/src FORD_options.md +all: docs src-browser + .PHONY: clean clean: - jupyter-book clean . \ No newline at end of file + jupyter-book clean . diff --git a/docs/_config.yml b/docs/_config.yml index 33cb3e794f..3c1416a0f3 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -7,7 +7,7 @@ # Book settings title : eCLM Documentation # The title of the book. Will be placed in the left navbar. author : HPSC TerrSys # The author of the book -copyright : "2024" # Copyright year to be placed in the footer +copyright : "2025" # Copyright year to be placed in the footer logo : "" # A path to the book logo # Force re-execution of notebooks on each build. diff --git a/docs/_toc.yml b/docs/_toc.yml index e5ccdeed6f..7f74453e61 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -3,57 +3,51 @@ format: jb-book root: INDEX parts: - - caption: User's Guide - chapters: - - - file: users_guide/introduction_to_eCLM/README - title: Introduction to eCLM - sections: - - file: users_guide/introduction_to_eCLM/introduction - - file: users_guide/introduction_to_eCLM/prerequisites - - file: users_guide/introduction_to_eCLM/how_to_use_this_document - - - file: users_guide/building_eCLM/README - title: Building eCLM - sections: - - file: users_guide/building_eCLM/Ubuntu - - file: users_guide/building_eCLM/JSC/README - title: JSC - sections: - - file: users_guide/building_eCLM/JSC/prerequisites_JSC_users - - file: users_guide/building_eCLM/JSC/setting_up_eCLM - - file: users_guide/building_eCLM/Levante - - - file: users_guide/case_examples/README - title: Running example cases - sections: - - file: users_guide/case_examples/Wuestebach - - file: users_guide/case_examples/NRW - - file: users_guide/case_examples/EURO-CORDEX - - - file: users_guide/case_creation/README - title: Creating a custom case - sections: - - file: users_guide/case_creation/1_create_grid_file - - file: users_guide/case_creation/2_create_mapping_file - - file: users_guide/case_creation/3_create_domain_file - - file: users_guide/case_creation/4_create_surface_file - - file: users_guide/case_creation/5_modifications_surface_domain_file - - file: users_guide/case_creation/6_create_atm_forcings - - - file: users_guide/running_eCLM/README - title: Running eCLM - sections: - - file: users_guide/running_eCLM/basic_commands - - file: users_guide/running_eCLM/case_customization - - - file: users_guide/analyzing_model_output - title: Analyzing model output - - - caption: Reference - chapters: - - file: reference/history_fields - - url: https://escomp.github.io/ctsm-docs/versions/release-clm5.0/html/tech_note/index.html - title: Technical Note - - url: https://hpscterrsys.github.io/eCLM/src - title: eCLM Source Code Browser \ No newline at end of file +- caption: Introduction + chapters: + - file: users_guide/installation/README + title: Installing eCLM + - file: users_guide/introduction/introduction + title: Scientific Background + +- caption: User's Guide + chapters: + - file: users_guide/case_examples/README + title: Running example cases + sections: + - file: users_guide/case_examples/Wuestebach + - file: users_guide/case_examples/NRW + - file: users_guide/case_examples/EURO-CORDEX + + - file: users_guide/running_cases/case_customization + title: Customizing eCLM namelists + + - file: users_guide/analyzing_model_output + title: Analyzing model output + + - file: users_guide/case_creation/README + title: Creating a custom case + sections: + - file: users_guide/case_creation/1_create_grid_file + - file: users_guide/case_creation/2_create_mapping_file + - file: users_guide/case_creation/3_create_domain_file + - file: users_guide/case_creation/4_create_surface_file + - file: users_guide/case_creation/5_modifications_surface_domain_file + - file: users_guide/case_creation/6_create_atm_forcings + +- caption: Developer's Guide + chapters: + - file: users_guide/installation/source_installation + title: Building eCLM from source + - url: https://hpscterrsys.github.io/eCLM/src + title: eCLM Source Code Browser + +- caption: Reference + chapters: + - file: reference/history_fields + - url: https://escomp.github.io/CTSM/release-clm5.0/tech_note/index.html + title: CLM5 Technical Note + - url: https://github.com/HPSCTerrSys/eCLM_static-file-generator/blob/main/README.md) + title: eCLM static file generator + - url: https://hpscterrsys.github.io/TSMP2_workflow-engine + title: TSMP2 Workflow Engine diff --git a/docs/users_guide/building_eCLM/JSC/README.md b/docs/users_guide/building_eCLM/JSC/README.md deleted file mode 100644 index b97a98c468..0000000000 --- a/docs/users_guide/building_eCLM/JSC/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# JSC - -The following section will explain the necessary steps to download the model from the official repository and get eCLM to run on JSC machines (Juwels or Jureca-DC). diff --git a/docs/users_guide/building_eCLM/JSC/prerequisites_JSC_users.md b/docs/users_guide/building_eCLM/JSC/prerequisites_JSC_users.md deleted file mode 100644 index b711f3e450..0000000000 --- a/docs/users_guide/building_eCLM/JSC/prerequisites_JSC_users.md +++ /dev/null @@ -1,11 +0,0 @@ -# Prerequisites for JSC users - -Before building and running eCLM on JSC machines, the following prerequisites should be fulfilled: - -* Create a JSC/JuDoor account -* Join a compute time project -* Logging in to JSC machines - -Follow the startup-guide for creating a JSC/JuDoor account, joining a compute time project and logging in to JSC machines. - -See also additional links and pages, e.g. Software tips for Windows users: https://gitlab.jsc.fz-juelich.de/sdlts/general-organisation/jsc-supercomputer-wiki-for-ibg-3/-/wikis/Software-for-Windows-users diff --git a/docs/users_guide/building_eCLM/JSC/setting_up_eCLM.md b/docs/users_guide/building_eCLM/JSC/setting_up_eCLM.md deleted file mode 100644 index 3ac68c0381..0000000000 --- a/docs/users_guide/building_eCLM/JSC/setting_up_eCLM.md +++ /dev/null @@ -1,154 +0,0 @@ -# Setting up eCLM - -Once you created a JSC account and have access to a compute time project, follow these steps which will guide you on how to download and build eCLM on a JSC system. This guide involves four major steps: - -1. Prepare the environment -2. Download eCLM -3. Build eCLM - - Loading dependencies - - Configuring build settings - - Building eCLM -4. Verify that eCLM works - -Begin by logging in to the JSC system on your local machine using the ssh key. -(Windows users: open a terminal with Putty) - -For juwels for example do this: - -```sh -ssh -X -i ~/.ssh/id_ed25519 user1@juwels.fz-juelich.de # replace user1 with your JuDoor username! -``` - -## Step 1: Prepare the environment - -First, if not already done, you will create your own folders within the `project1` and `scratch` directories of your compute project on the supercomputer. - -```sh -# Check your projects and select a compute project with a non-empty 'budget-accounts'. Use it to replace projectID below! -jutil user projects -u $USER - -# Activate your project -jutil env activate -p projectID - -# Create folders -MYPROJECT="$PROJECT/$USER" -MYSCRATCH="$SCRATCH/$USER" -mkdir -p $MYPROJECT $MYSCRATCH -``` - -## Step 2: Download eCLM - -Navigate to your `$MYPROJECT` directory and clone the eCLM repository from Github. Next you will navigate into the main model folder and set the `eCLM_ROOT` environment variable. - -```sh -# Clone eCLM Github repository -cd $MYPROJECT -git clone https://github.com/HPSCTerrSys/eCLM.git - -# Navigate into eCLM folder and export environment variable -cd eCLM -eCLM_ROOT=$(pwd) -``` -## Step 3: Build eCLM - -### Loading dependencies - -Next, you need to load the software libraries required by eCLM. This is important as eCLM would fail to build nor run without knowing the location of its dependencies on the supercomputer. As this step needs to be done each time you start a new session, it is convenient to create a shell script that automates this step. Run this command to create a shell script named `load-eclm-variables.sh` in your `$HOME` directory: - -```sh -cat << EOF > $HOME/load-eclm-variables.sh -``` - -Then copy the following in the shell file: -```{attention} -Before you copy, replace 'projectID' with your compute project! -``` - -```sh -#!/usr/bin/env bash - -# Activate compute project -jutil env activate -p projectID - -# Set helper variables -MYPROJECT=${MYPROJECT} -MYSCRATCH=${MYSCRATCH} -eCLM_ROOT=${eCLM_ROOT} - -# Load eCLM dependencies -module load Stages/2024 -module load Intel -module load ParaStationMPI -module load netCDF -module load netCDF-Fortran -module load PnetCDF -module load imkl -module load Python -module load Perl -module load CMake - -# Display environment variables -module li -echo MYPROJECT=${MYPROJECT} -echo MYSCRATCH=${MYSCRATCH} -echo eCLM_ROOT=${eCLM_ROOT} - -# Navigate into eCLM model folder -cd $eCLM_ROOT -EOF -``` - -Now, source the environment file by running: - -```sh -source $HOME/load-eclm-variables.sh -``` - -You should get an output similar to this one: - -```{figure} ../../images/load_env.png -:width: 100% -``` -

- -### Configuring build settings - -eCLM is built using the CMake build system. Initially, you need to pass some build settings to `cmake`, such as which C/Fortran compilers to be used and the location of the install folder. This is accomplished by running the following commands: - -```sh -# Set variables -BUILD_DIR="${eCLM_ROOT}/build" -INSTALL_DIR="${eCLM_ROOT}/install" - -# CMake configure step -cmake -S "${eCLM_ROOT}/src" \ - -B "${BUILD_DIR}" \ - -D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ - -D CMAKE_BUILD_TYPE="RELEASE" \ - -D CMAKE_C_COMPILER=mpicc \ - -D CMAKE_Fortran_COMPILER=mpifort -``` - -### Building eCLM - -Finally, you can build eCLM. The commands below should take approximately 15-20 minutes to finish. - -```sh -cmake --build "${BUILD_DIR}" && cmake --install "${BUILD_DIR}" -``` - -## Step 4: Verify that eCLM works - -You can check if eCLM has been properly installed. The following command will display a directory tree showing the eCLM executable `eclm.exe` and library files in the `lib` directory: - -```sh -tree $eCLM_ROOT/install -``` -You should get something similar to: - -```{figure} ../../images/eclm_build.png -:height: 300px -``` -

- -**Congratulations!** You have successfully built and installed eCLM. diff --git a/docs/users_guide/building_eCLM/Levante.md b/docs/users_guide/building_eCLM/Levante.md deleted file mode 100644 index ecb8893007..0000000000 --- a/docs/users_guide/building_eCLM/Levante.md +++ /dev/null @@ -1,5 +0,0 @@ -# Levante - -```{warning} -Page under construction -``` \ No newline at end of file diff --git a/docs/users_guide/building_eCLM/README.md b/docs/users_guide/building_eCLM/README.md deleted file mode 100644 index 4d4e679097..0000000000 --- a/docs/users_guide/building_eCLM/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Building eCLM - -## Installation - -This section shows you how to build eCLM. - -### Minimum system requirements - -* MPI 3.1 -* netCDF-C 4.7.4 -* netCDF-Fortran 4.5.2 -* PnetCDF 1.12.1 -* LAPACK -* CMake 3.16 -* Supported compilers - - GCC 9.3.0 - - Intel 19.1.2 - -### Building eCLM - -1. Configure CMake build options. - -```sh -# User-specific variables -BUILD_DIR="bld" -INSTALL_DIR="eclm" - -# Run cmake -cmake -S src -B "$BUILD_DIR" \ - -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \ - -DCMAKE_C_COMPILER=mpicc \ - -DCMAKE_Fortran_COMPILER=mpifort -``` - -Additionally, you may specify these optional build variables. - -* `CMAKE_BUILD_TYPE=DEBUG|RELEASE`. Defaults to `RELEASE`. -* `CMAKE_PREFIX_PATH`. Semicolon-separated list of paths (i.e. *install prefixes*) where external libraries might be found. You may need to specify this if CMake cannot find some of the required libraries (e.g. NetCDF, PnetCDF, LAPACK). - -2. Build and install eCLM. - -```sh -cmake --build "$BUILD_DIR" -cmake --install "$BUILD_DIR" -``` - -* adding the flag `--parallel 8` can potentially speed up the build - process (full command: `cmake --build "$BUILD_DIR" --parallel 8`), - https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-build-j - -### Install namelist generator Python package - -The namelist generator scripts require Python 3.X. - -```sh -# Upgrade to latest version of pip -python3 -m pip install --upgrade pip - -# Install package -pip3 install --user ./namelist_generator -``` diff --git a/docs/users_guide/building_eCLM/Ubuntu.md b/docs/users_guide/building_eCLM/Ubuntu.md deleted file mode 100644 index c246ca41d1..0000000000 --- a/docs/users_guide/building_eCLM/Ubuntu.md +++ /dev/null @@ -1,36 +0,0 @@ -# Ubuntu - -```{warning} -Page under construction -``` - -## Minimum system requirements for Ubuntu - -On Ubuntu the following command should load the necessary system -requirements - -```sh -# Update `apt` -sudo apt update - -# Install packages -# ---------------- - -# Utilities -sudo apt install libxml2-utils wget - -# Python -sudo apt install python3 python3-pip pylint - -# Compiler -sudo apt install gfortran openmpi-bin libopenmpi-dev cmake - -# Linear algebra -sudo apt install libblas-dev liblapack-dev - -# NetCDF -sudo apt install netcdf-bin libnetcdf-dev libnetcdff-dev libpnetcdf-dev -``` - -Have a look in `.github/workflows/CI.yml` to find the Ubuntu packages -installed for CI-testing eCLM. diff --git a/docs/users_guide/case_examples/README.md b/docs/users_guide/case_examples/README.md index 6f331739f5..3c98c12a5a 100644 --- a/docs/users_guide/case_examples/README.md +++ b/docs/users_guide/case_examples/README.md @@ -1,4 +1,4 @@ -# Runnning example cases +# Running example cases Always load the eCLM environment before creating a case. This only needs to be done once per terminal session. diff --git a/docs/users_guide/installation/README.md b/docs/users_guide/installation/README.md new file mode 100644 index 0000000000..5599edd686 --- /dev/null +++ b/docs/users_guide/installation/README.md @@ -0,0 +1,13 @@ +# Installing eCLM + +The easiest way to install eCLM is through [TSMP2 build system](https://github.com/HPSCTerrSys/TSMP2). + +```sh +# Download TSMP2 +git clone https://github.com/HPSCTerrSys/TSMP2.git +cd TSMP2 + +# Build and install eCLM +./build_tsmp2.sh --eCLM +``` + diff --git a/docs/users_guide/installation/source_installation.md b/docs/users_guide/installation/source_installation.md new file mode 100644 index 0000000000..2a2a304d6b --- /dev/null +++ b/docs/users_guide/installation/source_installation.md @@ -0,0 +1,37 @@ +# Installing eCLM from source + +```{warning} +For advanced users. +``` + +## Requirements + +* MPI compilers (e.g. OpenMPI) +* CMake +* LAPACK +* [netCDF C and Fortran libraries](https://downloads.unidata.ucar.edu/netcdf) +* [PnetCDF](https://github.com/Parallel-NetCDF/PnetCDF) + +## Steps + +```sh +# Download eCLM +git clone https://github.com/HPSCTerrSys/eCLM.git +cd eCLM + +# Create eCLM install directory +mkdir install + +# Set compilers +export CC=mpicc FC=mpifort + +# Build and install eCLM +cmake -S src -B bld -DCMAKE_INSTALL_PREFIX=install +cmake --build bld --parallel +cmake --install bld +``` + +## Reference build scripts + +- [eCLM build on Ubuntu](https://github.com/HPSCTerrSys/eCLM/blob/4d567d2d68cac0fba977914b4a9c3ba199afd0ff/.github/workflows/CI.yml#L70-L121) +- [eCLM build on TSMP2](https://github.com/HPSCTerrSys/TSMP2/blob/master/cmake/BuildeCLM.cmake) diff --git a/docs/users_guide/introduction_to_eCLM/introduction.md b/docs/users_guide/introduction/introduction.md similarity index 100% rename from docs/users_guide/introduction_to_eCLM/introduction.md rename to docs/users_guide/introduction/introduction.md diff --git a/docs/users_guide/introduction_to_eCLM/README.md b/docs/users_guide/introduction_to_eCLM/README.md deleted file mode 100644 index be9f55838f..0000000000 --- a/docs/users_guide/introduction_to_eCLM/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Introduction to eCLM - -eCLM is based on version 5.0 of the Community Land Model ([CLM5](https://www.cesm.ucar.edu/models/clm)) with simplified infrastructure for build and namelist generation. The build system is handled entirely by Cmake and namelists are generated through a small set of Python scripts. Similar to CLM5, eCLM is forced with meteorological data and uses numerous input streams on soil properties, land cover and land use, as well as complex parameter sets on crop phenology, and plant hydraulics for simulations. - -The following section will give you a brief overview of CLM5. diff --git a/docs/users_guide/introduction_to_eCLM/how_to_use_this_document.md b/docs/users_guide/introduction_to_eCLM/how_to_use_this_document.md deleted file mode 100644 index 4d93e1be07..0000000000 --- a/docs/users_guide/introduction_to_eCLM/how_to_use_this_document.md +++ /dev/null @@ -1,16 +0,0 @@ -# How to use this document - -Throughout the document, the following style is used: - -``` -this is code to be executed in the shell command line -``` -```sh -# this is a comment -``` - -This is a name of a `variable` or a `command` and this in an example for a `script.sh`. - -This indicates a path and a specific file that the path leads to: `this/is/a/path/file.nc`. - -This is a link to a website. \ No newline at end of file diff --git a/docs/users_guide/introduction_to_eCLM/prerequisites.md b/docs/users_guide/introduction_to_eCLM/prerequisites.md deleted file mode 100644 index 02fe47b301..0000000000 --- a/docs/users_guide/introduction_to_eCLM/prerequisites.md +++ /dev/null @@ -1,19 +0,0 @@ -# Prerequisites - -## Linux systems - -If you are new to using Linux systems, it’s helpful to get familiar with a couple of things which will make setting up and working in this environment easier. - -### 1. Get familiar with linux command line -There are many good resources on the internet to get familiar with the basic command line arguments to navigate through different directories, create folders and files, move or manipulate files etc.. - -Resources: -- https://www.linuxcommand.org/ -- Jülich Supercomputing Centre: https://go.fzj.de/JSC-usertips - -### 2. Get familiar with a text editor -A text editor is very useful for quick and effective editing of files on Linux systems. There are different options for text editors like vim, [Emacs](https://www.gnu.org/software/emacs/) or nano. Vim and Emacs are already installed on the JSC systems. Look for a cheat sheet or a tutorial online or check the documentation on their official websites. - -## netCDF files - -NetCDF (network Common Data Form) is a file format for storing multidimensional scientific data (variables). In order to handle model input and output data for eCLM, you will need to be able to work with this file format. If you are not familiar with it yet, look for some resources or tutorials online. A quick introduction to netCDF can, for example, be found here. diff --git a/docs/users_guide/running_eCLM/case_customization.md b/docs/users_guide/running_cases/case_customization.md similarity index 86% rename from docs/users_guide/running_eCLM/case_customization.md rename to docs/users_guide/running_cases/case_customization.md index f5dfa2e854..2284f5ea46 100644 --- a/docs/users_guide/running_eCLM/case_customization.md +++ b/docs/users_guide/running_cases/case_customization.md @@ -1,6 +1,9 @@ # Case customization -eCLM uses various namelist files that handle different settings and configurations for running a case. These namelists are similar to CLM5 (only the editing is different) so that you can refer to Section 1.2.3 and 1.2.4 of the CLM5 User's Guide for more detailed information. +eCLM uses various **namelist files** that handle different settings and configurations for running a case. +Namelist files are text files, where the user can provide values for various input fields, called **namelist items**. +Namelist items are parsed by eCLM and connected internal variables are set according to their values. +eCLM's namelist files are very similar to the namelist files used in CLM5 (only the editing is different) so that you can refer to Section 1.2.3 and 1.2.4 of the CLM5 User's Guide for more detailed information. The eCLM namelist files are: - Land model namelist `lnd_in` (see here for definitions of namelist items) @@ -61,7 +64,12 @@ Important namelist parameter in the `drv_in` are: ## 3. Customizing case output -To customize your simulation output, you can specify the history field options in the namelist file `lnd_in`. By default, there is one stream of monthly data files. The field options to customize are: +To customize your simulation output, you can specify the history field +options in the namelist file `lnd_in`. +By default, there is one stream of monthly data files called history +files. +History files are in NetCDF format. +The field options to customize are: - `hist_fincl1`: The list of history variables that you want to analyze - `hist_mfilt`: The number of records within one output file. Default is 1. @@ -103,4 +111,4 @@ You can then submit the job. sbatch run-eclm-job.sh ``` -You can monitor your job using `sacct` or `squeue -u $USER`. \ No newline at end of file +You can monitor your job using `sacct` or `squeue -u $USER`. diff --git a/docs/users_guide/running_eCLM/README.md b/docs/users_guide/running_eCLM/README.md deleted file mode 100644 index 97539a9383..0000000000 --- a/docs/users_guide/running_eCLM/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Running eCLM - -This section will explain how to run and customize your own eCLM cases. - -```{attention} -This workflow is still being further developed and is likely to change in the future. So make sure you check this website for updates! -``` \ No newline at end of file diff --git a/docs/users_guide/running_eCLM/basic_commands.md b/docs/users_guide/running_eCLM/basic_commands.md deleted file mode 100644 index 9241b7f851..0000000000 --- a/docs/users_guide/running_eCLM/basic_commands.md +++ /dev/null @@ -1,32 +0,0 @@ -# Basic commands to run eCLM - -Begin by logging in to the JSC (or another) system. Then, source the eCLM environment file. - -```sh -source load-eclm-variables.sh -``` - -You can create a folder called `cases` or similar in your main eCLM directory in which you create a new directory for every new case. - -```sh -mkdir -p cases/"your case name" -``` - -For the moment, copy the namelists from one of the test cases to use as a starting point for your new case. - -```sh -cd cases/"your case name" # replace with your case name -cp ../../test_cases/"test case"/. . # replace with the name of the test case -``` - -In the next step, you will customize the namelist files to your new case. - - - - - - - - - - diff --git a/src/clm5/biogeochem/ch4Mod.F90 b/src/clm5/biogeochem/ch4Mod.F90 index a35d92c7f7..b42d268415 100644 --- a/src/clm5/biogeochem/ch4Mod.F90 +++ b/src/clm5/biogeochem/ch4Mod.F90 @@ -2473,12 +2473,14 @@ subroutine ch4_prod (bounds, num_methc, filter_methc, num_methp, & end if ! If switched on, use pH factor for production based on spatial pH data defined in surface data. - if (.not. lake .and. usephfact .and. pH(c) > pHmin .and.pH(c) < pHmax) then + if (.not. lake .and. usephfact) then + if (pH(c) > pHmin .and.pH(c) < pHmax) then pH_fact_ch4 = 10._r8**(-0.2235_r8*pH(c)*pH(c) + 2.7727_r8*pH(c) - 8.6_r8) ! fitted function using data from Dunfield et al. 1993 ! Strictly less than one, with optimum at 6.5 ! From Lei Meng f_ch4_adj = f_ch4_adj * pH_fact_ch4 + end if else ! if no data, then no pH effects end if @@ -3552,10 +3554,11 @@ subroutine ch4_tran (bounds, & pondz = h2osfc(c) / 1000._r8 / frac_h2osfc(c) ! Assume all h2osfc corresponds to sat area ! mm / mm/m pondres = pondres + pondz / ponddiff - else if (.not. lake .and. sat == 1 .and. frac_h2osfc(c) > 0._r8 .and. & - h2osfc(c)/frac_h2osfc(c) > capthick) then ! Assuming short-circuit logic will avoid FPE here. + else if (.not. lake .and. sat == 1 .and. frac_h2osfc(c) > 0._r8) then + if (h2osfc(c)/frac_h2osfc(c) > capthick) then ! Assuming short-circuit logic will avoid FPE here. ! assume surface ice is impermeable pondres = 1/smallnumber + end if end if spec_grnd_cond(c,s) = 1._r8/(1._r8/grnd_ch4_cond(c) + snowres(c) + pondres) diff --git a/src/clm5/biogeophys/BalanceCheckMod.F90 b/src/clm5/biogeophys/BalanceCheckMod.F90 index c567e64877..7ca412955a 100644 --- a/src/clm5/biogeophys/BalanceCheckMod.F90 +++ b/src/clm5/biogeophys/BalanceCheckMod.F90 @@ -179,11 +179,11 @@ subroutine BalanceCheck( bounds, & ! error = abs(precipitation - change of water storage - evaporation - runoff) ! ! !USES: - use clm_varcon , only : spval - use clm_time_manager , only : get_step_size, get_nstep - use clm_time_manager , only : get_nstep_since_startup_or_lastDA_restart_or_pause - use CanopyStateType , only : canopystate_type - use SurfaceAlbedoType , only : surfalb_type + use clm_varcon , only : spval + use clm_time_manager , only : get_step_size, get_nstep + use clm_time_manager , only : get_nstep_since_startup_or_lastDA_restart_or_pause + use CanopyStateType , only : canopystate_type + use SurfaceAlbedoType , only : surfalb_type use subgridAveMod ! ! !ARGUMENTS: @@ -227,7 +227,6 @@ subroutine BalanceCheck( bounds, & errh2osno => waterstate_inst%errh2osno_col , & ! Output: [real(r8) (:) ] error in h2osno (kg m-2) endwb => waterstate_inst%endwb_col , & ! Output: [real(r8) (:) ] water mass end of the time step total_plant_stored_h2o_col => waterstate_inst%total_plant_stored_h2o_col, & ! Input: [real(r8) (:) ] water mass in plant tissues (kg m-2) - qflx_rootsoi_col => waterflux_inst%qflx_rootsoi_col , & ! Input [real(r8) (:) ] water loss in soil layers to root uptake (mm H2O/s) ! (ie transpiration demand, often = transpiration) qflx_rain_grnd_col => waterflux_inst%qflx_rain_grnd_col , & ! Input: [real(r8) (:) ] rain on ground after interception (mm H2O/s) [+] @@ -261,6 +260,7 @@ subroutine BalanceCheck( bounds, & qflx_ice_dynbal => waterflux_inst%qflx_ice_dynbal_grc , & ! Input: [real(r8) (:) ] ice runoff due to dynamic land cover change (mm H2O /s) snow_sources => waterflux_inst%snow_sources_col , & ! Output: [real(r8) (:) ] snow sources (mm H2O /s) snow_sinks => waterflux_inst%snow_sinks_col , & ! Output: [real(r8) (:) ] snow sinks (mm H2O /s) + qflx_irrig => irrigation_inst%qflx_irrig_col , & ! Input: [real(r8) (:) ] irrigation flux (mm H2O /s) qflx_glcice_dyn_water_flux => glacier_smb_inst%qflx_glcice_dyn_water_flux_col, & ! Input: [real(r8) (:)] water flux needed for balance check due to glc_dyn_runoff_routing (mm H2O/s) (positive means addition of water to the system) @@ -346,6 +346,7 @@ subroutine BalanceCheck( bounds, & - qflx_ice_runoff_xs(c) & - qflx_snwcp_discarded_liq(c) & - qflx_snwcp_discarded_ice(c)) * dtime + else errh2o(c) = 0.0_r8 @@ -355,12 +356,11 @@ subroutine BalanceCheck( bounds, & end do found = .false. - do c = bounds%begc, bounds%endc - if (abs(errh2o(c)) > 1.e-9_r8) then - found = .true. - indexc = c - end if + if (abs(errh2o(c)) > 1.e-9_r8) then + found = .true. + indexc = c + end if end do if ( found ) then @@ -406,12 +406,12 @@ subroutine BalanceCheck( bounds, & ! TODO: Balance errors must be fixed for fully coupled model (ICON-eCLM-ParFlow) write(iulog,*)'Ignoring water balance error...' #else - write(iulog,*)'clm model is stopping - error is greater than 1e-5 (mm)' + write(iulog,*)'clm model is stopping' call endrun(decomp_index=indexc, clmlevel=namec, msg=errmsg(sourcefile, __LINE__)) #endif else if (abs(errh2o(indexc)) > 1.e-5_r8 .and. (DAnstep > skip_steps) ) then - + write(iulog,*)'clm model is stopping - error is greater than 1e-5 (mm)' write(iulog,*)'nstep = ',nstep write(iulog,*)'errh2o = ',errh2o(indexc) write(iulog,*)'forc_rain = ',forc_rain_col(indexc)*dtime @@ -441,7 +441,7 @@ subroutine BalanceCheck( bounds, & ! TODO: Balance errors must be fixed for fully coupled model (ICON-eCLM-ParFlow) write(iulog,*)'Ignoring water balance error...' #else - write(iulog,*)'clm model is stopping - error is greater than 1e-5 (mm)' + write(iulog,*)'clm model is stopping' call endrun(decomp_index=indexc, clmlevel=namec, msg=errmsg(sourcefile, __LINE__)) #endif end if @@ -690,6 +690,7 @@ subroutine BalanceCheck( bounds, & end if ! Soil energy balance check + found = .false. do c = bounds%begc,bounds%endc if (col%active(c)) then @@ -707,8 +708,10 @@ subroutine BalanceCheck( bounds, & ! TODO: Balance errors must be fixed for fully coupled model (ICON-eCLM-ParFlow) write(iulog,*)'Ignoring soil balance error...' #else - write(iulog,*)'clm model is stopping - error is greater than 1e-5 (mm)' - call endrun(decomp_index=indexc, clmlevel=namec, msg=errmsg(sourcefile, __LINE__)) + if (abs(errsoi_col(indexc)) > 1.e-4_r8 .and. (DAnstep > skip_steps) ) then + write(iulog,*)'clm model is stopping' + call endrun(decomp_index=indexc, clmlevel=namec, msg=errmsg(sourcefile, __LINE__)) + end if #endif end if diff --git a/src/clm5/biogeophys/SoilHydrologyMod.F90 b/src/clm5/biogeophys/SoilHydrologyMod.F90 index 059f630823..d9f251602a 100644 --- a/src/clm5/biogeophys/SoilHydrologyMod.F90 +++ b/src/clm5/biogeophys/SoilHydrologyMod.F90 @@ -2362,9 +2362,13 @@ subroutine ParFlowDrainage(bounds, num_hydrologyc, filter_hydrologyc, & ! !LOCAL VARIABLES: character(len=32) :: subname = 'ParFlowDrainage' ! subroutine name integer :: c,j,fc ! indices + real(r8), parameter :: m_per_mm = 1.e-3_r8 ! 0.001 meters per mm + real(r8), parameter :: sec_per_hr = 3600._r8 ! 3600 s in 1 hour + !----------------------------------------------------------------------- - associate( & + associate( & + dz => col%dz , & ! Input: [real(r8) (:,:) ] layer depth (m) qflx_snwcp_liq => waterflux_inst%qflx_snwcp_liq_col , & ! excess rainfall due to snow capping (mm H2O /s) [+] qflx_drain => waterflux_inst%qflx_drain_col , & ! sub-surface runoff (mm H2O /s) qflx_drain_perched => waterflux_inst%qflx_drain_perched_col , & ! perched wt sub-surface runoff (mm H2O /s) @@ -2377,25 +2381,25 @@ subroutine ParFlowDrainage(bounds, num_hydrologyc, filter_hydrologyc, & ! COUP_OAS_PFL ! Calculate here the source/sink term for ParFlow - do j = 1, nlevsoi - do fc = 1, num_hydrologyc - c = filter_hydrologyc(fc) + do fc = 1, num_hydrologyc + c = filter_hydrologyc(fc) + do j = 1, nlevsoi if (j == 1) then ! From SoilWaterPlantSinkMod: ! qflx_rootsoi_col(c,j) = rootr_col(c,j)*qflx_tran_veg_col(c) - qflx_parflow(c,j) = (qflx_infl(c) - qflx_rootsoi(c,j)) !* 3.6_r8 / dz(c,j) + qflx_parflow(c,j) = (qflx_infl(c) - qflx_rootsoi(c,j)) !mm/s else - qflx_parflow(c,j) = -qflx_rootsoi(c,j) !* 3.6_r8 / dz(c,j) + qflx_parflow(c,j) = -qflx_rootsoi(c,j) !mm/s end if end do - end do - - do fc = 1, num_hydrologyc - c = filter_hydrologyc(fc) - qflx_drain(c) = -sum(qflx_parflow(c,:)) !0._r8 + ! Compute subsurface run-off (mm/s) + qflx_drain(c) = -sum(qflx_parflow(c,:)) qflx_drain_perched(c) = 0._r8 qflx_rsub_sat(c) = 0._r8 qflx_qrgwl(c) = qflx_snwcp_liq(c) ! Set imbalance for snow capping + ! Convert eCLM fluxes (mm/s) to ParFlow fluxes (1/hr): + ! 1/hr = [mm/s] * [s/hr] * [m/mm] * [1/m] + qflx_parflow(c,1:nlevsoi) = qflx_parflow(c,1:nlevsoi) * sec_per_hr * m_per_mm * (1._r8/dz(c,1:nlevsoi)) end do ! No drainage for urban columns (necessary to account for water balance errors) diff --git a/src/clm5/biogeophys/WaterfluxType.F90 b/src/clm5/biogeophys/WaterfluxType.F90 index ea1add1359..098de9849a 100644 --- a/src/clm5/biogeophys/WaterfluxType.F90 +++ b/src/clm5/biogeophys/WaterfluxType.F90 @@ -73,7 +73,7 @@ module WaterfluxType real(r8), pointer :: qflx_adv_col (:,:) ! col advective flux across different soil layer interfaces [mm H2O/s] [+ downward] real(r8), pointer :: qflx_rootsoi_col (:,:) ! col root and soil water exchange [mm H2O/s] [+ into root] #ifdef COUP_OAS_PFL - real(r8), pointer :: qflx_parflow_col (:,:) ! col source/sink flux per soil layer sent to ParFlow [mm H2O/s] [- out from root] + real(r8), pointer :: qflx_parflow_col (:,:) ! col source/sink flux per soil layer sent to ParFlow [1/hr] [- out from root] #endif real(r8), pointer :: qflx_infl_col (:) ! col infiltration (mm H2O /s) real(r8), pointer :: qflx_surf_col (:) ! col surface runoff (mm H2O /s) diff --git a/src/clm5/main/lnd2atmMod.F90 b/src/clm5/main/lnd2atmMod.F90 index 61e44b3f9b..950c8fd610 100644 --- a/src/clm5/main/lnd2atmMod.F90 +++ b/src/clm5/main/lnd2atmMod.F90 @@ -162,8 +162,6 @@ subroutine lnd2atm(bounds, & real(r8), parameter :: amC = 12.0_r8 ! Atomic mass number for Carbon real(r8), parameter :: amO = 16.0_r8 ! Atomic mass number for Oxygen real(r8), parameter :: amCO2 = amC + 2.0_r8*amO ! Atomic mass number for CO2 - real(r8), parameter :: m_per_mm = 1.e-3_r8 ! 0.001 meters per mm - real(r8), parameter :: sec_per_hr = 3600 ! 3600 s in 1 hour ! The following converts g of C to kg of CO2 real(r8), parameter :: convertgC2kgCO2 = 1.0e-3_r8 * (amCO2/amC) !------------------------------------------------------------------------ @@ -433,18 +431,15 @@ subroutine lnd2atm(bounds, & lnd2atm_inst%qflx_parflow_grc (bounds%begg:bounds%endg, :), & c2l_scale_type= 'unity', l2g_scale_type='unity' ) - do c = bounds%begc, bounds%endc - if (col%hydrologically_active(c)) then - if (col%itype(c) == istsoil .or. col%itype(c) == istcrop) then - g = col%gridcell(c) - do j = 1, nlevsoi - ! Convert eCLM fluxes (mm/s) to ParFlow fluxes (1/hr): - ! 1/hr = [mm/s] * [s/hr] * [m/mm] * [1/m] - lnd2atm_inst%qflx_parflow_grc(g,j) = lnd2atm_inst%qflx_parflow_grc(g,j) * sec_per_hr * m_per_mm * (1/col%dz(c,j)) - enddo - end if - end if - end do + ! adjust nan values after c2g, need to be rechecked + do g = bounds%begg, bounds%endg + do j = 1, nlevsoi + if (lnd2atm_inst%qflx_parflow_grc(g,j) == spval) then + lnd2atm_inst%qflx_parflow_grc(g,j) = 0._r8 + write(iulog,*)'WARNING: qflx_parflow_grc is nan at grid point ',g,' level',j,' replaced with 0.' + end if + end do + enddo #endif end subroutine lnd2atm diff --git a/src/eclm/cime_comp_mod.F90 b/src/eclm/cime_comp_mod.F90 index 3a343a7bd5..ada3780b31 100644 --- a/src/eclm/cime_comp_mod.F90 +++ b/src/eclm/cime_comp_mod.F90 @@ -4059,7 +4059,7 @@ subroutine cime_run() ice(ens1)%iamroot_compid .or. & glc(ens1)%iamroot_compid .or. & wav(ens1)%iamroot_compid) then - call shr_mem_getusage(msize,mrss,.true.) + call shr_mem_getusage(msize,mrss,.false.) write(logunit,105) ' memory_write: model date = ',ymd,tod, & ' memory = ',msize,' MB (highwater) ',mrss,' MB (usage)', & @@ -4119,12 +4119,12 @@ subroutine cime_run() endif #ifdef USE_PDAF - ! TSMP specific stop condition: + ! TSMP-PDAF specific stop condition: counter = counter + 1 if (present(ntsteps) .and. counter == ntsteps) then if (iamroot_CPLID) then write(logunit,*) ' ' - write(logunit,103) subname,' NOTE: Stopping from TSMP-PDAF alarm ntsteps' + write(logunit,'(A, A, i10.8, i8)') subname,' NOTE: Stopping from TSMP-PDAF alarm ntsteps at model date = ',ymd,tod write(logunit,*) ' ' endif stop_alarm = .true. diff --git a/src/externals/CMakeLists.txt b/src/externals/CMakeLists.txt index eb21ab1c84..1c00f8e136 100644 --- a/src/externals/CMakeLists.txt +++ b/src/externals/CMakeLists.txt @@ -57,6 +57,7 @@ if (BUILD_MCT) MPIFC=${CMAKE_Fortran_COMPILER} CFLAGS=${CMAKE_C_FLAGS} FCFLAGS=${CMAKE_Fortran_FLAGS} + BUILD_COMMAND make clean install BUILD_ALWAYS YES BUILD_BYPRODUCTS ${GPTL_BLD_DIR}/lib/libmct.a ${GPTL_BLD_DIR}/lib/libmpeu.a ) From ff6f67d272bc2656276bd11967dfb4b48d30a324 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 10:45:35 +0200 Subject: [PATCH 09/17] Added csm_share unit tests --- .github/workflows/CI.yml | 16 +- src/CMakeLists.txt | 16 +- src/csm_share/CMakeLists.txt | 4 + src/csm_share/esmf_wrf_timemgr/CMakeLists.txt | 19 - src/csm_share/mct/CMakeLists.txt | 9 - src/csm_share/util/CMakeLists.txt | 44 -- src/csm_share/util/test/CMakeLists.txt | 69 +++ src/csm_share/util/test/shr_abort_test/README | 2 + .../test/shr_abort_test/test_shr_abort.pf | 39 ++ .../util/test/shr_assert_test/test_assert.pf | 56 +++ .../test/shr_assert_test/test_assert_array.pf | 185 ++++++++ .../util/test/shr_assert_test/test_macro.pf | 87 ++++ .../util/test/shr_assert_test/test_ndebug.pf | 63 +++ .../util/test/shr_cal_test/test_shr_cal.pf | 396 ++++++++++++++++++ .../util/test/shr_infnan_test/test_infnan.F90 | 174 ++++++++ .../test/shr_log_test/test_error_printers.pf | 51 +++ .../test/shr_precip_test/test_shr_precip.pf | 62 +++ .../util/test/shr_spfn_test/test_erf_r4.pf | 132 ++++++ .../util/test/shr_spfn_test/test_erf_r8.pf | 132 ++++++ .../shr_spfn_test/test_gamma_factorial.pf | 98 +++++ .../util/test/shr_spfn_test/test_igamma.pf | 42 ++ .../test/shr_strconvert_test/test_toString.pf | 165 ++++++++ .../test/shr_string_test/test_shr_string.pf | 194 +++++++++ .../util/test/shr_vmath_test/test_vmath.F90 | 110 +++++ .../util/test/shr_wv_sat_test/test_wv_sat.pf | 256 +++++++++++ .../test_wv_sat_each_method.pf | 270 ++++++++++++ 26 files changed, 2605 insertions(+), 86 deletions(-) delete mode 100644 src/csm_share/esmf_wrf_timemgr/CMakeLists.txt delete mode 100644 src/csm_share/mct/CMakeLists.txt delete mode 100644 src/csm_share/util/CMakeLists.txt create mode 100644 src/csm_share/util/test/CMakeLists.txt create mode 100644 src/csm_share/util/test/shr_abort_test/README create mode 100644 src/csm_share/util/test/shr_abort_test/test_shr_abort.pf create mode 100644 src/csm_share/util/test/shr_assert_test/test_assert.pf create mode 100644 src/csm_share/util/test/shr_assert_test/test_assert_array.pf create mode 100644 src/csm_share/util/test/shr_assert_test/test_macro.pf create mode 100644 src/csm_share/util/test/shr_assert_test/test_ndebug.pf create mode 100644 src/csm_share/util/test/shr_cal_test/test_shr_cal.pf create mode 100644 src/csm_share/util/test/shr_infnan_test/test_infnan.F90 create mode 100644 src/csm_share/util/test/shr_log_test/test_error_printers.pf create mode 100644 src/csm_share/util/test/shr_precip_test/test_shr_precip.pf create mode 100644 src/csm_share/util/test/shr_spfn_test/test_erf_r4.pf create mode 100644 src/csm_share/util/test/shr_spfn_test/test_erf_r8.pf create mode 100644 src/csm_share/util/test/shr_spfn_test/test_gamma_factorial.pf create mode 100644 src/csm_share/util/test/shr_spfn_test/test_igamma.pf create mode 100644 src/csm_share/util/test/shr_strconvert_test/test_toString.pf create mode 100644 src/csm_share/util/test/shr_string_test/test_shr_string.pf create mode 100644 src/csm_share/util/test/shr_vmath_test/test_vmath.F90 create mode 100644 src/csm_share/util/test/shr_wv_sat_test/test_wv_sat.pf create mode 100644 src/csm_share/util/test/shr_wv_sat_test/test_wv_sat_each_method.pf diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8e4f80a24d..6649681194 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -14,6 +14,7 @@ jobs: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: config: - { @@ -74,7 +75,6 @@ jobs: OASIS_INSTALL_PREFIX: ${{ github.workspace }}/oasis3-mct/install PFUNIT_TAG: v4.10.0 PFUNIT_INSTALL_PREFIX: ${{ github.workspace }}/pFUnit/install - CMAKE_BUILD_PARALLEL_LEVEL: 4 steps: - uses: actions/checkout@v4 @@ -120,8 +120,6 @@ jobs: name: Add OASIS to CMAKE_PREFIX_PATH run: | echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" - tree -FUCh --du --filelimit 20 -L 2 ${OASIS_INSTALL_PREFIX} # # pFUnit # @@ -152,11 +150,9 @@ jobs: - name: Add pFUnit to CMAKE_PREFIX_PATH run: | echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${PFUNIT_INSTALL_PREFIX}" >> $GITHUB_ENV - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" - tree -FUCh --du --filelimit 20 -L 2 ${PFUNIT_INSTALL_PREFIX} # - # eCLM + # Configure, build, and install eCLM # - name: Configure eCLM run: | @@ -179,3 +175,11 @@ jobs: - name: Install eCLM namelist generator run: pip3 install --user ./namelist_generator + + # + # Run tests + # + - name: Run eCLM tests + run: | + cd $BUILD_DIR + ctest -V --output-on-failure diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9702fe666b..85a045eb84 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,14 +14,6 @@ if (USE_PDAF) set(CMAKE_Fortran_MODULE_DIRECTORY_GPTL ${CMAKE_BINARY_DIR}/externals/gptl/include) endif() -add_subdirectory(externals) -add_subdirectory(csm_share) -add_subdirectory(clm5) -add_subdirectory(stub_comps) -add_subdirectory(datm) -add_subdirectory(mosart) -add_subdirectory(eclm) - option(ENABLE_TESTS "Enable unit tests." OFF) if (ENABLE_TESTS) find_package(PFUNIT REQUIRED) @@ -30,6 +22,14 @@ if (ENABLE_TESTS) message(STATUS "Unit tests enabled.") endif() +add_subdirectory(externals) +add_subdirectory(csm_share) +add_subdirectory(clm5) +add_subdirectory(stub_comps) +add_subdirectory(datm) +add_subdirectory(mosart) +add_subdirectory(eclm) + # Make sure that the required libraries are always found # independent from LD_LIBRARY_PATH and the install location. # https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath diff --git a/src/csm_share/CMakeLists.txt b/src/csm_share/CMakeLists.txt index 42ad729297..fbb3a5cd76 100644 --- a/src/csm_share/CMakeLists.txt +++ b/src/csm_share/CMakeLists.txt @@ -132,4 +132,8 @@ if (USE_PDAF) target_compile_definitions(${PROJECT_NAME} PUBLIC USE_PDAF) endif() +if (ENABLE_TESTS) + add_subdirectory(util/test) +endif() + install (TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/csm_share/esmf_wrf_timemgr/CMakeLists.txt b/src/csm_share/esmf_wrf_timemgr/CMakeLists.txt deleted file mode 100644 index d27480573c..0000000000 --- a/src/csm_share/esmf_wrf_timemgr/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -list(APPEND esmf_wrf_timemgr_sources - ESMF.F90 - ESMF_AlarmClockMod.F90 - ESMF_AlarmMod.F90 - ESMF_BaseMod.F90 - ESMF_BaseTimeMod.F90 - ESMF_CalendarMod.F90 - ESMF_ClockMod.F90 - ESMF_FractionMod.F90 - ESMF_ShrTimeMod.F90 - ESMF_Stubs.F90 - ESMF_TimeIntervalMod.F90 - ESMF_TimeMod.F90 - MeatMod.F90 - wrf_error_fatal.F90 - wrf_message.F90 - ) - -sourcelist_to_parent(esmf_wrf_timemgr_sources) \ No newline at end of file diff --git a/src/csm_share/mct/CMakeLists.txt b/src/csm_share/mct/CMakeLists.txt deleted file mode 100644 index 37bf92fb90..0000000000 --- a/src/csm_share/mct/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -list(APPEND drv_sources - glc_elevclass_mod.F90 - seq_cdata_mod.F90 - seq_comm_mct.F90 - seq_infodata_mod.F90 - seq_io_read_mod.F90 - ) - -sourcelist_to_parent(drv_sources) diff --git a/src/csm_share/util/CMakeLists.txt b/src/csm_share/util/CMakeLists.txt deleted file mode 100644 index f68be557c2..0000000000 --- a/src/csm_share/util/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -set(genf90_files shr_infnan_mod.F90.in shr_assert_mod.F90.in) - -process_genf90_source_list("${genf90_files}" ${CMAKE_CURRENT_BINARY_DIR} - share_genf90_sources) - -sourcelist_to_parent(share_genf90_sources) - -list(APPEND share_sources "${share_genf90_sources}") - -list(APPEND share_sources - shr_file_mod.F90 - shr_kind_mod.F90 - shr_const_mod.F90 - shr_sys_mod.F90 - shr_log_mod.F90 - shr_orb_mod.F90 - shr_spfn_mod.F90 - shr_strconvert_mod.F90 - shr_cal_mod.F90 - shr_nl_mod.F90 - shr_precip_mod.F90 - shr_string_mod.F90 - shr_timer_mod.F90 - shr_vmath_mod.F90 - shr_wv_sat_mod.F90) - -# Build a separate list containing the mct wrapper and its dependencies. That -# way, this list can be easily included in unit test builds that link to mct, -# but excluded from builds that do not include mct. -list(APPEND share_mct_sources - mct_mod.F90 - shr_mct_mod.F90 - shr_mpi_mod.F90 - shr_pcdf_mod.F90) - -# Build a separate list containing the pio wrapper and its dependencies. That -# way, this list can be easily included in unit test builds that include PIO or -# a stub of PIO, but excluded from builds that do not include PIO. -list(APPEND share_pio_sources - shr_pio_mod.F90) - -sourcelist_to_parent(share_sources) -sourcelist_to_parent(share_mct_sources) -sourcelist_to_parent(share_pio_sources) diff --git a/src/csm_share/util/test/CMakeLists.txt b/src/csm_share/util/test/CMakeLists.txt new file mode 100644 index 0000000000..2f6060377f --- /dev/null +++ b/src/csm_share/util/test/CMakeLists.txt @@ -0,0 +1,69 @@ +# shr_abort_test +add_pfunit_ctest (${PROJECT_NAME}_abort + TEST_SOURCES + shr_abort_test/test_shr_abort.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_assert_test +add_pfunit_ctest (${PROJECT_NAME}_assert + TEST_SOURCES + shr_assert_test/test_assert_array.pf + shr_assert_test/test_assert.pf + shr_assert_test/test_macro.pf + shr_assert_test/test_ndebug.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_cal_test +add_pfunit_ctest (${PROJECT_NAME}_calendar + TEST_SOURCES + shr_cal_test/test_shr_cal.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_log_test +add_pfunit_ctest (${PROJECT_NAME}_log + TEST_SOURCES + shr_log_test/test_error_printers.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_precip_test +add_pfunit_ctest (${PROJECT_NAME}_precip + TEST_SOURCES + shr_precip_test/test_shr_precip.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_spfn_test +add_pfunit_ctest (${PROJECT_NAME}_spfn + TEST_SOURCES + shr_spfn_test/test_erf_r4.pf + shr_spfn_test/test_erf_r8.pf + shr_spfn_test/test_gamma_factorial.pf + shr_spfn_test/test_igamma.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_strconvert_test +add_pfunit_ctest (${PROJECT_NAME}_strconvert + TEST_SOURCES + shr_strconvert_test/test_toString.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_string_test +add_pfunit_ctest (${PROJECT_NAME}_string + TEST_SOURCES + shr_string_test/test_shr_string.pf + LINK_LIBRARIES ${PROJECT_NAME} +) + +# shr_wv_sat_test +add_pfunit_ctest (${PROJECT_NAME}_wv_sat + TEST_SOURCES + shr_wv_sat_test/test_wv_sat_each_method.pf + shr_wv_sat_test/test_wv_sat.pf + LINK_LIBRARIES ${PROJECT_NAME} +) \ No newline at end of file diff --git a/src/csm_share/util/test/shr_abort_test/README b/src/csm_share/util/test/shr_abort_test/README new file mode 100644 index 0000000000..4c579606c3 --- /dev/null +++ b/src/csm_share/util/test/shr_abort_test/README @@ -0,0 +1,2 @@ +This directory tests the version of shr_abort_mod that is used in unit tests. It +does NOT test the production version of shr_abort_mod. diff --git a/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf new file mode 100644 index 0000000000..04d7405c42 --- /dev/null +++ b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf @@ -0,0 +1,39 @@ +module test_shr_abort + + ! Tests of shr_abort_mod: version used in unit tests that throws a pfunit exception + ! rather than aborting + + use funit + use shr_abort_mod + use shr_kind_mod , only : r8 => shr_kind_r8 + + implicit none + + @TestCase + type, extends(TestCase) :: TestShrAbort + contains + procedure :: setUp + procedure :: tearDown + end type TestShrAbort + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestShrAbort), intent(inout) :: this + end subroutine setUp + + subroutine tearDown(this) + class(TestShrAbort), intent(inout) :: this + end subroutine tearDown + + @Test + subroutine test_abort(this) + class(TestShrAbort), intent(inout) :: this + + call shr_abort_abort('Test message') + @assertExceptionRaised('ABORTED: Test message') + end subroutine test_abort + +end module test_shr_abort diff --git a/src/csm_share/util/test/shr_assert_test/test_assert.pf b/src/csm_share/util/test/shr_assert_test/test_assert.pf new file mode 100644 index 0000000000..c4e5544e18 --- /dev/null +++ b/src/csm_share/util/test/shr_assert_test/test_assert.pf @@ -0,0 +1,56 @@ +module test_assert + +! Test basic assert functionality. + +use funit + +use shr_assert_mod, only: & + shr_assert, & + shr_assert_all, & + shr_assert_any + +implicit none +save + +contains + +@Test +subroutine assert_can_pass() + call shr_assert(.true., "Assert unexpectedly aborted!") +end subroutine assert_can_pass + +@Test +subroutine assert_can_fail() + call shr_assert(.false., "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_can_fail + +@Test +subroutine assert_prints_file_and_line() + call shr_assert(.false., "Expected failure.", file='foo', line=42) + call assertExceptionRaised("ABORTED: ERROR in foo at line 42: Expected failure.") +end subroutine assert_prints_file_and_line + +@Test +subroutine assert_all_scalar_can_pass() + call shr_assert_all(.true., "Assert unexpectedly aborted!") +end subroutine assert_all_scalar_can_pass + +@Test +subroutine assert_all_scalar_can_fail() + call shr_assert_all(.false., "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_all_scalar_can_fail + +@Test +subroutine assert_any_scalar_can_pass() + call shr_assert_any(.true., "Assert unexpectedly aborted!") +end subroutine assert_any_scalar_can_pass + +@Test +subroutine assert_any_scalar_can_fail() + call shr_assert_any(.false., "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_any_scalar_can_fail + +end module test_assert diff --git a/src/csm_share/util/test/shr_assert_test/test_assert_array.pf b/src/csm_share/util/test/shr_assert_test/test_assert_array.pf new file mode 100644 index 0000000000..ffe8be06f2 --- /dev/null +++ b/src/csm_share/util/test/shr_assert_test/test_assert_array.pf @@ -0,0 +1,185 @@ +module test_assert_array + +! Test shr_assert_all and shr_assert_any. + +use funit + +use shr_assert_mod, only: & + shr_assert_all, & + shr_assert_any + +implicit none +save + +@TestParameter +type, extends(AbstractTestParameter) :: ArrayRank + integer :: rank + contains + procedure :: toString +end type ArrayRank + +@TestCase(testParameters={getParameters()}, constructor=new_TestAssertArray) +type, extends(ParameterizedTestCase) :: TestAssertArray + integer :: rank +end type TestAssertArray + +contains + +function new_TestAssertArray(rank) result(test) + type(ArrayRank), intent(in) :: rank + type(TestAssertArray) :: test + + test%rank = rank%rank + +end function new_TestAssertArray + +function getParameters() result(params) + type(ArrayRank), allocatable :: params(:) + + integer :: i + + params = [( ArrayRank(i), i = 1, 7 )] + +end function getParameters + +function toString(this) result(string) + class(ArrayRank), intent(in) :: this + character(:), allocatable :: string + + character(len=30) :: buffer + + write(buffer, '(A,I1,A)') "(rank = ",this%rank,")" + + string = trim(buffer) + +end function toString + +@Test +subroutine assert_all_size_zero_passes(this) + class(TestAssertArray), intent(inout) :: this + call assert_all_wrapper([logical::], 0, this%rank, & + "Assert unexpectedly aborted!") +end subroutine assert_all_size_zero_passes + +@Test +subroutine assert_all_can_pass(this) + class(TestAssertArray), intent(inout) :: this + call assert_all_wrapper([.true.], 1, this%rank, & + "Assert unexpectedly aborted!") +end subroutine assert_all_can_pass + +@Test +subroutine assert_all_can_fail(this) + class(TestAssertArray), intent(inout) :: this + call assert_all_wrapper([.false.], 1, this%rank, & + "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_all_can_fail + +@Test +subroutine assert_all_partial_false_fails(this) + class(TestAssertArray), intent(inout) :: this + logical :: test_array(2**this%rank) + integer :: i + test_array = [( mod(i,2) == 0, i = 1, size(test_array) )] + call assert_all_wrapper(test_array, 2, this%rank, & + "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_all_partial_false_fails + +@Test +subroutine assert_any_size_zero_fails(this) + class(TestAssertArray), intent(inout) :: this + call assert_any_wrapper([logical::], 0, this%rank, & + "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_any_size_zero_fails + +@Test +subroutine assert_any_can_pass(this) + class(TestAssertArray), intent(inout) :: this + call assert_any_wrapper([.true.], 1, this%rank, & + "Assert unexpectedly aborted!") +end subroutine assert_any_can_pass + +@Test +subroutine assert_any_can_fail(this) + class(TestAssertArray), intent(inout) :: this + call assert_any_wrapper([.false.], 1, this%rank, & + "Expected failure.") + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine assert_any_can_fail + +@Test +subroutine assert_any_partial_false_passes(this) + class(TestAssertArray), intent(inout) :: this + logical :: test_array(2**this%rank) + integer :: i + test_array = [( mod(i,2) == 0, i = 1, size(test_array) )] + call assert_any_wrapper(test_array, 2, this%rank, & + "Assert unexpectedly aborted!") +end subroutine assert_any_partial_false_passes + +! The wrappers are to allow rank-generic programming. +! The routines assert with the given array and message, but the array is +! resized to have "rank" dimensions of size "dimsize". + +subroutine assert_all_wrapper(array, dimsize, rank, msg) + logical, intent(in) :: array(:) + integer, intent(in) :: dimsize + integer, intent(in) :: rank + character(len=*), intent(in) :: msg + + integer :: i + + select case (rank) + case(1) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 1)]), msg) + case(2) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 2)]), msg) + case(3) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 3)]), msg) + case(4) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 4)]), msg) + case(5) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 5)]), msg) + case(6) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 6)]), msg) + case(7) + call shr_assert_all(reshape(array, [(dimsize, i = 1, 7)]), msg) + case default + call throw("assert_all_wrapper was given a bad rank.") + end select + +end subroutine assert_all_wrapper + +subroutine assert_any_wrapper(array, dimsize, rank, msg) + logical, intent(in) :: array(:) + integer, intent(in) :: dimsize + integer, intent(in) :: rank + character(len=*), intent(in) :: msg + + integer :: i + + select case (rank) + case(1) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 1)]), msg) + case(2) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 2)]), msg) + case(3) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 3)]), msg) + case(4) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 4)]), msg) + case(5) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 5)]), msg) + case(6) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 6)]), msg) + case(7) + call shr_assert_any(reshape(array, [(dimsize, i = 1, 7)]), msg) + case default + call throw("assert_any_wrapper was given a bad rank.") + end select + +end subroutine assert_any_wrapper + +end module test_assert_array diff --git a/src/csm_share/util/test/shr_assert_test/test_macro.pf b/src/csm_share/util/test/shr_assert_test/test_macro.pf new file mode 100644 index 0000000000..b1626740ad --- /dev/null +++ b/src/csm_share/util/test/shr_assert_test/test_macro.pf @@ -0,0 +1,87 @@ +module test_macro + +! Test that if NDEBUG is not defined, shr_assert macros run assertions. + +use funit + +#undef NDEBUG +#include "shr_assert.h" + +contains + +@Test +subroutine macro_assert_can_pass() + SHR_ASSERT(.true., "Assert macro unexpectedly aborted!") +end subroutine macro_assert_can_pass + +@Test +subroutine macro_assert_can_fail() + SHR_ASSERT(.false., "Expected failure.") + ! When this was written, the preprocessor did not recognize this assert, + ! so call it directly instead of using an "@". + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine macro_assert_can_fail + +@Test +subroutine macro_assert_fl() + SHR_ASSERT_FL(.false., "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42") +end subroutine macro_assert_fl + +@Test +subroutine macro_assert_mfl() + SHR_ASSERT_MFL(.false., "Expected failure.", "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42: Expected failure.") +end subroutine macro_assert_mfl + +@Test +subroutine macro_assert_all_can_pass() + SHR_ASSERT_ALL(([.true., .true.]), "Assert macro unexpectedly aborted!") +end subroutine macro_assert_all_can_pass + +@Test +subroutine macro_assert_all_can_fail() + SHR_ASSERT_ALL(([.true., .false.]), "Expected failure.") + ! When this was written, the preprocessor did not recognize this assert, + ! so call it directly instead of using an "@". + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine macro_assert_all_can_fail + +@Test +subroutine macro_assert_all_fl() + SHR_ASSERT_ALL_FL(([.true., .false.]), "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42") +end subroutine macro_assert_all_fl + +@Test +subroutine macro_assert_all_mfl() + SHR_ASSERT_ALL_MFL(([.true., .false.]), "Expected failure.", "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42: Expected failure.") +end subroutine macro_assert_all_mfl + +@Test +subroutine macro_assert_any_can_pass() + SHR_ASSERT_ANY(([.true., .false.]), "Assert macro unexpectedly aborted!") +end subroutine macro_assert_any_can_pass + +@Test +subroutine macro_assert_any_can_fail() + SHR_ASSERT_ANY(([.false., .false.]), "Expected failure.") + ! When this was written, the preprocessor did not recognize this assert, + ! so call it directly instead of using an "@". + call assertExceptionRaised("ABORTED: ERROR: Expected failure.") +end subroutine macro_assert_any_can_fail + +@Test +subroutine macro_assert_any_fl() + SHR_ASSERT_ANY_FL(([.false., .false.]), "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42") +end subroutine macro_assert_any_fl + +@Test +subroutine macro_assert_any_mfl() + SHR_ASSERT_ANY_MFL(([.false., .false.]), "Expected failure.", "my_file", 42) + call assertExceptionRaised("ABORTED: ERROR in my_file at line 42: Expected failure.") +end subroutine macro_assert_any_mfl + +end module test_macro diff --git a/src/csm_share/util/test/shr_assert_test/test_ndebug.pf b/src/csm_share/util/test/shr_assert_test/test_ndebug.pf new file mode 100644 index 0000000000..8d4d9c2a01 --- /dev/null +++ b/src/csm_share/util/test/shr_assert_test/test_ndebug.pf @@ -0,0 +1,63 @@ +module test_ndebug + +! Test that if NDEBUG is defined, shr_assert macros do nothing. + +use funit + +#define NDEBUG +#include "shr_assert.h" + +contains + + logical function unreachable_function(macro_name) + character(len=*), intent(in) :: macro_name + + call throw("NDEBUG failed to turn off " // macro_name) + end function unreachable_function + +@Test +subroutine ndebug_controls_assert_macro() + SHR_ASSERT(unreachable_function("SHR_ASSERT"), "Fake message.") +end subroutine ndebug_controls_assert_macro + +@Test +subroutine ndebug_controls_assert_fl_macro() + SHR_ASSERT_FL(unreachable_function("SHR_ASSERT_FL"), "my_file", 42) +end subroutine ndebug_controls_assert_fl_macro + +@Test +subroutine ndebug_controls_assert_mfl_macro() + SHR_ASSERT_MFL(unreachable_function("SHR_ASSERT_MFL"), "Fake message.", "my_file", 42) +end subroutine ndebug_controls_assert_mfl_macro + +@Test +subroutine ndebug_controls_assert_all_macro() + SHR_ASSERT_ALL(unreachable_function("SHR_ASSERT_ALL"), "Fake message.") +end subroutine ndebug_controls_assert_all_macro + +@Test +subroutine ndebug_controls_assert_all_fl_macro() + SHR_ASSERT_ALL_FL(unreachable_function("SHR_ASSERT_ALL_FL"), "my_file", 42) +end subroutine ndebug_controls_assert_all_fl_macro + +@Test +subroutine ndebug_controls_assert_all_mfl_macro() + SHR_ASSERT_ALL_MFL(unreachable_function("SHR_ASSERT_ALL_MFL"), "Fake message.", "my_file", 42) +end subroutine ndebug_controls_assert_all_mfl_macro + +@Test +subroutine ndebug_controls_assert_any_macro() + SHR_ASSERT_ANY(unreachable_function("SHR_ASSERT_ANY"), "Fake message.") +end subroutine ndebug_controls_assert_any_macro + +@Test +subroutine ndebug_controls_assert_any_fl_macro() + SHR_ASSERT_ANY_FL(unreachable_function("SHR_ASSERT_ANY_FL"), "my_file", 42) +end subroutine ndebug_controls_assert_any_fl_macro + +@Test +subroutine ndebug_controls_assert_any_mfl_macro() + SHR_ASSERT_ANY_MFL(unreachable_function("SHR_ASSERT_ANY_MFL"), "Fake message.", "my_file", 42) +end subroutine ndebug_controls_assert_any_mfl_macro + +end module test_ndebug diff --git a/src/csm_share/util/test/shr_cal_test/test_shr_cal.pf b/src/csm_share/util/test/shr_cal_test/test_shr_cal.pf new file mode 100644 index 0000000000..fff7278685 --- /dev/null +++ b/src/csm_share/util/test/shr_cal_test/test_shr_cal.pf @@ -0,0 +1,396 @@ +module test_shr_cal + + ! Tests of shr_cal_mod + + use funit + use shr_cal_mod + use shr_kind_mod , only : r8 => shr_kind_r8, i4 => shr_kind_in, i8 => shr_kind_i8 + use esmf, only : ESMF_Initialize, ESMF_Finalize, ESMF_Time, ESMF_TimeSet + use esmf, only : ESMF_CALKIND_GREGORIAN + + implicit none + + @TestCase + type, extends(TestCase) :: TestShrCal + contains + procedure :: setUp + procedure :: tearDown + end type TestShrCal + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestShrCal), intent(inout) :: this + + call ESMF_Initialize() + end subroutine setUp + + subroutine tearDown(this) + class(TestShrCal), intent(inout) :: this + + call ESMF_Finalize() + end subroutine tearDown + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_date2ymd + ! ------------------------------------------------------------------------ + + @Test + subroutine date2ymd_int_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date, year, month, day + + date = 98760317 + call shr_cal_date2ymd(date, year, month, day) + @assertEqual(9876, year) + @assertEqual(3, month) + @assertEqual(17, day) + end subroutine date2ymd_int_basic + + @Test + subroutine date2ymd_long_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date + integer(i4) :: year, month, day + + date = 9876540317_i8 + call shr_cal_date2ymd(date, year, month, day) + @assertEqual(987654, year) + @assertEqual(3, month) + @assertEqual(17, day) + end subroutine date2ymd_long_basic + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_date2julian + ! ------------------------------------------------------------------------ + + @Test + subroutine date2julian_int_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date, sec + real(r8) :: jday + real(r8) :: expected + + date = 98760317 + sec = 86400/2 + call shr_cal_date2julian(date = date, sec = sec, jday = jday, calendar = 'noleap') + + expected = 31._r8 + 28._r8 + 17._r8 + 0.5_r8 + @assertEqual(expected, jday) + end subroutine date2julian_int_basic + + @Test + subroutine date2julian_long_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date + integer(i4) :: sec + real(r8) :: jday + real(r8) :: expected + + date = 9876540317_i8 + sec = 86400/2 + call shr_cal_date2julian(date = date, sec = sec, jday = jday, calendar = 'noleap') + + expected = 31._r8 + 28._r8 + 17._r8 + 0.5_r8 + @assertEqual(expected, jday) + end subroutine date2julian_long_basic + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_ymd2date + ! ------------------------------------------------------------------------ + + @Test + subroutine ymd2date_int_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date + + call shr_cal_ymd2date(year=9876, month=3, day=17, date=date) + @assertEqual(98760317, date) + end subroutine ymd2date_int_basic + + @Test + subroutine ymd2date_long_basic(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date + + call shr_cal_ymd2date(year=987654, month=3, day=17, date=date) + @assertEqual(9876540317_i8, date) + end subroutine ymd2date_long_basic + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_advDate + ! ------------------------------------------------------------------------ + + @Test + subroutine advDate_int_1dayPlus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date_in, date_out + real(r8) :: sec_in, sec_out + + date_in = 98760317 + sec_in = 100._r8 + call shr_cal_advDate(delta = 86401._r8, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(98760318, date_out) + @assertEqual(101._r8, sec_out, tolerance=tol) + end subroutine advDate_int_1dayPlus1sec + + @Test + subroutine advDate_int_minus1dayMinus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date_in, date_out + real(r8) :: sec_in, sec_out + + date_in = 98760317 + sec_in = 100._r8 + call shr_cal_advDate(delta = -86401._r8, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(98760316, date_out) + @assertEqual(99._r8, sec_out, tolerance=tol) + end subroutine advDate_int_minus1dayMinus1sec + + @Test + subroutine advDate_long_1dayPlus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date_in, date_out + real(r8) :: sec_in, sec_out + + date_in = 9876540317_i8 + sec_in = 100._r8 + call shr_cal_advDate(delta = 86401._r8, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(9876540318_i8, date_out) + @assertEqual(101._r8, sec_out, tolerance=tol) + end subroutine advDate_long_1dayPlus1sec + + @Test + subroutine advDate_long_minus1dayMinus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date_in, date_out + real(r8) :: sec_in, sec_out + + date_in = 9876540317_i8 + sec_in = 100._r8 + call shr_cal_advDate(delta = -86401._r8, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(9876540316_i8, date_out) + @assertEqual(99._r8, sec_out, tolerance=tol) + end subroutine advDate_long_minus1dayMinus1sec + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_advDateInt + ! ------------------------------------------------------------------------ + + @Test + subroutine advDateInt_int_1dayPlus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date_in, date_out + integer(i4) :: sec_in, sec_out + + date_in = 98760317 + sec_in = 100 + call shr_cal_advDateInt(delta = 86401, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(98760318, date_out) + @assertEqual(101, sec_out) + end subroutine advDateInt_int_1dayPlus1sec + + @Test + subroutine advDateInt_int_minus1dayMinus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i4) :: date_in, date_out + integer(i4) :: sec_in, sec_out + + date_in = 98760317 + sec_in = 100 + call shr_cal_advDateInt(delta = -86401, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(98760316, date_out) + @assertEqual(99, sec_out) + end subroutine advDateInt_int_minus1dayMinus1sec + + @Test + subroutine advDateInt_long_1dayPlus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date_in, date_out + integer(i4) :: sec_in, sec_out + + date_in = 9876540317_i8 + sec_in = 100 + call shr_cal_advDateInt(delta = 86401, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(9876540318_i8, date_out) + @assertEqual(101, sec_out) + end subroutine advDateInt_long_1dayPlus1sec + + @Test + subroutine advDateInt_long_minus1dayMinus1sec(this) + class(TestShrCal), intent(inout) :: this + integer(i8) :: date_in, date_out + integer(i4) :: sec_in, sec_out + + date_in = 9876540317_i8 + sec_in = 100 + call shr_cal_advDateInt(delta = -86401, units = 'seconds', & + dateIN = date_in, secIN = sec_in, & + dateOUT = date_out, secOUT = sec_out, & + calendar = 'noleap') + + @assertEqual(9876540316_i8, date_out) + @assertEqual(99, sec_out) + end subroutine advDateInt_long_minus1dayMinus1sec + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_ymdtod2string + ! ------------------------------------------------------------------------ + + @Test + subroutine ymdtod2string_smallYear(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123, mm=4, dd=5, tod=6789) + @assertEqual('0123-04-05-06789', date_str) + end subroutine ymdtod2string_smallYear + + @Test + subroutine ymdtod2string_largeYear(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123456, mm=4, dd=5, tod=6789) + @assertEqual('123456-04-05-06789', date_str) + end subroutine ymdtod2string_largeYear + + @Test + subroutine ymdtod2string_noTOD(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123, mm=4, dd=5) + @assertEqual('0123-04-05', date_str) + end subroutine ymdtod2string_noTOD + + @Test + subroutine ymdtod2string_noDay(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123, mm=4) + @assertEqual('0123-04', date_str) + end subroutine ymdtod2string_noDay + + @Test + subroutine ymdtod2string_noMonth(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123) + @assertEqual('0123', date_str) + end subroutine ymdtod2string_noMonth + + @Test + subroutine ymdtod2string_yearTooLarge(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=1234567, mm=4, dd=5) + @assertExceptionRaised('ABORTED: shr_cal_ymdtod2string : year too large (max of 999999)') + end subroutine ymdtod2string_yearTooLarge + + @Test + subroutine ymdtod2string_stringTooShort(this) + class(TestShrCal), intent(inout) :: this + character(len=17) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123456, mm=4, dd=5, tod=6789) + @assertExceptionRaised('ABORTED: shr_cal_ymdtod2string : output string too short') + end subroutine ymdtod2string_stringTooShort + + @Test + subroutine ymdtod2string_stringTooShort_noTOD(this) + class(TestShrCal), intent(inout) :: this + character(len=11) :: date_str + + call shr_cal_ymdtod2string(date_str, yy=123456, mm=4, dd=5) + @assertExceptionRaised('ABORTED: shr_cal_ymdtod2string : output string too short') + end subroutine ymdtod2string_stringTooShort_noTOD + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_datetod2string + ! ------------------------------------------------------------------------ + + @Test + subroutine datetod2string_int_basic(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_datetod2string(date_str = date_str, ymd = 1230405, tod = 6789) + @assertEqual('0123-04-05-06789', date_str) + end subroutine datetod2string_int_basic + + @Test + subroutine datetod2string_long_basic(this) + class(TestShrCal), intent(inout) :: this + character(len=18) :: date_str + + call shr_cal_datetod2string(date_str = date_str, ymd = 9876540405_i8, tod = 6789) + @assertEqual('987654-04-05-06789', date_str) + end subroutine datetod2string_long_basic + + ! ------------------------------------------------------------------------ + ! Tests of shr_cal_ymds2rday_offset + ! ------------------------------------------------------------------------ + + @Test + subroutine ymds2rdayOffset_basic(this) + class(TestShrCal), intent(inout) :: this + type(ESMF_Time) :: etime + real(r8) :: rdays_offset + real(r8) :: expected + + ! Most of the current time settings here are arbitrary. However, the year and month + ! are important. + call ESMF_TimeSet(etime, yy=2000, mm=4, dd=15, h=1, m=5, s=30, & + calkindflag = ESMF_CALKIND_GREGORIAN) + + call shr_cal_ymds2rday_offset(etime=etime, & + rdays_offset = rdays_offset, & + years_offset = -1, & + months_offset = -1, & + days_offset = -2, & + seconds_offset = -21600) + + expected = -366._r8 & ! -1 year, since year-2000 is a leap year + - 31._r8 & ! -1 month, since starting month is April + - 2._r8 & ! -2 days + - 0.25_r8 ! -21600 seconds = -0.25 days + + @assertEqual(expected, rdays_offset, tolerance=tol) + + end subroutine ymds2rdayOffset_basic + +end module test_shr_cal diff --git a/src/csm_share/util/test/shr_infnan_test/test_infnan.F90 b/src/csm_share/util/test/shr_infnan_test/test_infnan.F90 new file mode 100644 index 0000000000..01123ee239 --- /dev/null +++ b/src/csm_share/util/test/shr_infnan_test/test_infnan.F90 @@ -0,0 +1,174 @@ +program test_infnan + +! +! This is a test for the shr_infnan_mod module. It was created using the +! pre-CTest system, with minimal changes to keep it working. So it may not +! be a great example of a CTest test now. +! + +use shr_kind_mod, only: r8 => shr_kind_r8 +use shr_kind_mod, only: r4 => shr_kind_r4 +use shr_kind_mod, only: i8 => shr_kind_i8 +use shr_kind_mod, only: i4 => shr_kind_i4 +use, intrinsic :: ieee_exceptions, only : ieee_status_type, ieee_get_status, ieee_set_status +use, intrinsic :: ieee_exceptions, only : ieee_set_halting_mode +use, intrinsic :: ieee_exceptions, only : ieee_invalid, ieee_divide_by_zero +use shr_infnan_mod + +implicit none + +type(ieee_status_type) :: status_value +real(r8) :: x, zero +real(r4) :: y +real(r8) :: r8array(100), r82Darray(10,10), r83Darray(4,4,4) +real(r8) :: r84Darray(3,3,3,3), r85Darray(2,2,2,2,2) +real(r8) :: inf +real(r8) :: nan +real(r8) :: nans +real(r4) :: spnan +real(r4) :: spnans +integer(i8), parameter :: dpinfpat = int(O'0777600000000000000000',i8) +integer(i8), parameter :: dpnanpat = int(O'0777700000000000000000',i8) +integer(i8), parameter :: dpnanspat = int(O'0777610000000000000000',i8) +integer(i4), parameter :: spnanpat = int(Z'7FC00000',i4) +integer(i4), parameter :: spnanspat = int(Z'7FC10000',i4) +intrinsic :: count + +! Get initial ieee status so we can restore it later +call ieee_get_status(status_value) + +! Need to turn off ieee_invalid checks for some of these tests to pass +call ieee_set_halting_mode([ieee_invalid, ieee_divide_by_zero], .false.) + +inf = transfer(dpinfpat,inf) +nan = transfer(dpnanpat,nan) +nans = transfer(dpnanspat,nans) +spnan = transfer( spnanpat,spnan) +spnans = transfer( spnanspat,spnans) + +x = 0.0 +zero = 0.0 + +call assert( shr_infnan_isnan( nan ), "Test that value set to nan is nan" ) +call assert( shr_infnan_isnan( nans ), "Test that value set to nans is nan" ) +call assert( shr_infnan_isnan( spnan ), "Test that value set to sp nan is nan" ) +call assert( shr_infnan_isnan( spnans ), "Test that value set to sp nans is nan" ) +call assert( .not. shr_infnan_isnan( 1.0_r8 ), "Test that value set to one is NOT nan" ) +call assert( .not. shr_infnan_isnan( 1.0_r4 ), "Test that value set to SP one is NOT nan" ) +call assert( .not. shr_infnan_isnan( huge(x) ), "Test that value set to huge is NOT nan" ) +x = 1.0/zero +call assert( .not. shr_infnan_isnan( x ), "Test that 1/0 is NOT nan" ) +x = -1.0/zero +call assert( .not. shr_infnan_isnan( x ), "Test that -1/0 is NOT nan" ) + +r8array(:) = 1.0d00 +r8array(10) = nan +r8array(15) = nan +r82Darray(:,:) = 1.0d00 +r82Darray(5,5) = nan +r82Darray(10,7) = nan +r82Darray(7,9) = nan +r83Darray(:,:,:) = 1.0d00 +r83Darray(4,2,2) = nan +r83Darray(3,1,2) = nan +r83Darray(1,1,1) = nan +r83Darray(1,1,4) = nan +r84Darray(:,:,:,:) = 1.0d00 +r84Darray(3,2,2,1) = nan +r84Darray(3,1,2,1) = nan +r84Darray(1,1,1,1) = nan +r84Darray(1,1,3,1) = nan +r84Darray(1,2,3,1) = nan +r85Darray(:,:,:,:,:) = 1.0d00 +r85Darray(1,2,2,1,1) = nan +r85Darray(1,1,2,1,2) = nan +r85Darray(1,1,1,2,1) = nan +r85Darray(1,2,2,2,1) = nan +r85Darray(1,2,1,1,2) = nan +r85Darray(1,1,1,1,1) = nan +call assert( any(shr_infnan_isnan( r8array )), "Test that array with 2 nans is nan" ) +call assert( count(shr_infnan_isnan( r8array )) == 2, "Test that there are 2 nans in that array" ) +call assert( any(shr_infnan_isnan( r82Darray )), "Test that 2D array with 3 nans is nan" ) +call assert( count(shr_infnan_isnan( r82Darray )) == 3, "Test that there are 3 nans in that array" ) +call assert( any(shr_infnan_isnan( r83Darray )), "Test that 3D array with 4 nans is nan" ) +call assert( count(shr_infnan_isnan( r83Darray )) == 4, "Test that there are 4 nans in that array" ) +call assert( any(shr_infnan_isnan( r84Darray )), "Test that 4D array with 5 nans is nan" ) +call assert( count(shr_infnan_isnan( r84Darray )) == 5, "Test that there are 5 nans in that array" ) +call assert( any(shr_infnan_isnan( r85Darray )), "Test that 5D array with 6 nans is nan" ) +call assert( count(shr_infnan_isnan( r85Darray )) == 6, "Test that there are 6 nans in that array" ) +call assert( shr_infnan_isposinf( inf ), "Test that value set to inf is inf" ) +call assert( .not. shr_infnan_isposinf( 1.0_r8 ), "Test that value set to one is NOT inf" ) +call assert( .not. shr_infnan_isposinf( 1.0_r4 ), "Test that value set to SP one is NOT inf" ) +call assert( shr_infnan_isneginf( -inf ), "Test that value set to -inf is -inf" ) +call assert( .not. shr_infnan_isneginf( 1.0_r8 ), "Test that value set to one is NOT -inf" ) +call assert( .not. shr_infnan_isneginf( 1.0_r4 ), "Test that value set to SP one is NOT -inf" ) +x = 1.0/zero +call assert( shr_infnan_isposinf( x ), "Test that 1/0 is inf" ) +x = -1.0/zero +call assert( shr_infnan_isneginf( x ), "Test that -1/0 is -inf" ) + +x = -1.0 +call assert( shr_infnan_isnan( sqrt(x) ), "Test that sqrt-1 is nan" ) +call assert( shr_infnan_isnan( log(x) ), "Test that log-1 is nan" ) + +x = shr_infnan_nan +call assert( shr_infnan_isnan( x ), "Test that shr_infnan_nan sets r8 to nan" ) +y = shr_infnan_nan +call assert( shr_infnan_isnan( y ), "Test that shr_infnan_nan sets r4 to nan" ) + +x = shr_infnan_inf +call assert( shr_infnan_isinf( x ), "Test that shr_infnan_inf sets r8 to inf" ) +y = shr_infnan_inf +call assert( shr_infnan_isinf( y ), "Test that shr_infnan_inf sets r4 to inf" ) + +x = shr_infnan_posinf +call assert( shr_infnan_isposinf( x ), "Test that shr_infnan_posinf sets r8 to +inf" ) +y = shr_infnan_posinf +call assert( shr_infnan_isposinf( y ), "Test that shr_infnan_posinf sets r4 to +inf" ) + +x = shr_infnan_neginf +call assert( shr_infnan_isneginf( x ), "Test that shr_infnan_neginf sets r8 to -inf" ) +y = shr_infnan_neginf +call assert( shr_infnan_isneginf( y ), "Test that shr_infnan_neginf sets r4 to -inf" ) + +x = shr_infnan_to_r8(shr_infnan_qnan) +call assert( shr_infnan_isnan( x ), "Test that shr_infnan_to_r8(shr_infnan_qnan) sets r8 to nan" ) +y = shr_infnan_to_r4(shr_infnan_qnan) +call assert( shr_infnan_isnan( y ), "Test that shr_infnan_to_r4(shr_infnan_qnan) sets r4 to nan" ) + +x = shr_infnan_to_r8(shr_infnan_snan) +call assert( shr_infnan_isnan( x ), "Test that shr_infnan_to_r8(shr_infnan_snan) sets r8 to nan" ) +y = shr_infnan_to_r4(shr_infnan_snan) +call assert( shr_infnan_isnan( y ), "Test that shr_infnan_to_r4(shr_infnan_snan) sets r4 to nan" ) + +x = shr_infnan_to_r8(shr_infnan_posinf) +call assert( shr_infnan_isposinf( x ), "Test that shr_infnan_to_r8(shr_infnan_posinf) sets r8 to +inf" ) +y = shr_infnan_to_r4(shr_infnan_posinf) +call assert( shr_infnan_isposinf( y ), "Test that shr_infnan_to_r4(shr_infnan_posinf) sets r4 to +inf" ) + +x = shr_infnan_to_r8(shr_infnan_neginf) +call assert( shr_infnan_isneginf( x ), "Test that shr_infnan_to_r8(shr_infnan_neginf) sets r8 to -inf" ) +y = shr_infnan_to_r4(shr_infnan_neginf) +call assert( shr_infnan_isneginf( y ), "Test that shr_infnan_to_r4(shr_infnan_neginf) sets r4 to -inf" ) + +! Restore original status +! +! At least with gfortran, this restoration prevents floating point exceptions from being +! raised at the end of the run. Alternatively, we could probably set various flags to +! .false., using ieee_set_flag. +call ieee_set_status(status_value) + +contains + + subroutine assert(val, msg) + logical, intent(in) :: val + character(len=*), intent(in) :: msg + + if (.not. val) then + print *, msg + stop 1 + end if + + end subroutine assert + +end program test_infnan diff --git a/src/csm_share/util/test/shr_log_test/test_error_printers.pf b/src/csm_share/util/test/shr_log_test/test_error_printers.pf new file mode 100644 index 0000000000..1fc2b97273 --- /dev/null +++ b/src/csm_share/util/test/shr_log_test/test_error_printers.pf @@ -0,0 +1,51 @@ +module test_error_printers + +use funit + +! Tests for routines that create error messages. We obviously can't automate the +! process of deciding whether a message is correct or helpful, but we can test +! that the information provided is actually put into the output. + +use shr_kind_mod, only: cx => shr_kind_cx + +use shr_strconvert_mod, only: toString + +implicit none + +contains + +@Test +subroutine errMsg_prints_arguments() + use shr_log_mod, only: shr_log_errMsg + + character(len=*), parameter :: file_name = "foo.F90" + integer, parameter :: line_no = 20 + + character(len=cx) :: error_string + + error_string = shr_log_errMsg(file_name, line_no) + + @assertLessThan(0, index(error_string, file_name)) + @assertLessThan(0, index(error_string, toString(line_no))) + +end subroutine errMsg_prints_arguments + +@Test +subroutine OOBMsg_prints_arguments() + use shr_log_mod, only: shr_log_OOBMsg + + character(len=*), parameter :: operation = "foo" + integer, parameter :: bounds(2) = [2, 3], idx = 5 + + character(len=cx) :: error_string + + error_string = shr_log_OOBMsg(operation, bounds, idx) + + @assertLessThan(0, index(error_string, operation)) + @assertLessThan(0, index(error_string, toString(bounds(1)))) + @assertLessThan(0, index(error_string, toString(bounds(2)))) + @assertLessThan(0, index(error_string, toString(idx))) + +end subroutine OOBMsg_prints_arguments + +end module test_error_printers diff --git a/src/csm_share/util/test/shr_precip_test/test_shr_precip.pf b/src/csm_share/util/test/shr_precip_test/test_shr_precip.pf new file mode 100644 index 0000000000..5a93bf0c43 --- /dev/null +++ b/src/csm_share/util/test/shr_precip_test/test_shr_precip.pf @@ -0,0 +1,62 @@ +module test_shr_precip + + ! Tests of shr_precip_mod + + use funit + use shr_precip_mod + use shr_kind_mod, only : r8 => SHR_KIND_R8 + use shr_const_mod, only : SHR_CONST_TKFRZ + + implicit none + + @TestCase + type, extends(TestCase) :: TestShrPrecip + contains + procedure :: setUp + procedure :: tearDown + end type TestShrPrecip + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestShrPrecip), intent(inout) :: this + end subroutine setUp + + subroutine tearDown(this) + class(TestShrPrecip), intent(inout) :: this + end subroutine tearDown + + ! ------------------------------------------------------------------------ + ! Tests of shr_precip_partition_rain_snow_ramp + ! ------------------------------------------------------------------------ + + @Test + subroutine partition_rain_snow_ramp_allSnow(this) + class(TestShrPrecip), intent(inout) :: this + real(r8) :: frac_rain + + call shr_precip_partition_rain_snow_ramp(273._r8, frac_rain) + @assertEqual(0._r8, frac_rain) + end subroutine partition_rain_snow_ramp_allSnow + + @Test + subroutine partition_rain_snow_ramp_allRain(this) + class(TestShrPrecip), intent(inout) :: this + real(r8) :: frac_rain + + call shr_precip_partition_rain_snow_ramp(276._r8, frac_rain) + @assertEqual(1._r8, frac_rain) + end subroutine partition_rain_snow_ramp_allRain + + @Test + subroutine partition_rain_snow_ramp_mixture(this) + class(TestShrPrecip), intent(inout) :: this + real(r8) :: frac_rain + + call shr_precip_partition_rain_snow_ramp(SHR_CONST_TKFRZ + 1.5_r8, frac_rain) + @assertEqual(0.75_r8, frac_rain, tolerance=tol) + end subroutine partition_rain_snow_ramp_mixture + +end module test_shr_precip diff --git a/src/csm_share/util/test/shr_spfn_test/test_erf_r4.pf b/src/csm_share/util/test/shr_spfn_test/test_erf_r4.pf new file mode 100644 index 0000000000..9069006105 --- /dev/null +++ b/src/csm_share/util/test/shr_spfn_test/test_erf_r4.pf @@ -0,0 +1,132 @@ +module test_erf_r4 + +use funit + +use shr_kind_mod, only: & + r4 => shr_kind_r4 + +use shr_spfn_mod, only: & + erf => shr_spfn_erf, & + erfc => shr_spfn_erfc, & + erfc_scaled => shr_spfn_erfc_scaled + +implicit none +save + +! Approximately what (negative) number makes erfc_scaled overflow? +real(r4), parameter :: erfc_scaled_overflow = 9._r4 + +@TestParameter +type, extends(AbstractTestParameter) :: ErfR4Params + real(r4) :: test_point + real(r4) :: erf_val + real(r4) :: tol = 0._r4 + contains + procedure :: toString +end type ErfR4Params + +@TestCase(testParameters={getParameters()}, constructor=new_TestErfR4) +type, extends(ParameterizedTestCase) :: TestErfR4 + real(r4) :: test_point + real(r4) :: erf_val + real(r4) :: tol +end type TestErfR4 + +contains + +function new_TestErfR4(params) result(test) + type(ErfR4Params), intent(in) :: params + type(TestErfR4) :: test + + test%test_point = params%test_point + test%erf_val = params%erf_val + test%tol = params%tol + +end function new_TestErfR4 + +function getParameters() result(params) + type(ErfR4Params), allocatable :: params(:) + + params = [ & + ErfR4Params(0._r4, 0._r4), & + ErfR4Params(15._r4, 1._r4), & + ErfR4Params(-15._r4, -1._r4), & + ErfR4Params(1._r4, 0.842700792949714869341, tol=1.e-5_r4), & + ErfR4Params(-1._r4, -0.842700792949714869341, tol=1.e-5_r4) ] + +end function getParameters + +function toString(this) result(string) + class(ErfR4Params), intent(in) :: this + character(:), allocatable :: string + + character(len=80) :: buffer + + write(buffer, '(A,F8.4,A,F8.4,A)') & + "(point = ",this%test_point,", erf = ",this%erf_val,")" + + string = trim(buffer) + +end function toString + +! Check that the erf function gets the expected result. +@Test +subroutine erf_r4_has_correct_value(this) + class(TestErfR4), intent(inout) :: this + @assertEqual(this%erf_val, erf(this%test_point), tolerance=this%tol) +end subroutine erf_r4_has_correct_value + +! Check that two runs of the erf function get identical results. +@Test +subroutine erf_r4_is_reproducible(this) + class(TestErfR4), intent(inout) :: this + @assertEqual(erf(this%test_point), erf(this%test_point)) +end subroutine erf_r4_is_reproducible + +! Check that erfc(x) = 1 - erf(x). +@Test +subroutine erfc_r4_has_correct_value(this) + class(TestErfR4), intent(inout) :: this + @assertEqual(1._r4 - this%erf_val, erfc(this%test_point), tolerance=this%tol) +end subroutine erfc_r4_has_correct_value + +! Check that two runs of the erfc function get identical results. +@Test +subroutine erfc_r4_is_reproducible(this) + class(TestErfR4), intent(inout) :: this + @assertEqual(erfc(this%test_point), erfc(this%test_point)) +end subroutine erfc_r4_is_reproducible + +! Check that erfc_scaled(x) = exp(x**2) * (1 - erf(x)). +@Test +subroutine erfc_scaled_r4_has_correct_value(this) + class(TestErfR4), intent(inout) :: this + real(r4) :: erfc_scaled_expected + + ! Distinguish between where the test point has a modest value, or is too + ! big to use a naive calculation. + if (abs(this%test_point) < erfc_scaled_overflow) then + erfc_scaled_expected = exp(this%test_point**2)*(1._r4 - this%erf_val) + else + ! For larger positive values, we could use an approximation, but this + ! is not trivial. Large negative values should overflow; the only + ! thing we could possibly check in that case would be to ensure that + ! the implementation throws a floating-point error. + + ! For now, just automatically pass the test for large values. + return + end if + + @assertEqual(erfc_scaled_expected, erfc_scaled(this%test_point), tolerance=this%tol) +end subroutine erfc_scaled_r4_has_correct_value + +! Check that two runs of the erfc_scaled function get identical results. +@Test +subroutine erfc_scaled_r4_is_reproducible(this) + class(TestErfR4), intent(inout) :: this + ! Skip this if we overflow. + if (this%test_point < -erfc_scaled_overflow) return + @assertEqual(erfc_scaled(this%test_point), erfc_scaled(this%test_point)) +end subroutine erfc_scaled_r4_is_reproducible + +end module test_erf_r4 diff --git a/src/csm_share/util/test/shr_spfn_test/test_erf_r8.pf b/src/csm_share/util/test/shr_spfn_test/test_erf_r8.pf new file mode 100644 index 0000000000..8f5dd366be --- /dev/null +++ b/src/csm_share/util/test/shr_spfn_test/test_erf_r8.pf @@ -0,0 +1,132 @@ +module test_erf_r8 + +use funit + +use shr_kind_mod, only: & + r8 => shr_kind_r8 + +use shr_spfn_mod, only: & + erf => shr_spfn_erf, & + erfc => shr_spfn_erfc, & + erfc_scaled => shr_spfn_erfc_scaled + +implicit none +save + +! Approximately what (negative) number makes erfc_scaled overflow? +real(r8), parameter :: erfc_scaled_overflow = 26._r8 + +@TestParameter +type, extends(AbstractTestParameter) :: ErfR8Params + real(r8) :: test_point + real(r8) :: erf_val + real(r8) :: tol = 0._r8 + contains + procedure :: toString +end type ErfR8Params + +@TestCase(testParameters={getParameters()}, constructor=new_TestErfR8) +type, extends(ParameterizedTestCase) :: TestErfR8 + real(r8) :: test_point + real(r8) :: erf_val + real(r8) :: tol +end type TestErfR8 + +contains + +function new_TestErfR8(params) result(test) + type(ErfR8Params), intent(in) :: params + type(TestErfR8) :: test + + test%test_point = params%test_point + test%erf_val = params%erf_val + test%tol = params%tol + +end function new_TestErfR8 + +function getParameters() result(params) + type(ErfR8Params), allocatable :: params(:) + + params = [ & + ErfR8Params(0._r8, 0._r8), & + ErfR8Params(30._r8, 1._r8), & + ErfR8Params(-30._r8, -1._r8), & + ErfR8Params(1._r8, 0.842700792949714869341, tol=1.e-6_r8), & + ErfR8Params(-1._r8, -0.842700792949714869341, tol=1.e-6_r8) ] + +end function getParameters + +function toString(this) result(string) + class(ErfR8Params), intent(in) :: this + character(:), allocatable :: string + + character(len=80) :: buffer + + write(buffer, '(A,F8.4,A,F8.4,A)') & + "(point = ",this%test_point,", erf = ",this%erf_val,")" + + string = trim(buffer) + +end function toString + +! Check that the erf function gets the expected result. +@Test +subroutine erf_r8_has_correct_value(this) + class(TestErfR8), intent(inout) :: this + @assertEqual(this%erf_val, erf(this%test_point), tolerance=this%tol) +end subroutine erf_r8_has_correct_value + +! Check that two runs of the erf function get identical results. +@Test +subroutine erf_r8_is_reproducible(this) + class(TestErfR8), intent(inout) :: this + @assertEqual(erf(this%test_point), erf(this%test_point)) +end subroutine erf_r8_is_reproducible + +! Check that erfc(x) = 1 - erf(x). +@Test +subroutine erfc_r8_has_correct_value(this) + class(TestErfR8), intent(inout) :: this + @assertEqual(1._r8 - this%erf_val, erfc(this%test_point), tolerance=this%tol) +end subroutine erfc_r8_has_correct_value + +! Check that two runs of the erfc function get identical results. +@Test +subroutine erfc_r8_is_reproducible(this) + class(TestErfR8), intent(inout) :: this + @assertEqual(erfc(this%test_point), erfc(this%test_point)) +end subroutine erfc_r8_is_reproducible + +! Check that erfc_scaled(x) = exp(x**2) * (1 - erf(x)). +@Test +subroutine erfc_scaled_r8_has_correct_value(this) + class(TestErfR8), intent(inout) :: this + real(r8) :: erfc_scaled_expected + + ! Distinguish between where the test point has a modest value, or is too + ! big to use a naive calculation. + if (abs(this%test_point) < erfc_scaled_overflow) then + erfc_scaled_expected = exp(this%test_point**2)*(1._r8 - this%erf_val) + else + ! For larger positive values, we could use an approximation, but this + ! is not trivial. Large negative values should overflow; the only + ! thing we could possibly check in that case would be to ensure that + ! the implementation throws a floating-point error. + + ! For now, just automatically pass the test for large values. + return + end if + + @assertEqual(erfc_scaled_expected, erfc_scaled(this%test_point), tolerance=this%tol) +end subroutine erfc_scaled_r8_has_correct_value + +! Check that two runs of the erfc_scaled function get identical results. +@Test +subroutine erfc_scaled_r8_is_reproducible(this) + class(TestErfR8), intent(inout) :: this + ! Skip this if we overflow. + if (this%test_point < -erfc_scaled_overflow) return + @assertEqual(erfc_scaled(this%test_point), erfc_scaled(this%test_point)) +end subroutine erfc_scaled_r8_is_reproducible + +end module test_erf_r8 diff --git a/src/csm_share/util/test/shr_spfn_test/test_gamma_factorial.pf b/src/csm_share/util/test/shr_spfn_test/test_gamma_factorial.pf new file mode 100644 index 0000000000..1b99f563fb --- /dev/null +++ b/src/csm_share/util/test/shr_spfn_test/test_gamma_factorial.pf @@ -0,0 +1,98 @@ +module test_gamma_factorial + +use funit + +use shr_kind_mod, only: & + r8 => shr_kind_r8, & + i8 => shr_kind_i8 + +use shr_spfn_mod, only: & + gamma => shr_spfn_gamma, & + igamma => shr_spfn_igamma + +implicit none +save + +real(r8), parameter :: relative_error_tolerance = 1.e-12_r8 + +@TestParameter +type, extends(AbstractTestParameter) :: GammaTestInt + integer :: test_int + contains + procedure :: toString +end type GammaTestInt + +@TestCase(testParameters={getParameters()}, constructor=new_TestGammaFac) +type, extends(ParameterizedTestCase) :: TestGammaFac + real(r8) :: input_int + real(r8) :: test_factorial +end type TestGammaFac + +contains + +function new_TestGammaFac(params) result(test) + type(GammaTestInt), intent(in) :: params + type(TestGammaFac) :: test + + test%input_int = real(params%test_int,r8) + + ! A curious fact; because the factorial contains so many powers of 2, 20! + ! is exactly representable in an 8 byte double even though it is bigger + ! than 1/epsilon. + test%test_factorial = real(factorial(params%test_int-1),r8) + +contains + + function factorial(n) + integer, intent(in) :: n + integer(i8) :: factorial + integer(i8) :: i + factorial = product([( i, i = 1, n )]) + end function factorial + +end function new_TestGammaFac + +function getParameters() result(params) + type(GammaTestInt), allocatable :: params(:) + + integer :: i + + params = [( GammaTestInt(i), i = 1, 21 )] + +end function getParameters + +function toString(this) result(string) + class(GammaTestInt), intent(in) :: this + character(:), allocatable :: string + + character(len=80) :: buffer + + write(buffer, *) "(n = ",this%test_int,")" + + string = trim(buffer) + +end function toString + +@Test +subroutine gamma_is_factorial(this) + class(TestGammaFac), intent(inout) :: this + + real(r8) :: tol + + tol = relative_error_tolerance * this%test_factorial + + @assertEqual(this%test_factorial, gamma(this%input_int), tolerance=tol) +end subroutine gamma_is_factorial + +@Test +subroutine igamma_is_factorial(this) + class(TestGammaFac), intent(inout) :: this + + real(r8) :: tol + + tol = relative_error_tolerance * this%test_factorial + + @assertEqual(this%test_factorial, igamma(this%input_int,0._r8), tolerance=tol) +end subroutine igamma_is_factorial + +end module test_gamma_factorial diff --git a/src/csm_share/util/test/shr_spfn_test/test_igamma.pf b/src/csm_share/util/test/shr_spfn_test/test_igamma.pf new file mode 100644 index 0000000000..f663af9132 --- /dev/null +++ b/src/csm_share/util/test/shr_spfn_test/test_igamma.pf @@ -0,0 +1,42 @@ +module test_igamma + +use funit + +use shr_kind_mod, only: & + r8 => shr_kind_r8 + +use shr_const_mod, only: & + pi => shr_const_pi + +use shr_spfn_mod, only: & + igamma => shr_spfn_igamma, & + erfc => shr_spfn_erfc + +implicit none +save + +real(r8), parameter :: relative_error_tolerance = 1.e-12_r8 + +contains + +! igamma(1,x) = exp(-x) +! => igamma(1,1) = exp(-1) +@Test +subroutine igamma_matches_exp_1() + real(r8) :: tol + tol = relative_error_tolerance*exp(-1._r8) + @assertEqual(exp(-1._r8), igamma(1._r8, 1._r8), tolerance=tol) +end subroutine igamma_matches_exp_1 + +! igamma(1/2,x) = sqrt(pi)*erfc(sqrt(x)) +! => igamma(0.5,1) = sqrt(pi)*erfc(1) +@Test +subroutine igamma_matches_erfc_1() + real(r8) :: expected + real(r8) :: tol + expected = sqrt(pi)*erfc(1._r8) + tol = relative_error_tolerance*expected + @assertEqual(expected, igamma(0.5_r8, 1._r8), tolerance=tol) +end subroutine igamma_matches_erfc_1 + +end module test_igamma diff --git a/src/csm_share/util/test/shr_strconvert_test/test_toString.pf b/src/csm_share/util/test/shr_strconvert_test/test_toString.pf new file mode 100644 index 0000000000..ce55b4125c --- /dev/null +++ b/src/csm_share/util/test/shr_strconvert_test/test_toString.pf @@ -0,0 +1,165 @@ +module test_toString + +! Simple tests for printing intrinsic types. +! +! This module is somewhat repetitive, but it seems manageable enough that it's +! not worth invoking complex methods such as genf90, cpp hacks, or parameterized +! pFUnit tests to handle the different types. + +use funit + +use shr_kind_mod, only: & + i4 => shr_kind_i4, & + i8 => shr_kind_i8, & + r4 => shr_kind_r4, & + r8 => shr_kind_r8 + +use shr_infnan_mod, only: & + posinf => shr_infnan_posinf, & + neginf => shr_infnan_neginf, & + qnan => shr_infnan_qnan, & + snan => shr_infnan_snan, & + to_r4 => shr_infnan_to_r4, & + to_r8 => shr_infnan_to_r8 + +use shr_strconvert_mod, only: toString + +implicit none + +contains + +@Test +subroutine toString_prints_i4() + @assertEqual("1", toString(1_i4)) +end subroutine toString_prints_i4 + +@Test +subroutine toString_prints_i4_longest_value() + @assertEqual("-2147483648", toString(-huge(1_i4)-1_i4)) +end subroutine toString_prints_i4_longest_value + +@Test +subroutine toString_prints_i4_with_format() + @assertEqual("00001", toString(1_i4, format_string="(I0.5)")) +end subroutine toString_prints_i4_with_format + +@Test +subroutine toString_prints_i8() + @assertEqual("1", toString(1_i8)) +end subroutine toString_prints_i8 + +@Test +subroutine toString_prints_i8_longest_value() + @assertEqual("-9223372036854775808", toString(-huge(1_i8)-1_i8)) +end subroutine toString_prints_i8_longest_value + +@Test +subroutine toString_prints_i8_with_format() + @assertEqual("00001", toString(1_i8, format_string="(I0.5)")) +end subroutine toString_prints_i8_with_format + +@Test +subroutine toString_prints_positive_r4() + @assertEqual("+1.00000000E+00", toString(1._r4)) +end subroutine toString_prints_positive_r4 + +@Test +subroutine toString_prints_negative_r4() + @assertEqual("-1.00000000E+00", toString(-1._r4)) +end subroutine toString_prints_negative_r4 + +@Test +subroutine toString_prints_positive_infinity_r4() + character(len=:), allocatable :: string + string = toString(to_r4(posinf)) + @assertEqual("+Inf", string(1:4)) +end subroutine toString_prints_positive_infinity_r4 + +@Test +subroutine toString_prints_negative_infinity_r4() + character(len=:), allocatable :: string + string = toString(to_r4(neginf)) + @assertEqual("-Inf", string(1:4)) +end subroutine toString_prints_negative_infinity_r4 + +@Test +subroutine toString_prints_qnan_r4() + character(len=:), allocatable :: string + string = toString(to_r4(qnan)) + @assertLessThan(0, len(string), message="String is empty!") + @assertEqual("NaN", string(1:3)) +end subroutine toString_prints_qnan_r4 + +@Test +subroutine toString_prints_snan_r4() + character(len=:), allocatable :: string + string = toString(to_r4(snan)) + @assertLessThan(0, len(string), message="String is empty!") + @assertEqual("NaN", string(1:3)) +end subroutine toString_prints_snan_r4 + +@Test +subroutine toString_prints_r4_with_format() + ! Compiler-specific printing conventions, like the optional leading "+", or + ! putting a "0" before a leading decimal point, are not standardized if + ! format_string is specified. Therefore, pick a value that's not subject to + ! these compiler-defined behaviors. + @assertEqual("-1.50", toString(-1.5_r4, format_string="(F5.2)")) +end subroutine toString_prints_r4_with_format + +@Test +subroutine toString_prints_positive_r8() + @assertEqual("+1.0000000000000000E+000", toString(1._r8)) +end subroutine toString_prints_positive_r8 + +@Test +subroutine toString_prints_negative_r8() + @assertEqual("-1.0000000000000000E+000", toString(-1._r8)) +end subroutine toString_prints_negative_r8 + +@Test +subroutine toString_prints_positive_infinity_r8() + character(len=:), allocatable :: string + string = toString(to_r8(posinf)) + @assertEqual("+Inf", string(1:4)) +end subroutine toString_prints_positive_infinity_r8 + +@Test +subroutine toString_prints_negative_infinity_r8() + character(len=:), allocatable :: string + string = toString(to_r8(neginf)) + @assertEqual("-Inf", string(1:4)) +end subroutine toString_prints_negative_infinity_r8 + +@Test +subroutine toString_prints_qnan_r8() + character(len=:), allocatable :: string + string = toString(to_r8(qnan)) + @assertLessThan(0, len(string), message="String is empty!") + @assertEqual("NaN", string(1:3)) +end subroutine toString_prints_qnan_r8 + +@Test +subroutine toString_prints_snan_r8() + character(len=:), allocatable :: string + string = toString(to_r8(snan)) + @assertLessThan(0, len(string), message="String is empty!") + @assertEqual("NaN", string(1:3)) +end subroutine toString_prints_snan_r8 + +@Test +subroutine toString_prints_r8_with_format() + ! Compiler-specific printing conventions, like the optional leading "+", or + ! putting a "0" before a leading decimal point, are not standardized if + ! format_string is specified. Therefore, pick a value that's not subject to + ! these compiler-defined behaviors. + @assertEqual("-1.50", toString(-1.5_r8, format_string="(F5.2)")) +end subroutine toString_prints_r8_with_format + +@Test +subroutine toString_prints_logical() + @assertEqual("T", toString(.true.)) + @assertEqual("F", toString(.false.)) +end subroutine toString_prints_logical + +end module test_toString diff --git a/src/csm_share/util/test/shr_string_test/test_shr_string.pf b/src/csm_share/util/test/shr_string_test/test_shr_string.pf new file mode 100644 index 0000000000..886f757793 --- /dev/null +++ b/src/csm_share/util/test/shr_string_test/test_shr_string.pf @@ -0,0 +1,194 @@ +module test_shr_string + + ! Tests of shr_string_mod + + use funit + use shr_string_mod + + implicit none + + integer, parameter :: list_len = 256 + character, parameter :: tab_char = char(9) + +contains + + ! ------------------------------------------------------------------------ + ! Tests of shr_string_leftAlign_and_convert_tabs + ! ------------------------------------------------------------------------ + + @Test + subroutine test_shr_string_leftAlign_noInitialSpaces() + ! With no initial spaces, should have no effect + character(len=6) :: str + + str = 'foo ' + call shr_string_leftAlign_and_convert_tabs(str) + @assertEqual('foo ', str, whitespace=KEEP_ALL) + end subroutine test_shr_string_leftAlign_noInitialSpaces + + @Test + subroutine test_shr_string_leftAlign_initialSpacesAndTabs() + ! Should remove an initial mix of spaces and tabs + character(len=8) :: str + + str = ' ' // tab_char // ' ' // tab_char // ' ' // 'foo' + call shr_string_leftAlign_and_convert_tabs(str) + @assertEqual('foo ', str, whitespace=KEEP_ALL) + end subroutine test_shr_string_leftAlign_initialSpacesAndTabs + + @Test + subroutine test_shr_string_leftAlign_interiorSpaces() + ! Should NOT remove interior spaces + character(len=6) :: str + + str = 'f oo ' + call shr_string_leftAlign_and_convert_tabs(str) + @assertEqual('f oo ', str, whitespace=KEEP_ALL) + end subroutine test_shr_string_leftAlign_interiorSpaces + + @Test + subroutine test_shr_string_leftAlign_interiorTabs() + ! Convert interior tabs to spaces + character(len=6) :: str, expected + + str = 'f' // tab_char // 'oo ' + expected = 'f oo ' + call shr_string_leftAlign_and_convert_tabs(str) + @assertEqual(expected, str, whitespace=KEEP_ALL) + end subroutine test_shr_string_leftAlign_interiorTabs + + ! ------------------------------------------------------------------------ + ! Tests of shr_string_listDiff + ! ------------------------------------------------------------------------ + + @Test + subroutine test_shr_string_listDiff_default() + character(len=list_len) :: actual + + call shr_string_listDiff( & + list1 = 'first:second:third:fourth', & + list2 = 'fourth:second', & + listout = actual) + @assertEqual('first:third', actual) + end subroutine test_shr_string_listDiff_default + + @Test + subroutine test_shr_string_listDiff_oneElementList2() + ! Make sure that it correctly handles the edge case of a single element in list2 + ! (i.e., with no delimiters). + character(len=list_len) :: actual + + call shr_string_listDiff( & + list1 = 'first:second:third:fourth', & + list2 = 'third', & + listout = actual) + @assertEqual('first:second:fourth', actual) + end subroutine test_shr_string_listDiff_oneElementList2 + + @Test + subroutine test_shr_string_listDiff_emptyList2() + character(len=list_len) :: actual + + call shr_string_listDiff( & + list1 = 'first:second:third:fourth', & + list2 = ' ', & + listout = actual) + @assertEqual('first:second:third:fourth', actual) + end subroutine test_shr_string_listDiff_emptyList2 + + @Test + subroutine test_shr_string_listDiff_List2equalsList1() + character(len=list_len) :: actual + + call shr_string_listDiff( & + list1 = 'first:second:third:fourth', & + list2 = 'fourth:second:first:third', & ! same as list1, but different order + listout = actual) + @assertEqual(' ', actual) + end subroutine test_shr_string_listDiff_List2equalsList1 + + @Test + subroutine test_shr_string_listDiff_elementNotInList1() + character(len=list_len) :: actual + + call shr_string_listDiff( & + list1 = 'first:second:third:fourth', & + list2 = 'fifth', & + listout = actual) + @assertEqual('first:second:third:fourth', actual) + end subroutine test_shr_string_listDiff_elementNotInList1 + + ! ------------------------------------------------------------------------ + ! Tests of shr_string_listFromSuffixes + ! ------------------------------------------------------------------------ + + @Test + subroutine test_shr_string_listFromSuffixes_with_1() + ! 1 suffix -> list of length 1 + character(len=list_len) :: actual + + actual = shr_string_listFromSuffixes(suffixes = ['_s1'], strBase = 'foo') + @assertEqual('foo_s1', actual) + end subroutine test_shr_string_listFromSuffixes_with_1 + + @Test + subroutine test_shr_string_listFromSuffixes_with_3() + ! 3 suffixes -> list of length 3 + character(len=list_len) :: actual + + actual = shr_string_listFromSuffixes(suffixes = ['_s1', '_s2', '_s3'], strBase = 'foo') + @assertEqual('foo_s1:foo_s2:foo_s3', actual) + end subroutine test_shr_string_listFromSuffixes_with_3 + + ! ------------------------------------------------------------------------ + ! Tests of shr_string_listCreateField + ! ------------------------------------------------------------------------ + + @Test + subroutine test_shr_string_listCreateField_basic() + character(len=list_len) :: actual, expected + + actual = shr_string_listCreateField(numFields = 5, strBase = 'LAI') + expected = 'LAI_1:LAI_2:LAI_3:LAI_4:LAI_5' + @assertEqual(expected, actual) + end subroutine test_shr_string_listCreateField_basic + + ! ------------------------------------------------------------------------ + ! Tests of shr_string_listAddSuffix + ! ------------------------------------------------------------------------ + + @Test + subroutine test_shr_string_listAddSuffix_with_empty_list() + character(len=list_len) :: actual + + call shr_string_listAddSuffix(list=' ', suffix='00', new_list=actual) + @assertEqual(' ', actual) + end subroutine test_shr_string_listAddSuffix_with_empty_list + + @Test + subroutine test_shr_string_listAddSuffix_with_one_element() + character(len=list_len) :: actual + + call shr_string_listAddSuffix(list='first', suffix='00', new_list=actual) + @assertEqual('first00', actual) + end subroutine test_shr_string_listAddSuffix_with_one_element + + @Test + subroutine test_shr_string_listAddSuffix_with_multiple_elements() + character(len=list_len) :: actual, expected + + call shr_string_listAddSuffix(list='first:second:third', suffix='00', new_list=actual) + expected = 'first00:second00:third00' + @assertEqual(expected, actual) + end subroutine test_shr_string_listAddSuffix_with_multiple_elements + + @Test + subroutine test_shr_string_listAddSuffix_with_empty_suffix() + character(len=list_len) :: actual, expected + + call shr_string_listAddSuffix(list='first:second:third', suffix=' ', new_list=actual) + expected = 'first:second:third' + @assertEqual(expected, actual) + end subroutine test_shr_string_listAddSuffix_with_empty_suffix + +end module test_shr_string diff --git a/src/csm_share/util/test/shr_vmath_test/test_vmath.F90 b/src/csm_share/util/test/shr_vmath_test/test_vmath.F90 new file mode 100644 index 0000000000..6bea7453a4 --- /dev/null +++ b/src/csm_share/util/test/shr_vmath_test/test_vmath.F90 @@ -0,0 +1,110 @@ +program test_vmath + +! +! This is a test for the shr_vmath_mod module. +! + +use shr_kind_mod, only: r8 => shr_kind_r8 +use shr_kind_mod, only: r4 => shr_kind_r4 +use shr_kind_mod, only: i8 => shr_kind_i8 +use shr_kind_mod, only: i4 => shr_kind_i4 +use shr_const_mod, only: pi => shr_const_pi +use shr_vmath_mod + +implicit none +integer, parameter :: vlen = 128 +real(r8) :: ivec(vlen), rvec(vlen), ovec(vlen), nvec(vlen) +real(r8), parameter :: bigval = 1.0E300_r8 +real(r8), parameter :: smallval = 1.0E-300_r8 +real(r8), parameter :: tolerance = 1.0E-15_r8 +integer :: i +call random_number(ivec) ! numbers between 0 and 1 + +ivec = ivec * bigval ! numbers between 0 and 1e308 + +call shr_vmath_sqrt(ivec, rvec, vlen) + +ovec = dsqrt(ivec) +do i=1,vlen + if(abs(rvec(i)-ovec(i)) > tolerance) then + print *,__LINE__,i, ivec(i),rvec(i),ovec(i) + endif +enddo + +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_sqrt test failed") + +call shr_vmath_rsqrt(ivec, rvec, vlen) + +ovec = 1.0_r8/ovec + +do i=1,vlen + if(abs((rvec(i)-ovec(i))/ovec(i)) > tolerance) then + print *,__LINE__,i, ivec(i),rvec(i),ovec(i) + endif +enddo + +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_rsqrt test failed") + +call random_number(nvec) +nvec = (nvec - 0.5_r8)*bigval + +call shr_vmath_div(ivec, nvec, rvec, vlen) + +ovec = ivec/nvec + +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_div test failed") + +call random_number(ivec) +ivec = ivec*1400_r8 - 700_r8 + +call shr_vmath_exp(ivec, rvec, vlen) + +ovec = exp(ivec) +!print *,minval(abs(rvec)),maxval(rvec) + +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_exp test failed") + +ivec = ovec +call shr_vmath_log(ivec, rvec, vlen) +ovec = log(ivec) + +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_log test failed") + +call random_number(ivec) +ivec = (ivec-0.5_r8)*2.0_r8*pi +call shr_vmath_sin(ivec, rvec, vlen) +ovec = sin(ivec) +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_sin test failed") + +call shr_vmath_cos(ivec, rvec, vlen) +ovec = cos(ivec) +rvec = (rvec - ovec)/ovec + +call assert(all(abs(rvec) < tolerance),"shr_vmath_cos test failed") + +contains + + subroutine assert(val, msg) + logical, intent(in) :: val + character(len=*), intent(in) :: msg + + if (.not. val) then + print *, msg + stop 1 + end if + + end subroutine assert + +end program test_vmath diff --git a/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat.pf b/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat.pf new file mode 100644 index 0000000000..4e37f0628b --- /dev/null +++ b/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat.pf @@ -0,0 +1,256 @@ +module test_wv_sat + +use funit + +use shr_kind_mod, only: r8 => shr_kind_r8 +use shr_const_mod, only: & + tmelt => shr_const_tkfrz, & + h2otrip => shr_const_tktrip, & + mwwv => shr_const_mwwv, & + mwdair => shr_const_mwdair +use shr_wv_sat_mod + +implicit none +public + +real(r8), parameter :: t_transition = 20._r8 +real(r8), parameter :: epsilo = mwwv/mwdair + +contains + +@Before +subroutine setUp() + + character(len=128) :: errstring + + call shr_wv_sat_init(tmelt, h2otrip, t_transition, epsilo, errstring) + + if (errstring /= "") then + call throw("Error from shr_wv_sat_init: "//trim(errstring)) + end if + +end subroutine setUp + +@After +subroutine tearDown() + call shr_wv_sat_final() +end subroutine tearDown + +@Test +subroutine invalid_name_produces_invalid_index() + + integer :: idx + + idx = shr_wv_sat_get_scheme_idx("NotARealSaturationSchemeName") + @assertTrue(.not. shr_wv_sat_valid_idx(idx)) + +end subroutine invalid_name_produces_invalid_index + +@Test +subroutine reject_out_of_bounds_transition + + character(len=128) :: errstring + + ! Negative transition ranges are meaningless. + call shr_wv_sat_init(tmelt, h2otrip, -1._r8, epsilo, errstring) + @assertTrue(errstring /= "") + + ! A transition range of 0 is OK. + call shr_wv_sat_init(tmelt, h2otrip, 0._r8, epsilo, errstring) + @assertTrue(errstring == "") + +end subroutine reject_out_of_bounds_transition + +@Test +subroutine qsat_not_greater_than_one() + + ! Even if the SVP is greater the current pressure, the saturation specific + ! humidity returned should be capped at 1. + @assertEqual(1.0_r8, shr_wv_sat_svp_to_qsat(1.0_r8, 0.5_r8)) + @assertEqual(1.0_r8, shr_wv_sat_svp_to_qsat(2, [1.0_r8, 2.0_r8], [0.5_r8, 0.5_r8])) + +end subroutine qsat_not_greater_than_one + +@Test +subroutine qmmr_not_greater_than_epsilon() + + integer, parameter :: n = 3 + real(r8), parameter :: es(n) = [0.51_r8, 1.0_r8, 1.5_r8] + real(r8), parameter :: p(n) = [1.0_r8, 1.0_r8, 1.0_r8] + + integer :: i + + ! As SVP becomes close to the actual pressure, the mass mixing ratio goes to + ! infinity, so check that we actually cap it at epsilon once the SVP is more + ! than half the total pressure. + do i = 1, 3 + @assertEqual(epsilo, shr_wv_sat_svp_to_qmmr(es(i), p(i))) + end do + @assertEqual(epsilo, shr_wv_sat_svp_to_qmmr(n, es, p)) + +end subroutine qmmr_not_greater_than_epsilon + +@Test +subroutine esat_not_greater_than_p() + + real(r8) :: es, qs + real(r8) :: es_vec(1), qs_vec(1) + + ! For the combined routine, we don't allow the SVP to exceed the current + ! pressure. Tested here by simply providing an extremely low pressure. + + ! This is a guard against schemes that "blindly" attempt to reach saturation + ! by evaporating cloud water, no matter what the conditions. At very low + ! pressures this is impossible, so we return a limited value to prevent + ! numerical issues. + + call shr_wv_sat_qsat_liquid(280._r8, 1.e-30_r8, es, qs) + @assertEqual(1.e-30_r8, es) + + call shr_wv_sat_qsat_liquid(1, [280._r8], [1.e-30_r8], es_vec, qs_vec) + @assertEqual([1.e-30_r8], es_vec) + + call shr_wv_sat_qsat_ice(260._r8, 1.e-30_r8, es, qs) + @assertEqual(1.e-30_r8, es) + + call shr_wv_sat_qsat_ice(1, [260._r8], [1.e-30_r8], es_vec, qs_vec) + @assertEqual([1.e-30_r8], es_vec) + + call shr_wv_sat_qsat_mixed(270._r8, 1.e-30_r8, es, qs) + @assertEqual(1.e-30_r8, es) + + call shr_wv_sat_qsat_mixed(1, [270._r8], [1.e-30_r8], es_vec, qs_vec) + @assertEqual([1.e-30_r8], es_vec) + +end subroutine esat_not_greater_than_p + +@Test +subroutine liquid_vapor_table_is_used() + type(ShrWVSatTableSpec) :: liquid_table_spec + + real(r8) :: non_table_value + real(r8) :: table_value + + non_table_value = shr_wv_sat_svp_liquid(tmelt+7.5_r8) + + liquid_table_spec = ShrWVSatTableSpec(151, tmelt-50._r8, 1._r8) + call shr_wv_sat_make_tables(liquid_spec_in=liquid_table_spec) + + table_value = shr_wv_sat_svp_liquid(tmelt+7.5_r8) + + ! We can't really see directly whether the table is used, but we can pick a + ! value that requires interpolation and look for the difference. + @assertFalse(non_table_value == table_value) + +end subroutine liquid_vapor_table_is_used + +@Test +subroutine liquid_vapor_table_not_extrapolated() + type(ShrWVSatTableSpec) :: liquid_table_spec + + real(r8) :: non_table_low_value, non_table_high_value + real(r8) :: table_low_value, table_high_value + + non_table_low_value = shr_wv_sat_svp_liquid(tmelt-50.5_r8) + non_table_high_value = shr_wv_sat_svp_liquid(tmelt+150.5_r8) + + liquid_table_spec = ShrWVSatTableSpec(151, tmelt-50._r8, 1._r8) + call shr_wv_sat_make_tables(liquid_spec_in=liquid_table_spec) + + table_low_value = shr_wv_sat_svp_liquid(tmelt-50.5_r8) + table_high_value = shr_wv_sat_svp_liquid(tmelt+150.5_r8) + + ! Beyond the table boundaries, the lookup table should not be used, and so we + ! should get the same answer as before specifying any tables. + @assertEqual(non_table_low_value, table_low_value) + @assertEqual(non_table_high_value, table_high_value) + +end subroutine liquid_vapor_table_not_extrapolated + +@Test +subroutine ice_vapor_table_is_used() + type(ShrWVSatTableSpec) :: ice_table_spec + + real(r8) :: non_table_value + real(r8) :: table_value + + non_table_value = shr_wv_sat_svp_ice(tmelt-7.5_r8) + + ice_table_spec = ShrWVSatTableSpec(106, tmelt-100._r8, 1._r8) + call shr_wv_sat_make_tables(ice_spec_in=ice_table_spec) + + table_value = shr_wv_sat_svp_ice(tmelt-7.5_r8) + + ! We can't really see directly whether the table is used, but we can pick a + ! value that requires interpolation and look for the difference. + @assertFalse(non_table_value == table_value) + +end subroutine ice_vapor_table_is_used + +@Test +subroutine ice_vapor_table_not_extrapolated() + type(ShrWVSatTableSpec) :: ice_table_spec + + real(r8) :: non_table_low_value, non_table_high_value + real(r8) :: table_low_value, table_high_value + + non_table_low_value = shr_wv_sat_svp_ice(tmelt-100.5_r8) + non_table_high_value = shr_wv_sat_svp_ice(tmelt+5.5_r8) + + ice_table_spec = ShrWVSatTableSpec(106, tmelt-100._r8, 1._r8) + call shr_wv_sat_make_tables(ice_spec_in=ice_table_spec) + + table_low_value = shr_wv_sat_svp_ice(tmelt-100.5_r8) + table_high_value = shr_wv_sat_svp_ice(tmelt+5.5_r8) + + ! Beyond the table boundaries, the lookup table should not be used, and so we + ! should get the same answer as before specifying any tables. + @assertEqual(non_table_low_value, table_low_value) + @assertEqual(non_table_high_value, table_high_value) + +end subroutine ice_vapor_table_not_extrapolated + +@Test +subroutine mixed_vapor_table_is_used() + type(ShrWVSatTableSpec) :: mixed_table_spec + + real(r8) :: non_table_value + real(r8) :: table_value + + non_table_value = shr_wv_sat_svp_mixed(tmelt-7.5_r8) + + mixed_table_spec = ShrWVSatTableSpec(201, tmelt-100._r8, 1._r8) + call shr_wv_sat_make_tables(mixed_spec_in=mixed_table_spec) + + table_value = shr_wv_sat_svp_mixed(tmelt-7.5_r8) + + ! We can't really see directly whether the table is used, but we can pick a + ! value that requires interpolation and look for the difference. + @assertFalse(non_table_value == table_value) + +end subroutine mixed_vapor_table_is_used + +@Test +subroutine mixed_vapor_table_not_extrapolated() + type(ShrWVSatTableSpec) :: mixed_table_spec + + real(r8) :: non_table_low_value, non_table_high_value + real(r8) :: table_low_value, table_high_value + + non_table_low_value = shr_wv_sat_svp_mixed(tmelt-100.5_r8) + non_table_high_value = shr_wv_sat_svp_mixed(tmelt+100.5_r8) + + mixed_table_spec = ShrWVSatTableSpec(201, tmelt-100._r8, 1._r8) + call shr_wv_sat_make_tables(mixed_spec_in=mixed_table_spec) + + table_low_value = shr_wv_sat_svp_mixed(tmelt-100.5_r8) + table_high_value = shr_wv_sat_svp_mixed(tmelt+100.5_r8) + + ! Beyond the table boundaries, the lookup table should not be used, and so we + ! should get the same answer as before specifying any tables. + @assertEqual(non_table_low_value, table_low_value) + @assertEqual(non_table_high_value, table_high_value) + +end subroutine mixed_vapor_table_not_extrapolated + +end module test_wv_sat diff --git a/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat_each_method.pf b/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat_each_method.pf new file mode 100644 index 0000000000..5eafcdd89e --- /dev/null +++ b/src/csm_share/util/test/shr_wv_sat_test/test_wv_sat_each_method.pf @@ -0,0 +1,270 @@ +! This module has a parameterized test list for application to each of the +! individual methods provided by shr_wv_sat_mod. +module test_wv_sat_each_method + +use funit + +use shr_kind_mod, only: r8 => shr_kind_r8 +use shr_const_mod, only: & + tmelt => shr_const_tkfrz, & + h2otrip => shr_const_tktrip, & + mwwv => shr_const_mwwv, & + mwdair => shr_const_mwdair +use shr_wv_sat_mod + +implicit none +public + +real(r8), parameter :: t_transition = 20._r8 + +@TestParameter +type, extends(AbstractTestParameter) :: WVSchemeParameters + character(len=32) :: scheme_name + real(r8) :: relative_tol + logical :: make_table + logical :: use_vector + contains + procedure :: toString +end type WVSchemeParameters + +@TestCase(testParameters={getParameters()}, constructor=new_WVSchemeCase) +type, extends(ParameterizedTestCase) :: WVSchemeCase + character(len=32) :: scheme_name + real(r8) :: relative_tol + logical :: make_table + logical :: use_vector + contains + procedure :: setUp + procedure :: tearDown +end type WVSchemeCase + +contains + +! Simple routines to convert parameters to a test case or a string, +! respectively. + +function new_WVSchemeCase(params) result(test) + type(WVSchemeParameters), intent(in) :: params + type(WVSchemeCase) :: test + + test%scheme_name = params%scheme_name + test%relative_tol = params%relative_tol + test%make_table = params%make_table + test%use_vector = params%use_vector + +end function new_WVSchemeCase + +function toString(this) result(string) + class(WVSchemeParameters), intent(in) :: this + character(:), allocatable :: string + + character(len=80) :: buffer + + write(buffer,*) "(scheme=",this%scheme_name,",table=",this%make_table, & + ",vec=",this%use_vector,")" + + string = trim(buffer) + +end function toString + +! setUp/tearDown to init the module and to actually set the current scheme. +subroutine setUp(this) + + class(WVSchemeCase), intent(inout) :: this + + real(r8), parameter :: epsilo = mwwv/mwdair + + character(len=128) :: errstring + + type(ShrWVSatTableSpec) :: liquid_table_spec, ice_table_spec, mixed_table_spec + + call shr_wv_sat_init(tmelt, h2otrip, t_transition, epsilo, errstring) + + if (errstring /= "") then + call throw("Error from shr_wv_sat_init: "//trim(errstring)) + end if + + @assertTrue(shr_wv_sat_set_default(this%scheme_name)) + + if (this%make_table) then + liquid_table_spec = ShrWVSatTableSpec(151, tmelt-50._r8, 1._r8) + ice_table_spec = ShrWVSatTableSpec(106, tmelt-100._r8, 1._r8) + mixed_table_spec = ShrWVSatTableSpec(201, tmelt-100._r8, 1._r8) + call shr_wv_sat_make_tables(& + liquid_spec_in=liquid_table_spec, & + ice_spec_in=ice_table_spec, & + mixed_spec_in=mixed_table_spec) + end if + +end subroutine setUp + +subroutine tearDown(this) + + class(WVSchemeCase), intent(inout) :: this + + call shr_wv_sat_final() + +end subroutine tearDown + +! List of testable schemes. + +function getParameters() result(params) + type(WVSchemeParameters), allocatable :: params(:) + + params = [ & + WVSchemeParameters("GoffGratch", 0.002_r8, .false., .false.), & + WVSchemeParameters("MurphyKoop", 0.001_r8, .false., .false.), & + WVSchemeParameters("Flatau", 0.003_r8, .false., .false.), & + WVSchemeParameters("Bolton", 0.001_r8, .false., .false.), & + WVSchemeParameters("GoffGratch", 0.002_r8, .true., .false.), & + WVSchemeParameters("GoffGratch", 0.002_r8, .false., .true.), & + WVSchemeParameters("GoffGratch", 0.002_r8, .true., .true.) ] + +end function getParameters + +! Tests for water and ice functions for each scheme. + +@Test +subroutine scheme_has_correct_ice_trip_point(this) + class(WVSchemeCase), intent(inout) :: this + + if (this%use_vector) then + call assertRelativelyEqual([611.7_r8], shr_wv_sat_svp_ice(1, [h2otrip]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(611.7_r8, shr_wv_sat_svp_ice(h2otrip), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_ice_trip_point + +@Test +subroutine scheme_has_correct_liquid_trip_point(this) + class(WVSchemeCase), intent(inout) :: this + + if (this%use_vector) then + call assertRelativelyEqual([611.7_r8], shr_wv_sat_svp_liquid(1, [h2otrip]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(611.7_r8, shr_wv_sat_svp_liquid(h2otrip), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_liquid_trip_point + +@Test +subroutine scheme_has_correct_liquid_value(this) + class(WVSchemeCase), intent(inout) :: this + + ! Check a warm value (25 deg C). + if (this%use_vector) then + call assertRelativelyEqual([3169._r8], shr_wv_sat_svp_liquid(1, [tmelt+25._r8]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(3169._r8, shr_wv_sat_svp_liquid(tmelt+25._r8), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_liquid_value + +@Test +subroutine scheme_has_correct_ice_value(this) + class(WVSchemeCase), intent(inout) :: this + + ! Check a cold value (-50 deg C). + if (this%use_vector) then + call assertRelativelyEqual([3.935], shr_wv_sat_svp_ice(1, [tmelt-50._r8]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(3.935, shr_wv_sat_svp_ice(tmelt-50._r8), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_ice_value + +! Tests for the combined water-ice function with transition range. +! Technically, these don't have to be done for each scheme, but it doesn't hurt +! to run them many times, since the tests are very quick. + +@Test +subroutine scheme_has_correct_mixed_trip_point(this) + class(WVSchemeCase), intent(inout) :: this + + if (this%use_vector) then + call assertRelativelyEqual([611.7_r8], shr_wv_sat_svp_mixed(1, [h2otrip]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(611.7_r8, shr_wv_sat_svp_mixed(h2otrip), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_mixed_trip_point + +@Test +subroutine scheme_has_correct_mixed_as_ice(this) + class(WVSchemeCase), intent(inout) :: this + + real(r8) :: t_all_ice = tmelt - t_transition - 1._r8 + + real(r8) :: ice_svp + + ice_svp = shr_wv_sat_svp_ice(t_all_ice) + + ! Below the transition range, trans and ice should be equal. + if (this%use_vector) then + call assertRelativelyEqual([ice_svp], shr_wv_sat_svp_mixed(1, [t_all_ice]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(ice_svp, shr_wv_sat_svp_mixed(t_all_ice), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_mixed_as_ice + +@Test +subroutine scheme_has_correct_mixed_as_liquid(this) + class(WVSchemeCase), intent(inout) :: this + + real(r8) :: t_all_liquid = tmelt + 1._r8 + + real(r8) :: liquid_svp + + liquid_svp = shr_wv_sat_svp_liquid(t_all_liquid) + + ! Above the transition range, trans and water should be equal. + if (this%use_vector) then + call assertRelativelyEqual([liquid_svp], shr_wv_sat_svp_mixed(1, [t_all_liquid]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(liquid_svp, shr_wv_sat_svp_mixed(t_all_liquid), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_mixed_as_liquid + +@Test +subroutine scheme_has_correct_mixed_in_range(this) + class(WVSchemeCase), intent(inout) :: this + + ! Temperature at which we are halfway through the transition range. + real(r8), parameter :: t_half = tmelt - 0.5*t_transition + + real(r8) :: ice_svp, liquid_svp + + ice_svp = shr_wv_sat_svp_ice(t_half) + liquid_svp = shr_wv_sat_svp_liquid(t_half) + + ! Check that transition SVP is the average of the ice and water SVPs. + if (this%use_vector) then + call assertRelativelyEqual([0.5_r8 * (ice_svp+liquid_svp)], & + shr_wv_sat_svp_mixed(1, [t_half]), & + tolerance=this%relative_tol) + else + call assertRelativelyEqual(0.5_r8 * (ice_svp+liquid_svp), & + shr_wv_sat_svp_mixed(t_half), & + tolerance=this%relative_tol) + end if + +end subroutine scheme_has_correct_mixed_in_range + +end module test_wv_sat_each_method From 99e669910280dbb3e3c1e5a016551d257104ebcf Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 10:48:07 +0200 Subject: [PATCH 10/17] Upgraded pFUnit to v4.12.0 --- .github/workflows/CI.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6649681194..6f1d2044a9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -73,7 +73,7 @@ jobs: INSTALL_DIR: install OASIS_TAG: tsmp-patches-v0.1 OASIS_INSTALL_PREFIX: ${{ github.workspace }}/oasis3-mct/install - PFUNIT_TAG: v4.10.0 + PFUNIT_TAG: v4.12.0 PFUNIT_INSTALL_PREFIX: ${{ github.workspace }}/pFUnit/install steps: @@ -108,6 +108,7 @@ jobs: cd util/make_dir echo "include ${GITHUB_WORKSPACE}/.github/build.oasis3-mct.ubuntu22.04" > make.inc make realclean static-libs -f TopMakefileOasis3 + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' name: Cache OASIS3-MCT ${{ env.OASIS_TAG }} @@ -116,10 +117,6 @@ jobs: path: ${{ env.OASIS_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} - - if: matrix.config.use_oasis == 'True' - name: Add OASIS to CMAKE_PREFIX_PATH - run: | - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV # # pFUnit # @@ -139,6 +136,7 @@ jobs: cmake -S . -B bld -DCMAKE_INSTALL_PREFIX=install cmake --build bld cmake --install bld + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${PFUNIT_INSTALL_PREFIX}" >> $GITHUB_ENV - if: steps.cache-pFUnit-restore.outputs.cache-hit != 'true' name: Cache pFUnit ${{ env.PFUNIT_TAG }} @@ -147,10 +145,6 @@ jobs: path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} - - name: Add pFUnit to CMAKE_PREFIX_PATH - run: | - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${PFUNIT_INSTALL_PREFIX}" >> $GITHUB_ENV - # # Configure, build, and install eCLM # @@ -166,6 +160,7 @@ jobs: -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ -DUSE_PDAF=${{ matrix.config.use_pdaf }} + -DENABLE_TESTS="True" - name: Build eCLM run: cmake --build $BUILD_DIR From 7c501673949f56684635c3f6da61b2b38146033b Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 10:55:23 +0200 Subject: [PATCH 11/17] Set CMAKE_PREFIX_PATH on a separate job step --- .github/workflows/CI.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6f1d2044a9..564b3c2b10 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -108,7 +108,6 @@ jobs: cd util/make_dir echo "include ${GITHUB_WORKSPACE}/.github/build.oasis3-mct.ubuntu22.04" > make.inc make realclean static-libs -f TopMakefileOasis3 - echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV - if: matrix.config.use_oasis == 'True' && steps.cache-oasis-restore.outputs.cache-hit != 'true' name: Cache OASIS3-MCT ${{ env.OASIS_TAG }} @@ -117,6 +116,11 @@ jobs: path: ${{ env.OASIS_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.OASIS_TAG }} + - if: matrix.config.use_oasis == 'True' + name: Add OASIS to CMAKE_PREFIX_PATH + run: | + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${OASIS_INSTALL_PREFIX}" >> $GITHUB_ENV + # # pFUnit # @@ -145,6 +149,10 @@ jobs: path: ${{ env.PFUNIT_INSTALL_PREFIX }} key: cache-${{ matrix.config.name }}-${{ env.PFUNIT_TAG }} + - name: Add pFUnit to CMAKE_PREFIX_PATH + run: | + echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}:${PFUNIT_INSTALL_PREFIX}" >> $GITHUB_ENV + # # Configure, build, and install eCLM # From 6e4915a800e61c7a75bf8da31668cb22de63c1ec Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 11:09:16 +0200 Subject: [PATCH 12/17] CI.yml: Added missing backslash --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 564b3c2b10..c692409430 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -167,7 +167,7 @@ jobs: -DUSE_OASIS=${{ matrix.config.use_oasis }} \ -DCOUP_OAS_ICON=${{ matrix.config.coup_oas_icon }} \ -DCOUP_OAS_PFL=${{ matrix.config.coup_oas_pfl }} \ - -DUSE_PDAF=${{ matrix.config.use_pdaf }} + -DUSE_PDAF=${{ matrix.config.use_pdaf }} \ -DENABLE_TESTS="True" - name: Build eCLM From ee20dea67d582c51c0181b38833d7abaff0f2134 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 12:01:54 +0200 Subject: [PATCH 13/17] Imported special abort module for test_shr_abort Original source: https://github.com/ESMCI/cime/blob/cime5.6.47/src/share/unit_test_stubs/util/shr_abort_mod.abortthrows.F90 --- src/csm_share/util/test/CMakeLists.txt | 3 ++- src/csm_share/util/test/shr_abort_test/test_shr_abort.pf | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/csm_share/util/test/CMakeLists.txt b/src/csm_share/util/test/CMakeLists.txt index 2f6060377f..7e922b9317 100644 --- a/src/csm_share/util/test/CMakeLists.txt +++ b/src/csm_share/util/test/CMakeLists.txt @@ -2,6 +2,7 @@ add_pfunit_ctest (${PROJECT_NAME}_abort TEST_SOURCES shr_abort_test/test_shr_abort.pf + shr_abort_test/shr_abort_mod_abortthrows.F90 LINK_LIBRARIES ${PROJECT_NAME} ) @@ -66,4 +67,4 @@ add_pfunit_ctest (${PROJECT_NAME}_wv_sat shr_wv_sat_test/test_wv_sat_each_method.pf shr_wv_sat_test/test_wv_sat.pf LINK_LIBRARIES ${PROJECT_NAME} -) \ No newline at end of file +) diff --git a/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf index 04d7405c42..15db2909e2 100644 --- a/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf +++ b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf @@ -4,7 +4,7 @@ module test_shr_abort ! rather than aborting use funit - use shr_abort_mod + use shr_abort_mod_abortthrows use shr_kind_mod , only : r8 => shr_kind_r8 implicit none From f5b7a9e25f7d3a924943e6a2189274c6b501a27d Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 12:12:55 +0200 Subject: [PATCH 14/17] Added a separate step for rerunning failed tests --- .github/workflows/CI.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6dd6d18047..fc71aa393b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -183,6 +183,14 @@ jobs: # Run tests # - name: Run eCLM tests + id: eclm-unit-test + working-directory: ${{ env.BUILD_DIR }} + continue-on-error: true run: | - cd $BUILD_DIR - ctest -V --output-on-failure + ctest + + - name: Re-run failed eCLM tests with verbose logging + if: steps.eclm-unit-test.outcome == 'failure' + working-directory: ${{ env.BUILD_DIR }} + run: | + ctest --rerun-failed --output-on-failure From d18de8621e61bbcbaae226563b733782179c24eb Mon Sep 17 00:00:00 2001 From: kvrigor Date: Mon, 22 Sep 2025 12:15:32 +0200 Subject: [PATCH 15/17] Added shr_abort_mod_abortthrows Source: https://raw.githubusercontent.com/ESMCI/cime/refs/tags/cime5.6.47/src/share/unit_test_stubs/util/shr_abort_mod.abortthrows.F90 --- .../shr_abort_mod_abortthrows.F90 | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 diff --git a/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 b/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 new file mode 100644 index 0000000000..cc59d4458a --- /dev/null +++ b/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 @@ -0,0 +1,63 @@ +module shr_abort_mod_abortthrows + + ! This is a replacement for shr_abort_mod that throws a pfunit exception rather than + ! aborting + + use shr_kind_mod, only : shr_kind_in + use funit, only : throw + + implicit none + private + + public :: shr_abort_abort ! Replacement for shr_abort_abort that throws a pfunit exception rather than aborting + + public :: shr_abort_backtrace ! Just to satisfy the public interface of shr_abort_abort + +contains + + subroutine shr_abort_abort(string,rc) + ! Replacement for shr_abort_abort that throws a pfunit exception rather than aborting + ! + ! This can be used to test expected errors (i.e., failure testing). + ! + ! If this occurs within a pFUnit-based test: + ! + ! - If you have code like: + ! + ! @assertExceptionRaised(expected_message) + ! + ! then your test will pass if the actual message in the 'throw' call (including the + ! 'ABORTED: ' prefix) matches expected_message; it will fail if the actual message + ! doesn't match the expected message + ! + ! - If you don't have + ! + ! @assertExceptionRaised + ! + ! or + ! + ! call assertExceptionRaised + ! + ! then this will result in the given pFUnit test failing. + + !----- arguments ----- + character(len=*) , intent(in), optional :: string ! error message string + integer(shr_kind_in), intent(in), optional :: rc ! error code + + !----- locals ----- + integer(shr_kind_in) :: my_rc + + ! Prevent compiler spam about unused variables. + if (.false.) my_rc = rc + + call throw("ABORTED: "//trim(string)) + end subroutine shr_abort_abort + + subroutine shr_abort_backtrace() + ! Just to satisfy the public interface of shr_abort_abort + ! + ! Does nothing + + end subroutine shr_abort_backtrace + +end module shr_abort_mod_abortthrows From a8bdc9d8eacfbe457a27909039245c19332ea026 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Tue, 23 Sep 2025 08:58:48 +0200 Subject: [PATCH 16/17] Hardcoded test names for easier greppability --- src/csm_share/util/test/CMakeLists.txt | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/csm_share/util/test/CMakeLists.txt b/src/csm_share/util/test/CMakeLists.txt index 7e922b9317..0381973b65 100644 --- a/src/csm_share/util/test/CMakeLists.txt +++ b/src/csm_share/util/test/CMakeLists.txt @@ -1,70 +1,70 @@ # shr_abort_test -add_pfunit_ctest (${PROJECT_NAME}_abort +add_pfunit_ctest (csm_share_abort TEST_SOURCES - shr_abort_test/test_shr_abort.pf shr_abort_test/shr_abort_mod_abortthrows.F90 - LINK_LIBRARIES ${PROJECT_NAME} + shr_abort_test/test_shr_abort.pf + LINK_LIBRARIES csm_share ) # shr_assert_test -add_pfunit_ctest (${PROJECT_NAME}_assert +add_pfunit_ctest (csm_share_assert TEST_SOURCES shr_assert_test/test_assert_array.pf shr_assert_test/test_assert.pf shr_assert_test/test_macro.pf shr_assert_test/test_ndebug.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_cal_test -add_pfunit_ctest (${PROJECT_NAME}_calendar +add_pfunit_ctest (csm_share_calendar TEST_SOURCES shr_cal_test/test_shr_cal.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_log_test -add_pfunit_ctest (${PROJECT_NAME}_log +add_pfunit_ctest (csm_share_log TEST_SOURCES shr_log_test/test_error_printers.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_precip_test -add_pfunit_ctest (${PROJECT_NAME}_precip +add_pfunit_ctest (csm_share_precip TEST_SOURCES shr_precip_test/test_shr_precip.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_spfn_test -add_pfunit_ctest (${PROJECT_NAME}_spfn +add_pfunit_ctest (csm_share_spfn TEST_SOURCES shr_spfn_test/test_erf_r4.pf shr_spfn_test/test_erf_r8.pf shr_spfn_test/test_gamma_factorial.pf shr_spfn_test/test_igamma.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_strconvert_test -add_pfunit_ctest (${PROJECT_NAME}_strconvert +add_pfunit_ctest (csm_share_strconvert TEST_SOURCES shr_strconvert_test/test_toString.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_string_test -add_pfunit_ctest (${PROJECT_NAME}_string +add_pfunit_ctest (csm_share_string TEST_SOURCES shr_string_test/test_shr_string.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) # shr_wv_sat_test -add_pfunit_ctest (${PROJECT_NAME}_wv_sat +add_pfunit_ctest (csm_share_wv_sat TEST_SOURCES shr_wv_sat_test/test_wv_sat_each_method.pf shr_wv_sat_test/test_wv_sat.pf - LINK_LIBRARIES ${PROJECT_NAME} + LINK_LIBRARIES csm_share ) From a0152f618062755d5a63e0b8f97be5da1e938db1 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Tue, 23 Sep 2025 17:36:17 +0200 Subject: [PATCH 17/17] Made shr_sys_abort() overridable by pFUnit abort method Inspired by https://raw.githubusercontent.com/Goddard-Fortran-Ecosystem/pFUnit_demos/refs/heads/main/Basic/src/throw.F90 Note: This commit throws a compile error at abort_with_pfunit.F90. The fix requires upgrading to a Fortran compiler standard that introduces abstract types and procedure pointers (e.g. [F2003] and later) [F2003]: https://en.wikipedia.org/wiki/Fortran#Fortran_2003 --- src/csm_share/util/shr_abort_mod.F90 | 40 ++++++++++-- src/csm_share/util/test/CMakeLists.txt | 52 +++++++-------- src/csm_share/util/test/abort_with_pfunit.F90 | 32 ++++++++++ src/csm_share/util/test/shr_abort_test/README | 2 - .../shr_abort_mod_abortthrows.F90 | 63 ------------------- .../test/shr_abort_test/test_shr_abort.pf | 9 +-- 6 files changed, 93 insertions(+), 105 deletions(-) create mode 100644 src/csm_share/util/test/abort_with_pfunit.F90 delete mode 100644 src/csm_share/util/test/shr_abort_test/README delete mode 100644 src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 diff --git a/src/csm_share/util/shr_abort_mod.F90 b/src/csm_share/util/shr_abort_mod.F90 index 9e4de5bd00..8d1ba3f3a4 100644 --- a/src/csm_share/util/shr_abort_mod.F90 +++ b/src/csm_share/util/shr_abort_mod.F90 @@ -20,9 +20,6 @@ module shr_abort_mod #endif implicit none - - ! PUBLIC: Public interfaces - private ! The public routines here are only meant to be used directly by shr_sys_mod. Other code @@ -31,11 +28,42 @@ module shr_abort_mod ! when these routines were defined in shr_sys_mod.) public :: shr_abort_abort ! abort a program public :: shr_abort_backtrace ! print a backtrace, if possible - + public :: set_abort_method ! change abort method (necessary for unit testing) + public :: abort_program + + abstract interface + subroutine abort_interface(error_msg, error_code) + character(len=*) , intent(in), optional :: error_msg + integer , intent(in), optional :: error_code + end subroutine abort_interface + end interface + + procedure (abort_interface), pointer :: abort_method => null() + logical, save :: initialized = .false. contains + subroutine initialize() + abort_method => abort_program + initialized = .true. + end subroutine initialize + + subroutine set_abort_method(method) + procedure (abort_interface) :: method + if (.not. initialized) call initialize() + abort_method => method + end subroutine set_abort_method + + subroutine shr_abort_abort(error_msg, error_code) + character(len=*) , intent(in), optional :: error_msg + integer , intent(in), optional :: error_code + + if (.not. initialized) call initialize() + + call abort_method(error_msg, error_code) + end subroutine shr_abort_abort + !=============================================================================== - subroutine shr_abort_abort(string,rc) + subroutine abort_program(string,rc) ! Consistent stopping mechanism !----- arguments ----- @@ -75,7 +103,7 @@ subroutine shr_abort_abort(string,rc) ! usually sends SIGTERM to the process, and we don't catch that signal. call abort() - end subroutine shr_abort_abort + end subroutine abort_program !=============================================================================== !=============================================================================== diff --git a/src/csm_share/util/test/CMakeLists.txt b/src/csm_share/util/test/CMakeLists.txt index 0381973b65..beeb607bef 100644 --- a/src/csm_share/util/test/CMakeLists.txt +++ b/src/csm_share/util/test/CMakeLists.txt @@ -1,70 +1,66 @@ # shr_abort_test add_pfunit_ctest (csm_share_abort - TEST_SOURCES - shr_abort_test/shr_abort_mod_abortthrows.F90 - shr_abort_test/test_shr_abort.pf - LINK_LIBRARIES csm_share + TEST_SOURCES shr_abort_test/test_shr_abort.pf + OTHER_SOURCES abort_with_pfunit.F90 + LINK_LIBRARIES csm_share + EXTRA_USE abort_with_pfunit_mod + EXTRA_INITIALIZE initialize_abort ) # shr_assert_test add_pfunit_ctest (csm_share_assert - TEST_SOURCES - shr_assert_test/test_assert_array.pf - shr_assert_test/test_assert.pf - shr_assert_test/test_macro.pf - shr_assert_test/test_ndebug.pf - LINK_LIBRARIES csm_share + TEST_SOURCES shr_assert_test/test_assert_array.pf + shr_assert_test/test_assert.pf + shr_assert_test/test_macro.pf + shr_assert_test/test_ndebug.pf + OTHER_SOURCES abort_with_pfunit.F90 + LINK_LIBRARIES csm_share + EXTRA_USE abort_with_pfunit_mod + EXTRA_INITIALIZE initialize_abort ) # shr_cal_test add_pfunit_ctest (csm_share_calendar - TEST_SOURCES - shr_cal_test/test_shr_cal.pf + TEST_SOURCES shr_cal_test/test_shr_cal.pf LINK_LIBRARIES csm_share ) # shr_log_test add_pfunit_ctest (csm_share_log - TEST_SOURCES - shr_log_test/test_error_printers.pf + TEST_SOURCES shr_log_test/test_error_printers.pf LINK_LIBRARIES csm_share ) # shr_precip_test add_pfunit_ctest (csm_share_precip - TEST_SOURCES - shr_precip_test/test_shr_precip.pf + TEST_SOURCES shr_precip_test/test_shr_precip.pf LINK_LIBRARIES csm_share ) # shr_spfn_test add_pfunit_ctest (csm_share_spfn - TEST_SOURCES - shr_spfn_test/test_erf_r4.pf - shr_spfn_test/test_erf_r8.pf - shr_spfn_test/test_gamma_factorial.pf - shr_spfn_test/test_igamma.pf + TEST_SOURCES shr_spfn_test/test_erf_r4.pf + shr_spfn_test/test_erf_r8.pf + shr_spfn_test/test_gamma_factorial.pf + shr_spfn_test/test_igamma.pf LINK_LIBRARIES csm_share ) # shr_strconvert_test add_pfunit_ctest (csm_share_strconvert - TEST_SOURCES - shr_strconvert_test/test_toString.pf + TEST_SOURCES shr_strconvert_test/test_toString.pf LINK_LIBRARIES csm_share ) # shr_string_test add_pfunit_ctest (csm_share_string - TEST_SOURCES - shr_string_test/test_shr_string.pf + TEST_SOURCES shr_string_test/test_shr_string.pf LINK_LIBRARIES csm_share ) # shr_wv_sat_test add_pfunit_ctest (csm_share_wv_sat - TEST_SOURCES - shr_wv_sat_test/test_wv_sat_each_method.pf - shr_wv_sat_test/test_wv_sat.pf + TEST_SOURCES shr_wv_sat_test/test_wv_sat_each_method.pf + shr_wv_sat_test/test_wv_sat.pf LINK_LIBRARIES csm_share ) diff --git a/src/csm_share/util/test/abort_with_pfunit.F90 b/src/csm_share/util/test/abort_with_pfunit.F90 new file mode 100644 index 0000000000..712cec5699 --- /dev/null +++ b/src/csm_share/util/test/abort_with_pfunit.F90 @@ -0,0 +1,32 @@ +module abort_with_pfunit_mod + use shr_abort_mod, only: set_abort_method + implicit none + private + + public :: abort_unit_test + public :: initialize_abort + +contains + + subroutine abort_unit_test(error_msg, error_code) + use funit, only: pFUnit_throw => throw + + character(len=*), intent(in), optional :: error_msg + integer , intent(in), optional :: error_code + + character(len=:), allocatable :: message_ + + if (present(error_msg)) then + message_ = "pFUnit test aborted: "//trim(error_msg) + else + message_ = "pFUnit test aborted." + end if + call pFUnit_throw(message_) + + end subroutine abort_unit_test + + subroutine initialize_abort() + call set_abort_method(abort_unit_test) + end subroutine initialize_abort + +end module abort_with_pfunit_mod diff --git a/src/csm_share/util/test/shr_abort_test/README b/src/csm_share/util/test/shr_abort_test/README deleted file mode 100644 index 4c579606c3..0000000000 --- a/src/csm_share/util/test/shr_abort_test/README +++ /dev/null @@ -1,2 +0,0 @@ -This directory tests the version of shr_abort_mod that is used in unit tests. It -does NOT test the production version of shr_abort_mod. diff --git a/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 b/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 deleted file mode 100644 index cc59d4458a..0000000000 --- a/src/csm_share/util/test/shr_abort_test/shr_abort_mod_abortthrows.F90 +++ /dev/null @@ -1,63 +0,0 @@ -module shr_abort_mod_abortthrows - - ! This is a replacement for shr_abort_mod that throws a pfunit exception rather than - ! aborting - - use shr_kind_mod, only : shr_kind_in - use funit, only : throw - - implicit none - private - - public :: shr_abort_abort ! Replacement for shr_abort_abort that throws a pfunit exception rather than aborting - - public :: shr_abort_backtrace ! Just to satisfy the public interface of shr_abort_abort - -contains - - subroutine shr_abort_abort(string,rc) - ! Replacement for shr_abort_abort that throws a pfunit exception rather than aborting - ! - ! This can be used to test expected errors (i.e., failure testing). - ! - ! If this occurs within a pFUnit-based test: - ! - ! - If you have code like: - ! - ! @assertExceptionRaised(expected_message) - ! - ! then your test will pass if the actual message in the 'throw' call (including the - ! 'ABORTED: ' prefix) matches expected_message; it will fail if the actual message - ! doesn't match the expected message - ! - ! - If you don't have - ! - ! @assertExceptionRaised - ! - ! or - ! - ! call assertExceptionRaised - ! - ! then this will result in the given pFUnit test failing. - - !----- arguments ----- - character(len=*) , intent(in), optional :: string ! error message string - integer(shr_kind_in), intent(in), optional :: rc ! error code - - !----- locals ----- - integer(shr_kind_in) :: my_rc - - ! Prevent compiler spam about unused variables. - if (.false.) my_rc = rc - - call throw("ABORTED: "//trim(string)) - end subroutine shr_abort_abort - - subroutine shr_abort_backtrace() - ! Just to satisfy the public interface of shr_abort_abort - ! - ! Does nothing - - end subroutine shr_abort_backtrace - -end module shr_abort_mod_abortthrows diff --git a/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf index 15db2909e2..1bbff8d1d9 100644 --- a/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf +++ b/src/csm_share/util/test/shr_abort_test/test_shr_abort.pf @@ -1,11 +1,10 @@ module test_shr_abort - ! Tests of shr_abort_mod: version used in unit tests that throws a pfunit exception - ! rather than aborting + ! Tests of shr_abort_mod: version used in unit tests that + ! throws a pfunit exception rather than aborting use funit - use shr_abort_mod_abortthrows - use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_sys_mod , only: shr_sys_abort implicit none @@ -16,8 +15,6 @@ module test_shr_abort procedure :: tearDown end type TestShrAbort - real(r8), parameter :: tol = 1.e-13_r8 - contains subroutine setUp(this)