diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db81ffb9af..cbfbe32740 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,47 +25,4 @@ # These owners will be the default owners for all the files in the # repository. Unless a later match is found, these owners # will be requested for a review when a PR is opened. -* @thomas-robinson @bensonr @rem1776 - -# GNU autotools files -Makefile.am @uramirez8707 @rem1776 -/configure.ac @uramirez8707 @rem1776 -/m4/ @uramirez8707 @rem1776 -*.m4 @uramirez8707 @rem1776 - -# cmake files -CM* @mlee03 @ngs333 -cmake @mlee03 @ngs333 - -# Files specific to GitHub or GitLab -/.github/ @GFDL-Eric @rem1776 -/.gitlab/ @GFDL-Eric @rem1776 - -# Testing files -/.gitlab-ci.yml @uramirez8707 @mlee03 @bensonr @thomas-robinson @rem1776 -/test_fms/ @uramirez8707 @mlee03 @bensonr @thomas-robinson @rem1776 - -# Specific component directories -/affinity/ @bensonr -/test_fms/affinity/ @bensonr @rem1776 - -/block_control/ @bensonr -/test_fms/block_control/ @bensonr @rem1776 - -/data_override/ @GFDL-Eric -/test_fms/data_override/ @GFDL-Eric @rem1776 - -/diag_manager @thomas-robinson @ngs333 -/test_fms/diag_manager/ @thomas-robinson @ngs333 - -/fv3gfs/ @bensonr - -/fms/ @thomas-robinson @rem1776 -/test_fms/fms/ @thomas-robinson @rem1776 -/fms2/ @uramirez8707 @GFDL-Eric -/test_fms/fms2/ @uramirez8707 @GFDL-Eric - -/libFMS/ @thomas-robinson @rem1776 - -/mpp/ @thomas-robinson @bensonr -/test_fms/mpp/ @thomas-robinson @bensonr @rem1776 +* @uramirez8707 @bensonr @rem1776 @vithikashah001 \ No newline at end of file diff --git a/.github/workflows/am4_regression_parallelWorks_intel_tag.yml b/.github/workflows/am4_regression_parallelWorks_intel_tag.yml deleted file mode 100644 index 7c4cbf4978..0000000000 --- a/.github/workflows/am4_regression_parallelWorks_intel_tag.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Tag CI libFMS with AM4 regression - -on: - push: - tags: - - '*alpha*' - - '*beta*' - workflow_dispatch: -jobs: - parallelWorks: - runs-on: [self-hosted, pw-platform] - strategy: - fail-fast: false - max-parallel: 3 - matrix: - include: -# Runs AM4 with intel18 on AM4_intel18 -# - runname: AM4 build and run with intel 18 -# runscript: python3 /home/Thomas.Robinson/pw/storage/pw_api_python/AM4_intel18StartClusters.py am4_intel18 -# Runs AM4 using a container to build and run the model with intel 21 - - runname: AM4 regression with intel 21 and answer check - runscript: python3 /home/Thomas.Robinson/pw/storage/pw_api_python/AM4_intel21StartClusters.py main - - steps: - - name: FMS make check on paralellWorks - env: - RUNNAME: ${{ matrix.runname }} - RUNSCRIPT: ${{ matrix.runscript }} - run: $RUNSCRIPT - ShutDownCluster: - runs-on: [self-hosted, pw-platform] - if: always() - needs: [parallelWorks] - strategy: - matrix: - include: -# - cluster: am4_intel18 - - cluster: am4_container - steps: - - name: Turn off cluster - env: - CLUSTER: ${{ matrix.cluster }} - run: python3 /home/Thomas.Robinson/pw/storage/pw_api_python/stopClusters.py $CLUSTER diff --git a/.github/workflows/build_cmake_gnu.yml b/.github/workflows/build_cmake_gnu.yml deleted file mode 100644 index f649345d8a..0000000000 --- a/.github/workflows/build_cmake_gnu.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Build libFMS with cmake - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - omp-flags: [ -DOPENMP=on, -DOPENMP=off ] - libyaml-flag: [ "", -DWITH_YAML=on ] - container: - image: noaagfdl/hpc-me.ubuntu-minimal:cmake - env: - CMAKE_FLAGS: "${{ matrix.omp-flags }} ${{ matrix.libyaml-flag }} -D64BIT=on" - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Generate makefiles with CMake - run: cmake $CMAKE_FLAGS . - - name: Build the library - run: make diff --git a/.github/workflows/build_ubuntu_gnu.yml b/.github/workflows/build_ubuntu_gnu.yml deleted file mode 100644 index f4dc48225f..0000000000 --- a/.github/workflows/build_ubuntu_gnu.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build libFMS test with autotools - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - shell: bash - strategy: - matrix: - conf-flags: [--disable-openmp, --enable-mixed-mode, --disable-setting-flags, --with-mpi=no] - input-flag: [--with-yaml, --enable-test-input=/home/unit_tests_input] - container: - image: noaagfdl/hpc-me.ubuntu-minimal:gnu-input - env: - TEST_VERBOSE: 1 - DISTCHECK_CONFIGURE_FLAGS: "${{ matrix.conf-flags }} ${{ matrix.input-flag }}" - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Prepare GNU autoconf for build - run: autoreconf -if - - name: Configure the build - if: ${{ matrix.conf-flags != '--disable-setting-flags' }} - run: ./configure ${DISTCHECK_CONFIGURE_FLAGS} || cat config.log - - name: Configure the build with compiler flags - if: ${{ matrix.conf-flags == '--disable-setting-flags' }} - run: ./configure ${DISTCHECK_CONFIGURE_FLAGS} FCFLAGS="-fdefault-real-8 -fdefault-double-8 -fcray-pointer -ffree-line-length-none -I/usr/include $FCFLAGS" || cat config.log - - name: Build the library - run: make distcheck - if: ${{ matrix.conf-flags != '--with-mpi=no' }} - - name: Build the library (without test suite for serial build) - run: make - if: ${{ matrix.conf-flags == '--with-mpi=no' }} diff --git a/.github/workflows/coupler.yml b/.github/workflows/coupler.yml deleted file mode 100644 index ea24899b95..0000000000 --- a/.github/workflows/coupler.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Test coupler build -on: [pull_request] - -jobs: - coupler-build: - runs-on: ubuntu-latest - container: - image: ryanmulhall/hpc-me.ubuntu-minimal:coupler - env: - CC: mpicc - FC: mpif90 - CPPFLAGS: '-I/usr/include -Duse_LARGEFILE -DMAXFIELDMETHODS_=500' - FCFLAGS: '-fcray-pointer -fdefault-double-8 -fdefault-real-8 -Waliasing -ffree-line-length-none -fno-range-check -I/usr/include' - LDFLAGS: '-L/usr/lib' - VERBOSE: 1 - steps: - - name: Checkout FMS - uses: actions/checkout@v2 - with: - path: FMS - - name: Checkout FMScoupler - uses: actions/checkout@v2 - with: - repository: 'NOAA-GFDL/FMScoupler' - path: FMScoupler - - name: Test Null build - run: FMScoupler/t/null_model_build.sh --local-fms diff --git a/.github/workflows/github_autotools_gnu.yml b/.github/workflows/github_autotools_gnu.yml new file mode 100644 index 0000000000..e2974877d7 --- /dev/null +++ b/.github/workflows/github_autotools_gnu.yml @@ -0,0 +1,45 @@ +# 'main' required ci, does a distcheck (builds, tests, check install) +# image created off dockerfile in repo, compile/link flags are set there +name: Build libFMS test with autotools + +on: [push, pull_request] + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + conf-flag: [ --disable-openmp, --disable-setting-flags, --with-mpi=no, --disable-r8-default] + input-flag: [--with-yaml, --enable-test-input=/home/unit_tests_input] + exclude: + - conf-flag: --with-mpi=no + input-flag: --enable-test-input=/home/unit_tests_input + container: + image: ghcr.io/noaa-gfdl/fms/fms-ci-rocky-gnu:13.2.0 + env: + TEST_VERBOSE: 1 + DISTCHECK_CONFIGURE_FLAGS: "${{ matrix.conf-flag }} ${{ matrix.input-flag }} ${{ matrix.io-flag }}" + SKIP_TESTS: "test_horiz_interp2.[23-24]" # TODO (couldn't reproduce outside CI) + DEBUG_FLAGS: "-O0 -g -fbounds-check -ffpe-trap=invalid,zero,overflow" # debug compiler flags taken from the mkmf template + steps: + - name: Checkout code + uses: actions/checkout@v4.2.2 + - name: Prepare GNU autoconf for build + run: autoreconf -if + - name: Configure the build + if: ${{ matrix.conf-flag != '--disable-setting-flags' }} + run: ./configure ${DISTCHECK_CONFIGURE_FLAGS} FCFLAGS="$FCFLAGS $DEBUG_FLAGS" + - name: Configure the build with compiler flags + if: ${{ matrix.conf-flag == '--disable-setting-flags' }} + run: ./configure ${DISTCHECK_CONFIGURE_FLAGS} FCFLAGS="-fdefault-real-8 -fdefault-double-8 -fcray-pointer -ffree-line-length-none -I/usr/include $FCFLAGS $DEBUG_FLAGS" || cat config.log + - name: Build the library + run: make distcheck + if: ${{ matrix.conf-flag != '--with-mpi=no' }} + - name: Build the library (without test suite for serial build) + run: make + if: ${{ matrix.conf-flag == '--with-mpi=no' }} diff --git a/.github/workflows/intel_pr.yml b/.github/workflows/github_autotools_intel.yml similarity index 78% rename from .github/workflows/intel_pr.yml rename to .github/workflows/github_autotools_intel.yml index d95519fbf2..72c0655626 100644 --- a/.github/workflows/intel_pr.yml +++ b/.github/workflows/github_autotools_intel.yml @@ -1,9 +1,15 @@ on: pull_request + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: intel-autotools: runs-on: ubuntu-latest container: - image: intel/oneapi-hpckit:2022.2-devel-ubuntu20.04 + image: intel/oneapi-hpckit:2023.1.0-devel-ubuntu20.04 env: CC: mpiicc FC: mpiifort @@ -17,12 +23,12 @@ jobs: steps: - name: Cache dependencies id: cache - uses: actions/cache@v3 + uses: actions/cache@v4.2.0 with: path: /libs key: ${{ runner.os }}-intel-libs - name: Install packages for building - run: apt update && apt install -y autoconf libtool automake zlibc zlib1g-dev + run: apt-get update && apt-get install -y autoconf libtool automake zlibc zlib1g-dev - if: steps.cache.outputs.cache-hit != 'true' name: Build netcdf run: | @@ -44,10 +50,13 @@ jobs: ./configure --prefix=/libs make -j install && cd - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4.2.2 - name: Configure - run: autoreconf -if ./configure.ac && ./configure --with-yaml + run: | + autoreconf -if ./configure.ac + export LD_LIBRARY_PATH="/libs/lib:$LD_LIBRARY_PATH" + ./configure --with-yaml - name: Compile run: make -j || make - name: Run test suite - run: make check LD_LIBRARY_PATH="/libs/lib:$LD_LIBRARY_PATH" SKIP_TESTS="$SKIP_TESTS" VERBOSE=1 + run: make check LD_LIBRARY_PATH="/libs/lib:$LD_LIBRARY_PATH" TEST_VERBOSE=1 diff --git a/.github/workflows/github_cmake_gnu.yml b/.github/workflows/github_cmake_gnu.yml new file mode 100644 index 0000000000..7a45fe80ce --- /dev/null +++ b/.github/workflows/github_cmake_gnu.yml @@ -0,0 +1,53 @@ +name: Build libFMS with cmake + +on: [push, pull_request] + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + omp-flags: [ -DOPENMP=on, -DOPENMP=off ] + libyaml-flag: [ "", -DWITH_YAML=on ] + build-type: [ "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_BUILD_TYPE=Debug" ] + container: + image: ghcr.io/noaa-gfdl/fms/fms-ci-rocky-gnu:13.2.0 + env: + CMAKE_FLAGS: "${{ matrix.build-type }} ${{ matrix.omp-flags }} ${{ matrix.libyaml-flag }}" + steps: + - name: Checkout code + uses: actions/checkout@v4.2.2 + - name: Generate makefiles with CMake + run: | + mkdir build + cd build + cmake $CMAKE_FLAGS -DNetCDF_ROOT=/opt/view -DLIBYAML_ROOT=/opt/view .. + - name: Build the library + run: make -C build + + build_arm: + runs-on: ubuntu-24.04-arm + strategy: + matrix: + omp-flags: [ -DOPENMP=on, -DOPENMP=off ] + libyaml-flag: [ "", -DWITH_YAML=on ] + build-type: [ "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_BUILD_TYPE=Debug" ] + container: + image: ghcr.io/noaa-gfdl/fms/fms-ci-rocky-gnu:13.2.0-arm + env: + CMAKE_FLAGS: "${{ matrix.build-type }} ${{ matrix.omp-flags }} ${{ matrix.libyaml-flag }}" + steps: + - name: Checkout code + uses: actions/checkout@v4.2.2 + - name: Generate makefiles with CMake + run: | + mkdir build + cd build + cmake $CMAKE_FLAGS -DNetCDF_ROOT=/opt/view -DLIBYAML_ROOT=/opt/view .. + - name: Build the library + run: make -C build diff --git a/.github/workflows/github_coupler_gnu.yml b/.github/workflows/github_coupler_gnu.yml new file mode 100644 index 0000000000..4236e43eaf --- /dev/null +++ b/.github/workflows/github_coupler_gnu.yml @@ -0,0 +1,34 @@ +name: Test coupler build +on: [pull_request] + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coupler-build: + runs-on: ubuntu-latest + container: + image: ghcr.io/noaa-gfdl/fms/fms-ci-rocky-gnu:13.2.0 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.github_token }} + env: + CC: mpicc + FC: mpif90 + CPPFLAGS: '-I/opt/view/include' + FFLAGS: '-fallow-argument-mismatch' # mkmf uses FFLAGS instead of FC + LDFLAGS: '-L/opt/view/lib' + steps: + - name: Checkout FMS + uses: actions/checkout@v4.2.2 + with: + path: FMS + - name: Checkout FMScoupler + uses: actions/checkout@v4.2.2 + with: + repository: 'NOAA-GFDL/FMScoupler' + path: FMScoupler + - name: Test Null build + run: FMScoupler/t/null_model_build.sh --local-fms diff --git a/.github/workflows/update_docs.yml b/.github/workflows/github_doc_site.yml similarity index 55% rename from .github/workflows/update_docs.yml rename to .github/workflows/github_doc_site.yml index 6a4b4bf917..38ae7cd538 100644 --- a/.github/workflows/update_docs.yml +++ b/.github/workflows/github_doc_site.yml @@ -6,11 +6,11 @@ on: types: [published] workflow_dispatch: jobs: - update_docs: - runs-on: ubuntu-latest + build: + runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4.2.2 - name: Setup repo run: | # do autotool's job for substitutes since we don't need a full build environement mkdir gen_docs @@ -23,8 +23,26 @@ jobs: run: | sudo apt -y install doxygen graphviz doxygen gen_docs/Doxyfile - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + - name: Upload Pages Artifact + uses: actions/upload-pages-artifact@v3.0.1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./gen_docs/html + path: 'gen_docs/html' + deploy: + needs: build + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Specify runner + deployment step + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/.github/workflows/lint_fms.yml b/.github/workflows/github_linter.yml similarity index 87% rename from .github/workflows/lint_fms.yml rename to .github/workflows/github_linter.yml index c24de95783..24fa8969c8 100644 --- a/.github/workflows/lint_fms.yml +++ b/.github/workflows/github_linter.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4.2.2 - name: Run Lint uses: NOAA-GFDL/simple_lint@f5aa1fe976bd4c231db0536ba00cbfdc26708253 with: diff --git a/.github/workflows/github_mom_gnu.yml b/.github/workflows/github_mom_gnu.yml new file mode 100644 index 0000000000..7ee5c71449 --- /dev/null +++ b/.github/workflows/github_mom_gnu.yml @@ -0,0 +1,32 @@ +name: Run MOM6 test suite + +# runs on PR's or when manually triggered +on: [workflow_dispatch, pull_request] + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/noaa-gfdl/fms/fms-ci-rocky-gnu:13.2.0 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.github_token }} + steps: + - name: Checkout MOM6 repository + uses: actions/checkout@v4.2.2 + with: + repository: 'NOAA-GFDL/MOM6' + submodules: recursive + - name: Checkout FMS into MOM build + uses: actions/checkout@v4.2.2 + with: + path: .testing/deps/fms/src + - name: Build FMS and MOM test suite + run: make -C .testing -j + - name: Run MOM tests + run: make -C .testing -j test diff --git a/.github/workflows/pw_am5_intel.yaml b/.github/workflows/pw_am5_intel.yaml new file mode 100644 index 0000000000..b9422bf6e7 --- /dev/null +++ b/.github/workflows/pw_am5_intel.yaml @@ -0,0 +1,58 @@ +name: AM5 test +on: + push: + tags: + - '*alpha*' + - '*beta*' + pull_request: + workflow_dispatch: + +# cancel running jobs if theres a newer push +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + compile_AM5: + runs-on: [self-hosted, pw-platform] + strategy: + fail-fast: false + steps: + - name: Setup source and build directories + run: | + mkdir -p ${GITHUB_SHA} + cd ${GITHUB_SHA} + mkdir -p am5_src + mkdir -p am5_build + ln -s /contrib/am5/ci/latest/src/am5_phys am5_src/am5_phys + ln -s /contrib/am5/ci/latest/src/atmos_drivers am5_src/atmos_drivers + ln -s /contrib/am5/ci/latest/src/GFDL_atmos_cubed_sphere am5_src/GFDL_atmos_cubed_sphere + ln -s /contrib/am5/ci/latest/src/GRTCODE am5_src/GRTCODE + ln -s /contrib/am5/ci/latest/src/ice_param am5_src/ice_param + ln -s /contrib/am5/ci/latest/src/land_lad2 am5_src/land_lad2 + ln -s /contrib/am5/ci/latest/src/mom6 am5_src/mom6 + ln -s /contrib/am5/ci/latest/src/ocean_BGC am5_src/ocean_BGC + ln -s /contrib/am5/ci/latest/src/rte-ecckd am5_src/rte-ecckd + ln -s /contrib/am5/ci/latest/src/rte-rrtmgp am5_src/rte-rrtmgp + ln -s /contrib/am5/ci/latest/src/coupler am5_src/coupler + - name: Checkout FMS + uses: actions/checkout@v4.2.2 + with: + path: ${{github.sha}}/am5_src/FMS + - name: Build AM5 in Intel container + run: /contrib/am5/ci/latest/container_build.sh + - name: Check build succeeded + run: stat ${GITHUB_WORKSPACE}/${GITHUB_SHA}/am5_build/fms_am5*_compile.x + + run_AM5: + runs-on: [self-hosted, pw-platform] + strategy: + fail-fast: false + needs: [compile_AM5] + steps: + - name: Create run directory + run: | + cd ${GITHUB_SHA} + mkdir -p am5_run + - name: Launch runscript + run: /contrib/am5/ci/latest/run.sh diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index 1e71236386..1b16cf5ed4 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -1,3 +1,5 @@ +# appends -dev to the version upon release and opens pr +# CI won't run on generated PR, easiest workaround is to close + reopen on: release: types: [published] @@ -5,15 +7,15 @@ jobs: add-dev-to-version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4.2.2 - name: Append version with dev run: sed -i '/20[0-9][0-9]\.[0-9][0-9]/ s/]/-dev]/' configure.ac - name: Create pull request - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v7.0.6 with: base: main # creates a new branch off of main branch: add-dev-post-release # name of the created branch branch-suffix: timestamp # add a timestamp to branch name delete-branch: true # delete afer merge title: Append dev to version number post-release - body: automated change, adds '-dev' to the version number upon releases + body: automated change, adds '-dev' to the version number upon releases. This PR will need to be closed and reopened to run CI testing. diff --git a/AUTOTOOLS_INSTRUCTIONS.md b/AUTOTOOLS_INSTRUCTIONS.md index 6b5b39b3d1..fcc145a9d8 100644 --- a/AUTOTOOLS_INSTRUCTIONS.md +++ b/AUTOTOOLS_INSTRUCTIONS.md @@ -11,7 +11,7 @@ something like: ./configure && make check install -The configure step queries the system about many things, and contructs +The configure step queries the system about many things, and constructs makefiles. The make step uses the generated Makefiles to build, test, and install @@ -33,11 +33,11 @@ been added to .gitignore and should never be added to the repo. Previously, everyone built FMS by checking out code from git. However, with the new build system, only those who want to contribute to the -code base need check out the code from git. +code base need to check out the code from git. ## As an FMS Developer -All FMS developers will need a reasobably reacent version of tools +All FMS developers will need a reasonably recent version of tools autoconf, automake, and libtool. These are available on package management systems. (Ex. yum install automake autoconf libtool). @@ -51,7 +51,7 @@ The process of building FMS from the repo is: ## As an FMS User Users start with a tarball, not the git repo. They do not have to have -any of the autotools installed. Thier build process is: +any of the autotools installed. Their build process is: 1. Unpack the tarball and cd into the directory. 2. Run ./configure --prefix=/my/installdir diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc9802f8f..b8a533b82f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,417 @@ and this project uses `yyyy.rr[.pp]`, where `yyyy` is the year a patch is releas `rr` is a sequential release number (starting from `01`), and an optional two-digit sequential patch number (starting from `01`). +## [2025.03] - 2025-06-24 + +### Known Issues +- INTEL: Oneapi 2025.1 is currently unsupported due to an internal compiler error. The `-check uninit` flag for intel's LLVM compilers(ifx/icx) is also unsupported, see prior release for more information. +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so these differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. + +### Added +- LICENSE: added notice about the change to Apache license. (#1685) +- DIAG_MANAGER: added standard name to diag manifest yaml (#1692) +- FMS2_IO: added netcdf chunksizes argument to register_field routines (#1696) +- DIAG_MANAGER: added more robust diag manager tests for when outputting data at every time step (#1688) +- FM2_IO: added a test for collective netcdf mpi io (#1647) + +### Changed +- DOCS: style guide update (#1642) +- DOCS: updates to contributing guide to reflect modern FMS practices (#1708) +- DOCS: fixed typos throughout the code (#1703) +- DATA_OVERRIDE: simplify argument lists of subroutines in get_grid_version_mod (#1514) + +### Fixed +- DIAG_MANAGER: fixed the time variable in instantaneous outputs from diag_manager in both openmp and non-openmp cases (#1690) +- DIAG_MANAGER: fixed number of time levels in diag manifest yaml for subregional files. (#1698) +- LIBFMS: fixed missed aliases in libFMS file (#1303) +- DIAG_MANAGER: fixed new file and file duration in MODERN_DIAG_MANAGER (#1705) + +### Removed +- FMS_IO/MPP_IO: removed fms_io/mpp_io modules (#1669) +- AMIP_INTERP: removed the sst_anom and sst_ncep varaibles (#1695) + +### Tag Commit Hashes +- 2025.03-beta1 96e97c0e271167dd42700b5ed28fb1fe85d7ecce +- 2025.03-alpha1 44f182dc459c6a60d3b3f4257cf379c8bd71e797 + +## [2025.02] - 2025-04-28 + +### Known Issues +- DIAG_MANAGER(legacy): When attempting to use 0 days frequency in the legacy diag manager and the data is send to diag manager from an openmp region, the Time dimension will not be correct. +- INTEL: Oneapi 2025.1 is currently unsupported due to an internal compiler error. The `-check uninit` flag for intel's LLVM compilers(ifx/icx) is also unsupported, see prior release for more information. +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so these differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. + +### Added +- YAML SUPPORT: Messages indicating which table format being used (yaml or legacy) will now be output by the `diag_manager`, `field_manager`, and `data_override` modules. (#1676) +- DIAG_MANAGER: Adds the number of tiles, number of distributed files, and number of time levels written for a given file to the output diag manifest yaml file. (#1661) + (#1648) +- GENERIC_EXCHANGE: Adds a new "gex" module to the `coupler` subdirectory to provide a generic interface to pass (non-tracer) diagnostic fields across components. (#1637) + +### Changed +- MPP: Optimized `mpp_gather` and `mpp_scatter` interfaces by leveraging native MPI collectives in `mpp_pelist_scatter` and `mpp_gather_1D(_V)` routines. (#1655) +- DIAG_MANAGER: `diag_out.yaml` output file has been renamed to `diag_manifest` and will include the root PE number and ensemble number. ie: `diag_manifest.yaml.0` or `diag_manager.ens_01.yaml.0` when using an ensemble. (#1661) (#1654) (#1646) +- EXCHANGE: Increased max number of fields for exchange grid from 80 to 100 (#1637) +- DATA_OVERRIDE: Initializes both r4 and r8 versions of the module by default if no `mode` argument is given to specialize a specific real size.(#1638) + +### Fixed +- MPP: Errors from unallocated data on non-root pes in mpp_scatter/gather routines. (#1672) +- DIAG_MANAGER(legacy): Bug with instantaneous output variables where incorrect time steps would be output when using intervals larger than the physics timestep of 30 minutes. (#1667) +- DIAG_MANAGER: Issue causing an extra unlimited dimension to be written out in history files when mixing static and non-static variables.(#1668) +- CMAKE BUILD: Fixes missing "C" component for OpenMP libraries in FMS export configuration file (#1663) + +### Removed +- FIELD_MANAGER: Removed public declarations for routines that are already included in public interfaces. (#1317) + +### Tag Commit Hashes +- 2025.02-beta2 e8b8c19c5d67b35440acc10b3180f535e3c6e7d6 +- 2025.02-beta1 2df70639e371ab8d11edbbfed937951ad4e97c9d +- 2025.02-alpha1 71957e9d79fbd832c4e25ee452eb211669e0df1d + +## [2025.01.02] - 2025-03-06 + +### Known Issues +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so this differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. +- INTEL: The `-check uninit` flag for the Intel Oneapi Fortran compiler (ifx) is unsupported due to a bug causing false positives when using external libraries. If using the `-check all` flag, `-check all,nouninit` should be used instead. + +### Changed +- DIAG_MANAGER: Change name of yaml output file to include the root PE number at the end in order to prevent overwrites (#1654) + +## [2025.01.01] - 2025-02-26 + +### Known Issues +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so this differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. +- INTEL: The `-check uninit` flag for the Intel Oneapi Fortran compiler (ifx) is unsupported due to a bug causing false positives when using external libraries. If using the `-check all` flag, `-check all,nouninit` should be used instead. + +### Changed +- DIAG_MANAGER: Change name of yaml output file from "diag_out.yaml" to "diag_manifest.yaml" (#1646) + +### Added +- DIAG_MANAGER: Added field to the yaml output file to specify the number of time levels written to a given file (#1648) + + +## [2025.01] - 2025-01-30 + +### Known Issues +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so this differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. +- INTEL: The `-check uninit` flag for the Intel Oneapi Fortran compiler (ifx) is unsupported due to a bug causing false positives when using external libraries. If using the `-check all` flag, `-check all,nouninit` should be used instead. + +### Added +- ENSEMBLE YAML SUPPORT: Adds functionality to the diag_manager, field_manager, and data_override to support unique yaml tables for each member of an ensemble or nest. More information can be found in in the [diag_manager](diag_manager/README.md) and [data_override](data_override/README.MD) README files (#1585) +- BUILD: Adds options to set `PORTABLE_KINDS` macro in CMake and autotools builds (#1572) +- BUILD(autotools): Adds check for cray compiler .mod file capitalization and will now run a previously added, but unused, m4 check for `newunit` support. (#1561) +- BUILD(cmake): Adds CMake functionality to generate a pkgconfig file (#1565) +- DIAG_MANAGER: Modifies `diag_field_add_attribute` to accept both r4 and r8 values (#1625) + +### Changed +- MPP_IO/FMS_IO: A deprecation warning will now be output when using these modules or enabling the `-Duse_deprecated_io` flag via the build systems. Usage of these modules will be deprecated in the next release. (#1609) +- DIAG_MANAGER: Made changes to the reduction routine to improve performance of loop and to reduce unnecessary calls if there isn't any new data to process (#1634) +- MOSAIC/GRID_UTILS: The `mosaic` directory has been refactored and is now called `grid_utils`. (#1626) As part of these updates: + - The `create_xgrid.c/h` files have been moved to the `horiz_interp/include` directory + - Any any unused `.c`/`.h` files or functions from `mosaic` have been removed + - C functions have been moved between the `grid_utils.c`, `tree_utils.c` and `create_xgrid.c` files depending on the functions usage +- DIAG_MANAGER: Changes `fms_diag_accept_data` from a function to a subroutine (#1610) + +### Fixed +- DIAG_MANAGER: Fixes functionality of the `new_file_freq`, `start_time`, and `file_duration` keys in the diag_manager. (#1633) +- MOSAIC2: Fixes argument intent for `calc_mosaic_grid_area` and makes changes to the test to avoid divide-by-zero errors (#1597) +- YAML_PARSER: Fixes incorrect argument type in c-binding causing type mismatch errors with the cray compiler (#1580) +- BUILD(CMake): Removes usage of hardcoded `lib` directory path (#1589) +- HORIZ_INTERP: Adds allocation checks before checking fields for `horiz_interp_type_eq` (#1584) +- BUILD: Fixed syntax error in `column_diagnostics` Makefile.am (#1598) +- DIAG_MANAGER: Adds a workaround for a cray compiler bug where the `NULL()` intrinsic causes a type mismatch when passed in as an argument (#1560) +- LIBFMS: Adds missed `f2c_string` routine to libFMS.F90 (#1601) +- BUILD(autotools): Fixes false `./configure` failures when using intel compilers and the `-warn` flag (#1583) +- BLOCK_CONTROL: Updates non-uniform block size warning to only output on the root pe (#1588) +- FMS2_IO: Fixes z-axis domain reads when edge lengths and corners are specified (#1620) +- MPP: Fixes temporary array compiler warning during `mpp_init` (#1628) +- SAT_VAPOR_PRES: Fixes bad temperature output not printing on non-root pe's (#1619) +- ASTRONOMY: Adds allocation checks for module variables before being deallocated in `astronomy_end` (#1629) +- TESTS: Fixes multiple unit test failures, including any failing tests when compiled with GNU debug flags (#1575, #1595, #1579, #1607, #1622) +- TESTS: Fixes multiple compilation and runtime errors from the unit tests when compiled with the cray compiler (#1599, #1602, #1603, #1604, #1605) +- TESTS: Replaces usage of `fms_platform.h` includes with `platform_mod` in mpp unit tests (#1616) +- TESTS: Add workaround for ifx failures in `diag_integral` and `topography` tests (#1606) + +### Tag Commit Hashes +- 2025.01-beta1 15ec0c735cba2780d3da37f4e1e3b5d4a969b4ff +- 2025.01-alpha1 95c02333387e673c33ec66e9e78042ef4772cf3a + +## [2024.03] - 2024-08-22 + +### Known Issues +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so this differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. +- INTEL: The `-check uninit` flag for the Intel Oneapi Fortran compiler (ifx) is unsupported due to a bug causing false positives when using external libraries. If using the `-check all` flag, `-check all,nouninit` should be used instead. + +### Added +- DATA_OVERRIDE: Adds a namelist flag `use_center_grid_points` which, if true, enables reading the centroid values from the grid spec for ocean and ice models. This fixes issues with grid files that have longitudes ranges from 0:180 and then -180:0, but will cause answer changes if enabled. (#1566) +- DATA_OVERRIDE: Adds support for reading external weight files. Currently only supported for fregrid generated files while using the bilinear interpolation method. Documentation for this feature can be found in `data_override/README.md`. (#1556) +- PLATFORM: Adds two constants `FMS_PATH_LEN` and `FMS_FILE_LEN` and uses it across the code for any file path or file name character strings. They default to 1024 and 255 but can also be set by the `FMS_MAX_PATH_LEN` and `FMS_MAX_FILE_LEN` CPP macros. (#1567) +- CMAKE: Adds Cmake option to support building shared libraries (#1559) + +### Changed +- DIAG_MANAGER: Simplifies the diag_table.yaml format by allowing `module`, `reduction`, and `kind` to be set on a file level, with the ability to override for a specific field. (#1545) +- FIELD_MANAGER: Updated and refactored the `fm_yaml_mod` module for a new table format to remove the `subparams` key. (#1547) +- DATA_OVERRIDE: Updates the yaml format to be improve readablity and to be more consistent in key names. Additional documentation for the new format can be found in `data_override/README.md`. (#1556) + +### Fixed +- PARSER: Adds error code checks to the yaml parser to prevent code from hanging on invalid yamls (#1563) +- PARSER: Adds ability to read in generic blocks to support `field_manager` updates. (#1519) +- CRAY COMPILER SUPPORT: Updated any multi-line string literals to be compatible with the cray compiler. (#1554) + +### Removed +- DIAG_MANAGER: Removed diag_table schemas from FMS and moved them to the `gfdl_msd_schemas` repository, and updates the `diag_manager` documentation markdowns. (#1543) + +### Tag Commit Hashes +- 2024.03-beta1 a5de6a54abeb00be2443db4cf07aa267b7faa724 + +## [2024.02] - 2024-07-11 + +### Known Issues +- Diag Manager Rewrite: See [below](#20240102---2024-06-14) for known output file differences regarding the new diag manager. The new diag_manager is disabled by default, so this differences will only be present if `use_modern_diag` is set to true in the `diag_manager_nml`. +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. FPE traps are turned on when using the debug target in mkmf. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. + +### Added +- TIME_INTERP: Enables use of `verbose` option in `time_interp_external2` calls from `data_override`. The option is enabled in `data_override_nml` by setting `debug_data_override` to true. (#1516) +- COUPLER: Adds optional argument to `coupler_types_send_data` routine that contains the return statuses for any calls made to the diag_manager's `send_data` routine. (#1530) +- MPP: Adds a separate error log file `warnfile..out` that only holds output from any `mpp_error` calls made during a run (#1544) +### Changed +- DIAG_MANAGER: The `diag_field_log.out` output file of all registered fields will now include the PE number of the root PE at the time of writing (ie. diag_field_log.out.0). This is to prevent overwriting the file in cases where the root PE may change. (#1497) + +### Fixed +- CMAKE: Fixes real kind flags being overwritten when using the Debug release type (#1532) +- HORIZ_INTERP: Fixes allocation issues when using method-specific horiz_interp_new routines (such as `horiz_interp_bilinear_new`) by setting `is_allocated` and the `method_type` during initialization for each method. (#1538) + + +### Tag Commit Hashes +- 2024.02-alpha1 5757c7813f1170efd28f5a4206395534894095b4 +- 2024.02-alpha2 5757c7813f1170efd28f5a4206395534894095b4 +- 2024.02-beta1 ca592ef8f47c246f4dc56d348d62235bd0ceaa9d +- 2024.02-beta2 ca592ef8f47c246f4dc56d348d62235bd0ceaa9d + +## [2024.01.02] - 2024-06-14 + +### Known Issues +- Diag Manager Rewrite: + - Expected output file changes: + - If the model run time is less than the output frequency, old diag_manager would write a specific value (9.96921e+36). The new diag_manager will not, so only fill values will be present. + - A `scalar_axis` dimension will not be added to scalar variables + - The `average_*` variables will no longer be added as they are non-standard conventions + - Attributes added via `diag_field_add_attributes` in the old code were saved as `NF90_FLOAT` regardless of precision, but will now be written as the precision that is passed in + - Subregional output will have a global attribute `is_subregional = True` set for non-global history files. + - The `grid_type` and `grid_tile` global attributes will no longer be added for all files, and some differences may be seen in the exact order of the `associated_files` attribute + +- DIAG_MANAGER: When using the `do_diag_field_log` nml option, the output log file may be ovewritten if using a multiple root pe's +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. + +### Fixed +- DIAG_MANAGER: Fixes incorrect dates being appended to static file names + +## [2024.01.01] - 2024-05-30 + +### Known Issues +- Diag Manager Rewrite: + - Expected output file changes: + - If the model run time is less than the output frequency, old diag_manager would write a specific value (9.96921e+36). The new diag_manager will not, so only fill values will be present. + - A `scalar_axis` dimension will not be added to scalar variables + - The `average_*` variables will no longer be added as they are non-standard conventions + - Attributes added via `diag_field_add_attributes` in the old code were saved as `NF90_FLOAT` regardless of precision, but will now be written as the precision that is passed in + - Subregional output will have a global attribute `is_subregional = True` set for non-global history files. + - The `grid_type` and `grid_tile` global attributes will no longer be added for all files, and some differences may be seen in the exact order of the `associated_files` attribute + +- DIAG_MANAGER: When using the `do_diag_field_log` nml option, the output log file may be ovewritten if using a multiple root pe's +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. +- GCC: version 14.1.0 is unsupported due to a bug with strings that has come up previously in earlier versions. This will be caught by the configure script, but will cause compilation errors if using other build systems. + +### Added +- DIAG_MANAGER: Implements `flush_nc_files` functionality from legacy diag_manager. + +### Changed +- FMS2_IO: Changed `register_unlimited_compressed_axis` to use a collective gather rather than send and receives to improve efficiency when reading in iceberg restarts. + +### Fixed +- DIAG_MANAGER: Fixes 0 day output frequencies causing error stating a time_step was skipped. Also adds checks to crash if averaged fields have -1 or 0 day frequencies or if mixing averaged and non-averaged fields in the same file. +- DIAG_MANAGER: Fixes issue with the weight argument not getting passed through to reduction methods. +- DIAG_MANAGER: Allocation errors when using two empty files. +- DIAG_MANAGER: `time` and `time_bnds` being larger than expected when running for 1 day and using daily data. +- DIAG_MANAGER: Allows for mixing static and non-static fields when frequency is 0 days. +- TESTS: Fixes compile failure with ifort 2024.01 from test_mpp_gatscat.F90. + +### Removed +- DIAG_MANAGER: The `mix_snapshot_average_fields` option is deprecated for the rewritten diag_manager only. + +### Tag Commit Hashes +- 2024.01.01-beta2 c00367fa810960e87610162f0f012c5da724c5a9 +- 2024.01.01-beta1 42f8506512e1b5b43982320f5b9d4ca1ca9cbebd + +## [2024.01] - 2024-05-03 + +### Known Issues +- Diag Manager Rewrite: + - If two empty files are present in the diag_table.yaml file the code will crash with a allocation error (#1506) + - Setting an output frequency of '0 days' does not work as expected and may cause an error stating a time_step has been skipped (#1502) + - The `flush_nc_files` and `mix_snapshot_average_fields` nml options are not yet functional. The `mix_snapshot_average_fields` option is planned to be deprecated (for the rewritten diag_manager only). + - Expected output file changes: + - If the model run time is less than the output frequency, old diag_manager would write a specific value (9.96921e+36). The new diag_manager will not, so only fill values will be present. + - A `scalar_axis` dimension will not be added to scalar variables + - The `average_*` variables will no longer be added as they are non-standard conventions + - Attributes added via `diag_field_add_attributes` in the old code were saved as `NF90_FLOAT` regardless of precision, but will now be written as the precision that is passed in + - Subregional output will have a global attribute `is_subregional = True` set for non-global history files. + - The `grid_type` and `grid_tile` global attributes will no longer be added for all files, and some differences may be seen in the exact order of the `associated_files` attribute + +- DIAG_MANAGER: When using the `do_diag_field_log` nml option, the output log file may be ovewritten if using a multiple root pe's +- TESTS: `test_mpp_gatscat.F90` fails to compile with the Intel Oneapi 2024.01's version of ifort +- BUILD(HDF5): HDF5 version 1.14.3 generates floating point exceptions, and will cause errors if FMS is built with FPE traps enabled. + +### Added +- DIAG_MANAGER: The diag manager has been rewritten with a object oriented design. The old diag_manager code has been kept intact and will be used by default. The rewritten diag manager can be enabled via `use_modern_diag = .true.` to your `diag_manager_nml`. New features include: + - Self-describing YAML formatting for diag_table's + - Allows 4d variables + - Support defining subregions with indices + - More flexibility when adding metadata and defining output frequency +- FMS2_IO: Adds support for collective parallel reads to improve model startup time. The collective reads are disabled by default and enabled via the `use_collective` flag in `netcdf_io_mod`. +- DATA_OVERRIDE: Adds option to use multiple data files for one field within data_override in order to use annual data files in yearly runs without having to append/prepend timesteps from previous and next year. With the legacy data_table, filenames can be set in order and separated with `:` ie. `prev_year.nc:curr_year.nc:next_year.nc`. With the data_table.yaml format, the key `is_multi_file` enables the functionality and `prev_file_name` and `next_file_name` sets the file paths. + +- INTERPOLATOR: Adds support for yearly/annual data +- DATA_OVERRIDE: Adds support for monotonically decreasing arrays for `nearest_index`, `axis_edges`, `horiz_interp`(bilinear), and `data_override` (#1388) +- DOCS: Add documentation for the exchange grid (xgrid_mod) and update the contribution guide to add a section on code reviews +- MPP: MPI sub-communicators for domains are now accessible via `mpp_get_domain_tile_commid` and `mpp_get_domain_commid` in `mpp_domains_mod` + +### Changed +- DATA_OVERRIDE: Changes behavior to crash if both data_table and data_table.yaml are present and adds error checking when reading in yaml files +- FIELD_MANAGER: Changes behavior to crash if both field_table and field_table.yaml are present as well as adds a namelist flag (`use_field_table_yaml`) to enable support for the yaml input. + +### Fixed +- DATA_OVERRIDE: Fixes allocation error with scalar routine and replaces pointers with allocatables +- INTERPOLATOR: Increase max string size for file paths +- AXIS_UTILS: Improves performance of `nearest_index` routine +- CMAKE: Fixes macOS linking issues with OpenMP + +### Tag Commit Hashes +- 2024.01-beta5 d3bab5a84b6a51eddd46ab6fb65eaa532830c6c7 +- 2024.01-beta4 ac363ddfd3075637cecae30ddfbae7a78751197b +- 2024.01-alpha6 2ace94564a08aec4d7ab7eca0e57c0289e52d5b1 +- 2024.01-alpha5 5ed0bd373cc59a9681052fa837cb83a67169d102 +- 2024.01-alpha4 8dd90d72b58f0de3632dc62920f8adfb996b2265 +- 2024.01-beta3 f71405a075102aef42f5811dc09e239ddd002637 +- 2024.01-beta2 bb6de937f70a08a440f5e63b8553b047c1921509 +- 2024.01-beta1 913f8aaecca374d5e10280056de862d5e4a7a668 +- 2024.01-alpha3 085c6bfc945a6f1c586b842ca6268fca442884d8 +- 2024.01-alpha2 38bfde30e1cb8bf5222410a9c37e71529567bf69 +- 2024.01-alpha1 ac0d086296ea8b9196552463655cb9a848db39fe + +## [2023.04] - 2023-12-04 +### Known Issues +- GCC 9 and below as well as GCC 11.1.0 are unsupported due to compilation issues. See prior releases for more details. +- `NO_QUAD_PRECISION` macro is no longer set by FMS, the `ENABLE_QUAD_PRECISION` macro has replaced prior usage of `NO_QUAD_PRECISION`. `-DENABLE_QUAD_PRECISION` should be set if quad precision is to be used, otherwise FMS will not use quad precision reals where applicable. + +### Added +- DATA_OVERRIDE: A new namelist flag `use_data_table_yaml` has been added to enable usage of the yaml format data_override tables. This allows an executable built with yaml support be able to accept either format. + +### Changed +- RESERVED KEYWORD CHANGES: Various routines in FMS have been updated to not use fortran keywords for variable names. The names changed were: `data`, `unit`, and `value`. This may affect usage of external code if argument names are explicitly used. Only required arguement names were changed to mitigate any breaking changes. +- TESTS: Changes the testing scripts to allow for the `MPI_LAUNCHER` environment variable override to work with any provided arguments. + +### Fixed +- CMAKE: Fixed build issue with CMake where precision default flags were being overwritten when using GNU and MPICH. +- AUTOTOOLS: Fixes issue affecting installs where the global libFMS.F90 module was not being installed correctly and adds post-install message. +- DIAG_MANAGER: Fixes issue with incorrect start_time functionality (from the 2023.02.01 patch) + +### Tag Commit Hashes +- 2023.04-beta1 be1856c45accfe2fb15953c5f51e0d58a8816882 + +## [2023.03] - 2023-10-27 +### Known Issues +- GCC 9 and below as well as GCC 11.1.0 are unsupported due to compilation issues. See prior releases for more details. +- `NO_QUAD_PRECISION` macro is no longer set by FMS, the `ENABLE_QUAD_PRECISION` macro has replaced prior usage of `NO_QUAD_PRECISION`. `-DENABLE_QUAD_PRECISION` should be set if quad precision is to be used, otherwise FMS will not use quad precision reals where applicable. + +### Added +- UNIT_TESTS: New unit tests have been created or and existing ones expanded on for any modules utilizing mixed precision support. + +### Changed +- MIXED PRECISION: Most subroutines and functions in FMS have been updated to simultaneously accept both 4 byte and 8 byte reals as arguments. This deprecates the `--enable-mixed-mode` option, which enabled similar functionality but was limited to certain directories and was not enabled by default. To facilitate easier testing of these code changes, the CMake precision options for default real size were left in (along with an equivalent `--disable-r8-default` flag for autotools). The resulting libraries will support mixed-precision real kinds regardless of default real size. It should also be noted that many routines that accept real arguments have been moved to include files along with headers in order to be compiled with both kinds. Most module level variables were explicitly declared as r8_kind for these updates. +- Some type/module changes were made to facilitate mixed precision support. They are **intended** to have minimal impact to other codebases: + - COUPLER_TYPES: In coupler_types.F90, `coupler_nd_field_type` and `coupler_nd_values_type` have been renamed to indicate real kind value: `coupler_nd_real4/8_field_type` and `coupler_nd_real4/8_values_type`. The `bc` field within `coupler_nd_bc_type` was modified to use r8_kind within the value and field types, and an additional field added `bc_r4` to use r4_kind values. + - TRIDIAGONAL: Module state between r4 and r8 calls are distinct (ie. subsequent calls will only be affected by calls of the same precision). This behaviour can be changed via the `save_both_kinds` optional argument to `tri_invert`. +- CODE_STYLE: has been updated to reflect the formatting used for the mixed precision support updates. + +### Fixed +- DIAG_MANAGER: Tile number (ie. tileX) will now be added to filenames for sub-regional diagnostics. +- MPP: Bug affecting non-intel compilers coming from uninitialized pointer in the `nest_domain_type` +- MPP: Bug fix for unallocated field causing seg faults in `mpp_check_field` +- FMS2_IO: Fixed segfault occuring from use of cray pointer remapping along with mpp_scatter/gather +- TEST_FMS: Added various fixes for different compilers within test programs for fms2_io, mpp, diag_manager, parser, and sat_vapor_pres. +- INTERPOLATOR: Deallocates fields in the type that were previously left out in `interpolator_end` + +### Removed +- CPP MACROS: + - `no_4byte_reals` was removed and will not set any additional macros if used. `no_8byte_integers` is still functional. + - `NO_QUAD_PRECISION` was removed. It was conditionally set if ENABLE_QUAD_PRECISION was undefined. ENABLE_QUAD_PRECISION should be used in model components instead (logic is flipped) + - `use_netCDF` was set by autotools previously but wasn't consistently used in the code. FMS should always be compiled with netcdf installed so this was removed with the exception of its use in deprecated IO modules. +- DRIFTERS: The drifters subdirectory has been deprecated. It will only be compiled if using the `-Duse_drifters` CPP flag. + +### Tag Commit Hashes +- 2023.03-beta1 06b94a7f574e7794684b8584391744ded68e2989 +- 2023.03-alpha3 b25a7c52a27dfd52edc10bc0ebe12776af0f03df +- 2023.03-alpha2 9983ce308e62e9f7215b04c227cebd30fd75e784 +- 2023.03-alpha1 a46bd94fd8dd1f6f021501e29179003ff28180ec + + +## [2023.02.01] - 2023-10-13 +### Fixed +- DIAG_MANAGER: Fixes issue with incorrect start_time functionality + + +## [2023.02] - 2023-07-27 +### Known Issues +- GCC 11.1.0 is unsupported due to compilation issues with select type. The issue is resolved in later GCC releases. +- When outputting sub-region diagnostics, the current diag_manager does not add "tileX" to the filename when using a cube sphere. This leads to trouble when trying to combine the files and regrid them (if the region is in two different tiles) +- GCC 10 and greater causing io issues when compiled using O2 optimization flags +- GNU compilers prior to the GCC 9.0 release are unsupported for this release due to lack of support for the findloc intrinsic function. This will result in an error saying 'findloc' has no IMPLICIT type and can be resolved by compiling with gcc version 9.0 or greater. + +### Added +- MPP/EXCHANGE: Adds association checks before pointer deallocations in mpp includes and xgrid + +### Changed +- LIBFMS: The libFMS.F90 file (module name `fms`) meant to provide global access has been updated to include 'fms' and it's module/subdirectory name as prefixes for all names. This will only affect external codes that are already using the global module (via `use fms`) and not individual modules. +- MIXED PRECISION: Updates the axis_utils2, horiz_interp, sat_vapor_pressure, and axis_utils subdirectories to support mixed precision real values. +- FMS2_IO: Added in mpp_scatter and mpp_gather performance changes from the 2023.01.01 patch. See below for more details. +- FMS2_IO: Improved error messages to give more debugging information +- FMS_MOD: Changed fms_init to include a system call to set the stack size to unlimited, removed previously added stack size fixes +- MONIN_OBUKHOV: Restructures the subroutines in `stable_mix` interface so that 1d calls the underlying implementation, and 2 and 3d call it on 1d slices of the data as opposed to passing in mismatched arrays. +- MPP: Updates from JEDI for ajoint version the mpp halo filling (mpp_do_update_ad.fh), adds checkpoint for forward buffer information. + +### Fixed +- MPP: mpp_broadcast causing an unintended error message due to checking the wrong pe value +- MPP: Added workaround for GCC 12 issues causing errors with string lengths in fms2_io +- FMS2_IO: Fixed support for 'packed' data when using NF_SHORT variables. Scale_factor and add_offset attributes will now be applied if present. +- DOCS: Improved doxygen comments for tranlon, updated deployment action for site +- TESTS: Workaround added for ICE coming from mpp_alltoall test with intel 2022.3, and fixes for any test scripts missing input.nml creation. Fixes for mpp/test_global_array failures. +- TIME_INTERP: Fixes crashes when calling with a non-existant field +- DIAG_MANAGER: Fixes a module dependency issue causing failures during parallel builds +- AXIS_UTILS2: Fixes an out of bounds memory index + +### Removed +- FMS_IO/MPP_IO: The two older io modules, fms_io_mod and mpp_io_mod, have been deprecated and will not be compiled by default. If you wish to compile these modules, you must use the -Duse_deprecated_io CPP flag or the --enable-deprecated-io configure option if building with autotools. + +### Tag Commit Hashes +- 2023.02-beta1 2be8aa452ad3e5f43e92c38a64f12d1ae6c43fb8 +- 2023.02-alpha3 8c73bd18dc1d580f2ee524c37cf903ff54d40501 +- 2023.02-alpha2 783019fdec89a8db2b26247c2f63d4782e1495c0 +- 2023.02-alpga1 419c66be31f82ebb13a91ea5e837c707eb54473b + + ## [2023.01.01] - 2023-06-06 ### Changed - FMS2_IO: Performance changes for domain_reads_2d and domain_reads_3d: diff --git a/CI.md b/CI.md index 225b25129b..89b4db256e 100644 --- a/CI.md +++ b/CI.md @@ -8,24 +8,30 @@ Required CI for pull requests are listed first. ## Pull Request CI and checks ### Build libFMS with autotools + Required GNU build test for all pull requests/pushes. Runs `make distcheck` after configuring via GNU autotools. +Runs on a container image with spack installed dependencies, on top a rocky linux base. + +Dockerfile for image is stored at .github/workflows/Dockerfile.gnu for more specific information on the CI environment. + Container environment: -gcc v7.3.0 -mpich v3.3a2 -netcdf v4.6.0 -netcdf-fortran v4.4.4 +gcc v12.3.0 +mpich v4.0.2 +netcdf v4.9.0 +netcdf-fortran v4.6.0 autoconf v2.69 +libyaml v0.2.5 -container hosted at [noaagfdl/ubuntu_libfms_gnu:latest](https://hub.docker.com/r/noaagfdl/ubuntu_libfms_gnu) - -`./configure` flags: -- `--enable-openmp` +`./configure` flags tested: - `--disable-openmp` - `--enable-mixed-mode` +- `--with-mpi=no` (disables unit testing) - `--disable-setting-flags` - `--with-yaml` +- `--enable-test-input=/home/unit_tests_input` + ### Build libfms with cmake Required GNU build test for all pull requests/pushes. diff --git a/CMAKE_INSTRUCTIONS.md b/CMAKE_INSTRUCTIONS.md index d627f12fa1..cb6e927a24 100644 --- a/CMAKE_INSTRUCTIONS.md +++ b/CMAKE_INSTRUCTIONS.md @@ -1,42 +1,55 @@ \page cmake Building with CMake # Instructions for building FMS with cmake -## 1. Environment Variables +## 1. Configuring the build -### For GNU compilers on Linux with the bash shell: +### Environment Variables +#### For GNU compilers on Linux with the bash shell: ``` export FC=mpifort export CC=mpicc ``` - -### For Intel Compilers on Linux with the bash shell: - +#### For Intel Compilers on Linux with the bash shell: ``` export FC=mpiifort export CC=mpiicc ``` +MPI compiler wrappers may be named different on your system, so its best to ensure the compiler commands work prior to being set. -### NetCDF is provided via the `nc-config` command: +#### NetCDF is provided via the `nc-config` command: ``` export NetCDF_ROOT=`nc-config --prefix` ``` -### If building with yaml parser (-DWITH_YAML) +#### If building with yaml parser (-DWITH_YAML) ``` export LIBYAML_ROOT= ``` -### Setting custom flags with the bash shell -To override the default compiler flags: +### Running CMake +It's best to create a build directory inside of the FMS folder to avoid building on top of the source code. +Once that is done, CMake can be ran to generate the necessary build files: ``` -export FCFLAGS="" -export CFLAGS="" +mkdir build && cd build +cmake [-DWITH_YAML=on] .. ``` -In addition, the flag below must be included with the cmake command: + +### Setting a Build Type +CMake uses "build types" to set compiler flags. By default (.ie no -DCMAKE_BUILD_TYPE="Type" argument when cmake is run), it will use the "Release" build type which corresponds the the prod/opt flags used by the GFDL mkmf templates. + +Similarly "Repro" and "Debug" also correspond to whatever options would be set by theircorresponding mkmf template files. + +To reproduce the old behavior for the cmake build (2025.03 and prior), the "ReleaseUFS" and "DebugUFS" will use the same flags as before. + +Please open an issue in the FMS repository if you have any issues or would like to request any changes to the current build type options. + +### Setting custom flags +To override the default compiler flags, you can set them via CMake arguments: ``` -cmake -DCMAKE_BUILD_TYPE=Debug .. +cmake -DCMAKE_BUILD_TYPE="NoFlags" -DCMAKE_C_FLAGS="" -DCMAKE_Fortran_FLAGS="" ``` +By using the "NoFlags" build type, only necessary compilation flags will be added besides what is set via the cmake arguments. ## 2. Build and install FMS with CMake `` is the full install directory for FMS provided by user @@ -45,26 +58,33 @@ cmake -DCMAKE_BUILD_TYPE=Debug .. cd FMS mkdir -p build && cd build cmake -DCMAKE_INSTALL_PREFIX= .. -make -j4 +make -j make install ``` ### User configurable options: -By default, FMS is built without `OpenMP` and in `single precision (r4)` +By default, FMS is built with `OpenMP` enabled and delivered in static library files. + +FMS has mixed precision real support for most interfaces. By default, one library will be created, +`libfms`, that is compiled with r8 defaults but also contains overloaded r4 routines. The 64BIT and 32BIT precision options will build distinct libraries when enabled with the given default -real size, libfms_r4 or libfms_r8. +real size, libfms_r4 or libfms_r8. These option are provided for backwards compatibility, but are no longer +supported by our development team, since mixed precision can now be used with a single library. +Unit tests currently only work when no precision option is specified. The following build options are available: ``` --DOPENMP "Build FMS with OpenMP support" DEFAULT: OFF --D32BIT "Build 32-bit (r4) FMS library" DEFAULT: ON --D64BIT "Build 64-bit (r8) FMS library" DEFAULT: OFF --DFPIC "Build with position independent code" DEFAULT: OFF +-DOPENMP "Build FMS with OpenMP support" DEFAULT: ON +-D32BIT "Build 32-bit (r4) FMS library" DEFAULT: OFF +-D64BIT "Build 64-bit (r8) FMS library" DEFAULT: OFF +-DFPIC "Build with position independent code" DEFAULT: OFF +-DSHARED_LIBS "Build shared/dynamic libraries" DEFAULT: OFF -DCONSTANTS "Build with constants parameter definitions" DEFAULT:GFDL OPTIONS:GFS|GEOS|GFDL -DINTERNAL_FILE_NML "Enable compiler definition -DINTERNAL_FILE_NML" DEFAULT: ON -DENABLE_QUAD_PRECISION "Enable compiler definition -DENABLE_QUAD_PRECISION" DEFAULT: ON +-DPORTABLE_KINDS "Enable compiler definition -DPORTABLE_KINDS" DEFAULT:OFF -DGFS_PHYS "Enable compiler definition -DGFS_PHYS" DEFAULT:OFF -DLARGEFILE "Enable compiler definition -Duse_LARGEFILE" DEFAULT:OFF -DWITH_YAML "Enable compiler definition -Duse_yaml" DEFAULT:OFF @@ -72,10 +92,10 @@ The following build options are available: ## 3. Installation structure -When the above command finishes, the `` will have an `include_r4` and a `lib` directory. The `lib ` directory will have these files: +When the above command finishes, the `` will have an `include` and a `lib` directory. The `lib ` directory will have these files: ``` -libfms_r4.a +libfms.a cmake/fms/fms-targets.cmake cmake/fms/fms-targets-release.cmake cmake/fms/fms-config.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 473d8b91f9..51768e79d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,24 +21,21 @@ cmake_minimum_required(VERSION 3.12 FATAL_ERROR) -# add build type for debug, overrides default flags (set with $FCFLAGS, $CFLAGS) -set(CMAKE_Fortran_FLAGS_DEBUG) - # Define the CMake project project(FMS - VERSION 2023.01.0 + VERSION 2025.03.0 DESCRIPTION "GFDL FMS Library" HOMEPAGE_URL "https://www.gfdl.noaa.gov/fms" LANGUAGES C Fortran) include(GNUInstallDirs) -if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel)$") +if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo|MinSizeRel|DebugUFS|ReleaseUFS)$") message(STATUS "Setting build type to 'Release' as none was specified.") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "DebugUFS" "ReleaseUFS") endif() if(NOT CMAKE_C_COMPILER_ID MATCHES "^(Intel|GNU|Clang|IntelLLVM)$") @@ -55,17 +52,20 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # Build options -option(OPENMP "Build FMS with OpenMP support" OFF) -option(32BIT "Build 32-bit (r4) FMS library" ON) -option(64BIT "Build 64-bit (r8) FMS library" OFF) -option(FPIC "Build with position independent code" OFF) +option(OPENMP "Build FMS with OpenMP support" ON) +option(32BIT "Build 32-bit (r4) FMS library" OFF) +option(64BIT "Build 64-bit (r8) FMS library" OFF) +option(FPIC "Build with position independent code" OFF) +option(SHARED_LIBS "Build shared/dynamic libraries" OFF) # Options for compiler definitions option(INTERNAL_FILE_NML "Enable compiler definition -DINTERNAL_FILE_NML" ON) option(ENABLE_QUAD_PRECISION "Enable compiler definition -DENABLE_QUAD_PRECISION" ON) +option(PORTABLE_KINDS "Enable compiler definition -DPORTABLE_KINDS" OFF) option(GFS_PHYS "Enable compiler definition -DGFS_PHYS" OFF) option(LARGEFILE "Enable compiler definition -Duse_LARGEFILE" OFF) option(WITH_YAML "Enable compiler definition -Duse_yaml" OFF) +option(USE_DEPRECATED_IO "THIS OPTION HAS NO EFFECT AND WILL BE REMOVED IN A FUTURE RELEASE" OFF) if(32BIT) list(APPEND kinds "r4") @@ -76,7 +76,7 @@ endif() if(NOT kinds) message(STATUS "Single Precision 32BIT: ${32BIT}") message(STATUS "Double Precision 64BIT: ${64BIT}") - message(FATAL_ERROR "Either 32BIT or 64BIT should be ON") + message(STATUS "No kind specified, building default double precision (with mixed precision support). Real size will not be included in built library name.") endif() # Find dependencies @@ -89,7 +89,7 @@ if (OPENMP) endif() if (WITH_YAML) - find_package(libyaml REQUIRED) + find_package(libyaml REQUIRED COMPONENTS C) include_directories(${LIBYAML_INCLUDE_DIR}) endif () @@ -103,7 +103,6 @@ list(APPEND fms_fortran_src_files affinity/fms_affinity.F90 amip_interp/amip_interp.F90 astronomy/astronomy.F90 - axis_utils/axis_utils.F90 axis_utils/axis_utils2.F90 block_control/block_control.F90 column_diagnostics/column_diagnostics.F90 @@ -112,6 +111,7 @@ list(APPEND fms_fortran_src_files constants4/constantsr4.F90 constants4/fmsconstantsr4.F90 coupler/atmos_ocean_fluxes.F90 + coupler/gex.F90 coupler/coupler_types.F90 coupler/ensemble_manager.F90 data_override/get_grid_version.F90 @@ -124,11 +124,20 @@ list(APPEND fms_fortran_src_files diag_manager/diag_output.F90 diag_manager/diag_table.F90 diag_manager/diag_util.F90 + diag_manager/fms_diag_time_utils.F90 + diag_manager/fms_diag_object.F90 + diag_manager/fms_diag_yaml.F90 + diag_manager/fms_diag_file_object.F90 + diag_manager/fms_diag_field_object.F90 + diag_manager/fms_diag_axis_object.F90 + diag_manager/fms_diag_output_buffer.F90 + diag_manager/fms_diag_input_buffer.F90 diag_manager/fms_diag_time_reduction.F90 diag_manager/fms_diag_outfield.F90 diag_manager/fms_diag_elem_weight_procs.F90 diag_manager/fms_diag_fieldbuff_update.F90 diag_manager/fms_diag_bbox.F90 + diag_manager/fms_diag_reduction_methods.F90 drifters/cloud_interpolator.F90 drifters/drifters.F90 drifters/drifters_comm.F90 @@ -141,7 +150,6 @@ list(APPEND fms_fortran_src_files field_manager/field_manager.F90 field_manager/fm_util.F90 field_manager/fm_yaml.F90 - fms/fms_io.F90 fms/fms.F90 fms2_io/blackboxio.F90 fms2_io/fms_io_utils.F90 @@ -159,16 +167,13 @@ list(APPEND fms_fortran_src_files memutils/memutils.F90 monin_obukhov/monin_obukhov_inter.F90 monin_obukhov/monin_obukhov.F90 - mosaic/gradient.F90 - mosaic/grid.F90 - mosaic/mosaic.F90 + grid_utils/gradient.F90 mosaic2/grid2.F90 mosaic2/mosaic2.F90 mpp/mpp.F90 mpp/mpp_data.F90 mpp/mpp_domains.F90 mpp/mpp_efp.F90 - mpp/mpp_io.F90 mpp/mpp_memutils.F90 mpp/mpp_parameter.F90 mpp/mpp_utilities.F90 @@ -180,7 +185,6 @@ list(APPEND fms_fortran_src_files sat_vapor_pres/sat_vapor_pres_k.F90 sat_vapor_pres/sat_vapor_pres.F90 string_utils/fms_string_utils.F90 - time_interp/time_interp_external.F90 time_interp/time_interp_external2.F90 time_interp/time_interp.F90 time_manager/get_cal_time.F90 @@ -195,15 +199,16 @@ list(APPEND fms_fortran_src_files # Collect FMS C source files list(APPEND fms_c_src_files affinity/affinity.c - mosaic/create_xgrid.c - mosaic/gradient_c2l.c - mosaic/interp.c - mosaic/mosaic_util.c - mosaic/read_mosaic.c + fms/fms_stacksize.c + grid_utils/gradient_c2l.c + grid_utils/grid_utils.c + grid_utils/tree_utils.c + horiz_interp/include/horiz_interp_conserve_xgrid.c mpp/mpp_memuse.c parser/yaml_parser_binding.c parser/yaml_output_functions.c string_utils/fms_string_utils_binding.c + test_fms/fms/test_fmsC.c # TODO just easier to lump this with the rest of the C src ) # Collect FMS header files @@ -246,6 +251,11 @@ if(WITH_YAML) list(APPEND fms_defs use_yaml) endif() +if(USE_DEPRECATED_IO) + message( FATAL_ERROR "FMS_IO HAS BEEN REMOVED! PLEASE UPDATE TO USE FMS2_IO AND REMOVE " + "-DUSE_DEPRECATED_IO=on FROM YOUR OPTIONS OR REVERT TO VERSION 2025.02") +endif() + if(INTERNAL_FILE_NML) list(APPEND fms_defs INTERNAL_FILE_NML) endif() @@ -254,6 +264,10 @@ if(ENABLE_QUAD_PRECISION) list(APPEND fms_defs ENABLE_QUAD_PRECISION) endif() +if(PORTABLE_KINDS) + list(APPEND fms_defs PORTABLE_KINDS) +endif() + if(LARGEFILE) list(APPEND fms_defs use_LARGEFILE) endif() @@ -271,6 +285,13 @@ endif() # Obtain compiler-specific flags include(fms_compiler_flags) +# If netCDF was not built with HDF5 parallel I/O features, set up the macro -DNO_NC_PARALLEL4 +IF(NOT NetCDF_PARALLEL) + MESSAGE(WARNING "netCDF was not build with HDF5 parallel I/O features, so collective netcdf io is not allowed") + list(APPEND fms_defs NO_NC_PARALLEL4) +ENDIF() + +# TODO remove this section eventually, separate builds are no longer required foreach(kind ${kinds}) set(libTgt fms_${kind}) @@ -280,9 +301,9 @@ foreach(kind ${kinds}) # C add_library(${libTgt}_c OBJECT ${fms_c_src_files}) - target_include_directories(${libTgt}_c PRIVATE include) + target_include_directories(${libTgt}_c PRIVATE include + grid_utils) target_compile_definitions(${libTgt}_c PRIVATE "${fms_defs}") - target_link_libraries(${libTgt}_c PRIVATE NetCDF::NetCDF_C MPI::MPI_C) @@ -295,50 +316,97 @@ foreach(kind ${kinds}) target_include_directories(${libTgt}_f PRIVATE include fms + fms/include fms2_io/include string_utils/include mpp/include + column_diagnostics/include + monin_obukhov/include + sat_vapor_pres/include + horiz_interp/include + diag_integral/include + random_numbers/include diag_manager/include constants4 - constants) + topography/include + axis_utils/include + mosaic2/include + constants + astronomy/include + field_manager/include + time_interp/include + tracer_manager/include + tridiagonal/include + interpolator/include + coupler/include + data_override/include + amip_interp/include) target_compile_definitions(${libTgt}_f PRIVATE "${fms_defs}") target_compile_definitions(${libTgt}_f PRIVATE "${${kind}_defs}") - - string(TOLOWER ${CMAKE_BUILD_TYPE} build_type) - if (NOT build_type STREQUAL debug) - set_target_properties(${libTgt}_f PROPERTIES COMPILE_FLAGS - "${${kind}_flags}") - endif() + set_target_properties(${libTgt}_f PROPERTIES COMPILE_FLAGS ${${kind}_flags}) set_target_properties(${libTgt}_f PROPERTIES Fortran_MODULE_DIRECTORY ${moduleDir}) - target_link_libraries(${libTgt}_f PRIVATE NetCDF::NetCDF_Fortran MPI::MPI_Fortran) - if(OpenMP_Fortran_FOUND) target_link_libraries(${libTgt}_f PRIVATE OpenMP::OpenMP_Fortran) endif() - # Check if gnu 10 or higher with mpich + + # Check if gnu 10 or higher + # this should only be needed with mpich, but wasn't able to find a good way to find the MPI flavor consistently if ( CMAKE_Fortran_COMPILER_VERSION MATCHES "1[0-9]\.[0-9]*\.[0-9]*" AND CMAKE_Fortran_COMPILER_ID MATCHES "GNU") - if(MPI_C_COMPILER MATCHES ".*mpich.*" ) + include(CheckFortranCompilerFlag) + check_fortran_compiler_flag("-fallow-argument-mismatch" _arg_mismatch_flag) + if(_arg_mismatch_flag) message(STATUS "Adding -fallow-argument-mismatch flag to compile with GCC >=10 and MPICH") - set_target_properties(${libTgt}_f PROPERTIES COMPILE_FLAGS "-fallow-argument-mismatch -w") + target_compile_options(${libTgt}_f PRIVATE "-fallow-argument-mismatch;-w") endif() endif() + if ( CMAKE_Fortran_COMPILER_VERSION MATCHES "2025\.1\..*" AND CMAKE_Fortran_COMPILER_ID MATCHES "IntelLLVM") + message(FATAL_ERROR "Intel LLVM Fortran compiler version 2025.1 is not supported due to internal compiler error") + endif() + # FMS (C + Fortran) - add_library(${libTgt} STATIC $ - $) + if (SHARED_LIBS) + message(STATUS "Shared library target: ${libTgt}") + add_library(${libTgt} SHARED $ + $) + else () + message(STATUS "Static library target: ${libTgt}") + add_library(${libTgt} STATIC $ + $) + endif () target_include_directories(${libTgt} PUBLIC $ $ + $ $ + $ + $ + $ + $ + $ + $ + $ + $ $ $ - $) - + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $) + set(moduleDir "${CMAKE_CURRENT_BINARY_DIR}/${includeDir}") target_include_directories(${libTgt} INTERFACE $ $) @@ -346,11 +414,12 @@ foreach(kind ${kinds}) target_compile_definitions(${libTgt} PRIVATE "${fms_defs}") target_compile_definitions(${libTgt} PRIVATE "${${kind}_defs}") - target_link_libraries(${libTgt} PUBLIC NetCDF::NetCDF_Fortran + target_link_libraries(${libTgt} PUBLIC NetCDF::NetCDF_C + NetCDF::NetCDF_Fortran MPI::MPI_Fortran) if(OpenMP_Fortran_FOUND) - target_link_libraries(${libTgt} PRIVATE OpenMP::OpenMP_Fortran) + target_link_libraries(${libTgt} PRIVATE OpenMP::OpenMP_C OpenMP::OpenMP_Fortran) endif() add_library(FMS::${libTgt} ALIAS ${libTgt}) @@ -361,16 +430,460 @@ foreach(kind ${kinds}) endforeach() +# default build, same as an r8 build just different name +if(NOT kinds) + message(STATUS "building default kind") + set(libTgt "fms") + set(includeDir "include") + set(moduleDir "${CMAKE_CURRENT_BINARY_DIR}/${includeDir}") + set(kind "r8") + + # C + add_library(${libTgt}_c OBJECT ${fms_c_src_files}) + + target_include_directories(${libTgt}_c PRIVATE include + grid_utils) + target_compile_definitions(${libTgt}_c PRIVATE "${fms_defs}") + target_link_libraries(${libTgt}_c PRIVATE NetCDF::NetCDF_C + MPI::MPI_C) + + if(OpenMP_C_FOUND) + target_link_libraries(${libTgt}_c PRIVATE OpenMP::OpenMP_C) + endif() + + if(WITH_YAML) + target_link_libraries(${libTgt}_c PRIVATE libyaml_C) + set_target_properties(${libTgt}_c PROPERTIES COMPILE_FLAGS "-L${LIBYAML_LIBRARIES} -lyaml") + endif() + + + # Fortran + add_library(${libTgt}_f OBJECT ${fms_fortran_src_files}) + + target_include_directories(${libTgt}_f PRIVATE include + fms + fms/include + fms2_io/include + string_utils/include + mpp/include + column_diagnostics/include + monin_obukhov/include + sat_vapor_pres/include + horiz_interp/include + diag_integral/include + random_numbers/include + diag_manager/include + constants4 + topography/include + axis_utils/include + mosaic2/include + constants + astronomy/include + field_manager/include + time_interp/include + tracer_manager/include + tridiagonal/include + interpolator/include + coupler/include + data_override/include + amip_interp/include) + target_compile_definitions(${libTgt}_f PRIVATE "${fms_defs}") + target_compile_definitions(${libTgt}_f PRIVATE "${${kind}_defs}") + set_target_properties(${libTgt}_f PROPERTIES COMPILE_FLAGS ${${kind}_flags}) + set_target_properties(${libTgt}_f PROPERTIES Fortran_MODULE_DIRECTORY + ${moduleDir}) + target_link_libraries(${libTgt}_f PRIVATE NetCDF::NetCDF_Fortran + MPI::MPI_Fortran) + if(OpenMP_Fortran_FOUND) + target_link_libraries(${libTgt}_f PRIVATE OpenMP::OpenMP_Fortran) + endif() + + # Check if gnu 10 or higher + # this should only be needed with mpich, but wasn't able to find a good way to find the MPI flavor consistently + if ( CMAKE_Fortran_COMPILER_VERSION MATCHES "1[0-9]\.[0-9]*\.[0-9]*" AND CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + include(CheckFortranCompilerFlag) + check_fortran_compiler_flag("-fallow-argument-mismatch" _arg_mismatch_flag) + if(_arg_mismatch_flag) + message(STATUS "Adding -fallow-argument-mismatch flag to compile with GCC >=10 and MPICH") + target_compile_options(${libTgt}_f PRIVATE "-fallow-argument-mismatch;-w") + endif() + endif() + + # FMS (C + Fortran) + if (SHARED_LIBS) + message(STATUS "Shared library target: ${libTgt}") + add_library(${libTgt} SHARED $ + $) + else () + message(STATUS "Static library target: ${libTgt}") + add_library(${libTgt} STATIC $ + $) + endif () + + target_include_directories(${libTgt} PUBLIC + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $) + + set(moduleDir "${CMAKE_CURRENT_BINARY_DIR}/${includeDir}") + target_include_directories(${libTgt} INTERFACE + $ + $) + + target_compile_definitions(${libTgt} PRIVATE "${fms_defs}") + target_compile_definitions(${libTgt} PRIVATE "${${kind}_defs}") + set_target_properties(${libTgt}_f PROPERTIES COMPILE_FLAGS ${${kind}_flags}) + + target_link_libraries(${libTgt} PUBLIC NetCDF::NetCDF_C + NetCDF::NetCDF_Fortran + MPI::MPI_Fortran) + + if(OpenMP_Fortran_FOUND) + target_link_libraries(${libTgt} PRIVATE OpenMP::OpenMP_C OpenMP::OpenMP_Fortran) + endif() + + add_library(FMS::${libTgt} ALIAS ${libTgt}) + + list(APPEND LIB_TARGETS ${libTgt}) + install(DIRECTORY ${moduleDir} DESTINATION ${CMAKE_INSTALL_PREFIX}) + install(FILES ${fms_header_files} DESTINATION ${CMAKE_INSTALL_PREFIX}/${includeDir}) + + +endif() + install( TARGETS ${LIB_TARGETS} EXPORT FMSExports - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +### Unit Testing +include(CTest) + +# TODO autotools also checks if srun is available +set(MPI_LAUNCHER "mpirun") +# used in the test-lib.sh.in to make it behave differently when parsed by cmake +set(USING_CMAKE "true") + +# parse and add build info to test script util file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_fms/test-lib.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/test-lib.sh + @ONLY) + +# build any helper modules that are used by unit tests +list(APPEND TEST_MODS_SRC + test_fms/diag_manager/testing_utils.F90 + test_fms/fms/test_fms.F90 + test_fms/fms2_io/argparse.F90 + test_fms/fms2_io/setup.F90 + test_fms/mosaic2/write_files.F90 + test_fms/mpp/compare_data_checksums.F90 + test_fms/mpp/compare_data_checksums_int.F90 + test_fms/mpp/fill_halo.F90 + test_fms/mpp/test_domains_utility_mod.F90 + test_fms/mpp/test_mpp_update_domains_int.F90 + test_fms/mpp/test_mpp_update_domains_real.F90 + test_fms/mpp/test_mpp_update_domains_ad.F90 + test_fms/mpp/test_system_clock.F90) + +add_library(testLibs "${TEST_MODS_SRC}") +target_link_libraries(testLibs FMS::fms) +target_compile_definitions(testLibs PRIVATE TEST_MOS_KIND_=r8_kind) +target_include_directories(testLibs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/fms/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/data_override/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/mosaic2 + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +set_target_properties(testLibs PROPERTIES Fortran_MODULE_DIRECTORY + ${moduleDir}) + +# add all our test source code + +# this lists all source code that creates a single executable for unit testing (r8 default) +list(APPEND TEST_SRC_SINGLE_TARGET + test_fms/affinity/test_affinity.F90 + test_fms/block_control/test_block_control.F90 + test_fms/coupler/test_gex.F90 + test_fms/data_override/test_data_override_init.F90 + test_fms/data_override/test_data_override_ongrid.F90 + test_fms/diag_manager/test_output_every_freq.F90 + test_fms/diag_manager/test_dm_weights.F90 + test_fms/diag_manager/test_diag_manager.F90 + test_fms/diag_manager/test_diag_manager_time.F90 + test_fms/diag_manager/test_diag_update_buffer.F90 + test_fms/diag_manager/test_diag_yaml.F90 + test_fms/diag_manager/test_diag_ocean.F90 + test_fms/diag_manager/test_modern_diag.F90 + test_fms/diag_manager/test_diag_buffer.F90 + test_fms/diag_manager/test_flexible_time.F90 + test_fms/diag_manager/test_flush_nc_file.F90 + test_fms/diag_manager/test_diag_out_yaml.F90 + test_fms/diag_manager/test_reduction_methods.F90 + test_fms/diag_manager/testing_utils.F90 + test_fms/diag_manager/test_diag_diurnal.F90 + test_fms/diag_manager/check_time_none.F90 + test_fms/diag_manager/check_time_min.F90 + test_fms/diag_manager/check_time_max.F90 + test_fms/diag_manager/check_time_avg.F90 + test_fms/diag_manager/check_time_diurnal.F90 + test_fms/diag_manager/check_time_pow.F90 + test_fms/diag_manager/check_time_rms.F90 + test_fms/diag_manager/test_cell_measures.F90 + test_fms/diag_manager/check_subregional.F90 + test_fms/diag_manager/test_var_masks.F90 + test_fms/diag_manager/check_var_masks.F90 + test_fms/diag_manager/test_multiple_send_data.F90 + test_fms/diag_manager/test_prepend_date.F90 + test_fms/diag_manager/test_diag_multi_file.F90 + test_fms/diag_manager/test_ens_runs.F90 + test_fms/diag_manager/test_diag_attribute_add.F90 + test_fms/diag_manager/check_new_file_freq.F90 + test_fms/drifters/test_cloud_interpolator.F90 + test_fms/drifters/test_drifters_io.F90 + test_fms/drifters/test_drifters_input.F90 + test_fms/drifters/test_drifters_comm.F90 + test_fms/drifters/test_drifters_core.F90 + test_fms/drifters/test_quicksort.F90 + test_fms/drifters/test_drifters.F90 + test_fms/exchange/test_xgrid.F90 + test_fms/field_manager/test_field_table_read.F90 + test_fms/fms2_io/test_get_is_valid.F90 + test_fms/fms2_io/test_fms2_io.F90 + test_fms/fms2_io/test_atmosphere_io.F90 + test_fms/fms2_io/test_io_simple.F90 + test_fms/fms2_io/setup.F90 + test_fms/fms2_io/argparse.F90 + test_fms/fms2_io/test_global_att.F90 + test_fms/fms2_io/test_io_with_mask.F90 + test_fms/fms2_io/test_bc_restart.F90 + test_fms/fms2_io/test_get_mosaic_tile_grid.F90 + test_fms/fms2_io/test_read_ascii_file.F90 + test_fms/fms2_io/test_file_appendix.F90 + test_fms/fms2_io/test_unlimit_compressed.F90 + test_fms/fms2_io/test_chunksizes.F90 + test_fms/fms2_io/test_packed_reads.F90 + test_fms/fms2_io/test_compressed_writes.F90 + test_fms/fms2_io/test_domain_io.F90 + test_fms/fms2_io/test_collective_io.F90 + test_fms/fms2_io/test_generalized_indices.F90 + test_fms/fms/test_fms.F90 + test_fms/interpolator/test_interpolator.F90 + test_fms/mpp/test_clock_init.F90 + test_fms/mpp/test_domains_simple.F90 + test_fms/mpp/test_domains_utility_mod.F90 + test_fms/mpp/test_global_arrays.F90 + test_fms/mpp/test_minmax.F90 + test_fms/mpp/test_mpp_alltoall.F90 + test_fms/mpp/test_mpp_broadcast.F90 + test_fms/mpp/test_mpp_chksum.F90 + test_fms/mpp/test_mpp_clock_begin_end_id.F90 + test_fms/mpp/test_mpp_domains.F90 + test_fms/mpp/test_mpp.F90 + test_fms/mpp/test_mpp_gatscat.F90 + test_fms/mpp/test_mpp_get_ascii_lines.F90 + test_fms/mpp/test_mpp_global_field.F90 + test_fms/mpp/test_mpp_global_field_ug.F90 + test_fms/mpp/test_mpp_global_sum_ad.F90 + test_fms/mpp/test_mpp_init_logfile.F90 + test_fms/mpp/test_mpp_mem_dump.F90 + test_fms/mpp/test_mpp_memuse.F90 + test_fms/mpp/test_mpp_memutils_begin_2x.F90 + test_fms/mpp/test_mpp_memutils_begin_end.F90 + test_fms/mpp/test_mpp_memutils_end_before_begin.F90 + test_fms/mpp/test_mpp_nesting.F90 + test_fms/mpp/test_mpp_npes.F90 + test_fms/mpp/test_mpp_pe.F90 + test_fms/mpp/test_mpp_print_memuse_stats_file.F90 + test_fms/mpp/test_mpp_print_memuse_stats_stderr.F90 + test_fms/mpp/test_mpp_root_pe.F90 + test_fms/mpp/test_mpp_sendrecv.F90 + test_fms/mpp/test_mpp_sum.F90 + test_fms/mpp/test_mpp_transmit.F90 + test_fms/mpp/test_mpp_update_domains.F90 + test_fms/mpp/test_mpp_update_domains_ad.F90 + test_fms/mpp/test_peset.F90 + test_fms/mpp/test_mpp_read_ascii_file.F90 + test_fms/mpp/test_read_input_nml.F90 + test_fms/mpp/test_redistribute_int.F90 + test_fms/mpp/test_stderr.F90 + test_fms/mpp/test_stdin.F90 + test_fms/mpp/test_stdlog.F90 + test_fms/mpp/test_stdout.F90 + test_fms/mpp/test_super_grid.F90 + test_fms/mpp/test_system_clock.F90 + test_fms/mpp/test_update_domains_performance.F90 + test_fms/parser/test_yaml_parser.F90 + test_fms/parser/check_crashes.F90 + test_fms/parser/parser_demo.F90 + test_fms/parser/parser_demo2.F90 + test_fms/parser/test_output_yaml.F90 + test_fms/parser/generic_blocks.F90 + test_fms/string_utils/test_string_utils.F90 + test_fms/time_manager/test_time_manager.F90 + ) +# list any test sources that are used for two executables (r4 and r8) +list(APPEND TEST_SRC_MULTI_TARGET + test_fms/astronomy/test_astronomy.F90 + test_fms/axis_utils/test_axis_utils.F90 + test_fms/column_diagnostics/test_column_diagnostics.F90 + test_fms/coupler/test_coupler_2d.F90 + test_fms/coupler/test_coupler_3d.F90 + test_fms/coupler/test_coupler_types.F90 + test_fms/coupler/test_atmos_ocean_fluxes.F90 + test_fms/data_override/test_data_override.F90 + test_fms/data_override/test_get_grid_v1.F90 + test_fms/diag_integral/test_diag_integral.F90 + test_fms/field_manager/test_field_manager.F90 + test_fms/horiz_interp/test_horiz_interp.F90 + test_fms/horiz_interp/test_create_xgrid_order2.F90 + test_fms/interpolator/test_interpolator2.F90 + test_fms/monin_obukhov/test_monin_obukhov.F90 + test_fms/mosaic2/test_mosaic2.F90 + test_fms/mosaic2/test_grid2.F90 + test_fms/random_numbers/test_random_numbers.F90 + test_fms/sat_vapor_pres/test_sat_vapor_pres.F90 + test_fms/time_interp/test_time_interp.F90 + test_fms/time_interp/test_time_interp_external.F90 + test_fms/topography/test_topography.F90 + test_fms/tracer_manager/test_tracer_manager.F90 + test_fms/tridiagonal/test_tridiagonal.F90 + ) + +# add all the test executables and set up linking + compile flags +foreach (testFile ${TEST_SRC_SINGLE_TARGET}) + get_filename_component (TName ${testFile} NAME_WE) + + add_executable(${TName} ${testFile}) + target_compile_definitions(${TName} PRIVATE "${r8_defs}") + set_target_properties(${TName} PROPERTIES COMPILE_FLAGS ${r8_flags}) + target_link_libraries(${TName} PUBLIC FMS::fms + PRIVATE testLibs + ) + + if(WITH_YAML) + target_link_libraries(${TName} PRIVATE yaml ${LIBYAML_LIBRARIES}) + endif() + + if(OPENMP) + target_link_libraries(${TName} PRIVATE OpenMP::OpenMP_Fortran) + endif() + + set_target_properties(${TName} PROPERTIES Fortran_MODULE_DIRECTORY + ${moduleDir}) + target_include_directories(${TName} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/fms/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/data_override/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/mosaic2 + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +endforeach () + +foreach (testFile ${TEST_SRC_MULTI_TARGET}) + get_filename_component (TName ${testFile} NAME_WE) + + add_executable(${TName}_r8 ${testFile}) + target_compile_definitions(${TName}_r8 PRIVATE "${r8_defs}") + set_target_properties(${TName}_r8 PROPERTIES COMPILE_FLAGS ${r8_flags}) + target_link_libraries(${TName}_r8 PUBLIC FMS::fms + PRIVATE testLibs + ) + + add_executable(${TName}_r4 ${testFile}) + target_compile_definitions(${TName}_r4 PRIVATE "${r4_defs}") + # seems counterintuitive but r4 tests use r8 default + # they specify kind values explicitly with the preprocessor where needed (TEST_FMS_KIND_) + set_target_properties(${TName}_r4 PROPERTIES COMPILE_FLAGS ${r8_flags}) + target_link_libraries(${TName}_r4 PUBLIC FMS::fms + PRIVATE testLibs + ) + + if(WITH_YAML) + target_link_libraries(${TName}_r4 PRIVATE yaml ${LIBYAML_LIBRARIES}) + target_link_libraries(${TName}_r8 PRIVATE yaml ${LIBYAML_LIBRARIES}) + endif() + + if(OPENMP) + target_link_libraries(${TName}_r4 PRIVATE OpenMP::OpenMP_Fortran) + target_link_libraries(${TName}_r8 PRIVATE OpenMP::OpenMP_Fortran) + endif() + + set_target_properties(${TName}_r4 PROPERTIES Fortran_MODULE_DIRECTORY + ${moduleDir}) + set_target_properties(${TName}_r8 PROPERTIES Fortran_MODULE_DIRECTORY + ${moduleDir}) + target_include_directories(${TName}_r4 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/fms/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/data_override/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/mosaic2 + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_include_directories(${TName}_r8 PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/fms/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/data_override/include + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/mosaic2 + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + #set macros used to test mixed precision + target_compile_definitions(${TName}_r8 PRIVATE TEST_FMS_KIND_=r8_kind) + target_compile_definitions(${TName}_r4 PRIVATE TEST_FMS_KIND_=r4_kind) + target_compile_definitions(${TName}_r8 PRIVATE FMS_TEST_BC_TYPE_=bc) + target_compile_definitions(${TName}_r4 PRIVATE FMS_TEST_BC_TYPE_=bc_r4) + target_compile_definitions(${TName}_r8 PRIVATE FMS_TEST_BC_TYPE_=bc) + target_compile_definitions(${TName}_r4 PRIVATE FMS_TEST_BC_TYPE_=bc_r4) + target_compile_definitions(${TName}_r8 PRIVATE WRITE_FILES_MOD_=write_files_r8) + target_compile_definitions(${TName}_r4 PRIVATE WRITE_FILES_MOD_=write_files_r4) +endforeach () + +# gather all test scripts +file(GLOB_RECURSE TEST_SCRIPTS ${CMAKE_CURRENT_SOURCE_DIR}/test_fms/*/*.sh) +# leave out scripts that aren't tests +list(FILTER TEST_SCRIPTS EXCLUDE REGEX "check_crashes.*") +list(FILTER TEST_SCRIPTS EXCLUDE REGEX "test-lib*") +list(FILTER TEST_SCRIPTS EXCLUDE REGEX "tap-driver.*") + +# add scripts to ctest +foreach (testScript ${TEST_SCRIPTS}) + get_filename_component(testName ${testScript} NAME_WE) + get_filename_component(testDirNameFull ${testScript} DIRECTORY) + get_filename_component(testDirName ${testDirNameFull} NAME_WE) + add_test(NAME ${testName} + COMMAND ${testScript} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + # all tests are labeled with directory name + set_property(TEST ${testName} PROPERTY LABELS ${testDirName}) + # set an environment variable so the scripts know to skip tests that use libyaml + if(NOT WITH_YAML) + set_tests_properties(${testName} PROPERTIES + ENVIRONMENT "parser_skip=skip" + ) + endif() +endforeach() + +set(CMAKE_CTEST_ARGUMENTS "--output-on-failure") ### Package config include(CMakePackageConfigHelpers) -set(CONFIG_INSTALL_DESTINATION lib/cmake/fms) +set(CONFIG_INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fms) export(EXPORT FMSExports NAMESPACE FMS:: @@ -379,7 +892,7 @@ export(EXPORT FMSExports configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/FMSConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/fms-config.cmake INSTALL_DESTINATION ${CONFIG_INSTALL_DESTINATION}) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/fms-config.cmake +install(FILES ${CMAKE_SOURCE_DIR}/cmake/FindNetCDF.cmake ${CMAKE_CURRENT_BINARY_DIR}/fms-config.cmake DESTINATION ${CONFIG_INSTALL_DESTINATION}) write_basic_package_version_file( @@ -393,3 +906,34 @@ install(EXPORT FMSExports NAMESPACE FMS:: FILE fms-targets.cmake DESTINATION ${CONFIG_INSTALL_DESTINATION}) + +# pkgconf +set(prefix ${CMAKE_INSTALL_PREFIX}) +set(exec_prefix ${CMAKE_INSTALL_PREFIX}) +set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) +set(includedir ${CMAKE_INSTALL_PREFIX}/${includeDir}) + +set(CC ${CMAKE_C_COMPILER}) +set(FC ${CMAKE_Fortran_COMPILER}) +set(CFLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}}") +set(CPPFLAGS "${CMAKE_CPP_FLAGS} ${CMAKE_CPP_FLAGS_${CMAKE_BUILD_TYPE}}") +set(FCFLAGS "${CMAKE_Fortran_FLAGS} ${CMAKE_Fortran_FLAGS_${CMAKE_BUILD_TYPE}}") +set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS_${CMAKE_BUILD_TYPE}}") + +set(VERSION ${PROJECT_VERSION}) + +# TODO: If FMS depends on a library that is built as a static library, it +# should be listed here as an ldflag. +set(LIBS "") + +if(NOT ${NetCDF_Fortran_LIBRARY_SHARED}) + # autotools: Libs.private: -lnetcdff -lnetcdf + string(APPEND LIBS ${NetCDF_Fortran_LIBRARIES}) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/FMS.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/FMS.pc @ONLY) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FMS.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT utilities) diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 0db572c07e..254b1db41c 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -10,10 +10,15 @@ * A copy of the [Gnu Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.en.html) must be included at the top of each file. * Documentation should be written so that it can be parsed by [Doxygen](http://www.doxygen.nl/). +* Files, modules, and public routines should be prefixed with `fms_subfolder_` where `subfolder` is + the name of the subfolder that the file is in. Shorten subfolder names (ie diag) are acceptable + as long as they are consistent within the subfolder. * All variables should be defined, and include units. Unit-less variables should be marked `unitless` * Provide detailed descriptions of modules, interfaces, functions, and subroutines * Define all function/subroutine arguments, and function results (see below) * Follow coding style of the current file, as much as possible. +* Add public routines to be used ouside of FMS to the libFMS.F90 file so they can be accessed + through `use FMS`. ## Fortran @@ -21,8 +26,7 @@ * Use Fortran 95 standard or newer * Two space indentation -* Use `KIND` parameters from intrinsic fortran modules such as iso_fortran_env - or iso_c_binding to ensure portability +* Use `KIND` parameters from platform_mod * Never use implicit variables (i.e., always specify `IMPLICIT NONE`) * Lines must be <= 120 characters long (including comments) * logical, compound logical, and relational if statements may be one line, @@ -43,7 +47,7 @@ * Inline doxygen descriptions for all member variables. ## Functions -* If a function has a result variable, it should be declared on its own line, +* If a function has a result variable, it should be declared on its own line, and the variable should not be declared with a specific intent. * Inline doxygen descriptions for all arguments, except the result variable. * Doxygen description on the line(s) before the function definition. This must @@ -60,9 +64,37 @@ all shared and private variables. * All critical sections must have a unique name. +## Precision +* Precision of all real arguments are explicitly defined as `real(kind=r4_kind)`, + `real(kind=r8_kind)`, or as any other precision parameters defined in platform_mod. +* The precision of real numerical values should be consistent with the precision + of the associated variable. For example, if the variable `a` has been declared + as r8_kind, then `a=1.4_r8_kind` is acceptable. The following, a=1.4 and a=(1.4,kind=r8_kind), + are not acceptable since the numerical value of 1.4 will be represented in the default precision + set by the compiler. +* The precision of integers do not need to be explicitly defined and can be determined at compile time. +* If the precision of integers are explicitly defined, they are defined with the precision parameters, + _e.g._ i4_kind, i8_kind, found in platform_mod. + +## Macros +* All letters in the macro names are capitalized +* All macro names end with an underscore "_" +* All precision related macro names start with the letters "FMS" +* Macro names should be unique to each module. For example, + `FMS_AU_KIND_` is used in axis_utils_mod. + `FMS_HI_KIND_` is used in horiz_interp_mod + +## .fh and .inc files +* The .fh header files contain macro definitions. +* If the .fh files contain mainly precision related macro definitions, the files + should be named with `_r4.fh` and `_r8.fh` extensions in the include subdirectory found + in the module directory. These .fh files are `#include`-ed at the end of the .F90 module files. +* For precision related .inc files, the .inc files contain the procedure definitions and are + `#include`-ed at the end of both *_r4.fh and *_f8.fh files. These .inc files are located in the + same include subdirectory as the .fh files. See below for details. ## Fortran Example -```Fortran +```Fortran ./fms_subfolder_example.F90 file !*********************************************************************** !* GNU Lesser General Public License @@ -88,46 +120,166 @@ !! @author !! @email gfdl.climate.model.info@noaa.gov -module example_mod - use, intrinsic :: iso_fortran_env, only: INT32, REAL32 +module fms_subfolder_example_mod + use platform_mod, only r4_kind, r8_kind, i4_kind, i8_kind use util_mod, only: util_func1 implicit none private - public :: sub1 - public :: func1 + public :: fms_subfolder_example_sub1 + public :: fms_subfolder_example_func1 + public :: fms_subfolder_example_ex_subroutine + + interface fms_subfolder_example_ex_subroutine !< generic interface block. When the user + module procedure ex_subroutine_r4 !! calls ex_subroutine, the compiler checks + module procedure ex_subroutine_r8 !! the input arguments and invokes either + end interface ex_subroutine !! ex_subroutine_r4 or ex_subroutine_r8 + !! ex_subroutine_r4/8 are generated by the preprocessor + !! which requires example_r4.fh, example_r8.fh, and + !! example.inc files !> @brief Doxygen description of type. - type,public :: CustomType + type,public :: fms_subfolder_example_CustomType private - integer(kind=INT32) :: a_var !< Inline doxygen description. - real(kind=REAL32),dimension(:),allocatable :: b_arr !< long description - !! continued on - !! multiple lines. - endtype CustomType + integer(kind=i4_kind) :: a_var !< Inline doxygen description. + real(kind=r8_kind),dimension(:),allocatable :: b_arr !< long description + !! continued on + !! multiple lines. + endtype fms_subfolder_example_CustomType contains !> @brief Doxygen description. - subroutine sub1(arg1, & - & arg2, & + subroutine fms_subfolder_example_sub1(arg1, arg2, & & arg3) - real(kind=REAL32),intent(in) :: arg1 !< Inline doxygen description. - integer(kind=INT32),intent(inout) :: arg2 !< Inline doxygen description. + real(kind=r4_kind),intent(in) :: arg1 !< Inline doxygen description. + integer(kind=i8_kind),intent(inout) :: arg2 !< Inline doxygen description. character(len=*),intent(inout) :: arg3 !< Long inline doxygen !! description. - end subroutine sub1 + + arg1=2.456_r4_kind + end subroutine fms_subfolder_example_sub1 !> @brief Doxygen description !! @return Function return value. - function func1(arg1, arg2) result(res) - integer(kind=INT32),intent(in) :: arg1 !< Inline doxygen description - integer(kind=INT32),intent(in) :: arg2 !< Inline doxygen description - integer(kind=INT32) :: res - end function func1 + function fms_subfolder_example_ func1(arg1, arg2) result(res) + integer(kind=i4_kind),intent(in) :: arg1 !< Inline doxygen description + integer(kind=i4_kind),intent(in) :: arg2 !< Inline doxygen description + integer(kind=r8_kind) :: res + + res=real(arg1,r8_kind) * 3.14_r8_kind + end function fms_subfolder_example_func1 +#include "example_r4.fh" !< These two header file contains the macro definition +#include "example_r8.fh" !! and an "#include example.inc" where the procedure + !! is defined. See below. end module example_mod ``` +```Fortran ./include/example_r4.fh file +!*********************************************************************** +!* GNU Lesser General Public License +!* +!* This file is part of the GFDL Flexible Modeling System (FMS). +!* +!* FMS is free software: you can redistribute it and/or modify it under +!* the terms of the GNU Lesser General Public License as published by +!* the Free Software Foundation, either version 3 of the License, or (at +!* your option) any later version. +!* +!* FMS is distributed in the hope that it will be useful, but WITHOUT +!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +!* for more details. +!* +!* You should have received a copy of the GNU Lesser General Public +!* License along with FMS. If not, see . +!*********************************************************************** + +!> @file +!! @brief Example _r4.fh file containing macro definitions +!! @author +!! @email gfdl.climate.model.info@noaa.gov + +#undef FMS_EX_KIND_ +#define FMS_EX_KIND_ r4_kind + +#undef EX_SUBROUTINE_ +#define EX_SUBROUTINE_ ex_subroutine_r4 + +#include "example.inc" !< example.inc file contains the procedure definition +``` +```Fortran ./include/example_r8.fh file +!*********************************************************************** +!* GNU Lesser General Public License +!* +!* This file is part of the GFDL Flexible Modeling System (FMS). +!* +!* FMS is free software: you can redistribute it and/or modify it under +!* the terms of the GNU Lesser General Public License as published by +!* the Free Software Foundation, either version 3 of the License, or (at +!* your option) any later version. +!* +!* FMS is distributed in the hope that it will be useful, but WITHOUT +!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +!* for more details. +!* +!* You should have received a copy of the GNU Lesser General Public +!* License along with FMS. If not, see . +!*********************************************************************** + +!> @file +!! @brief Example file _r8.fh file containing macro definitions +!! @author +!! @email gfdl.climate.model.info@noaa.gov + +#undef FMS_EX_KIND_ +#define FMS_EX_KIND_ r8_kind + +#undef EX_SUBROUTINE_ +#define EX_SUBROUTINE_ ex_subroutine_r8 + +#include "example.inc" !< example.inc file contains the procedure definition +``` +``` Fortran ./include/example.inc file +!*********************************************************************** +!* GNU Lesser General Public License +!* +!* This file is part of the GFDL Flexible Modeling System (FMS). +!* +!* FMS is free software: you can redistribute it and/or modify it under +!* the terms of the GNU Lesser General Public License as published by +!* the Free Software Foundation, either version 3 of the License, or (at +!* your option) any later version. +!* +!* FMS is distributed in the hope that it will be useful, but WITHOUT +!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +!* for more details. +!* +!* You should have received a copy of the GNU Lesser General Public +!* License along with FMS. If not, see . +!*********************************************************************** +!> @file +!! @brief Example .inc file containing subroutine definitions/declarations +!! @author +!! @email gfdl.climate.model.info@noaa.gov + +!> The macro EX_SUBROUTINE_ gets replaced by the preprocessor +!! as ex_subroutine_r4 (as defined in the example_r4.fh file) and +!! as ex_subroutine r8 (as defined in the example_r8.fh file) + +subroutine EX_SUBROUTINE_(arg1, arg2, arg3) + real(FMS_EX_KIND_), intent(in) :: arg2 !< FMS_EX_KIND_ gets replaced by the preprocessor + real(FMS_EX_KIND_), intent(out) :: arg1 !< FMS_EX_KIND_ gets replaced by the preprocessor + integer(i4_kind) :: arg3 + integer, parameter :: lkind=FMS_EX_KIND_ !< kind parameter local to the subroutine + + arg1 = arg2 / 4.0_lkind !< GCC does not like 4.0_FMS_EX_KIND_. Thus, the + !! parameter lkind is declared and used. + +end subroutine EX_SUBROUTINE_ +``` ## C/C++ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ad061ba1c..185024c5db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,9 @@ Thank you for taking time to contribute. -libFMS is a software framework for supporting the efficient development, +libFMS is a software framework for supporting the **seamless** development, construction, execution, and scientific interpretation of atmospheric, oceanic, -and climate system models. +and land models across timescales. Contributions to this project are released to the public under the [projects open source license](LICENSE.md). @@ -94,9 +94,36 @@ long-term, portability, and the scope of the impact on the code base. Therefore, Modeling Systems does not guarantee that all pull requests will be accepted, even if the changes pass the initial testing phases, and are otherwise correct. +## Reviewing Pull Requests + +When reviewing a pull request, members of MSD should look for the following: + +- Design + - Does the code change belong in the FMS library or does it better belong elsewhere such as a component repository or the FMScoupler? + - Could existing routines/modules be utilized to reduce redundancy? + - Temporary changes/fixes meant to be removed should be avoided whenever possible +- Functionality + - Does this PR do what is intended (and stated) + - Are the changes good for both end-users and developers? + - Will the code change impact existing end-users needlessly? +- Complexity + - Are the changes easily understood by the reader / reviewer? +- Testing + - Code changes should include a test program or a modification to a test program to ensure the code is covered by the test suite +- Comments + - Inline comments for complex code segments or intricacies to make the purpose of the code reasonably clear +- Style and Consistency + - Code should follow the syle guide in general, but should also be consistent to the file the change is made in +- Documentation + - If a PR changes the behaviour or instructions, accompanying documentation should also change +- Thoroughness + - Reviews should be done line by line, and the surrounding context/file should be taken into account + +Comments on pull requests should be courteous and constructive, giving useful feedback and explanations for why changes should be made. See the [code of conduct](CODE_OF_CONDUCT.md) for more information. + ## Tests -FMS uses TravisCI and gitlab-CI to run build tests for libFMS. Users may create +FMS uses github actons to run build tests for libFMS. Users should create unit tests, code coverage tests, and regression tests for new and existing code in yaml (.yml) files. Github provides a guide (https://help.github.com/en/articles/about-continuous-integration) for @@ -111,7 +138,8 @@ Code updates should follow the coding style for the project, contained in Releases will be tagged using the format `yyyy.rr[.pp]`, where `yyyy` is the 4-digit year, `rr` is the 2-digit release number, and `pp` is the 2-digit patch number. Preliminary releases mean for testing (i.e., code that is still under -development) will be marked `yyyy.rr.alpha.pp` or `yyyy.rr.beta.pp`. Alpha tags +development) will be marked `yyyy.rr-alpha#` or `yyyy.rr-beta#` where `#` is +the incremented number of the testing tag. Alpha tags mark code updates that are intended for developers to include in their baseline regression tests to determine whether the code contains bugs not encountered during baseline testing. Beta tags are intended for a wider audience of diff --git a/FMS.pc.in b/FMS.pc.in index bc993b5740..c9f2a96e5c 100644 --- a/FMS.pc.in +++ b/FMS.pc.in @@ -35,7 +35,7 @@ Name: FMS Description: The Flexible Modeling System Infrastructure Library URL: https://www.gfdl.noaa.gov/fms Version: @VERSION@ -Libs: -L$(libdir) -lFMS +Libs: -L${libdir} -lFMS Libs.private: @LIBS@ Cflags: -I${includedir} Fflags: -I${includedir} diff --git a/Makefile.am b/Makefile.am index ffb12344ea..d46b3b6559 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,8 +35,8 @@ endif # Make targets will be run in each subdirectory. Order is significant. SUBDIRS = \ platform \ - tridiagonal \ mpp \ + tridiagonal \ constants \ constants4 \ memutils \ @@ -45,8 +45,9 @@ SUBDIRS = \ mosaic2 \ fms \ parser \ + string_utils \ affinity \ - mosaic \ + grid_utils \ time_manager \ axis_utils \ diag_manager \ @@ -58,6 +59,7 @@ SUBDIRS = \ data_override \ astronomy \ field_manager \ + tracer_manager \ coupler \ diag_integral \ monin_obukhov \ @@ -65,7 +67,6 @@ SUBDIRS = \ amip_interp \ exchange \ topography \ - tracer_manager \ sat_vapor_pres \ random_numbers \ . \ @@ -79,7 +80,7 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = FMS.pc ## Build libFMS module -AM_CPPFLAGS = -I${top_srcdir}/include -I${top_srcdir}/mpp/include +AM_CPPFLAGS = -I${top_srcdir}/include AM_FCFLAGS = $(FC_MODINC). $(FC_MODOUT)$(MODDIR) noinst_LTLIBRARIES = libFMS_mod.la @@ -87,7 +88,7 @@ libFMS_mod_la_SOURCES = libFMS.F90 fms.$(FC_MODEXT): .mods/*_mod.$(FC_MODEXT) -nodist_include_HEADERS = libFMS_mod.la +nodist_include_HEADERS = .mods/fms.$(FC_MODEXT) include $(top_srcdir)/mkmods.mk @@ -125,8 +126,24 @@ check-code-coverage: check .PHONY: check-code-coverage clean-local: - -rm -rf .mods coverage-data coverage-report + -rm -rf .mods coverage-data coverage-report test.nc else clean-local: - -rm -rf .mods + -rm -rf .mods test.nc endif + +install-data-hook: + @echo '' + @echo '+-------------------------------------------------------------+' + @echo '| Congratulations! You have successfully installed the FMS |' + @echo '| Fortran library. |' + @echo '| |' + @echo '| After the installed include and link paths have been |' + @echo '| specified, code using FMS should be compiled using the |' + @echo '| "-lFMS" flag. |' + @echo '| |' + @echo '| FMS is developed and maintained at the GFDL publicly on |' + @echo '| Github. To report an issue or view available documentation, |' + @echo '| please see our page: https://www.github.com/NOAA-GFDL/FMS |' + @echo '+-------------------------------------------------------------+' + @echo '' diff --git a/README.md b/README.md index 857450bd42..323fa43237 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -\page rm General Project Information +# License Announcement + +FMS will switch to the +[Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +with the 2025.04 release scheduled for September 2025. Please refer to +[issue 1683 on changing the license](https://github.com/NOAA-GFDL/FMS/issues/1683) +to participate in or follow any discussion on the topic. + # Modeling Framework: Flexible Modeling System (FMS) Today’s climate models simulate highly complex systems. In response to @@ -103,7 +110,7 @@ Compilers used in our Github continuous integration testing are in bold. Source code documentation for the FMS code base is available at http://noaa-gfdl.github.io/FMS. The documentation is generated by doxygen and updated upon releases, and a copy of the site can be obtained through the `gh-pages` branch or generated manually with -`./configure --enable-docs && make -C docs`. For more information on documentating the code +`./configure --enable-docs && make -C docs`. For more information on documenting the code with doxygen please see the [documentation style guide](http://noaa-gfdl.github.io/FMS/md_docs_doxygenGuide.html). diff --git a/TESTING.md b/TESTING.md index e1e5146094..a414904a72 100644 --- a/TESTING.md +++ b/TESTING.md @@ -24,7 +24,7 @@ This will compile any code not already compiled and then proceed to run the test ### Debugging Output and Test Options Setting the environment variable TEST_VERBOSE will direct output to stdout as the test runs, while setting VERBOSE will only output on failure. -Logs are created for each test as well, with the name \.log in it's corresponding test_fms/ directory. +Logs are created for each test as well, with the name \.log in its corresponding test_fms/ directory. To run an individual test: ``` @@ -43,4 +43,4 @@ for the build system: If enabled a coverage report can be generated with `make check-code-coverage` - `--enable-test-input=/path/to/input` turns on test scripts that require input netcdf files (interpolator, xgrid, data_override). This option is mainly used internally and in automated testing since we do not host the input data publicly. -- `--with-yaml` compile with yaml input and enable it's associated tests +- `--with-yaml` compile with yaml input and enable its associated tests diff --git a/affinity/fms_affinity.F90 b/affinity/fms_affinity.F90 index c02fbc69cf..33a305ebb0 100644 --- a/affinity/fms_affinity.F90 +++ b/affinity/fms_affinity.F90 @@ -91,7 +91,7 @@ subroutine fms_affinity_init() !--- local variables integer:: io_stat integer:: ierr - integer:: unit + integer:: iunit !--- return if module is initialized if (module_is_initialized) return @@ -105,8 +105,8 @@ subroutine fms_affinity_init() !--- output information to logfile call write_version_number("fms_affinity_mod", version) - unit = stdlog() - write(unit,nml=fms_affinity_nml) + iunit = stdlog() + write(iunit,nml=fms_affinity_nml) module_is_initialized = .TRUE. diff --git a/amip_interp/Makefile.am b/amip_interp/Makefile.am index f5358101d4..27f50fcbf8 100644 --- a/amip_interp/Makefile.am +++ b/amip_interp/Makefile.am @@ -23,14 +23,18 @@ # Ed Hartnett 2/22/19 # Include .h and .mod files. -AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/amip_interp/include AM_FCFLAGS = $(FC_MODINC). $(FC_MODOUT)$(MODDIR) # Build this uninstalled convenience library. noinst_LTLIBRARIES = libamip_interp.la # The convenience library depends on its source. -libamip_interp_la_SOURCES = amip_interp.F90 +libamip_interp_la_SOURCES = \ + amip_interp.F90 \ + include/amip_interp.inc \ + include/amip_interp_r4.fh \ + include/amip_interp_r8.fh BUILT_SOURCES = amip_interp_mod.$(FC_MODEXT) nodist_include_HEADERS = amip_interp_mod.$(FC_MODEXT) diff --git a/amip_interp/amip_interp.F90 b/amip_interp/amip_interp.F90 index 98914feaa3..876ee0f5c9 100644 --- a/amip_interp/amip_interp.F90 +++ b/amip_interp/amip_interp.F90 @@ -135,10 +135,11 @@ module amip_interp_mod NOTE, mpp_error, fms_error_handler use constants_mod, only: TFREEZE, pi -use platform_mod, only: R4_KIND, I2_KIND +use platform_mod, only: r4_kind, r8_kind, i2_kind, FMS_FILE_LEN use mpp_mod, only: input_nml_file use fms2_io_mod, only: FmsNetcdfFile_t, fms2_io_file_exists=>file_exists, open_file, close_file, & get_dimension_size, fms2_io_read_data=>read_data +use netcdf, only: NF90_MAX_NAME implicit none private @@ -147,17 +148,16 @@ module amip_interp_mod !----------------- Public interfaces ----------------------------------- public amip_interp_init, get_amip_sst, get_amip_ice, amip_interp_new, & - amip_interp_del, amip_interp_type, assignment(=) + & amip_interp_del, amip_interp_type, assignment(=) !----------------------------------------------------------------------- !----------------- Public Data ----------------------------------- integer :: i_sst = 1200 integer :: j_sst = 600 -real, parameter:: big_number = 1.E30 +real(r8_kind), parameter:: big_number = 1.E30_r8_kind logical :: forecast_mode = .false. -real, allocatable, dimension(:,:) :: sst_ncep, sst_anom -public i_sst, j_sst, sst_ncep, sst_anom, forecast_mode, use_ncep_sst +public i_sst, j_sst, forecast_mode, use_ncep_sst !----------------------------------------------------------------------- !--------------------- private below here ------------------------------ @@ -167,9 +167,8 @@ module amip_interp_mod ! Include variable "version" to be written to log file. #include - real, allocatable:: temp1(:,:), temp2(:,:) ! add by JHC - real, allocatable, dimension(:,:) :: tempamip + real(r8_kind), allocatable, dimension(:,:) :: tempamip ! end add by JHC !----------------------------------------------------------------------- !------ private defined data type -------- @@ -186,7 +185,7 @@ module amip_interp_mod !> Assignment overload to allow native assignment between amip_interp_type variables. !> @ingroup amip_interp_mod interface assignment(=) - module procedure amip_interp_type_eq + module procedure amip_interp_type_eq end interface !> Private logical equality overload for amip_interp_type @@ -207,6 +206,15 @@ module amip_interp_mod module procedure date_gt end interface +!> Retrieve sea surface temperature data and interpolated grid +interface get_amip_sst + module procedure get_amip_sst_r4, get_amip_sst_r8 +end interface + +!> AMIP interpolation for ice +interface get_amip_ice + module procedure get_amip_ice_r4, get_amip_ice_r8 +end interface !> Initializes data needed for the horizontal !! interpolation between the sst data and model grid. @@ -265,23 +273,24 @@ module amip_interp_mod !! !> @ingroup amip_interp_mod interface amip_interp_new - module procedure amip_interp_new_1d - module procedure amip_interp_new_2d + module procedure amip_interp_new_1d_r4, amip_interp_new_1d_r8 + module procedure amip_interp_new_2d_r4, amip_interp_new_2d_r8 end interface - !----- public data type ------ -!> @brief Contains information needed by the interpolation module (exchange_mod) and buffers data. +!> @brief Contains information needed by the interpolation module (exchange_mod) and buffers +!! data (r4_kind flavor). !> @ingroup amip_interp_mod type amip_interp_type private - type (horiz_interp_type) :: Hintrp, Hintrp2 ! add by JHC - real, allocatable :: data1(:,:), data2(:,:) - type (date_type) :: Date1, Date2 - logical :: use_climo, use_annual - logical :: I_am_initialized=.false. -end type + type (horiz_interp_type) :: Hintrp, Hintrp2 ! add by JHC + real(r4_kind), dimension(:,:), allocatable :: data1_r4, data2_r4 + real(r8_kind), dimension(:,:), allocatable :: data1_r8, data2_r8 + type (date_type) :: Date1, Date2 + logical :: use_climo, use_annual + logical :: I_am_initialized=.false. +end type amip_interp_type !> @addtogroup amip_interp_mod !> @{ @@ -289,20 +298,19 @@ module amip_interp_mod ! ---- resolution/grid variables ---- integer :: mobs, nobs - real, allocatable :: lon_bnd(:), lat_bnd(:) + real(r8_kind), allocatable, dimension(:) :: lon_bnd, lat_bnd ! ---- global unit & date ---- - integer, parameter :: maxc = 128 - integer :: unit - character(len=maxc) :: file_name_sst, file_name_ice + integer :: iunit + character(len=FMS_FILE_LEN) :: file_name_sst, file_name_ice type(FmsNetcdfFile_t), target :: fileobj_sst, fileobj_ice type (date_type) :: Curr_date = date_type( -99, -99, -99 ) type (date_type) :: Date_end = date_type( -99, -99, -99 ) - real :: tice_crit_k - integer(I2_KIND) :: ice_crit + real(r8_kind) :: tice_crit_k + integer(i2_kind) :: ice_crit logical :: module_is_initialized = .false. @@ -316,19 +324,19 @@ module amip_interp_mod character(len=16) :: date_out_of_range = 'fail' !< use 'fail', 'initclimo', or 'climo' - real :: tice_crit = -1.80 !< in degC or degK + real(r8_kind) :: tice_crit = -1.80_r8_kind !< in degC or degK integer :: verbose = 0 !< 0 <= verbose <= 3 logical :: use_zonal = .false. !< parameters for prescribed zonal sst option - real :: teq = 305. !< parameters for prescribed zonal sst option - real :: tdif = 50. !< parameters for prescribed zonal sst option - real :: tann = 20. !< parameters for prescribed zonal sst option - real :: tlag = 0.875 !< parameters for prescribed zonal sst option + real(r8_kind) :: teq = 305._r8_kind !< parameters for prescribed zonal sst option + real(r8_kind) :: tdif = 50._r8_kind !< parameters for prescribed zonal sst option + real(r8_kind) :: tann = 20._r8_kind !< parameters for prescribed zonal sst option + real(r8_kind) :: tlag = 0.875_r8_kind !< parameters for prescribed zonal sst option integer :: amip_date(3)=(/-1,-1,-1/) !< amip date for repeating single day (rsd) option - real :: sst_pert = 0. !< global temperature perturbation used for sensitivity experiments + real(r8_kind) :: sst_pert = 0._r8_kind !< global temperature perturbation used for sensitivity experiments character(len=6) :: sst_pert_type = 'fixed' !< use 'random' or 'fixed' logical :: do_sst_pert = .false. @@ -356,508 +364,9 @@ module amip_interp_mod contains -! modified by JHC -!> Retrieve sea surface temperature data and interpolated grid -subroutine get_amip_sst (Time, Interp, sst, err_msg, lon_model, lat_model) - - type (time_type), intent(in) :: Time !< Time to interpolate - type (amip_interp_type), intent(inout) :: Interp !< Holds data for interpolation - real, intent(out) :: sst(:,:) !< Sea surface temperature data - character(len=*), optional, intent(out) :: err_msg !< Holds error message string if present - - real, dimension(mobs,nobs) :: sice - - integer :: year1, year2, month1, month2 - real :: fmonth - type (date_type) :: Date1, Date2, Udate1, Udate2 - - type(time_type) :: Amip_Time - integer :: tod(3),dum(3) - -! add by JHC - real, intent(in), dimension(:,:), optional :: lon_model, lat_model - real :: pert - integer :: i, j, mobs_sst, nobs_sst - integer :: jhctod(6) - type (time_type) :: Udate - character(len=4) :: yyyy - integer :: nrecords, ierr, k, yr, mo, dy - integer, dimension(:), allocatable :: ryr, rmo, rdy - character(len=30) :: time_unit - real, dimension(:), allocatable :: timeval - character(len=maxc) :: ncfilename - type(FmsNetcdfFile_t) :: fileobj - logical :: the_file_exists -! end add by JHC - logical, parameter :: DEBUG = .false. !> switch for debugging output - !> These are fms_io specific - integer :: unit - - if(present(err_msg)) err_msg = '' - if(.not.Interp%I_am_initialized) then - if(fms_error_handler('get_amip_sst','The amip_interp_type variable is not initialized',err_msg)) return - endif - -!----------------------------------------------------------------------- -!----- compute zonally symetric sst --------------- - - if ( use_ncep_sst .and. forecast_mode ) no_anom_sst = .false. - - if (all(amip_date>0)) then - call get_date(Time,dum(1),dum(2),dum(3),tod(1),tod(2),tod(3)) - Amip_Time = set_date(amip_date(1),amip_date(2),amip_date(3),tod(1),tod(2),tod(3)) - else - Amip_Time = Time - endif - -! add by JHC -if ( .not.use_daily ) then -! end add by JHC - - if ( .not. allocated(temp1) ) allocate (temp1(mobs,nobs)) - if ( .not. allocated(temp2) ) allocate (temp2(mobs,nobs)) - - if (use_zonal) then - call zonal_sst (Amip_Time, sice, temp1) - call horiz_interp ( Interp%Hintrp, temp1, sst ) - else - -!----------------------------------------------------------------------- -!---------- get new observed sea surface temperature ------------------- - -! ---- time interpolation for months ----- - call time_interp (Amip_Time, fmonth, year1, year2, month1, month2) -! ---- force climatology ---- - if (Interp % use_climo) then - year1=0; year2=0 - endif - if (Interp % use_annual) then - year1=0; year2=0 - month1=0; month2=0 - endif -! --------------------------- - - Date1 = date_type( year1, month1, 0 ) - Date2 = date_type( year2, month2, 0 ) - -! -- open/rewind file -- - unit = -1 -!----------------------------------------------------------------------- - - - if (Date1 /= Interp % Date1) then -! ---- use Date2 for Date1 ---- - if (Date1 == Interp % Date2) then - Interp % Date1 = Interp % Date2 - Interp % data1 = Interp % data2 - temp1(:,:) = temp2(:,:) ! SJL BUG fix: June 24, 2011 - else - call read_record ('sst', Date1, Udate1, temp1) - if ( use_ncep_sst .and. (.not. no_anom_sst) ) then - temp1(:,:) = temp1(:,:) + sst_anom(:,:) - endif - call horiz_interp ( Interp%Hintrp, temp1, Interp%data1 ) - call clip_data ('sst', Interp%data1) - Interp % Date1 = Date1 - endif - endif - -!----------------------------------------------------------------------- - - if (Date2 /= Interp % Date2) then - call read_record ('sst', Date2, Udate2, temp2) - if ( use_ncep_sst .and. (.not. no_anom_sst) ) then - temp2(:,:) = temp2(:,:) + sst_anom(:,:) - endif - call horiz_interp ( Interp%Hintrp, temp2, Interp%data2 ) - call clip_data ('sst', Interp%data2) - Interp % Date2 = Date2 - endif - -!----------------------------------------------------------------------- -!---------- time interpolation (between months) of sst's --------------- -!----------------------------------------------------------------------- - sst = Interp % data1 + fmonth * (Interp % data2 - Interp % data1) - -!------------------------------------------------------------------------------- -! SJL mods for NWP and TCSF --- -! Nudging runs: (Note: NCEP SST updated only every 6-hr) -! Compute SST anomaly from global SST datasets for subsequent forecast runs -!------------------------------------------------------------------------------- - if ( use_ncep_sst .and. no_anom_sst ) then - sst_anom(:,:) = sst_ncep(:,:) - (temp1(:,:) + fmonth*(temp2(:,:) - temp1(:,:)) ) - call horiz_interp ( Interp%Hintrp, sst_ncep, sst ) - call clip_data ('sst', sst) - endif - -!! DEBUG CODE - if (DEBUG) then - call get_date(Amip_Time,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) - if (mpp_pe() == 0) then - write (*,200) 'JHC: use_daily = F, AMIP_Time: ',jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5), & - & jhctod(6) - write (*,300) 'JHC: use_daily = F, interped SST: ', sst(1,1),sst(5,5),sst(10,10) - endif - endif - - - endif - -! add by JHC -else - call get_date(Amip_Time,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) - if (mpp_pe() == mpp_root_pe()) write(*,200) 'amip_interp_mod: use_daily = T, Amip_Time = ',jhctod(1), & - & jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6) - - yr = jhctod(1); mo = jhctod(2); dy = jhctod(3) - - write (yyyy,'(i4)') jhctod(1) - - file_name_sst = 'INPUT/' // 'sst.day.mean.'//yyyy//'.v2.nc' - ncfilename = trim(file_name_sst) - time_unit = 'days since 1978-01-01 00:00:00' - - mobs_sst = 1440; nobs_sst = 720 - - call set_sst_grid_edges_daily(mobs_sst, nobs_sst) - call horiz_interp_new ( Interp%Hintrp2, lon_bnd, lat_bnd, & - lon_model, lat_model, interp_method="bilinear" ) - - the_file_exists = fms2_io_file_exists(ncfilename) - - if ( (.NOT. the_file_exists) ) then - call mpp_error ('amip_interp_mod', & - 'cannot find daily SST input data file: '//trim(ncfilename), NOTE) - else - if (mpp_pe() == mpp_root_pe()) call mpp_error ('amip_interp_mod', & - 'Reading NetCDF formatted daily SST from: '//trim(ncfilename), NOTE) - - if(.not. open_file(fileobj, trim(ncfilename), 'read')) & - call error_mesg ('get_amip_sst', 'Error in opening file '//trim(ncfilename), FATAL) - - call get_dimension_size(fileobj, 'TIME', nrecords) - if (nrecords < 1) call mpp_error('amip_interp_mod', & - 'Invalid number of SST records in daily SST data file: '//trim(ncfilename), FATAL) - allocate(timeval(nrecords), ryr(nrecords), rmo(nrecords), rdy(nrecords)) - call fms2_io_read_data(fileobj, 'TIME', timeval) -!!! DEBUG CODE - if(DEBUG) then - if (mpp_pe() == 0) then - print *, 'JHC: nrecords = ', nrecords - print *, 'JHC: TIME = ', timeval - endif - endif - - ierr = 1 - do k = 1, nrecords - - Udate = get_cal_time (timeval(k), time_unit, 'julian') - call get_date(Udate,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) - ryr(k) = jhctod(1); rmo(k) = jhctod(2); rdy(k) = jhctod(3) - - if ( yr == ryr(k) .and. mo == rmo(k) .and. dy == rdy (k) ) ierr = 0 - if (ierr==0) exit - - enddo - - if(DEBUG) then - if (mpp_pe() == 0) then - print *, 'JHC: k =', k - print *, 'JHC: ryr(k) rmo(k) rdy(k)',ryr(k), rmo(k), rdy(k) - print *, 'JHC: yr mo dy ',yr, mo, dy - endif - endif - - if (ierr .ne. 0) call mpp_error('amip_interp_mod', & - 'Model time is out of range not in SST data: '//trim(ncfilename), FATAL) - endif ! if(file_exist(ncfilename)) - - - !---- read NETCDF data ---- - if ( .not. allocated(tempamip) ) allocate (tempamip(mobs_sst,nobs_sst)) - - if (the_file_exists) then - call fms2_io_read_data(fileobj, 'SST', tempamip, unlim_dim_level=k) - call close_file(fileobj) - tempamip = tempamip + TFREEZE - -!!! DEBUG CODE - if(DEBUG) then - if (mpp_pe() == 0) then - print*, 'JHC: TFREEZE = ', TFREEZE - print*, lbound(sst) - print*, ubound(sst) - print*, lbound(tempamip) - print*, ubound(tempamip) - write(*,300) 'JHC: tempamip : ', tempamip(100,100), tempamip(200,200), tempamip(300,300) - endif - endif - - call horiz_interp ( Interp%Hintrp2, tempamip, sst ) - call clip_data ('sst', sst) - - endif - - if(DEBUG) then - if (mpp_pe() == 400) then - write(*,300)'JHC: use_daily = T, daily SST: ', sst(1,1),sst(5,5),sst(10,10) - print *,'JHC: use_daily = T, daily SST: ', sst - endif - endif - -200 format(a35, 6(i5,1x)) -300 format(a35, 3(f7.3,2x)) - -endif -! end add by JHC - -! add by JHC: add on non-zero sea surface temperature perturbation (namelist option) -! This perturbation may be useful in accessing model sensitivities - - if ( do_sst_pert ) then - - if ( trim(sst_pert_type) == 'fixed' ) then - sst = sst + sst_pert - else if ( trim(sst_pert_type) == 'random' ) then - call random_seed() - - if(DEBUG) then - if (mpp_pe() == 0) then - print*, 'mobs = ', mobs - print*, 'nobs = ', nobs - print*, lbound(sst) - print*, ubound(sst) - endif - endif - - do i = 1, size(sst,1) - do j = 1, size(sst,2) - call random_number(pert) - sst (i,j) = sst (i,j) + sst_pert*((pert-0.5)*2) - end do - end do - endif - - endif -! end add by JHC - -!----------------------------------------------------------------------- - - end subroutine get_amip_sst - -!> AMIP interpolation for ice -subroutine get_amip_ice (Time, Interp, ice, err_msg) - - type (time_type), intent(in) :: Time !< Time to interpolate - type (amip_interp_type), intent(inout) :: Interp !< Holds data for interpolation - real, intent(out) :: ice(:,:) !< ice data - character(len=*), optional, intent(out) :: err_msg !< Holds error message string if present - - real, dimension(mobs,nobs) :: sice, temp - - integer :: year1, year2, month1, month2 - real :: fmonth - type (date_type) :: Date1, Date2, Udate1, Udate2 - - type(time_type) :: Amip_Time - integer :: tod(3),dum(3) - - if(present(err_msg)) err_msg = '' - if(.not.Interp%I_am_initialized) then - if(fms_error_handler('get_amip_ice','The amip_interp_type variable is not initialized',err_msg)) return - endif - -!----------------------------------------------------------------------- -!----- compute zonally symetric sst --------------- - - - if (any(amip_date>0)) then - - call get_date(Time,dum(1),dum(2),dum(3),tod(1),tod(2),tod(3)) - - Amip_Time = set_date(amip_date(1),amip_date(2),amip_date(3),tod(1),tod(2),tod(3)) - - else - - Amip_Time = Time - - endif - - -if (use_zonal) then - call zonal_sst (Amip_Time, sice, temp) - call horiz_interp ( Interp%Hintrp, sice, ice ) -else - -!----------------------------------------------------------------------- -!---------- get new observed sea surface temperature ------------------- - -! ---- time interpolation for months ----- - - call time_interp (Amip_Time, fmonth, year1, year2, month1, month2) - -! ---- force climatology ---- - if (Interp % use_climo) then - year1=0; year2=0 - endif - if (Interp % use_annual) then - year1=0; year2=0 - month1=0; month2=0 - endif -! --------------------------- - - Date1 = date_type( year1, month1, 0 ) - Date2 = date_type( year2, month2, 0 ) - - unit = -1 -!----------------------------------------------------------------------- - - if (Date1 /= Interp % Date1) then -! ---- use Date2 for Date1 ---- - if (Date1 == Interp % Date2) then - Interp % Date1 = Interp % Date2 - Interp % data1 = Interp % data2 - else -!-- SJL ------------------------------------------------------------- -! Can NOT use ncep_sst to determine sea_ice For seasonal forecast -! Use climo sea ice for seasonal runs - if ( use_ncep_sst .and. use_ncep_ice ) then - where ( sst_ncep <= (TFREEZE+tice_crit) ) - sice = 1. - elsewhere - sice = 0. - endwhere - else - call read_record ('ice', Date1, Udate1, sice) - endif -!-------------------------------------------------------------------- - call horiz_interp ( Interp%Hintrp, sice, Interp%data1 ) - call clip_data ('ice', Interp%data1) - Interp % Date1 = Date1 - endif - endif - -!----------------------------------------------------------------------- - - if (Date2 /= Interp % Date2) then - -!-- SJL ------------------------------------------------------------- - if ( use_ncep_sst .and. use_ncep_ice ) then - where ( sst_ncep <= (TFREEZE+tice_crit) ) - sice = 1. - elsewhere - sice = 0. - endwhere - else - call read_record ('ice', Date2, Udate2, sice) - endif -!-------------------------------------------------------------------- - call horiz_interp ( Interp%Hintrp, sice, Interp%data2 ) - call clip_data ('ice', Interp%data2) - Interp % Date2 = Date2 - - endif - -!----------------------------------------------------------------------- -!---------- time interpolation (between months) ------------------------ -!----------------------------------------------------------------------- - - ice = Interp % data1 + fmonth * (Interp % data2 - Interp % data1) - -endif - -!----------------------------------------------------------------------- - - end subroutine get_amip_ice - -!####################################################################### - - !> @return A newly created @ref amip_interp_type - function amip_interp_new_1d ( lon , lat , mask , use_climo, use_annual, & - interp_method ) result (Interp) - - real, intent(in), dimension(:) :: lon, lat - logical, intent(in), dimension(:,:) :: mask - character(len=*), intent(in), optional :: interp_method - logical, intent(in), optional :: use_climo, use_annual - - type (amip_interp_type) :: Interp - - if(.not.module_is_initialized) call amip_interp_init - - Interp % use_climo = .false. - if (present(use_climo)) Interp % use_climo = use_climo - Interp % use_annual = .false. - if (present(use_annual)) Interp % use_annual = use_annual - - if ( date_out_of_range == 'fail' .and. Interp%use_climo ) & - call error_mesg ('amip_interp_new_1d', 'use_climo mismatch', FATAL) - - if ( date_out_of_range == 'fail' .and. Interp%use_annual ) & - call error_mesg ('amip_interp_new_1d', 'use_annual(climo) mismatch', FATAL) - - Interp % Date1 = date_type( -99, -99, -99 ) - Interp % Date2 = date_type( -99, -99, -99 ) - -!----------------------------------------------------------------------- -! ---- initialization of horizontal interpolation ---- - - call horiz_interp_new ( Interp%Hintrp, lon_bnd, lat_bnd, & - lon, lat, interp_method= interp_method ) - - allocate ( Interp % data1 (size(lon(:))-1,size(lat(:))-1), & - Interp % data2 (size(lon(:))-1,size(lat(:))-1) ) - - Interp%I_am_initialized = .true. - - end function amip_interp_new_1d - - !> @return A newly created @ref amip_interp_type - function amip_interp_new_2d ( lon , lat , mask , use_climo, use_annual, & - interp_method ) result (Interp) - - real, intent(in), dimension(:,:) :: lon, lat - logical, intent(in), dimension(:,:) :: mask - character(len=*), intent(in), optional :: interp_method - logical, intent(in), optional :: use_climo, use_annual - - type (amip_interp_type) :: Interp - - if(.not.module_is_initialized) call amip_interp_init - - Interp % use_climo = .false. - if (present(use_climo)) Interp % use_climo = use_climo - Interp % use_annual = .false. - if (present(use_annual)) Interp % use_annual = use_annual - - if ( date_out_of_range == 'fail' .and. Interp%use_climo ) & - call error_mesg ('amip_interp_new_2d', 'use_climo mismatch', FATAL) - - if ( date_out_of_range == 'fail' .and. Interp%use_annual ) & - call error_mesg ('amip_interp_new_2d', 'use_annual(climo) mismatch', FATAL) - - Interp % Date1 = date_type( -99, -99, -99 ) - Interp % Date2 = date_type( -99, -99, -99 ) - -!----------------------------------------------------------------------- -! ---- initialization of horizontal interpolation ---- - - call horiz_interp_new ( Interp%Hintrp, lon_bnd, lat_bnd, & - lon, lat, interp_method = interp_method) - - allocate ( Interp % data1 (size(lon,1),size(lat,2)), & - Interp % data2 (size(lon,1),size(lat,2))) - - Interp%I_am_initialized = .true. - - end function amip_interp_new_2d - -!####################################################################### - !> initialize @ref amip_interp_mod for use - subroutine amip_interp_init() - - integer :: unit,io,ierr + subroutine amip_interp_init + integer :: iunit,io,ierr !----------------------------------------------------------------------- @@ -871,9 +380,9 @@ subroutine amip_interp_init() ! ----- write namelist/version info ----- call write_version_number("AMIP_INTERP_MOD", version) - unit = stdlog ( ) + iunit = stdlog ( ) if (mpp_pe() == 0) then - write (unit,nml=amip_interp_nml) + write (iunit,nml=amip_interp_nml) endif if (use_mpp_io) then @@ -887,8 +396,10 @@ subroutine amip_interp_init() ! ---- freezing point of sea water in deg K --- tice_crit_k = tice_crit - if ( tice_crit_k < 200. ) tice_crit_k = tice_crit_k + TFREEZE - ice_crit = nint((tice_crit_k-TFREEZE)*100., I2_KIND) + if ( tice_crit_k < 200._r8_kind ) then + tice_crit_k = tice_crit_k + TFREEZE + endif + ice_crit = nint((tice_crit_k-TFREEZE)*100._r8_kind, I2_KIND) ! ---- set up file dependent variable ---- ! ---- global file name ---- @@ -909,7 +420,7 @@ subroutine amip_interp_init() mobs = 360; nobs = 180 call set_sst_grid_edges_oi ! --- specfied min for amip2 --- - tice_crit_k = 271.38 + tice_crit_k = 271.38_r8_kind if (mpp_pe() == 0) & call error_mesg ('amip_interp_init', 'using AMIP 2 sst', NOTE) Date_end = date_type( 1996, 3, 0 ) @@ -919,7 +430,7 @@ subroutine amip_interp_init() mobs = 360; nobs = 180 call set_sst_grid_edges_oi ! --- specfied min for hurrell --- - tice_crit_k = 271.38 + tice_crit_k = 271.38_r8_kind if (mpp_pe() == 0) & call error_mesg ('amip_interp_init', 'using HURRELL sst', NOTE) Date_end = date_type( 2011, 8, 16 ) ! updated by JHC @@ -948,14 +459,6 @@ subroutine amip_interp_init() !--- Added by SJL ---------------------------------------------- if ( use_ncep_sst ) then mobs = i_sst; nobs = j_sst - if (.not. allocated (sst_ncep)) then - allocate (sst_ncep(i_sst,j_sst)) - sst_ncep(:,:) = big_number - endif - if (.not. allocated (sst_anom)) then - allocate (sst_anom(i_sst,j_sst)) - sst_anom(:,:) = big_number - endif else mobs = 360; nobs = 180 endif @@ -991,230 +494,96 @@ subroutine amip_interp_init() if (.not. open_file(fileobj_ice, trim(file_name_ice), 'read')) & call error_mesg ('amip_interp_init', 'Error in opening file '//trim(file_name_ice), FATAL) module_is_initialized = .true. - end subroutine amip_interp_init -!####################################################################### - -!> Frees data associated with a amip_interp_type variable. Should be used for any -!! variables initialized via @ref amip_interp_new. -!> @param[inout] Interp A defined data type variable initialized by amip_interp_new and used -!! when calling get_amip_sst and get_amip_ice. - subroutine amip_interp_del (Interp) - type (amip_interp_type), intent(inout) :: Interp - if(allocated(Interp%data1)) deallocate(Interp%data1) - if(allocated(Interp%data2)) deallocate(Interp%data2) - if(allocated(lon_bnd)) deallocate(lon_bnd) - if(allocated(lat_bnd)) deallocate(lat_bnd) - call horiz_interp_del ( Interp%Hintrp ) - - Interp%I_am_initialized = .false. - - end subroutine amip_interp_del - -!####################################################################### - subroutine set_sst_grid_edges_amip1 - integer :: i, j - real :: hpie, dlon, dlat, wb, sb + real(r8_kind) :: hpie, dlon, dlat, wb, sb - allocate ( lon_bnd(mobs+1), lat_bnd(nobs+1) ) + allocate(lon_bnd(mobs+1)) + allocate(lat_bnd(nobs+1)) ! ---- compute grid edges (do only once) ----- - hpie = 0.5*pi + hpie = pi / 2._r8_kind + + dlon = 4._r8_kind*hpie/real(mobs, r8_kind) + wb = -0.5_r8_kind*dlon - dlon = 4.*hpie/float(mobs); wb = -0.5*dlon do i = 1, mobs+1 - lon_bnd(i) = wb + dlon * float(i-1) + lon_bnd(i) = wb + dlon*real(i-1, r8_kind) enddo - lon_bnd(mobs+1) = lon_bnd(1) + 4.*hpie + lon_bnd(mobs+1) = lon_bnd(1) + 4._r8_kind*hpie + + dlat = 2._r8_kind*hpie/real(nobs-1, r8_kind) + sb = -hpie + 0.5_r8_kind*dlat - dlat = 2.*hpie/float(nobs-1); sb = -hpie + 0.5*dlat - lat_bnd(1) = -hpie; lat_bnd(nobs+1) = hpie + lat_bnd(1) = -hpie + lat_bnd(nobs+1) = hpie do j = 2, nobs - lat_bnd(j) = sb + dlat * float(j-2) + lat_bnd(j) = sb + dlat * real(j-2, r8_kind) enddo - end subroutine set_sst_grid_edges_amip1 -!####################################################################### subroutine set_sst_grid_edges_oi - integer :: i, j - real :: hpie, dlon, dlat, wb, sb + real(r8_kind) :: hpie, dlon, dlat, wb, sb ! add by JHC - if(allocated(lon_bnd)) deallocate(lon_bnd) - if(allocated(lat_bnd)) deallocate(lat_bnd) + if(allocated(lon_bnd)) deallocate(lon_bnd) + if(allocated(lat_bnd)) deallocate(lat_bnd) ! end add by JHC - allocate ( lon_bnd(mobs+1), lat_bnd(nobs+1) ) + + allocate(lon_bnd(mobs+1)) + allocate(lat_bnd(nobs+1)) ! ---- compute grid edges (do only once) ----- - hpie = 0.5*pi + hpie = pi / 2._r8_kind + dlon = 4._r8_kind*hpie/real(mobs, r8_kind) + wb = 0.0_r8_kind - dlon = 4.*hpie/float(mobs); wb = 0.0 - lon_bnd(1) = wb + lon_bnd(1) = wb do i = 2, mobs+1 - lon_bnd(i) = wb + dlon * float(i-1) + lon_bnd(i) = wb + dlon * real(i-1, r8_kind) enddo - lon_bnd(mobs+1) = lon_bnd(1) + 4.*hpie + lon_bnd(mobs+1) = lon_bnd(1) + 4._r8_kind*hpie + + dlat = 2._r8_kind*hpie/real(nobs, r8_kind) + sb = -hpie - dlat = 2.*hpie/float(nobs); sb = -hpie - lat_bnd(1) = sb; lat_bnd(nobs+1) = hpie + lat_bnd(1) = sb + lat_bnd(nobs+1) = hpie do j = 2, nobs - lat_bnd(j) = sb + dlat * float(j-1) + lat_bnd(j) = sb + dlat * real(j-1, r8_kind) enddo - end subroutine set_sst_grid_edges_oi -!####################################################################### -! add by JHC - subroutine set_sst_grid_edges_daily(mobs_sst, nobs_sst) - - integer :: i, j, mobs_sst, nobs_sst - real :: hpie, dlon, dlat, wb, sb - - if(allocated(lon_bnd)) deallocate(lon_bnd) - if(allocated(lat_bnd)) deallocate(lat_bnd) - allocate ( lon_bnd(mobs_sst+1), lat_bnd(nobs_sst+1) ) -! ---- compute grid edges (do only once) ----- +!> Frees data associated with a amip_interp_type variable. Should be used for any +!! variables initialized via @ref amip_interp_new. +!> @param[inout] Interp A defined data type variable initialized by amip_interp_new and used +!! when calling get_amip_sst and get_amip_ice. + subroutine amip_interp_del (Interp) + type (amip_interp_type), intent(inout) :: Interp - hpie = 0.5*pi + if(allocated(Interp%data1_r4)) deallocate(Interp%data1_r4) + if(allocated(Interp%data1_r8)) deallocate(Interp%data1_r8) + if(allocated(Interp%data2_r4)) deallocate(Interp%data2_r4) + if(allocated(Interp%data2_r8)) deallocate(Interp%data2_r8) - dlon = 4.*hpie/float(mobs_sst); wb = 0.0 - lon_bnd(1) = wb - do i = 2, mobs_sst+1 - lon_bnd(i) = wb + dlon * float(i-1) - enddo - lon_bnd(mobs_sst+1) = lon_bnd(1) + 4.*hpie + if(allocated(lon_bnd)) deallocate(lon_bnd) + if(allocated(lat_bnd)) deallocate(lat_bnd) - dlat = 2.*hpie/float(nobs_sst); sb = -hpie - lat_bnd(1) = sb; lat_bnd(nobs_sst+1) = hpie - do j = 2, nobs_sst - lat_bnd(j) = sb + dlat * float(j-1) - enddo + call horiz_interp_del ( Interp%Hintrp ) - end subroutine set_sst_grid_edges_daily -! end add by JHC -!####################################################################### - - - subroutine a2a_bilinear(nx, ny, dat1, n1, n2, dat2) - integer, intent(in):: nx, ny - integer, intent(in):: n1, n2 - real, intent(in) :: dat1(nx,ny) - real, intent(out):: dat2(n1,n2) !> output interpolated data - -! local: - real:: lon1(nx), lat1(ny) - real:: lon2(n1), lat2(n2) - real:: dx1, dy1, dx2, dy2 - real:: xc, yc - real:: a1, b1, c1, c2, c3, c4 - integer i1, i2, jc, i0, j0, it, jt - integer i,j - - -!----------------------------------------------------------- -! * Interpolate from "FMS" 1x1 SST data grid to a finer grid -! lon: 0.5, 1.5, ..., 359.5 -! lat: -89.5, -88.5, ... , 88.5, 89.5 -!----------------------------------------------------------- - - dx1 = 360./real(nx) !> INput Grid - dy1 = 180./real(ny) !> INput Grid - - do i=1,nx - lon1(i) = 0.5*dx1 + real(i-1)*dx1 - enddo - do j=1,ny - lat1(j) = -90. + 0.5*dy1 + real(j-1)*dy1 - enddo - - dx2 = 360./real(n1) !> OutPut Grid: - dy2 = 180./real(n2) !> OutPut Grid: - - do i=1,n1 - lon2(i) = 0.5*dx2 + real(i-1)*dx2 - enddo - do j=1,n2 - lat2(j) = -90. + 0.5*dy2 + real(j-1)*dy2 - enddo - - jt = 1 - do 5000 j=1,n2 - - yc = lat2(j) - if ( yclat1(ny) ) then - jc = ny-1 - b1 = 1. - else - do j0=jt,ny-1 - if ( yc>=lat1(j0) .and. yc<=lat1(j0+1) ) then - jc = j0 - jt = j0 - b1 = (yc-lat1(jc)) / dy1 - go to 222 - endif - enddo - endif -222 continue - - it = 1 - do i=1,n1 - xc = lon2(i) - if ( xc>lon1(nx) ) then - i1 = nx; i2 = 1 - a1 = (xc-lon1(nx)) / dx1 - elseif ( xc=lon1(i0) .and. xc<=lon1(i0+1) ) then - i1 = i0; i2 = i0+1 - it = i0 - a1 = (xc-lon1(i1)) / dx1 - go to 111 - endif - enddo - endif -111 continue - -! Debug code: - if ( a1<-0.001 .or. a1>1.001 .or. b1<-0.001 .or. b1>1.001 ) then - write(*,*) i,j,a1, b1 - call mpp_error(FATAL,'a2a bilinear interpolation') - endif - - c1 = (1.-a1) * (1.-b1) - c2 = a1 * (1.-b1) - c3 = a1 * b1 - c4 = (1.-a1) * b1 - -! Bilinear interpolation: - dat2(i,j) = c1*dat1(i1,jc) + c2*dat1(i2,jc) + c3*dat1(i2,jc+1) + c4*dat1(i1,jc+1) - - enddo !i-loop - -5000 continue ! j-loop - - end subroutine a2a_bilinear - -!####################################################################### + Interp%I_am_initialized = .false. + end subroutine amip_interp_del !> @brief Returns the size (i.e., number of longitude and latitude !! points) of the observed data grid. !! @throws FATAL have not called amip_interp_new !! Must call amip_interp_new before get_sst_grid_size. subroutine get_sst_grid_size (nlon, nlat) - integer, intent(out) :: nlon !> The number of longitude points (first dimension) in the !! observed data grid. For AMIP 1 nlon = 180, and the Reynolds nlon = 360. integer, intent(out) :: nlat !> The number of latitude points (second dimension) in the @@ -1223,179 +592,8 @@ subroutine get_sst_grid_size (nlon, nlat) if ( .not.module_is_initialized ) call amip_interp_init nlon = mobs; nlat = nobs - end subroutine get_sst_grid_size -!####################################################################### - -!> @brief Returns the grid box boundaries of the observed data grid. -!! -!! @throws FATAL, have not called amip_interp_new -!! Must call amip_interp_new before get_sst_grid_boundary. -!! -!! @throws FATAL, invalid argument dimensions -!! The size of the output argument arrays do not agree with -!! the size of the observed data. See the documentation for -!! interfaces get_sst_grid_size and get_sst_grid_boundary. - subroutine get_sst_grid_boundary (blon, blat, mask) - - real, intent(out) :: blon(:) !> The grid box edges (in radians) for longitude points of the - !! observed data grid. The size of this argument must be nlon+1. - real, intent(out) :: blat(:) !> The grid box edges (in radians) for latitude points of the - !! observed data grid. The size of this argument must be nlat+1. - logical, intent(out) :: mask(:,:) - - if ( .not.module_is_initialized ) call amip_interp_init - -! ---- check size of argument(s) ---- - - if (size(blon(:)) /= mobs+1 .or. size(blat(:)) /= nobs+1) & - call error_mesg ('get_sst_grid_boundary in amip_interp_mod', & - 'invalid argument dimensions', FATAL) - -! ---- return grid box edges ----- - - blon = lon_bnd - blat = lat_bnd - -! ---- masking (data exists at all points) ---- - - mask = .true. - - - end subroutine get_sst_grid_boundary - -!####################################################################### - - subroutine read_record (type, Date, Adate, dat) - - character(len=*), intent(in) :: type - type (date_type), intent(in) :: Date - type (date_type), intent(inout) :: Adate - real, intent(out) :: dat(mobs,nobs) - real :: tmp_dat(360,180) - - integer(I2_KIND) :: idat(mobs,nobs) - integer :: nrecords, yr, mo, dy, ierr, k - integer, dimension(:), allocatable :: ryr, rmo, rdy - character(len=maxc) :: ncfilename, ncfieldname - type(FmsNetcdfFile_t), pointer :: fileobj - - !---- set file and field name for NETCDF data sets ---- - - ncfieldname = 'sst' - if(type(1:3) == 'sst') then - ncfilename = trim(file_name_sst) - fileobj => fileobj_sst - else if(type(1:3) == 'ice') then - ncfilename = trim(file_name_ice) - fileobj => fileobj_ice - if (lowercase(trim(data_set)) == 'amip2' .or. & - lowercase(trim(data_set)) == 'hurrell' .or. & - lowercase(trim(data_set)) == 'daily') ncfieldname = 'ice' ! modified by JHC - endif - - dy = 0 ! only processing monthly data - - if (verbose > 2 .and. mpp_pe() == 0) & - print *, 'looking for date = ', Date - - ! This code can handle amip1, reynolds, or reyoi type SST data files in netCDF format - if (mpp_pe() == mpp_root_pe()) call mpp_error ('amip_interp_mod', & - 'Reading NetCDF formatted input data file: '//trim(ncfilename), NOTE) - - call fms2_io_read_data (fileobj, 'nrecords', nrecords) - if (nrecords < 1) call mpp_error('amip_interp_mod', & - 'Invalid number of SST records in SST datafile: '//trim(ncfilename), FATAL) - allocate(ryr(nrecords), rmo(nrecords), rdy(nrecords)) - call fms2_io_read_data(fileobj, 'yr', ryr) - call fms2_io_read_data(fileobj, 'mo', rmo) - call fms2_io_read_data(fileobj, 'dy', rdy) - - ierr = 1 - do k = 1, nrecords - yr = ryr(k); mo = rmo(k) - Adate = date_type( yr, mo, 0) - Curr_date = Adate - if (verbose > 2 .and. mpp_pe() == 0) & - print *, '....... checking ', Adate - if (Date == Adate) ierr = 0 - if (yr == 0 .and. mo == Date%month) ierr = 0 - if (ierr == 0) exit - enddo - if (ierr .ne. 0) call mpp_error('amip_interp_mod', & - 'Model time is out of range not in SST data: '//trim(ncfilename), FATAL) - deallocate(ryr, rmo, rdy) - !PRINT *, 'New SST data: ', k, yr, mo, dy, Date%year, Date%month, Date%day, ryr(1), rmo(1) - - !---- check if climatological data should be used ---- - - if (yr == 0 .or. mo == 0) then - ierr = 0 - if (date_out_of_range == 'fail' ) ierr = 1 - if (date_out_of_range == 'initclimo' .and. & - Date > Date_end ) ierr = 1 - if (ierr /= 0) call error_mesg & - ('read_record in amip_interp_mod', & - 'climo data read when NO climo data requested', FATAL) - endif - - !---- read NETCDF data ---- - - if ( interp_oi_sst ) then - call fms2_io_read_data(fileobj, ncfieldname, tmp_dat, unlim_dim_level=k) -! interpolate tmp_dat(360, 180) ---> dat(mobs,nobs) (to enable SST anom computation) - if ( mobs/=360 .or. nobs/=180 ) then - call a2a_bilinear(360, 180, tmp_dat, mobs, nobs, dat) - else - dat(:,:) = tmp_dat(:,:) - endif - else - call fms2_io_read_data(fileobj, ncfieldname, dat, unlim_dim_level=k) - endif - idat = nint(dat, I2_KIND) ! reconstruct packed data for reproducibility - - !---- unpacking of data ---- - - if (type(1:3) == 'ice') then - !---- create fractional [0,1] ice mask - if (lowercase(trim(data_set)) /= 'amip2' .and. lowercase(trim(data_set)) /= 'hurrell') then - where ( idat <= ice_crit ) - dat = 1. - elsewhere - dat = 0. - endwhere - else - dat = dat*0.01 - endif - else if (type(1:3) == 'sst') then - !---- unpack sst ---- - if (lowercase(trim(data_set)) /= 'amip2' .and. lowercase(trim(data_set)) /= 'hurrell') then - dat = real(idat)*0.01 + TFREEZE - endif - endif - - return - - end subroutine read_record - -!####################################################################### - - subroutine clip_data (type, dat) - - character(len=*), intent(in) :: type - real, intent(inout) :: dat(:,:) - - if (type(1:3) == 'ice') then - dat = min(max(dat,0.0),1.0) - else if (type(1:3) == 'sst') then - dat = max(tice_crit_k,dat) - endif - - end subroutine clip_data - -!####################################################################### - !> @return logical answer function date_equals (Left, Right) result (answer) type (date_type), intent(in) :: Left, Right @@ -1408,11 +606,8 @@ function date_equals (Left, Right) result (answer) else answer = .false. endif - end function date_equals -!####################################################################### - !> @return logical answer function date_not_equals (Left, Right) result (answer) type (date_type), intent(in) :: Left, Right @@ -1425,11 +620,8 @@ function date_not_equals (Left, Right) result (answer) else answer = .true. endif - end function date_not_equals -!####################################################################### - !> @return logical answer function date_gt (Left, Right) result (answer) type (date_type), intent(in) :: Left, Right @@ -1448,85 +640,9 @@ function date_gt (Left, Right) result (answer) exit endif enddo - end function date_gt -!####################################################################### - -subroutine print_dates (Time, Date1, Udate1, & - Date2, Udate2, fmonth) - - type (time_type), intent(in) :: Time - type (date_type), intent(in) :: Date1, Udate1, Date2, Udate2 - real, intent(in) :: fmonth - - integer :: year, month, day, hour, minute, second - - call get_date (Time, year, month, day, hour, minute, second) - - write (*,10) year,month,day, hour,minute,second - write (*,20) fmonth - write (*,30) Date1, Udate1 - write (*,40) Date2, Udate2 - -10 format (/,' date(y/m/d h:m:s) = ',i4,2('/',i2.2),1x,2(i2.2,':'),i2.2) -20 format (' fmonth = ',f9.7) -30 format (' date1(y/m/d) = ',i4,2('/',i2.2),6x, & - 'used = ',i4,2('/',i2.2),6x ) -40 format (' date2(y/m/d) = ',i4,2('/',i2.2),6x, & - 'used = ',i4,2('/',i2.2),6x ) - -end subroutine print_dates - -!####################################################################### - -subroutine zonal_sst (Time, ice, sst) - - type (time_type), intent(in) :: Time - real, intent(out) :: ice(mobs,nobs), sst(mobs,nobs) - - real :: tpi, fdate, eps, ph, sph, sph2, ts - integer :: j - -! namelist needed -! -! teq = sst at equator -! tdif = equator to pole sst difference -! tann = amplitude of annual cycle -! tlag = offset for time of year (for annual cycle) -! - - tpi = 2.0*pi - - fdate = fraction_of_year (Time) - - eps = sin( tpi*(fdate-tlag) ) * tann - - do j = 1, nobs - - ph = 0.5*(lat_bnd(j)+lat_bnd(j+1)) - sph = sin(ph) - sph2 = sph*sph - - ts = teq - tdif*sph2 - eps*sph - - sst(:,j) = ts - - enddo - - where ( sst < tice_crit_k ) - ice = 1.0 - sst = tice_crit_k - elsewhere - ice = 0.0 - endwhere - - -end subroutine zonal_sst - -!####################################################################### - -subroutine amip_interp_type_eq(amip_interp_out, amip_interp_in) +subroutine amip_interp_type_eq (amip_interp_out, amip_interp_in) type(amip_interp_type), intent(inout) :: amip_interp_out type(amip_interp_type), intent(in) :: amip_interp_in @@ -1536,8 +652,10 @@ subroutine amip_interp_type_eq(amip_interp_out, amip_interp_in) amip_interp_out%Hintrp = amip_interp_in%Hintrp amip_interp_out%Hintrp2 = amip_interp_in%Hintrp2 !< missing assignment statement; added by GPP - amip_interp_out%data1 = amip_interp_in%data1 - amip_interp_out%data2 = amip_interp_in%data2 + amip_interp_out%data1_r4 = amip_interp_in%data1_r4 + amip_interp_out%data1_r8 = amip_interp_in%data1_r8 + amip_interp_out%data2_r4 = amip_interp_in%data2_r4 + amip_interp_out%data2_r8 = amip_interp_in%data2_r8 amip_interp_out%Date1 = amip_interp_in%Date1 amip_interp_out%Date2 = amip_interp_in%Date2 amip_interp_out%Date1 = amip_interp_in%Date1 @@ -1545,10 +663,10 @@ subroutine amip_interp_type_eq(amip_interp_out, amip_interp_in) amip_interp_out%use_climo = amip_interp_in%use_climo amip_interp_out%use_annual = amip_interp_in%use_annual amip_interp_out%I_am_initialized = .true. - end subroutine amip_interp_type_eq -!####################################################################### +#include "amip_interp_r4.fh" +#include "amip_interp_r8.fh" end module amip_interp_mod !> @} diff --git a/amip_interp/include/amip_interp.inc b/amip_interp/include/amip_interp.inc new file mode 100644 index 0000000000..ccb261fffa --- /dev/null +++ b/amip_interp/include/amip_interp.inc @@ -0,0 +1,784 @@ +!*********************************************************************** +!* GNU Lesser General Public License +!* +!* This file is part of the GFDL Flexible Modeling System (FMS). +!* +!* FMS is free software: you can redistribute it and/or modify it under +!* the terms of the GNU Lesser General Public License as published by +!* the Free Software Foundation, either version 3 of the License, or (at +!* your option) any later version. +!* +!* FMS is distributed in the hope that it will be useful, but WITHOUT +!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +!* for more details. +!* +!* You should have received a copy of the GNU Lesser General Public +!* License along with FMS. If not, see . +!*********************************************************************** + +! modified by JHC +!> Retrieve sea surface temperature data and interpolated grid +subroutine GET_AMIP_SST_ (Time, Interp, sst, err_msg, lon_model, lat_model) + type (time_type), intent(in) :: Time !< Time to interpolate + type (amip_interp_type), target, intent(inout) :: Interp !< Holds data for interpolation + real(FMS_AMIP_INTERP_KIND_), intent(out) :: sst(:,:) !< Sea surface temperature data + character(len=*), optional, intent(out) :: err_msg !< Holds error message string if present + + real(FMS_AMIP_INTERP_KIND_), dimension(mobs,nobs) :: sice + real(FMS_AMIP_INTERP_KIND_), allocatable, save :: temp1(:,:), temp2(:,:) + + integer :: year1, year2, month1, month2 + real(FMS_AMIP_INTERP_KIND_) :: fmonth + type (date_type) :: Date1, Date2, Udate1, Udate2 + + type(time_type) :: Amip_Time + integer :: tod(3),dum(3) + +! add by JHC + real(FMS_AMIP_INTERP_KIND_), intent(in), dimension(:,:), optional :: lon_model, lat_model + real(FMS_AMIP_INTERP_KIND_) :: pert + integer :: i, j, mobs_sst, nobs_sst + integer :: jhctod(6) + type (time_type) :: Udate + character(len=4) :: yyyy + integer :: nrecords, ierr, k, yr, mo, dy + integer, dimension(:), allocatable :: ryr, rmo, rdy + character(len=30) :: time_unit + real(FMS_AMIP_INTERP_KIND_), dimension(:), allocatable :: timeval + character(len=FMS_FILE_LEN) :: ncfilename + type(FmsNetcdfFile_t) :: fileobj + logical :: the_file_exists +! end add by JHC + logical, parameter :: DEBUG = .false. !> switch for debugging output + !> These are fms_io specific + integer :: iunit + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + if(present(err_msg)) err_msg = '' + if(.not.Interp%I_am_initialized) then + if(fms_error_handler('get_amip_sst','The amip_interp_type variable is not initialized',err_msg)) return + endif + +!----------------------------------------------------------------------- +!----- compute zonally symetric sst --------------- + + if ( use_ncep_sst .and. forecast_mode ) no_anom_sst = .false. + + if (all(amip_date>0)) then + call get_date(Time,dum(1),dum(2),dum(3),tod(1),tod(2),tod(3)) + Amip_Time = set_date(amip_date(1),amip_date(2),amip_date(3),tod(1),tod(2),tod(3)) + else + Amip_Time = Time + endif + +! add by JHC +if ( .not.use_daily ) then +! end add by JHC + + if ( .not. allocated(temp1) ) allocate (temp1(mobs,nobs)) + if ( .not. allocated(temp2) ) allocate (temp2(mobs,nobs)) + + if (use_zonal) then + call ZONAL_SST_ (Amip_Time, sice, temp1) + call horiz_interp (Interp%Hintrp, temp1, sst) + else + +!----------------------------------------------------------------------- +!---------- get new observed sea surface temperature ------------------- + +! ---- time interpolation for months ----- + call time_interp (Amip_Time, fmonth, year1, year2, month1, month2) +! ---- force climatology ---- + if (Interp%use_climo) then + year1=0; year2=0 + endif + if (Interp%use_annual) then + year1=0; year2=0 + month1=0; month2=0 + endif +! --------------------------- + + Date1 = date_type( year1, month1, 0 ) + Date2 = date_type( year2, month2, 0 ) + +! -- open/rewind file -- + iunit = -1 +!----------------------------------------------------------------------- + + if (Date1 /= Interp%Date1) then +! ---- use Date2 for Date1 ---- + if (Date1 == Interp%Date2) then + Interp%Date1 = Interp%Date2 + Interp%DATA1_ = Interp%DATA2_ + temp1(:,:) = temp2(:,:) ! SJL BUG fix: June 24, 2011 + else + call READ_RECORD_ ('sst', Date1, Udate1, temp1) + call horiz_interp ( Interp%Hintrp, temp1, Interp%DATA1_) + call CLIP_DATA_ ('sst', Interp%DATA1_) + Interp%Date1 = Date1 + endif + endif + +!----------------------------------------------------------------------- + + if (Date2 /= Interp%Date2) then + call READ_RECORD_ ('sst', Date2, Udate2, temp2) + call horiz_interp ( Interp%Hintrp, temp2, Interp%DATA2_) + call CLIP_DATA_ ('sst', Interp%DATA2_) + Interp%Date2 = Date2 + endif + +!----------------------------------------------------------------------- +!---------- time interpolation (between months) of sst's --------------- +!----------------------------------------------------------------------- + sst = Interp%DATA1_ + fmonth * (Interp%DATA2_ - Interp%DATA1_) + +!------------------------------------------------------------------------------- +! SJL mods for NWP and TCSF --- +! Nudging runs: (Note: NCEP SST updated only every 6-hr) +! Compute SST anomaly from global SST datasets for subsequent forecast runs +!------------------------------------------------------------------------------- + +!! DEBUG CODE + if (DEBUG) then + call get_date(Amip_Time,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) + if (mpp_pe() == 0) then + write (*,200) 'JHC: use_daily = F, AMIP_Time: ',jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5), & + & jhctod(6) + write (*,300) 'JHC: use_daily = F, interped SST: ', sst(1,1),sst(5,5),sst(10,10) + endif + endif + + + endif + +! add by JHC +else + call get_date(Amip_Time,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) + if (mpp_pe() == mpp_root_pe()) write(*,200) 'amip_interp_mod: use_daily = T, Amip_Time = ',jhctod(1), & + & jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6) + + yr = jhctod(1); mo = jhctod(2); dy = jhctod(3) + + write (yyyy,'(i4)') jhctod(1) + + file_name_sst = 'INPUT/' // 'sst.day.mean.'//yyyy//'.v2.nc' + ncfilename = trim(file_name_sst) + time_unit = 'days since 1978-01-01 00:00:00' + + mobs_sst = 1440; nobs_sst = 720 + + call SET_SST_GRID_EDGES_DAILY_ (mobs_sst, nobs_sst) + call horiz_interp_new ( Interp%Hintrp2, LON_BND_, LAT_BND_, & + lon_model, lat_model, interp_method="bilinear" ) + + the_file_exists = fms2_io_file_exists(ncfilename) + + if ( (.NOT. the_file_exists) ) then + call mpp_error ('amip_interp_mod', & + 'cannot find daily SST input data file: '//trim(ncfilename), NOTE) + else + if (mpp_pe() == mpp_root_pe()) call mpp_error ('amip_interp_mod', & + 'Reading NetCDF formatted daily SST from: '//trim(ncfilename), NOTE) + + if(.not. open_file(fileobj, trim(ncfilename), 'read')) & + call error_mesg ('get_amip_sst', 'Error in opening file '//trim(ncfilename), FATAL) + + call get_dimension_size(fileobj, 'TIME', nrecords) + if (nrecords < 1) call mpp_error('amip_interp_mod', & + 'Invalid number of SST records in daily SST data file: '//trim(ncfilename), FATAL) + allocate(timeval(nrecords), ryr(nrecords), rmo(nrecords), rdy(nrecords)) + call fms2_io_read_data(fileobj, 'TIME', timeval) +!!! DEBUG CODE + if(DEBUG) then + if (mpp_pe() == 0) then + print *, 'JHC: nrecords = ', nrecords + print *, 'JHC: TIME = ', timeval + endif + endif + + ierr = 1 + do k = 1, nrecords + + Udate = get_cal_time (timeval(k), time_unit, 'julian') + call get_date(Udate,jhctod(1),jhctod(2),jhctod(3),jhctod(4),jhctod(5),jhctod(6)) + ryr(k) = jhctod(1); rmo(k) = jhctod(2); rdy(k) = jhctod(3) + + if ( yr == ryr(k) .and. mo == rmo(k) .and. dy == rdy (k) ) ierr = 0 + if (ierr==0) exit + + enddo + + if(DEBUG) then + if (mpp_pe() == 0) then + print *, 'JHC: k =', k + print *, 'JHC: ryr(k) rmo(k) rdy(k)',ryr(k), rmo(k), rdy(k) + print *, 'JHC: yr mo dy ',yr, mo, dy + endif + endif + + if (ierr .ne. 0) call mpp_error('amip_interp_mod', & + 'Model time is out of range not in SST data: '//trim(ncfilename), FATAL) + endif ! if(file_exist(ncfilename)) + + + !---- read NETCDF data ---- + if ( .not. allocated(tempamip) ) & + & allocate (tempamip(mobs_sst,nobs_sst)) + + if (the_file_exists) then + call fms2_io_read_data(fileobj, 'SST', tempamip, unlim_dim_level=k) + call close_file(fileobj) + tempamip = tempamip + TFREEZE + +!!! DEBUG CODE + if(DEBUG) then + if (mpp_pe() == 0) then + print*, 'JHC: TFREEZE = ', real(TFREEZE, FMS_AMIP_INTERP_KIND_) + print*, lbound(sst) + print*, ubound(sst) + print*, lbound(tempamip) + print*, ubound(tempamip) + write(*,300) 'JHC: tempamip : ', tempamip(100,100), tempamip(200,200), tempamip(300,300) + endif + endif + + call horiz_interp ( Interp%Hintrp2, TEMPAMIP_, sst ) + call CLIP_DATA_ ('sst', sst) + + endif + + if(DEBUG) then + if (mpp_pe() == 400) then + write(*,300)'JHC: use_daily = T, daily SST: ', sst(1,1),sst(5,5),sst(10,10) + print *,'JHC: use_daily = T, daily SST: ', sst + endif + endif + +200 format(a35, 6(i5,1x)) +300 format(a35, 3(f7.3,2x)) + +endif +! end add by JHC + +! add by JHC: add on non-zero sea surface temperature perturbation (namelist option) +! This perturbation may be useful in accessing model sensitivities + + if ( do_sst_pert ) then + + if ( trim(sst_pert_type) == 'fixed' ) then + sst = sst + real(sst_pert, FMS_AMIP_INTERP_KIND_) + else if ( trim(sst_pert_type) == 'random' ) then + call random_seed() + + if(DEBUG) then + if (mpp_pe() == 0) then + print*, 'mobs = ', mobs + print*, 'nobs = ', nobs + print*, lbound(sst) + print*, ubound(sst) + endif + endif + + do i = 1, size(sst,1) + do j = 1, size(sst,2) + call random_number(pert) + sst (i,j) = sst (i,j) + real(sst_pert, FMS_AMIP_INTERP_KIND_)*((pert-0.5_lkind)*2) + end do + end do + endif + + endif +! end add by JHC + end subroutine GET_AMIP_SST_ + +!> AMIP interpolation for ice +subroutine GET_AMIP_ICE_ (Time, Interp, ice, err_msg) + type (time_type), intent(in) :: Time !< Time to interpolate + type (amip_interp_type), target, intent(inout) :: Interp !< Holds data for interpolation + real(FMS_AMIP_INTERP_KIND_), intent(out) :: ice(:,:) !< ice data + character(len=*), optional, intent(out) :: err_msg !< Holds error message string if present + + real(FMS_AMIP_INTERP_KIND_), dimension(mobs,nobs) :: sice, temp + + integer :: year1, year2, month1, month2 + real(FMS_AMIP_INTERP_KIND_) :: fmonth + type (date_type) :: Date1, Date2, Udate1, Udate2 + + type(time_type) :: Amip_Time + integer :: tod(3),dum(3) + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + if(present(err_msg)) err_msg = '' + if(.not.Interp%I_am_initialized) then + if(fms_error_handler('get_amip_ice','The amip_interp_type variable is not initialized',err_msg)) return + endif + +!----------------------------------------------------------------------- +!----- compute zonally symetric sst --------------- + + + if (any(amip_date>0)) then + + call get_date(Time,dum(1),dum(2),dum(3),tod(1),tod(2),tod(3)) + + Amip_Time = set_date(amip_date(1),amip_date(2),amip_date(3),tod(1),tod(2),tod(3)) + + else + + Amip_Time = Time + + endif + + +if (use_zonal) then + call ZONAL_SST_ (Amip_Time, sice, temp) + call horiz_interp ( Interp%Hintrp, sice, ice ) +else + +!----------------------------------------------------------------------- +!---------- get new observed sea surface temperature ------------------- + +! ---- time interpolation for months ----- + + call time_interp (Amip_Time, fmonth, year1, year2, month1, month2) + +! ---- force climatology ---- + if (Interp%use_climo) then + year1=0; year2=0 + endif + if (Interp%use_annual) then + year1=0; year2=0 + month1=0; month2=0 + endif +! --------------------------- + + Date1 = date_type( year1, month1, 0 ) + Date2 = date_type( year2, month2, 0 ) + + iunit = -1 +!----------------------------------------------------------------------- + + if (Date1 /= Interp%Date1) then +! ---- use Date2 for Date1 ---- + if (Date1 == Interp%Date2) then + Interp%Date1 = Interp%Date2 + Interp%DATA1_ = Interp%DATA2_ + else +!-- SJL ------------------------------------------------------------- +! Can NOT use ncep_sst to determine sea_ice For seasonal forecast +! Use climo sea ice for seasonal runs + call READ_RECORD_ ('ice', Date1, Udate1, sice) +!-------------------------------------------------------------------- + call horiz_interp ( Interp%Hintrp, sice, Interp%DATA1_) + call CLIP_DATA_ ('ice', Interp%DATA1_) + Interp%Date1 = Date1 + endif + endif + +!----------------------------------------------------------------------- + + if (Date2 /= Interp%Date2) then + +!-- SJL ------------------------------------------------------------- + call READ_RECORD_ ('ice', Date2, Udate2, sice) +!-------------------------------------------------------------------- + call horiz_interp ( Interp%Hintrp, sice, Interp%DATA2_) + call CLIP_DATA_ ('ice', Interp%DATA2_) + Interp%Date2 = Date2 + + endif + +!----------------------------------------------------------------------- +!---------- time interpolation (between months) ------------------------ +!----------------------------------------------------------------------- + + ice = Interp%DATA1_ + fmonth * (Interp%DATA2_ - Interp%DATA1_) + +endif + end subroutine GET_AMIP_ICE_ + + !> @return A newly created @ref amip_interp_type + function AMIP_INTERP_NEW_1D_ ( lon , lat , mask , use_climo, use_annual, & + interp_method ) result (Interp) + real(FMS_AMIP_INTERP_KIND_), intent(in), dimension(:) :: lon, lat + logical, intent(in), dimension(:,:) :: mask + character(len=*), intent(in), optional :: interp_method + logical, intent(in), optional :: use_climo, use_annual + + type (amip_interp_type) :: Interp + + if(.not.module_is_initialized) call amip_interp_init + + Interp%use_climo = .false. + if (present(use_climo)) Interp%use_climo = use_climo + Interp%use_annual = .false. + if (present(use_annual)) Interp%use_annual = use_annual + + if ( date_out_of_range == 'fail' .and. Interp%use_climo ) & + call error_mesg ('amip_interp_new_1d', 'use_climo mismatch', FATAL) + + if ( date_out_of_range == 'fail' .and. Interp%use_annual ) & + call error_mesg ('amip_interp_new_1d', 'use_annual(climo) mismatch', FATAL) + + Interp%Date1 = date_type( -99, -99, -99 ) + Interp%Date2 = date_type( -99, -99, -99 ) + +!----------------------------------------------------------------------- +! ---- initialization of horizontal interpolation ---- + + call horiz_interp_new ( Interp%Hintrp, LON_BND_, LAT_BND_, & + lon, lat, interp_method= interp_method ) + + allocate(Interp%DATA1_ (size(lon(:))-1,size(lat(:))-1)) + allocate(Interp%DATA2_ (size(lon(:))-1,size(lat(:))-1)) + + Interp%I_am_initialized = .true. + end function AMIP_INTERP_NEW_1D_ + + !> @return A newly created @ref amip_interp_type + function AMIP_INTERP_NEW_2D_ ( lon , lat , mask , use_climo, use_annual, & + interp_method ) result (Interp) + real(FMS_AMIP_INTERP_KIND_), intent(in), dimension(:,:) :: lon, lat + logical, intent(in), dimension(:,:) :: mask + character(len=*), intent(in), optional :: interp_method + logical, intent(in), optional :: use_climo, use_annual + + type (amip_interp_type) :: Interp + + if(.not.module_is_initialized) call amip_interp_init + + Interp%use_climo = .false. + if (present(use_climo)) Interp%use_climo = use_climo + Interp%use_annual = .false. + if (present(use_annual)) Interp%use_annual = use_annual + + if ( date_out_of_range == 'fail' .and. Interp%use_climo ) & + call error_mesg ('amip_interp_new_2d', 'use_climo mismatch', FATAL) + + if ( date_out_of_range == 'fail' .and. Interp%use_annual ) & + call error_mesg ('amip_interp_new_2d', 'use_annual(climo) mismatch', FATAL) + + Interp%Date1 = date_type( -99, -99, -99 ) + Interp%Date2 = date_type( -99, -99, -99 ) + +!----------------------------------------------------------------------- +! ---- initialization of horizontal interpolation ---- + + call horiz_interp_new ( Interp%Hintrp, LON_BND_, LAT_BND_, & + lon, lat, interp_method = interp_method) + + allocate(Interp%DATA1_ (size(lon,1),size(lat,2))) + allocate(Interp%DATA2_ (size(lon,1),size(lat,2))) + + Interp%I_am_initialized = .true. + end function AMIP_INTERP_NEW_2D_ + +! add by JHC + subroutine SET_SST_GRID_EDGES_DAILY_ (mobs_sst, nobs_sst) + integer :: i, j, mobs_sst, nobs_sst + real(FMS_AMIP_INTERP_KIND_) :: hpie, dlon, dlat, wb, sb + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + if(allocated(lon_bnd)) deallocate(lon_bnd) + if(allocated(lat_bnd)) deallocate(lat_bnd) + + allocate(lon_bnd(mobs_sst+1)) + allocate(lat_bnd(nobs_sst+1)) + +! ---- compute grid edges (do only once) ----- + + hpie = pi / 2._r8_kind + dlon = 4._r8_kind*hpie/real(mobs_sst, r8_kind) + wb = 0.0_r8_kind + + lon_bnd(1) = wb + do i = 2, mobs_sst+1 + lon_bnd(i) = wb + dlon * real(i-1, r8_kind) + enddo + lon_bnd(mobs_sst+1) = lon_bnd(1) + 4._r8_kind*hpie + + dlat = 2._r8_kind*hpie/real(nobs_sst, r8_kind) + sb = -hpie + + lat_bnd(1) = sb + lat_bnd(nobs_sst+1) = hpie + do j = 2, nobs_sst + lat_bnd(j) = sb + dlat * real(j-1, r8_kind) + enddo + end subroutine SET_SST_GRID_EDGES_DAILY_ +! end add by JHC + + subroutine A2A_BILINEAR_ (nx, ny, dat1, n1, n2, dat2) + integer, intent(in) :: nx, ny + integer, intent(in) :: n1, n2 + real(FMS_AMIP_INTERP_KIND_), intent(in) :: dat1(nx,ny) + real(FMS_AMIP_INTERP_KIND_), intent(out) :: dat2(n1,n2) !> output interpolated data + +! local: + real(FMS_AMIP_INTERP_KIND_) :: lon1(nx), lat1(ny) + real(FMS_AMIP_INTERP_KIND_) :: lon2(n1), lat2(n2) + real(FMS_AMIP_INTERP_KIND_) :: dx1, dy1, dx2, dy2 + real(FMS_AMIP_INTERP_KIND_) :: xc, yc + real(FMS_AMIP_INTERP_KIND_) :: a1, b1, c1, c2, c3, c4 + integer :: i1, i2, jc, i0, j0, it, jt + integer :: i, j + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + +!----------------------------------------------------------- +! * Interpolate from "FMS" 1x1 SST data grid to a finer grid +! lon: 0.5, 1.5, ..., 359.5 +! lat: -89.5, -88.5, ... , 88.5, 89.5 +!----------------------------------------------------------- + + dx1 = 360._lkind/real(nx, FMS_AMIP_INTERP_KIND_) !> INput Grid + dy1 = 180._lkind/real(ny, FMS_AMIP_INTERP_KIND_) !> INput Grid + + do i=1,nx + lon1(i) = 0.5_lkind*dx1 + real(i-1, FMS_AMIP_INTERP_KIND_)*dx1 + enddo + do j=1,ny + lat1(j) = -90._lkind + 0.5_lkind*dy1 + real(j-1, FMS_AMIP_INTERP_KIND_)*dy1 + enddo + + dx2 = 360._lkind/real(n1, FMS_AMIP_INTERP_KIND_) !> OutPut Grid: + dy2 = 180._lkind/real(n2, FMS_AMIP_INTERP_KIND_) !> OutPut Grid: + + do i=1,n1 + lon2(i) = 0.5_lkind*dx2 + real(i-1, FMS_AMIP_INTERP_KIND_)*dx2 + enddo + do j=1,n2 + lat2(j) = -90._lkind + 0.5_lkind*dy2 + real(j-1, FMS_AMIP_INTERP_KIND_)*dy2 + enddo + + jt = 1 + do 5000 j=1,n2 + + yc = lat2(j) + if ( yclat1(ny) ) then + jc = ny-1 + b1 = 1._lkind + else + do j0=jt,ny-1 + if ( yc>=lat1(j0) .and. yc<=lat1(j0+1) ) then + jc = j0 + jt = j0 + b1 = (yc-lat1(jc)) / dy1 + go to 222 + endif + enddo + endif +222 continue + + it = 1 + do i=1,n1 + xc = lon2(i) + if ( xc>lon1(nx) ) then + i1 = nx; i2 = 1 + a1 = (xc-lon1(nx)) / dx1 + elseif ( xc=lon1(i0) .and. xc<=lon1(i0+1) ) then + i1 = i0; i2 = i0+1 + it = i0 + a1 = (xc-lon1(i1)) / dx1 + go to 111 + endif + enddo + endif +111 continue + +! Debug code: + if ( a1<-0.001_lkind .or. a1>1.001_lkind .or. b1<-0.001_lkind .or. b1>1.001_lkind ) then + write(*,*) i,j,a1, b1 + call mpp_error(FATAL,'a2a bilinear interpolation') + endif + + c1 = (1._lkind-a1) * (1._lkind-b1) + c2 = a1 * (1._lkind-b1) + c3 = a1 * b1 + c4 = (1._lkind-a1) * b1 + +! Bilinear interpolation: + dat2(i,j) = c1*dat1(i1,jc) + c2*dat1(i2,jc) + c3*dat1(i2,jc+1) + c4*dat1(i1,jc+1) + + enddo !i-loop + +5000 continue ! j-loop + end subroutine A2A_BILINEAR_ + + subroutine READ_RECORD_ (type, Date, Adate, dat) + character(len=*), intent(in) :: type + type (date_type), intent(in) :: Date + type (date_type), intent(inout) :: Adate + real(FMS_AMIP_INTERP_KIND_), intent(out) :: dat(mobs,nobs) + real(FMS_AMIP_INTERP_KIND_) :: tmp_dat(360,180) + + integer(I2_KIND) :: idat(mobs,nobs) + integer :: nrecords, yr, mo, dy, ierr, k + integer, dimension(:), allocatable :: ryr, rmo, rdy + character(len=FMS_FILE_LEN) :: ncfilename + character(len=NF90_MAX_NAME) :: ncfieldname + type(FmsNetcdfFile_t), pointer :: fileobj + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + !---- set file and field name for NETCDF data sets ---- + + ncfieldname = 'sst' + if(type(1:3) == 'sst') then + ncfilename = trim(file_name_sst) + fileobj => fileobj_sst + else if(type(1:3) == 'ice') then + ncfilename = trim(file_name_ice) + fileobj => fileobj_ice + if (lowercase(trim(data_set)) == 'amip2' .or. & + lowercase(trim(data_set)) == 'hurrell' .or. & + lowercase(trim(data_set)) == 'daily') ncfieldname = 'ice' ! modified by JHC + endif + + dy = 0 ! only processing monthly data + + if (verbose > 2 .and. mpp_pe() == 0) & + print *, 'looking for date = ', Date + + ! This code can handle amip1, reynolds, or reyoi type SST data files in netCDF format + if (mpp_pe() == mpp_root_pe()) call mpp_error ('amip_interp_mod', & + 'Reading NetCDF formatted input data file: '//trim(ncfilename), NOTE) + + call fms2_io_read_data (fileobj, 'nrecords', nrecords) + if (nrecords < 1) call mpp_error('amip_interp_mod', & + 'Invalid number of SST records in SST datafile: '//trim(ncfilename), FATAL) + allocate(ryr(nrecords), rmo(nrecords), rdy(nrecords)) + call fms2_io_read_data(fileobj, 'yr', ryr) + call fms2_io_read_data(fileobj, 'mo', rmo) + call fms2_io_read_data(fileobj, 'dy', rdy) + + ierr = 1 + do k = 1, nrecords + yr = ryr(k); mo = rmo(k) + Adate = date_type( yr, mo, 0) + Curr_date = Adate + if (verbose > 2 .and. mpp_pe() == 0) & + print *, '....... checking ', Adate + if (Date == Adate) ierr = 0 + if (yr == 0 .and. mo == Date%month) ierr = 0 + if (ierr == 0) exit + enddo + if (ierr .ne. 0) call mpp_error('amip_interp_mod', & + 'Model time is out of range not in SST data: '//trim(ncfilename), FATAL) + deallocate(ryr, rmo, rdy) + !PRINT *, 'New SST data: ', k, yr, mo, dy, Date%year, Date%month, Date%day, ryr(1), rmo(1) + + !---- check if climatological data should be used ---- + + if (yr == 0 .or. mo == 0) then + ierr = 0 + if (date_out_of_range == 'fail' ) ierr = 1 + if (date_out_of_range == 'initclimo' .and. & + Date > Date_end ) ierr = 1 + if (ierr /= 0) call error_mesg & + ('read_record in amip_interp_mod', & + 'climo data read when NO climo data requested', FATAL) + endif + + !---- read NETCDF data ---- + + if ( interp_oi_sst ) then + call fms2_io_read_data(fileobj, ncfieldname, tmp_dat, unlim_dim_level=k) +! interpolate tmp_dat(360, 180) ---> dat(mobs,nobs) (to enable SST anom computation) + if ( mobs/=360 .or. nobs/=180 ) then + call A2A_BILINEAR_ (360, 180, tmp_dat, mobs, nobs, dat) + else + dat(:,:) = tmp_dat(:,:) + endif + else + call fms2_io_read_data(fileobj, ncfieldname, dat, unlim_dim_level=k) + endif + !TODO This assumes that the data is "packed" (has the scale_factor and add_offset attributes) + ! in fms2_io_read_data the data is unpacked (data_in_file*scale_factor + add_offset) + ! the line below "packs" the data again. This is needed for reproducibility + idat = nint(dat*100._lkind, I2_KIND) + + !---- unpacking of data ---- + + if (type(1:3) == 'ice') then + !---- create fractional [0,1] ice mask + if (lowercase(trim(data_set)) /= 'amip2' .and. lowercase(trim(data_set)) /= 'hurrell') then + where ( idat <= ice_crit ) + dat = 1._lkind + elsewhere + dat = 0._lkind + endwhere + else + dat = dat*0.01_lkind + endif + else if (type(1:3) == 'sst') then + !---- unpack sst ---- + if (lowercase(trim(data_set)) /= 'amip2' .and. lowercase(trim(data_set)) /= 'hurrell') then + dat = real(idat, FMS_AMIP_INTERP_KIND_)*0.01_lkind + real(TFREEZE, FMS_AMIP_INTERP_KIND_) + endif + endif + + return + end subroutine READ_RECORD_ + + subroutine CLIP_DATA_ (type, dat) + character(len=*), intent(in) :: type + real(FMS_AMIP_INTERP_KIND_), intent(inout) :: dat(:,:) + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + + if (type(1:3) == 'ice') then + dat = min(max(dat,0.0_lkind), 1.0_lkind) + else if (type(1:3) == 'sst') then + dat = max(real(tice_crit_k, FMS_AMIP_INTERP_KIND_),dat) + endif + end subroutine CLIP_DATA_ + +subroutine ZONAL_SST_ (Time, ice, sst) + type (time_type), intent(in) :: Time + real(FMS_AMIP_INTERP_KIND_), intent(out) :: ice(mobs,nobs), sst(mobs,nobs) + real(FMS_AMIP_INTERP_KIND_) :: tpi, fdate, eps, ph, sph, sph2, ts + integer :: j + integer, parameter :: lkind = FMS_AMIP_INTERP_KIND_ + +! namelist needed +! +! teq = sst at equator +! tdif = equator to pole sst difference +! tann = amplitude of annual cycle +! tlag = offset for time of year (for annual cycle) +! + + tpi = 2.0_lkind*real(pi, FMS_AMIP_INTERP_KIND_) + + fdate = fraction_of_year (Time) + + eps = sin( tpi*(fdate-real(tlag, FMS_AMIP_INTERP_KIND_)) ) * real(tann, FMS_AMIP_INTERP_KIND_) + + do j = 1, nobs + + ph = 0.5_lkind * real(lat_bnd(j)+lat_bnd(j+1), FMS_AMIP_INTERP_KIND_) + sph = sin(ph) + sph2 = sph*sph + + ts = real(teq, FMS_AMIP_INTERP_KIND_) - real(tdif, FMS_AMIP_INTERP_KIND_)*sph2 - eps*sph + + sst(:,j) = ts + + enddo + + where ( sst < real(tice_crit_k, FMS_AMIP_INTERP_KIND_) ) + ice = 1.0_lkind + sst = real(tice_crit_k, FMS_AMIP_INTERP_KIND_) + elsewhere + ice = 0.0_lkind + endwhere +end subroutine ZONAL_SST_ diff --git a/amip_interp/include/amip_interp_r4.fh b/amip_interp/include/amip_interp_r4.fh new file mode 100644 index 0000000000..b3ed0e733b --- /dev/null +++ b/amip_interp/include/amip_interp_r4.fh @@ -0,0 +1,52 @@ +#undef FMS_AMIP_INTERP_KIND_ +#undef LON_BND_ +#undef LAT_BND_ +#undef TEMPAMIP_ +#undef DATA1_ +#undef DATA2_ + +#undef GET_AMIP_SST_ +#undef GET_AMIP_ICE_ +#undef AMIP_INTERP_NEW_1D_ +#undef AMIP_INTERP_NEW_2D_ +#undef SET_SST_GRID_EDGES_DAILY_ +#undef A2A_BILINEAR_ +#undef READ_RECORD_ +#undef CLIP_DATA_ +#undef ZONAL_SST_ + +#define FMS_AMIP_INTERP_KIND_ r4_kind +#define LON_BND_ real(lon_bnd, r4_kind) +#define LAT_BND_ real(lat_bnd, r4_kind) +#define TEMPAMIP_ real(tempamip, r4_kind) +#define DATA1_ data1_r4 +#define DATA2_ data2_r4 + +#define GET_AMIP_SST_ get_amip_sst_r4 +#define GET_AMIP_ICE_ get_amip_ice_r4 +#define AMIP_INTERP_NEW_1D_ amip_interp_new_1d_r4 +#define AMIP_INTERP_NEW_2D_ amip_interp_new_2d_r4 +#define SET_SST_GRID_EDGES_DAILY_ set_sst_grid_edges_daily_r4 +#define A2A_BILINEAR_ a2a_bilinear_r4 +#define READ_RECORD_ read_record_r4 +#define CLIP_DATA_ clip_data_r4 +#define ZONAL_SST_ zonal_sst_r4 + +#include "amip_interp.inc" + +#undef FMS_AMIP_INTERP_KIND_ +#undef LON_BND_ +#undef LAT_BND_ +#undef TEMPAMIP_ +#undef DATA1_ +#undef DATA2_ + +#undef GET_AMIP_SST_ +#undef GET_AMIP_ICE_ +#undef AMIP_INTERP_NEW_1D_ +#undef AMIP_INTERP_NEW_2D_ +#undef SET_SST_GRID_EDGES_DAILY_ +#undef A2A_BILINEAR_ +#undef READ_RECORD_ +#undef CLIP_DATA_ +#undef ZONAL_SST_ diff --git a/amip_interp/include/amip_interp_r8.fh b/amip_interp/include/amip_interp_r8.fh new file mode 100644 index 0000000000..306fb08748 --- /dev/null +++ b/amip_interp/include/amip_interp_r8.fh @@ -0,0 +1,52 @@ +#undef FMS_AMIP_INTERP_KIND_ +#undef LON_BND_ +#undef LAT_BND_ +#undef TEMPAMIP_ +#undef DATA1_ +#undef DATA2_ + +#undef GET_AMIP_SST_ +#undef GET_AMIP_ICE_ +#undef AMIP_INTERP_NEW_1D_ +#undef AMIP_INTERP_NEW_2D_ +#undef SET_SST_GRID_EDGES_DAILY_ +#undef A2A_BILINEAR_ +#undef READ_RECORD_ +#undef CLIP_DATA_ +#undef ZONAL_SST_ + +#define FMS_AMIP_INTERP_KIND_ r8_kind +#define LON_BND_ lon_bnd +#define LAT_BND_ lat_bnd +#define TEMPAMIP_ tempamip +#define DATA1_ data1_r8 +#define DATA2_ data2_r8 + +#define GET_AMIP_SST_ get_amip_sst_r8 +#define GET_AMIP_ICE_ get_amip_ice_r8 +#define AMIP_INTERP_NEW_1D_ amip_interp_new_1d_r8 +#define AMIP_INTERP_NEW_2D_ amip_interp_new_2d_r8 +#define SET_SST_GRID_EDGES_DAILY_ set_sst_grid_edges_daily_r8 +#define A2A_BILINEAR_ a2a_bilinear_r8 +#define READ_RECORD_ read_record_r8 +#define CLIP_DATA_ clip_data_r8 +#define ZONAL_SST_ zonal_sst_r8 + +#include "amip_interp.inc" + +#undef FMS_AMIP_INTERP_KIND_ +#undef LON_BND_ +#undef LAT_BND_ +#undef TEMPAMIP_ +#undef DATA1_ +#undef DATA2_ + +#undef GET_AMIP_SST_ +#undef GET_AMIP_ICE_ +#undef AMIP_INTERP_NEW_1D_ +#undef AMIP_INTERP_NEW_2D_ +#undef SET_SST_GRID_EDGES_DAILY_ +#undef A2A_BILINEAR_ +#undef READ_RECORD_ +#undef CLIP_DATA_ +#undef ZONAL_SST_ diff --git a/astronomy/Makefile.am b/astronomy/Makefile.am index 33d4acaf3c..50a1449412 100644 --- a/astronomy/Makefile.am +++ b/astronomy/Makefile.am @@ -23,14 +23,23 @@ # Ed Hartnett 2/22/19 # Include .h and .mod files. -AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/astronomy/include AM_FCFLAGS = $(FC_MODINC). $(FC_MODOUT)$(MODDIR) # Build this uninstalled convenience library. noinst_LTLIBRARIES = libastronomy.la # The convenience library depends on its source. -libastronomy_la_SOURCES = astronomy.F90 +libastronomy_la_SOURCES = \ + astronomy.F90 \ + include/astronomy_r4.fh \ + include/astronomy_r8.fh \ + include/astronomy.inc + +astronomy.$(FC_MODEXT): \ +include/astronomy_r4.fh \ +include/astronomy_r8.fh \ +include/astronomy.inc BUILT_SOURCES = astronomy_mod.$(FC_MODEXT) nodist_include_HEADERS = astronomy_mod.$(FC_MODEXT) diff --git a/astronomy/astronomy.F90 b/astronomy/astronomy.F90 index 192fc8f22a..7bcce62d71 100644 --- a/astronomy/astronomy.F90 +++ b/astronomy/astronomy.F90 @@ -29,296 +29,321 @@ module astronomy_mod -use fms_mod, only: fms_init, & - mpp_pe, mpp_root_pe, stdlog, & - write_version_number, & - check_nml_error, error_mesg, & - FATAL, NOTE, WARNING -use time_manager_mod, only: time_type, set_time, get_time, & - get_date_julian, set_date_julian, & - set_date, length_of_year, & - time_manager_init, & - operator(-), operator(+), & - operator( // ), operator(<) -use constants_mod, only: constants_init, PI -use mpp_mod, only: input_nml_file - -!-------------------------------------------------------------------- - -implicit none -private - -!--------------------------------------------------------------------- -!----------- version number for this module -------------------------- + use fms_mod, only: fms_init, & + mpp_pe, mpp_root_pe, stdlog, & + write_version_number, & + check_nml_error, error_mesg, & + FATAL, NOTE, WARNING + use time_manager_mod, only: time_type, set_time, get_time, & + get_date_julian, set_date_julian, & + set_date, length_of_year, & + time_manager_init, & + operator(-), operator(+), & + operator( // ), operator(<) + use fms_io_utils_mod, only: get_data_type_string + use constants_mod, only: constants_init, PI + use mpp_mod, only: input_nml_file + use platform_mod, only: r4_kind, r8_kind, i4_kind, i8_kind + + !-------------------------------------------------------------------- + + implicit none + private + + !--------------------------------------------------------------------- + !----------- version number for this module -------------------------- ! Include variable "version" to be written to log file. #include -!--------------------------------------------------------------------- -!------- interfaces -------- - -public & - astronomy_init, get_period, set_period, & - set_orbital_parameters, get_orbital_parameters, & - set_ref_date_of_ae, get_ref_date_of_ae, & - diurnal_solar, daily_mean_solar, annual_mean_solar, & - astronomy_end, universal_time, orbital_time - -!> @} - -!> @brief Calculates solar information for the given location(lat & lon) and time -!! -!> ~~~~~~~~~~{.f90} -!! call diurnal_solar (lat, lon, time, cosz, fracday, rrsun, dt_time) -!! call diurnal_solar (lat, lon, gmt, time_since_ae, cosz, fracday, rrsun, dt) -!! ~~~~~~~~~~ -!! -!! The first option (used in conjunction with time_manager_mod) -!! generates the real variables gmt and time_since_ae from the -!! time_type input, and then calls diurnal_solar with these real inputs. -!! -!! The time of day is set by -!! ~~~~~~~~~~{.f90} -!! real, intent(in) :: gmt -!! ~~~~~~~~~~ -!! The time of year is set by -!! ~~~~~~~~~~{.f90} -!! real, intent(in) :: time_since_ae -!! ~~~~~~~~~~ -!! with time_type input, both of these are extracted from -!! ~~~~~~~~~~{.f90} -!! type(time_type), intent(in) :: time -!! ~~~~~~~~~~ -!! -!! Separate routines exist within this interface for scalar, -!! 1D or 2D input and output fields: -!! -!! ~~~~~~~~~~{.f90} -!! real, intent(in), dimension(:,:) :: lat, lon -!! real, intent(in), dimension(:) :: lat, lon -!! real, intent(in) :: lat, lon -!! -!! real, intent(out), dimension(:,:) :: cosz, fracday -!! real, intent(out), dimension(:) :: cosz, fracday -!! real, intent(out) :: cosz, fracday -!! ~~~~~~~~~~ -!! -!! One may also average the output fields over the time interval -!! between gmt and gmt + dt by including the optional argument dt (or -!! dt_time). dt is measured in radians and must be less than pi -!! (1/2 day). This average is computed analytically, and should be -!! exact except for the fact that changes in earth-sun distance over -!! the time interval dt are ignored. In the context of a diurnal GCM, -!! this option should always be employed to insure that the total flux -!! at the top of the atmosphere is not modified by time truncation error. -!! -!! ~~~~~~~~~~{.f90} -!! real, intent(in), optional :: dt -!! type(time_type), optional :: dt_time -!! ~~~~~~~~~~ -!! -!! @param [in] Latitudes of model grid points [radians] -!! @param [in] Longitudes of model grid points [radians] -!! @param [in] Time of day at longitude 0.0; midnight = 0.0, one day = 2 * pi [radians] -!! @param [in] Time of year; autumnal equinox = 0.0, one year = 2 * pi [radians] -!! @param [in]