diff --git a/.github/workflows/intel.yml b/.github/workflows/intel.yml index 700553cea..08ec15b50 100644 --- a/.github/workflows/intel.yml +++ b/.github/workflows/intel.yml @@ -51,7 +51,7 @@ jobs: sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list sudo apt-get update - sudo apt-get install intel-oneapi-dev-utilities intel-oneapi-mpi-devel intel-oneapi-openmp intel-oneapi-compiler-fortran intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic + sudo apt-get install intel-oneapi-dev-utilities intel-oneapi-mpi-devel intel-oneapi-compiler-fortran-2023.2.1 intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.2.1 intel-oneapi-openmp # Build WW3 spack environment - name: install-dependencies-with-spack diff --git a/.github/workflows/io_gnu.yml b/.github/workflows/io_gnu.yml new file mode 100644 index 000000000..89acaed0c --- /dev/null +++ b/.github/workflows/io_gnu.yml @@ -0,0 +1,123 @@ +name: io_gnu +on: [push, pull_request, workflow_dispatch] + +# Cancel in-progress workflows when pushing to a branch +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + cache_key: gnu11-1 + CC: gcc-10 + FC: gfortran-10 + CXX: g++-10 + + +# Split into a steup step, and a WW3 build step which +# builds multiple switches in a matrix. The setup is run once and +# the environment is cached so each build of WW3 can share the dependencies. + +jobs: + setup: + runs-on: ubuntu-latest + + steps: + - name: checkout-ww3 + if: steps.cache-env.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + path: ww3 + # Cache spack, OASIS, and compiler + # No way to flush Action cache, so key may have # appended + - name: cache-env + id: cache-env + uses: actions/cache@v3 + with: + path: | + spack + ~/.spack + work_oasis3-mct + key: spack-${{ runner.os }}-${{ env.cache_key }}-${{ hashFiles('ww3/model/ci/spack_gnu.yaml') }} + + # Build WW3 spack environment + - name: install-dependencies-with-spack + if: steps.cache-env.outputs.cache-hit != 'true' + run: | + # Install NetCDF, ESMF, g2, etc using Spack + sudo apt install cmake + git clone -c feature.manyFiles=true https://github.com/JCSDA/spack.git + source spack/share/spack/setup-env.sh + spack env create ww3-gnu ww3/model/ci/spack_gnu.yaml + spack env activate ww3-gnu + spack compiler find + spack external find cmake + spack add mpich@3.4.2 + spack concretize + spack install --dirty -v + + - name: build-oasis + if: steps.cache-env.outputs.cache-hit != 'true' + run: | + source spack/share/spack/setup-env.sh + spack env activate ww3-gnu + export WWATCH3_DIR=${GITHUB_WORKSPACE}/ww3/model + export OASIS_INPUT_PATH=${GITHUB_WORKSPACE}/ww3/regtests/ww3_tp2.14/input/oasis3-mct + export OASIS_WORK_PATH=${GITHUB_WORKSPACE}/ww3/regtests/ww3_tp2.14/input/work_oasis3-mct + cd ww3/regtests/ww3_tp2.14/input/oasis3-mct/util/make_dir + cmake . + make VERBOSE=1 + cp -r ${GITHUB_WORKSPACE}/ww3/regtests/ww3_tp2.14/input/work_oasis3-mct ${GITHUB_WORKSPACE} + + io_gnu: + needs: setup + runs-on: ubuntu-latest + + steps: + - name: install-dependencies + run: | + sudo apt-get update + sudo apt-get install doxygen gcovr valgrind + + - name: checkout-ww3 + uses: actions/checkout@v3 + with: + path: ww3 + + - name: cache-env + id: cache-env + uses: actions/cache@v3 + with: + path: | + spack + ~/.spack + work_oasis3-mct + key: spack-${{ runner.os }}-${{ env.cache_key }}-${{ hashFiles('ww3/model/ci/spack_gnu.yaml') }} + + - name: build-ww3 + run: | + source spack/share/spack/setup-env.sh + spack env activate ww3-gnu + set -x + cd ww3 + export CC=mpicc + export FC=mpif90 + export OASISDIR=${GITHUB_WORKSPACE}/work_oasis3-mct + mkdir build && cd build + pwd + echo "${GITHUB_WORKSPACE}" + export LD_LIBRARY_PATH="/home/runner/work/WW3/WW3/spack/var/spack/environments/ww3-gnu/.spack-env/view/:$LD_LIBRARY_PATH" + which ncdump + cmake -DSWITCH=${GITHUB_WORKSPACE}/ww3/model/tests/data/switch.io -DCMAKE_BUILD_TYPE=Debug -DCMAKE_Fortran_FLAGS="-g -fprofile-abs-path -fprofile-arcs -ftest-coverage -O0 -Wall -fno-omit-frame-pointer -fsanitize=address" -DCMAKE_C_FLAGS="-g -fprofile-abs-path -fprofile-arcs -ftest-coverage -O0 -Wall -fno-omit-frame-pointer -fsanitize=address" .. + make -j2 VERBOSE=1 + ctest --verbose --output-on-failure --rerun-failed + gcovr --root .. -v --html-details --exclude ../model/tests --exclude CMakeFiles --print-summary -o test-coverage.html &> /dev/null + + - name: upload-test-coverage + uses: actions/upload-artifact@v3 + with: + name: ww3-test-coverage + path: | + ww3/build/*.html + ww3/build/*.css + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 58115b3aa..ed3d93075 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,3 +58,10 @@ if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)$") endif() add_subdirectory(model) + +# Turn on unit testing. +include(CTest) +if(BUILD_TESTING) + add_subdirectory(model/tests) +endif() + diff --git a/model/CMakeLists.txt b/model/CMakeLists.txt index febd4f0ab..b7178e664 100644 --- a/model/CMakeLists.txt +++ b/model/CMakeLists.txt @@ -1 +1,3 @@ add_subdirectory(src) + + diff --git a/model/src/w3iopomd.F90 b/model/src/w3iopomd.F90 index 0c15ea8c6..552ef06ed 100644 --- a/model/src/w3iopomd.F90 +++ b/model/src/w3iopomd.F90 @@ -118,6 +118,115 @@ MODULE W3IOPOMD CHARACTER(LEN=10), PARAMETER, PRIVATE :: VEROPT = '2021-04-06' CHARACTER(LEN=31), PARAMETER, PRIVATE :: & IDSTR = 'WAVEWATCH III POINT OUTPUT FILE' + + !> Dimension name for the netCDF point output file, for NOPTS, the + !> Number of Output Points. + character(*), parameter, private :: DNAME_NOPTS = 'NOPTS' + + !> Dimension name for the netCDF point output file, for NSPEC. + character(*), parameter, private :: DNAME_NSPEC = 'NSPEC' + + !> Dimension name for the netCDF point output file, for VSIZE. This + !> is for the vector size for points, which is 2. + character(*), parameter, private :: DNAME_VSIZE = 'VSIZE' + + !> Dimension name for the netCDF point output file, for + !> NAMELEN. This is the length of the PTNME strings, which contains + !> the names of the points. + character(*), parameter, private :: DNAME_NAMELEN = 'NAMELEN' + + !> Dimension name for the netCDF point output file, for GRDIDLEN, + !> this is the length of the GRDID character array. + character(*), parameter, private :: DNAME_GRDIDLEN = 'GRDIDLEN' + + !> Variable name for the netCDF point output file, for NK. + character(*), parameter, private :: VNAME_NK = 'NK' + + !> Variable name for the netCDF point output file, for MTH. + character(*), parameter, private :: VNAME_MTH = 'MTH' + + !> Variable name for the netCDF point output file, for PTLOC. + character(*), parameter, private :: VNAME_PTLOC = 'PTLOC' + + !> Variable name for the netCDF point output file, for PTNME. + character(*), parameter, private :: VNAME_PTNME = 'PTNME' + + !> Variable name for the netCDF point output file, for IW. + character(*), parameter, private :: VNAME_IW = 'IW' + + !> Variable name for the netCDF point output file, for II. + character(*), parameter, private :: VNAME_II = 'II' + + !> Variable name for the netCDF point output file, for IL. + character(*), parameter, private :: VNAME_IL = 'IL' + + !> Variable name for the netCDF point output file, for DPO. + character(*), parameter, private :: VNAME_DPO = 'DPO' + + !> Variable name for the netCDF point output file, for WAO. + character(*), parameter, private :: VNAME_WAO = 'WAO' + + !> Variable name for the netCDF point output file, for WDO. + character(*), parameter, private :: VNAME_WDO = 'WDO' + + !> Variable name for the netCDF point output file, for TAUAO. + character(*), parameter, private :: VNAME_TAUAO = 'TAUAO' + + !> Variable name for the netCDF point output file, for TAIDO. + character(*), parameter, private :: VNAME_TAIDO = 'TAIDO' + + !> Variable name for the netCDF point output file, for DAIRO. + character(*), parameter, private :: VNAME_DAIRO = 'DAIRO' + + !> Variable name for the netCDF point output file, for ZET_SETO. + character(*), parameter, private :: VNAME_ZET_SETO = 'ZET_SETO' + + !> Variable name for the netCDF point output file, for ASO. + character(*), parameter, private :: VNAME_ASO = 'ASO' + + !> Variable name for the netCDF point output file, for CAO. + character(*), parameter, private :: VNAME_CAO = 'CAO' + + !> Variable name for the netCDF point output file, for CDO. + character(*), parameter, private :: VNAME_CDO = 'CDO' + + !> Variable name for the netCDF point output file, for ICEO. + character(*), parameter, private :: VNAME_ICEO = 'ICEO' + + !> Variable name for the netCDF point output file, for ICEHO. + character(*), parameter, private :: VNAME_ICEHO = 'ICEHO' + + !> Variable name for the netCDF point output file, for ICEFO. + character(*), parameter, private :: VNAME_ICEFO = 'ICEFO' + + !> Variable name for the netCDF point output file, for GRDID. + character(*), parameter, private :: VNAME_GRDID = 'GRDID' + + !> Variable name for the netCDF point output file, for SPCO. + character(*), parameter, private :: VNAME_SPCO = 'SPCO' + + + + + + + + + + + + + + + + + + + + + + + !/ CONTAINS !/ ------------------------------------------------------------------- / @@ -1024,6 +1133,454 @@ SUBROUTINE W3IOPE ( A ) !/ END SUBROUTINE W3IOPE + !> Read point output in netCDF format. + !> + !> @param[out] IOTST Test indictor for reading. + !> @param[in] IMOD Model number for W3GDAT etc. + !> @param[in] filename Name of file to read. + !> @param[inout] ncerr Error code, 0 for success, netCDF error code + !> otherwise. + !> + !> @author Edward Hartnett @date 1-Nov-2023 + !> + SUBROUTINE W3IOPON_READ(IOTST, IMOD, filename, ncerr) + use netcdf + USE W3GDATMD, ONLY: NTH, NK, NSPEC, FILEXT + USE W3ODATMD, ONLY: NDST, NDSE, IPASS => IPASS2, NOPTS, IPTINT, & + IL, IW, II, PTLOC, PTIFAC, DPO, WAO, WDO, & + ASO, CAO, CDO, SPCO, PTNME, O2INIT, FNMPRE, & + GRDID, ICEO, ICEHO, ICEFO +#ifdef W3_FLX5 + USE W3ODATMD, ONLY: TAUAO, TAUDO, DAIRO +#endif +#ifdef W3_SETUP + USE W3ODATMD, ONLY: ZET_SETO +#endif + IMPLICIT NONE + + INTEGER, INTENT(OUT) :: IOTST + INTEGER, INTENT(IN), OPTIONAL :: IMOD + character(*), intent(in) :: filename + integer, intent(inout) :: ncerr + integer :: fh + integer :: d_nopts, d_nspec, d_vsize, d_namelen, d_grdidlen + integer :: d_nopts_len, d_nspec_len, d_vsize_len, d_namelen_len, d_grdidlen_len + integer :: v_idtst, v_vertst, v_nk, v_mth, v_ptloc, v_ptnme + integer :: v_iw, v_ii, v_il, v_dpo, v_wao, v_wdo, v_tauao + integer :: v_taido, v_dairo, v_zet_seto, v_aso, v_cao, v_cdo, v_iceo + integer :: v_iceho, v_icefo, v_grdid, v_spco + + print *,filename + IOTST = 0 + + ! Open the netCDF file. + ncerr = nf90_open(filename, NF90_NOWRITE, fh) + if (ncerr .ne. 0) return + + ! Read the dimension information for NOPTS. + ncerr = nf90_inq_dimid(fh, DNAME_NOPTS, d_nopts) + if (ncerr .ne. 0) return + ncerr = nf90_inquire_dimension(fh, d_nopts, len = d_nopts_len) + if (ncerr .ne. 0) return + + ! Read the dimension information for NSPEC. + ncerr = nf90_inq_dimid(fh, DNAME_NSPEC, d_nspec) + if (ncerr .ne. 0) return + ncerr = nf90_inquire_dimension(fh, d_nspec, len = d_nspec_len) + if (ncerr .ne. 0) return + + ! Read the dimension information for VSIZE. + ncerr = nf90_inq_dimid(fh, DNAME_VSIZE, d_vsize) + if (ncerr .ne. 0) return + ncerr = nf90_inquire_dimension(fh, d_vsize, len = d_vsize_len) + if (ncerr .ne. 0) return + + ! Read the dimension information for NAMELEN. + ncerr = nf90_inq_dimid(fh, DNAME_NAMELEN, d_namelen) + if (ncerr .ne. 0) return + ncerr = nf90_inquire_dimension(fh, d_namelen, len = d_namelen_len) + if (ncerr .ne. 0) return + + ! Read the dimension information for GRDIDLEN. + ncerr = nf90_inq_dimid(fh, DNAME_GRDIDLEN, d_grdidlen) + if (ncerr .ne. 0) return + ncerr = nf90_inquire_dimension(fh, d_grdidlen, len = d_grdidlen_len) + if (ncerr .ne. 0) return + + ! Read scalar variables. + ncerr = nf90_inq_varid(fh, VNAME_NK, v_nk) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_nk, NK) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_MTH, v_mth) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_mth, NTH) + if (ncerr .ne. 0) return + + ! Read vars with nopts as a dimension. + ncerr = nf90_inq_varid(fh, VNAME_PTLOC, v_ptloc) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_ptloc, PTLOC) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_PTNME, v_ptnme) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_ptnme, PTNME) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_IW, v_iw) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_iw, IW) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_II, v_ii) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_ii, II) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_IL, v_il) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_il, IL) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_DPO, v_dpo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_dpo, DPO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_WAO, v_wao) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_wao, WAO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_WDO, v_wdo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_wdo, WDO) + if (ncerr .ne. 0) return +#ifdef W3_FLX5 + ncerr = nf90_inq_varid(fh, VNAME_TAUAO, v_tauao) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_tauao, TAUAO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_TAIDO, v_taido) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_taido, TAIDO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_DAIRO, v_dairo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_dairo, DAIRO) + if (ncerr .ne. 0) return +#endif +#ifdef W3_SETUP + ncerr = nf90_inq_varid(fh, ZET_SETO, v_zet_seto) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_zet_seto, ZET_SETO) + if (ncerr .ne. 0) return +#endif + ncerr = nf90_inq_varid(fh, VNAME_ASO, v_aso) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_aso, ASO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_CAO, v_cao) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_cao, CAO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_CDO, v_cdo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_cdo, CDO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_ICEO, v_iceo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_iceo, ICEO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_ICEHO, v_iceho) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_iceho, ICEHO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_ICEFO, v_icefo) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_icefo, ICEFO) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_GRDID, v_grdid) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_grdid, GRDID) + if (ncerr .ne. 0) return + ncerr = nf90_inq_varid(fh, VNAME_SPCO, v_spco) + if (ncerr .ne. 0) return + ncerr = nf90_get_var(fh, v_spco, SPCO) + if (ncerr .ne. 0) return + + ! Close the file. + ncerr = nf90_close(fh) + if (ncerr .ne. 0) return + + END SUBROUTINE W3IOPON_READ + + !/ ------------------------------------------------------------------- / + !> + !> @brief Write point output in netCDF format. + !> + !> @param[in] IMOD Model number for W3GDAT etc. + !> @param[in] filename Name of file to write. + !> @param[in] timestep_only Will be 0 if whole model run should be + !> written, 1 if only one timestep should be written. + !> @param[inout] ncerr Error code, 0 for success, netCDF error code + !> otherwise. + !> + !> @author Edward Hartnett @date 1-Nov-2023 + !> + SUBROUTINE W3IOPON_WRITE(timestep_only, IMOD, filename, ncerr) + use netcdf + USE W3GDATMD, ONLY: NTH, NK, NSPEC, FILEXT + USE W3ODATMD, ONLY: NDST, NDSE, IPASS => IPASS2, NOPTS, IPTINT, & + IL, IW, II, PTLOC, PTIFAC, DPO, WAO, WDO, & + ASO, CAO, CDO, SPCO, PTNME, O2INIT, FNMPRE, & + GRDID, ICEO, ICEHO, ICEFO +#ifdef W3_FLX5 + USE W3ODATMD, ONLY: TAUAO, TAUDO, DAIRO +#endif +#ifdef W3_SETUP + USE W3ODATMD, ONLY: ZET_SETO +#endif + + IMPLICIT NONE + integer, intent(in) :: timestep_only ! 1 if only timestep should be written. + INTEGER, INTENT(IN) :: IMOD + character(*), intent(in) :: filename + integer, intent(inout) :: ncerr + integer :: fh, ndim, nvar, fmt + integer :: d_nopts, d_nspec, d_vsize, d_namelen, d_grdidlen + integer :: v_idtst, v_vertst, v_nk, v_mth, v_ptloc, v_ptnme + integer :: v_iw, v_ii, v_il, v_dpo, v_wao, v_wdo, v_tauao + integer :: v_taido, v_dairo, v_zet_seto, v_aso, v_cao, v_cdo, v_iceo + integer :: v_iceho, v_icefo, v_grdid, v_spco + character (len = *), parameter :: FILE_NAME = "f90tst_nc4.nc" + CHARACTER(LEN=31), PARAMETER :: IDSTR = 'WAVEWATCH III POINT OUTPUT FILE' + CHARACTER(LEN=10), PARAMETER :: VEROPT = '2021-04-06' + + print *, 'WRITE ',filename, len(filename) + + ! Create the netCDF file. + ncerr = nf90_create(filename, NF90_NETCDF4, fh) + if (ncerr .ne. 0) return + + ! Define dimensions. + ncerr = nf90_def_dim(fh, DNAME_NOPTS, NOPTS, d_nopts) + if (ncerr .ne. 0) return + ncerr = nf90_def_dim(fh, DNAME_NSPEC, NSPEC, d_nspec) + if (ncerr .ne. 0) return + ncerr = nf90_def_dim(fh, DNAME_VSIZE, 2, d_vsize) + if (ncerr .ne. 0) return + ncerr = nf90_def_dim(fh, DNAME_NAMELEN, 40, d_namelen) + if (ncerr .ne. 0) return + ncerr = nf90_def_dim(fh, DNAME_GRDIDLEN, 13, d_grdidlen) + if (ncerr .ne. 0) return + + ! Define global attributes. + ncerr = nf90_put_att(fh, NF90_GLOBAL, 'title', IDSTR) + if (ncerr .ne. 0) return + ncerr = nf90_put_att(fh, NF90_GLOBAL, 'version', VEROPT) + if (ncerr .ne. 0) return + + ! Define scalar variables. + ncerr = nf90_def_var(fh, VNAME_NK, NF90_INT, v_nk) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_MTH, NF90_INT, v_mth) + if (ncerr .ne. 0) return + + ! Define vars with nopts as a dimension. + ncerr = nf90_def_var(fh, VNAME_PTLOC, NF90_INT, (/d_vsize, d_nopts/), v_ptloc) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_PTNME, NF90_CHAR, (/d_namelen, d_nopts/), v_ptnme) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_IW, NF90_INT, (/d_nopts/), v_iw) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_II, NF90_INT, (/d_nopts/), v_ii) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_IL, NF90_INT, (/d_nopts/), v_il) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_DPO, NF90_INT, (/d_nopts/), v_dpo) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_WAO, NF90_INT, (/d_nopts/), v_wao) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_WDO, NF90_INT, (/d_nopts/), v_wdo) + if (ncerr .ne. 0) return +#ifdef W3_FLX5 + ncerr = nf90_def_var(fh, VNAME_TAUAO, NF90_INT, (/d_nopts/), v_tauao) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_TAIDO, NF90_INT, (/d_nopts/), v_taido) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_DAIRO, NF90_INT, (/d_nopts/), v_dairo) + if (ncerr .ne. 0) return +#endif +#ifdef W3_SETUP + ncerr = nf90_def_var(fh, VNAME_ZET_SETO, NF90_INT, (/d_nopts/), v_zet_seto) + if (ncerr .ne. 0) return +#endif + ncerr = nf90_def_var(fh, VNAME_ASO, NF90_INT, (/d_nopts/), v_aso) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_CAO, NF90_INT, (/d_nopts/), v_cao) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_CDO, NF90_INT, (/d_nopts/), v_cdo) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_ICEO, NF90_INT, (/d_nopts/), v_iceo) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_ICEHO, NF90_INT, (/d_nopts/), v_iceho) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_ICEFO, NF90_INT, (/d_nopts/), v_icefo) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_GRDID, NF90_CHAR, (/d_grdidlen, d_nopts/), v_grdid) + if (ncerr .ne. 0) return + ncerr = nf90_def_var(fh, VNAME_SPCO, NF90_INT, (/d_nspec, d_nopts/), v_spco) + if (ncerr .ne. 0) return + + ! Write the scalar data. + ncerr = nf90_put_var(fh, v_nk, NK) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_mth, NTH) + if (ncerr .ne. 0) return + + ! Write the data with NOPTS as a dimension. + ncerr = nf90_put_var(fh, v_ptloc, PTLOC) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_ptnme, PTNME) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_iw, IW) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_ii, II) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_il, IL) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_dpo, DPO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_wao, WAO) + if (ncerr .ne. 0) return +#ifdef W3_FLX5 + ncerr = nf90_put_var(fh, v_tauao, TAUAO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_taido, TAIDO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_dairo, DAIRO) + if (ncerr .ne. 0) return +#endif +#ifdef W3_SETUP + ncerr = nf90_put_var(fh, v_zet_seto, ZET_SETO) + if (ncerr .ne. 0) return +#endif + ncerr = nf90_put_var(fh, v_aso, ASO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_cao, CAO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_iceo, ICEO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_iceho, ICEHO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_icefo, ICEFO) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_grdid, GRDID) + if (ncerr .ne. 0) return + ncerr = nf90_put_var(fh, v_spco, SPCO) + if (ncerr .ne. 0) return + + ! Close the file. + ncerr = nf90_close(fh) + if (ncerr .ne. 0) return + + END SUBROUTINE W3IOPON_WRITE + + !> Read/write point output in netCDF format. + !> + !> @param[in] INXOUT String indicating read/write. Must be 'READ' or + !> 'WRITE'. + !> @param[in] NDSOP File unit number. + !> @param[out] IOTST Error code: + !> - 0 No error. + !> - -1 Unexpected end of file when reading. + !> @param[in] IMOD Model number for W3GDAT etc. +#ifdef W3_ASCII + !> @param[in] NDSOA File unit number for ASCII output. +#endif + !> + !> @author Edward Hartnett @date 1-Nov-2023 + SUBROUTINE W3IOPON ( INXOUT, NDSOP, IOTST, IMOD & +#ifdef W3_ASCII + ,NDSOA & +#endif + ) + USE W3GDATMD, ONLY: W3SETG + USE W3WDATMD, ONLY: W3SETW + USE W3ODATMD, ONLY: W3SETO, W3DMO2 + !/ + USE W3GDATMD, ONLY: NTH, NK, NSPEC, FILEXT + USE W3WDATMD, ONLY: TIME + USE W3ODATMD, ONLY: NDST, NDSE, IPASS => IPASS2, NOPTS, IPTINT, & + IW, PTLOC, PTIFAC, WAO, WDO, & + SPCO, PTNME, O2INIT, FNMPRE +#ifdef W3_FLX5 + USE W3ODATMD, ONLY: TAUAO, TAUDO, DAIRO +#endif + USE W3ODATMD, ONLY : OFILES + !/ +#ifdef W3_SETUP + USE W3ODATMD, ONLY: ZET_SETO +#endif + !/ + USE W3SERVMD, ONLY: EXTCDE +#ifdef W3_S + USE W3SERVMD, ONLY: STRACE +#endif + use netcdf + IMPLICIT NONE + + CHARACTER, INTENT(IN) :: INXOUT*(*) + INTEGER, INTENT(IN) :: NDSOP + INTEGER, INTENT(OUT) :: IOTST + INTEGER, INTENT(IN), OPTIONAL :: IMOD +#ifdef W3_ASCII + INTEGER, INTENT(IN), OPTIONAL :: NDSOA +#endif + + INTEGER :: IGRD, MK, MTH + character(len = 124) :: filename + integer :: ncerr + + ! Optimistically assume success. + IOTST = 0 + + ! Has a model number been specified? + IF (PRESENT(IMOD)) THEN + IGRD = IMOD + ELSE + IGRD = 1 + END IF + + CALL W3SETO(IGRD, NDSE, NDST) + CALL W3SETG(IGRD, NDSE, NDST) + CALL W3SETW(IGRD, NDSE, NDST) + + ! INXOUT must be 'READ' or 'WRITE'. + IF (INXOUT .NE. 'READ' .AND. INXOUT .NE. 'WRITE') THEN + WRITE (NDSE, 900) INXOUT + CALL EXTCDE(1) + END IF + + ! Determine filename. + filename = '' + filename = transfer(FNMPRE(:LEN_TRIM(FNMPRE))//'out_pnt_nc.'//FILEXT(:LEN_TRIM(FILEXT)), filename) + !print *, filename + + ! Do a read or a write of the point file. + IF (INXOUT .EQ. 'READ') THEN + CALL W3IOPON_READ(IOTST, IMOD, filename, ncerr) + ELSE + CALL W3IOPON_WRITE(OFILES(2), IMOD, filename, ncerr) + ENDIF + if (ncerr .ne. 0) then + print *, nf90_strerror(ncerr) + CALL EXTCDE(21) + endif + + !/ + !/ End of W3IOPON ----------------------------------------------------- / + !/ + +900 FORMAT (/' *** WAVEWATCH III ERROR IN W3IOPO :'/ & + ' ILEGAL INXOUT VALUE: ',A/) + END SUBROUTINE W3IOPON + !/ ------------------------------------------------------------------- / !> Read or write point output. !> !> This subroutine can either read or write the point output file, @@ -1495,7 +2052,7 @@ SUBROUTINE W3IOPO ( INXOUT, NDSOP, IOTST, IMOD & #endif 'ASO(I), CAO(I), CDO(I), ICEO(I), ICEHO(I):', & ASO(I), CAO(I), CDO(I), ICEO(I), ICEHO(I), & - 'ICEFO(I), GRDID(I), (SPCO(J,I),J=1,NSPEC):', & + 'ICEFO(I), GRDID(I), (SPCO(J,I),J=1,NSPEC):', & ICEFO(I), GRDID(I), (SPCO(J,I),J=1,NSPEC) #endif ELSE diff --git a/model/tests/CMakeLists.txt b/model/tests/CMakeLists.txt new file mode 100644 index 000000000..97717233b --- /dev/null +++ b/model/tests/CMakeLists.txt @@ -0,0 +1,40 @@ +# This is the CMake file for the model/tests directory in the WW3 +# project. +# +# Ed Hartnett, 10/14/23 + +# Some very small test files may be committed to the repo. This +# function copies such a data file to the build directory. +function(copy_test_data name) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/data/${name}" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + FILE_PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) +endfunction() + +# Some very small test files may be committed to the repo. This +# function copies such a data file to the build directory. +function(copy_test_data_2 srcname destname) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/data/${srcname}" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) + file(RENAME "${CMAKE_CURRENT_BINARY_DIR}/${srcname}" "${CMAKE_CURRENT_BINARY_DIR}/${destname}") +endfunction() + +# Function to build and run a test. +function(unit_test name) + add_executable(${name} ${name}.F90) + target_link_libraries(${name} PRIVATE ww3_lib) + add_test(NAME ${name} COMMAND ${name}) +endfunction() + +# Copy test data files that are in the repo to the build directory. +copy_test_data(out_pnt.ww3) +copy_test_data_2(io_mod_def.ww3 mod_def.ww3) +copy_test_data(switch.io) +copy_test_data(ww3_outp.inp) + +# Build and run the tests. +unit_test(test_io_points_bin) +unit_test(test_io) + + diff --git a/model/tests/data/io_mod_def.ww3 b/model/tests/data/io_mod_def.ww3 new file mode 100644 index 000000000..b8d754881 Binary files /dev/null and b/model/tests/data/io_mod_def.ww3 differ diff --git a/model/tests/data/out_pnt.points b/model/tests/data/out_pnt.points new file mode 100644 index 000000000..a85412a73 Binary files /dev/null and b/model/tests/data/out_pnt.points differ diff --git a/model/tests/data/out_pnt.ww3 b/model/tests/data/out_pnt.ww3 new file mode 100644 index 000000000..a911732ac Binary files /dev/null and b/model/tests/data/out_pnt.ww3 differ diff --git a/model/tests/data/switch.io b/model/tests/data/switch.io new file mode 100644 index 000000000..c97e44765 --- /dev/null +++ b/model/tests/data/switch.io @@ -0,0 +1 @@ +NOGRB SHRD PR1 FLX2 LN0 ST0 NL0 BT0 DB0 TR0 BS0 IC0 IS0 REF0 WNT1 WNX1 CRT1 CRX1 O0 O1 O2 O3 O4 O5 O6 O7 O10 O11 \ No newline at end of file diff --git a/model/tests/data/ww3_outp.inp b/model/tests/data/ww3_outp.inp new file mode 100644 index 000000000..dd728819f --- /dev/null +++ b/model/tests/data/ww3_outp.inp @@ -0,0 +1,8 @@ +$ + 20100101 000000 3600 1 +$ + 1 + -1 +$ + 4 + 2 30 20100101 000000 'UTC' diff --git a/model/tests/test_io.F90 b/model/tests/test_io.F90 new file mode 100644 index 000000000..7ae39f3f8 --- /dev/null +++ b/model/tests/test_io.F90 @@ -0,0 +1,99 @@ +! This is a test for model IO for WW3. +! +! Ed Hartnett 10/14/23 +program test_io + use w3iopomd + use w3gdatmd + use w3wdatmd + use w3odatmd + use w3iogrmd + use w3adatmd + implicit none + + integer, target :: i, j, k, l + integer :: ndsop, iotest, imod, ndstst, ierr, ndsbul, ndsm + integer :: ndstrc, ntrace + real :: m2km + character*7 expected_ptnme + character*6 my_fmt + real :: expected_loc_1 + + print *, 'Testing WW3 binary point file code.' + + ! These are mysterious but have to be called or else the IPASS + ! variable does not exist and w3iopo() crashes. + call w3nmod(1, 6, 6) + call w3setg(1, 6, 6) + call w3ndat(6, 6) + call w3setw(1, 6, 6) + call w3nout(6, 6) + call w3seto(1, 6, 6) + + ndsm = 20 + ndsop = 20 + ndsbul = 0 + ndstrc = 6 + ntrace = 10 + + write (ndso,900) +900 FORMAT (/15X,' *** WAVEWATCH III Point output post.*** '/ & + 15X,'==============================================='/) + + ! Open the file with the output settings for WW3. It is not needed actually. + ! open(ndsi, file = 'ww3_outp.inp', status='old', iostat = ierr) + ! if (ierr .ne. 0) stop 10 + + ! 2. Read model definition file. + CALL W3IOGR('READ', NDSM) + WRITE (NDSO,920) GNAME +920 FORMAT (' Grid name : ',A/) + + ! IF (FLAGLL) THEN + ! M2KM = 1. + ! ELSE + ! M2KM = 1.E-3 + ! END IF + + ! Read the file out_pnt.ww3 from the model/tests/data directory. + call w3iopo('READ', ndsop, iotest) + if (iotest .ne. 0) stop 10 + close(ndsop) + + ! Make sure we got the values we expected. + if (nopts .ne. 11) stop 11 + expected_loc_1 = 0.0 + do i = 1, nopts + ! Check ptnme and ptloc arrays. + print *, ptnme(i), ptloc(1, i), ptloc(2, i) + if (i .lt. 10) then + my_fmt = '(a,i1)' + else + my_fmt = '(a,i2)' + endif + write(fmt = my_fmt, unit=expected_ptnme) 'Point', i + if (ptnme(i) .ne. expected_ptnme) stop 20 + print *, expected_loc_1 + if (ptloc(1, i) .ne. expected_loc_1) stop 21 + expected_loc_1 = expected_loc_1 + 5000.0 + if (ptloc(2, i) .ne. 0) stop 22 + end do + + print *, 'OK!' + print *, 'testing writing the WW3 binary point file in netCDF...' + + ! Write in netCDF. + call w3iopon('WRITE', ndsop, iotest) + if (iotest .ne. 0) stop 100 + print *, 'OK!' + + print *, 'testing reading the WW3 binary point file in netCDF...' + call w3iopon('READ', ndsop, iotest) + print *, iotest + if (iotest .ne. 0) stop 100 + print *, 'OK!' + + print *, 'OK!' + + print *, 'SUCCESS!' +end program test_io + diff --git a/model/tests/test_io_points_bin.F90 b/model/tests/test_io_points_bin.F90 new file mode 100644 index 000000000..8bc0e5b75 --- /dev/null +++ b/model/tests/test_io_points_bin.F90 @@ -0,0 +1,83 @@ +! This is a test for model IO for WW3. This tests the legacy (binary) +! output of points data, done by function W3IOPO(). +! +! Ed Hartnett 10/14/23 +program test_io_points_bin + use w3iopomd + use w3gdatmd + use w3wdatmd + use w3odatmd + use w3iogrmd + use w3adatmd + implicit none + + integer, target :: i, j, k, l + integer :: ndsop, iotest, imod, ndstst, ierr, ndsbul, ndsm + integer :: ndstrc, ntrace + real :: m2km + character*7 expected_ptnme + character*6 my_fmt + real :: expected_loc_1 + + print *, 'Testing WW3 binary point file code.' + + ! These are mysterious but have to be called or else the IPASS + ! variable does not exist and w3iopo() crashes. + call w3nmod(1, 6, 6) + call w3setg(1, 6, 6) + call w3ndat(6, 6) + call w3setw(1, 6, 6) + call w3nout(6, 6) + call w3seto(1, 6, 6) + + ndsm = 20 + ndsop = 20 + ndsbul = 0 + ndstrc = 6 + ntrace = 10 + + write (ndso,900) +900 FORMAT (/15X,' *** WAVEWATCH III Point output post.*** '/ & + 15X,'==============================================='/) + + ! Open the file with the output settings for WW3. It is not needed actually. + ! open(ndsi, file = 'ww3_outp.inp', status='old', iostat = ierr) + ! if (ierr .ne. 0) stop 10 + + ! 2. Read model definition file. + CALL W3IOGR('READ', NDSM) + WRITE (NDSO,920) GNAME +920 FORMAT (' Grid name : ',A/) + + ! This will not work. But cannot be tested because it will change the value of IPASS, +! call w3iopo('EAD', ndsop, iotest) +! if (iotest .ne. 1) stop 7 + + ! Read the file out_pnt.ww3 from the model/tests/data directory. + call w3iopo('READ', ndsop, iotest) + if (iotest .ne. 0) stop 10 + close(ndsop) + + ! Make sure we got the values we expected. + if (nopts .ne. 11) stop 11 + expected_loc_1 = 0.0 + do i = 1, nopts + ! Check ptnme and ptloc arrays. + print *, ptnme(i), ptloc(1, i), ptloc(2, i) + if (i .lt. 10) then + my_fmt = '(a,i1)' + else + my_fmt = '(a,i2)' + endif + write(fmt = my_fmt, unit=expected_ptnme) 'Point', i + if (ptnme(i) .ne. expected_ptnme) stop 20 + print *, expected_loc_1 + if (ptloc(1, i) .ne. expected_loc_1) stop 21 + expected_loc_1 = expected_loc_1 + 5000.0 + if (ptloc(2, i) .ne. 0) stop 22 + end do + + print *, 'OK!' + print *, 'SUCCESS!' +end program test_io_points_bin +