diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5698586e43..e56311635e 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -64,3 +64,14 @@ e8fc526e0d7818d45f171488c78392c4ff63902a cdf40d265cc82775607a1bf25f5f527bacc97405 251e389b361ba673b508e07d04ddcc06b2681989 8ec50135eca1b99c8b903ecdaa1bd436644688bd +3b7a2876933263f8986e4069f5d23bd45635756f +3dd489af7ebe06566e2c6a1c7ade18550f1eb4ba +742cfa606039ab89602fde5fef46458516f56fd4 +4ad46f46de7dde753b4653c15f05326f55116b73 +75db098206b064b8b7b2a0604d3f0bf8fdb950cc +84609494b54ea9732f64add43b2f1dd035632b4c +7eb17f3ef0b9829fb55e0e3d7f02e157b0e41cfb +62d7711506a0fb9a3ad138ceceffbac1b79a6caa +49ad0f7ebe0b07459abc00a5c33c55a646f1e7e0 +ac03492012837799b7111607188acff9f739044a +d858665d799690d73b56bcb961684382551193f4 diff --git a/.github/ISSUE_TEMPLATE/03_documentation.md b/.github/ISSUE_TEMPLATE/03_documentation.md new file mode 100644 index 0000000000..e6c3fa0ff8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_documentation.md @@ -0,0 +1,24 @@ +--- +name: Documentation +about: Something should be added to or fixed in the documentation + +--- + + +### What sort(s) of documentation issue is this? +- [ ] Something is missing. +- [ ] Something is (or might be) incorrect or outdated. +- [ ] Something is confusing. +- [ ] Something is broken. + +### What part(s) of the documentation does this concern? +- [ ] [Technical Note](https://escomp.github.io/CTSM/tech_note/index.html) (science and design of the model) +- [ ] [User's Guide](https://escomp.github.io/CTSM/users_guide/index.html) (using the model and related tools) +- [ ] Somewhere else (e.g., README file, tool help text, or code comment): _Please specify_ +- [ ] I don't know + +### Describe the issue +A clear and concise description of what is missing or wrong. + +### Additional context (optional) +Add any other context or screenshots about the issue here. diff --git a/.github/ISSUE_TEMPLATE/03_other.md b/.github/ISSUE_TEMPLATE/03_other.md deleted file mode 100644 index 61898d3a75..0000000000 --- a/.github/ISSUE_TEMPLATE/03_other.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Other -about: Other issues (enhancement, cleanup, documentation, etc.) - ---- - - diff --git a/.github/ISSUE_TEMPLATE/04_other.md b/.github/ISSUE_TEMPLATE/04_other.md new file mode 100644 index 0000000000..f2cfcc7407 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04_other.md @@ -0,0 +1,7 @@ +--- +name: Other +about: Other issues (enhancement, cleanup, etc.) + +--- + + diff --git a/.github/workflows/check-clm6-aliases.sh b/.github/workflows/check-clm6-aliases.sh new file mode 100755 index 0000000000..32778f15d6 --- /dev/null +++ b/.github/workflows/check-clm6-aliases.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -e + +# Check that clm6* compset aliases return CLM6* longnames + +# Change to top level of clone +cd "$(git rev-parse --show-toplevel)" + +# Check that query_config can run without error +cime/scripts/query_config --compsets 1>/dev/null + +# Find bad compsets +OLD_IFS=$IFS +IFS='\n' +set +e +# Relies on case sensitivity here: Alias should have Clm6 and longname should have CLM6 +bad_compsets="$(cime/scripts/query_config --compsets | sort | uniq | grep Clm6 | grep -v CLM6)" +set -e +if [[ "${bad_compsets}" != "" ]]; then + echo "One or more compsets with Clm6 alias but not CLM6 longname:" >&2 + echo $bad_compsets >&2 + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/.github/workflows/check-clm6-aliases.yml b/.github/workflows/check-clm6-aliases.yml new file mode 100644 index 0000000000..7bdcf8af3b --- /dev/null +++ b/.github/workflows/check-clm6-aliases.yml @@ -0,0 +1,40 @@ +name: Check that clm6* compset aliases return CLM6* longnames +# Only check files in our repo that AREN'T in submodules +# Use a Python command to check each file because xmllint isn't available on GH runners + +on: + push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] + paths: + - '.github/workflows/check-clm6-aliases.sh' + - 'cime/**' + - 'cime_config/config_compsets.xml' + + pull_request: + # Run on pull requests that change the listed files + paths: + - '.github/workflows/check-clm6-aliases.sh' + - 'cime/**' + - 'cime_config/config_compsets.xml' + + workflow_dispatch: + +jobs: + check-clm6-aliases: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Checkout submodules + run: | + bin/git-fleximod update + + - name: Install xmllint for CIME + run: | + sudo apt-get update && sudo apt-get install --no-install-recommends -y libxml2-utils + + - name: Check aliases + run: | + .github/workflows/check-clm6-aliases.sh diff --git a/.github/workflows/docker-image-build-publish.yml b/.github/workflows/docker-image-build-publish.yml index b52be7f031..d22ab701e6 100644 --- a/.github/workflows/docker-image-build-publish.yml +++ b/.github/workflows/docker-image-build-publish.yml @@ -2,12 +2,12 @@ name: Build and publish ctsm-docs Docker image on: - # Run this whenever something gets pushed to master + # Run this whenever a change to certain files gets pushed to master push: branches: ['master'] paths: - - 'doc/ctsm-docs_container/Dockerfile' - - 'doc/ctsm-docs_container/requirements.txt' + - 'doc/ctsm-docs_container/**' + - '!doc/ctsm-docs_container/README.md' # Run this whenever it's manually called workflow_dispatch: @@ -31,7 +31,7 @@ jobs: env: REGISTRY: ${{ needs.build-image-and-test-docs.outputs.REGISTRY }} IMAGE_NAME: ${{ needs.build-image-and-test-docs.outputs.IMAGE_NAME }} - IMAGE_TAG: ${{ needs.build-image-and-test-docs.outputs.image_tag }} + VERSION_TAG: ${{ needs.build-image-and-test-docs.outputs.version_tag }} # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry @@ -61,6 +61,7 @@ jobs: # This step uses the `docker/build-push-action` action to build the image, based on the ctsm-docs `Dockerfile`. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + # Note that we should avoid relying on the "latest" tag for anything, but it's good practice to have one. # v6.15.0 - name: Push Docker image id: push @@ -70,12 +71,14 @@ jobs: platforms: linux/amd64,linux/arm64 push: true load: false - tags: ${{ env.IMAGE_TAG }} + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.VERSION_TAG }} labels: "" # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds). - name: Generate artifact attestation - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} diff --git a/.github/workflows/docker-image-build.yml b/.github/workflows/docker-image-build.yml index e013b888a7..03f3afa362 100644 --- a/.github/workflows/docker-image-build.yml +++ b/.github/workflows/docker-image-build.yml @@ -1,25 +1,29 @@ # Modified from https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#publishing-a-package-using-an-action (last accessed 2025-05-09) -name: Test building ctsm-docs Docker image and using it to build the docs +name: Build and test ctsm-docs container -# Configures this workflow to run every time a change in the Docker container setup is pushed to the master branch +# Configures this workflow to run every time a change in the Docker container setup is pushed or included in a PR on: push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] paths: - 'doc/ctsm-docs_container/**' - - '.github/workflows/docker-image-ctsm-docs-build.yml' - - '.github/workflows/docker-image-build-common.yml' + - '!doc/ctsm-docs_container/README.md' + - '.github/workflows/docker-image-common.yml' pull_request: + # Run on pull requests that change the listed files paths: - 'doc/ctsm-docs_container/**' - - '.github/workflows/docker-image-ctsm-docs-build.yml' - - '.github/workflows/docker-image-build-common.yml' + - '!doc/ctsm-docs_container/README.md' + - '.github/workflows/docker-image-common.yml' workflow_dispatch: # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: build-image-and-test-docs: + if: ${{ ! github.repository == 'ESCOMP/CTSM' }} name: Build image and test docs uses: ./.github/workflows/docker-image-common.yml secrets: inherit diff --git a/.github/workflows/docker-image-common.yml b/.github/workflows/docker-image-common.yml index 40cbd90ac3..86de4fc2f2 100644 --- a/.github/workflows/docker-image-common.yml +++ b/.github/workflows/docker-image-common.yml @@ -9,14 +9,15 @@ on: IMAGE_NAME: description: "Docker image name" value: ${{ jobs.build-image-and-test-docs.outputs.IMAGE_NAME }} - image_tag: - description: "First image tag" - value: ${{ jobs.build-image-and-test-docs.outputs.image_tag }} + version_tag: + description: "Version tag from Dockerfile" + value: ${{ jobs.check-version.outputs.VERSION_TAG }} -# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +# Defines custom environment variables for the workflow. env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}/ctsm-docs + IMAGE_BASENAME: ctsm-docs + REPO: ${{ github.repository }} # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: @@ -25,19 +26,23 @@ jobs: # Variables that might be needed by the calling workflow outputs: REGISTRY: ${{ env.REGISTRY }} - IMAGE_NAME: ${{ env.IMAGE_NAME }} - image_tag: ${{ steps.set-image-tag.outputs.IMAGE_TAG }} + IMAGE_NAME: ${{ steps.set-image-name.outputs.IMAGE_NAME }} # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read - packages: write - attestations: write - id-token: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Ensure that the repository part of IMAGE_NAME is lowercase. This is needed because Docker requires image names to be entirely lowercase. Note that the *image name* part, set as IMAGE_BASENAME in the env block above, is *not* converted. This will cause the check-version job to fail if the IMAGE_BASENAME contains capitals. We don't want to silently fix that here; rather, we require the user to specify a lowercase IMAGE_BASENAME. + - name: Get image name with lowercase repo + id: set-image-name + run: | + lowercase_repo=$(echo $REPO | tr '[:upper:]' '[:lower:]') + echo "IMAGE_NAME=${lowercase_repo}/${IMAGE_BASENAME}" >> $GITHUB_ENV + echo "IMAGE_NAME=${lowercase_repo}/${IMAGE_BASENAME}" >> $GITHUB_OUTPUT # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry @@ -52,7 +57,7 @@ jobs: id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.REGISTRY }}/${{ steps.set-image-name.outputs.IMAGE_NAME }} # This step uses the `docker/build-push-action` action to build the image, based on the ctsm-docs `Dockerfile`. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository. @@ -68,16 +73,25 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - # Try building our docs using the new container - - name: Checkout doc-builder external + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules run: | - bin/git-fleximod update doc-builder + bin/git-fleximod update -o + - name: Set image tag for docs build id: set-image-tag run: | - echo "IMAGE_TAG=$(echo '${{ steps.meta.outputs.tags }}' | cut -d',' -f1)" >> $GITHUB_ENV - echo "IMAGE_TAG=$(echo '${{ steps.meta.outputs.tags }}' | cut -d',' -f1)" >> $GITHUB_OUTPUT - - name: Build docs using container + echo "IMAGE_TAG=$(echo '${{ steps.meta.outputs.tags }}' | head -n 1 | cut -d',' -f1)" >> $GITHUB_ENV + + - name: Build docs using Docker (Podman has trouble on GitHub runners) id: build-docs run: | cd doc && ./build_docs -b ${PWD}/_build -c -d -i $IMAGE_TAG + + + check-version: + needs: build-image-and-test-docs + uses: ./.github/workflows/docker-image-get-version.yml + with: + registry: ${{ needs.build-image-and-test-docs.outputs.REGISTRY }} + image_name: ${{ needs.build-image-and-test-docs.outputs.IMAGE_NAME }} diff --git a/.github/workflows/docker-image-get-version.yml b/.github/workflows/docker-image-get-version.yml new file mode 100644 index 0000000000..c405b861b5 --- /dev/null +++ b/.github/workflows/docker-image-get-version.yml @@ -0,0 +1,72 @@ +name: Get and check version specified in a Dockerfile + +on: + workflow_call: + inputs: + registry: + required: true # Require any workflows calling this one to provide input + type: string + default: 'ghcr.io' # Provide default so this workflow works standalone too + image_name: + required: true # Require any workflows calling this one to provide input + type: string + default: 'escomp/ctsm/ctsm-docs' # Provide default so this workflow works standalone too + outputs: + VERSION_TAG: + description: "Tag to be pushed to container registry" + value: ${{ jobs.get-check-version.outputs.VERSION_TAG }} + workflow_dispatch: + inputs: + registry: + description: 'Container registry' + required: false + type: string + default: 'ghcr.io' + image_name: + description: 'Image name' + required: false + type: string + default: 'escomp/ctsm/ctsm-docs' + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + get-check-version: + name: Get version number from Dockerfile and check it + runs-on: ubuntu-latest + outputs: + VERSION_TAG: ${{ steps.get-check-version.outputs.version_tag }} + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: read + + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get version number from Dockerfile and check it + id: get-check-version + run: | + set -e + set -o pipefail + set -u + VERSION="$(doc/ctsm-docs_container/get_version.sh)" + VERSION_TAG="${{ inputs.registry }}/${{ inputs.image_name }}:${VERSION}" + + # Store the manifest inspect result and output + set +e + INSPECT_RESULT="$(docker manifest inspect "$VERSION_TAG" 2>&1)" + INSPECT_STATUS=$? + set -e + + if [[ "${INSPECT_RESULT}" == *"schemaVersion"* ]]; then + echo "Tag $VERSION_TAG already exists!" >&2 + exit 123 + elif [[ "${INSPECT_RESULT}" != "manifest unknown" ]]; then + # "manifest unknown" means the tag doesn't exist, which is what we want + echo -e "Error checking manifest for $VERSION_TAG:\n${INSPECT_RESULT}" >&2 + exit $INSPECT_STATUS + fi + + echo "Setting version_tag to $VERSION_TAG" + echo "version_tag=$VERSION_TAG" >> $GITHUB_OUTPUT diff --git a/.github/workflows/docs-build-and-deploy.yml b/.github/workflows/docs-build-and-deploy.yml new file mode 100644 index 0000000000..55ad033ed7 --- /dev/null +++ b/.github/workflows/docs-build-and-deploy.yml @@ -0,0 +1,74 @@ +name: Deploy static content to Pages + +on: + push: + # Run when a change to these files is pushed to master. + branches: ['master', 'release-clm5.0'] + paths: + - 'doc/**' + - '!doc/test/*' + - '!doc/*ChangeLog*' + - '!doc/*ChangeSum*' + - '!doc/UpdateChangelog.pl' + # Include all include::ed files outside doc/ directory! + - 'src/README.unit_testing' + - 'tools/README' + - 'doc/test/test_container_eq_ctsm_pylib.sh' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + + build-and-deploy: + + # Only run on upstream repository + if: ${{ github.repository == 'ESCOMP/CTSM' }} + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Get all history, ensuring all branches are available for checkout + fetch-depth: 0 + + - name: Setup Pages + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules + run: | + bin/git-fleximod update -o + + - name: Build docs using container + id: build-docs + run: | + cd doc + ./build_docs_to_publish -d --site-root https://escomp.github.io/CTSM + + - name: Upload artifact + uses: actions/upload-pages-artifact@0252fc4ba7626f0298f0cf00902a25c6afc77fa8 # v3 + with: + # Upload publish dir + path: 'doc/_publish' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@f33f41b675f0ab2dc5a6863c9a170fe83af3571e # v4 diff --git a/.github/workflows/docs-common.yml b/.github/workflows/docs-common.yml index 6dd8f7d53b..a339b0b7f9 100644 --- a/.github/workflows/docs-common.yml +++ b/.github/workflows/docs-common.yml @@ -21,25 +21,26 @@ jobs: build-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 lfs: true - - name: Checkout doc-builder external + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules run: | - bin/git-fleximod update doc-builder + bin/git-fleximod update -o # Do this if not using conda # Based on https://github.com/actions/cache/blob/main/examples.md#python---pip - name: Install python if: ${{ ! inputs.use_conda }} - uses: actions/setup-python@v2 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.13.2' # needs to be coordinated with version in python/conda_env_ctsm_py.txt - name: Cache pip if: ${{ ! inputs.use_conda }} - uses: actions/cache@v3 + uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('doc/ctsm-docs_container/requirements.txt') }} @@ -53,7 +54,7 @@ jobs: # Do this if using conda - name: Set up conda environment if: ${{ inputs.use_conda }} - uses: conda-incubator/setup-miniconda@v3 + uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 with: activate-environment: ${{ inputs.conda_env_name }} environment-file: ${{ inputs.conda_env_file }} diff --git a/.github/workflows/docs-ctsm_pylib.yml b/.github/workflows/docs-ctsm_pylib.yml deleted file mode 100644 index e4efc973a2..0000000000 --- a/.github/workflows/docs-ctsm_pylib.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Test building docs with ctsm_pylib - -on: - push: - paths: - - 'python/conda_env_ctsm_py.txt' - - pull_request: - paths: - - 'python/conda_env_ctsm_py.txt' - - schedule: - # 8 am every Monday UTC - - cron: '0 8 * * 1' - - workflow_dispatch: - -permissions: - contents: read -jobs: - test-build-docs-ctsm_pylib: - if: ${{ always() }} - name: With ctsm_pylib - uses: ./.github/workflows/docs-common.yml - with: - use_conda: true - conda_env_file: python/conda_env_ctsm_py.yml - conda_env_name: ctsm_pylib - - # File an issue if the docs build failed during a scheduled run - file-issue-on-failure: - if: | - failure() && - github.event_name == 'schedule' - needs: test-build-docs-ctsm_pylib - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Create issue - uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - filename: .github/workflows/docs-ctsm_pylib.issue_template.md - update_existing: true - search_existing: open - - diff --git a/.github/workflows/docs-omnibus.yml b/.github/workflows/docs-omnibus.yml new file mode 100644 index 0000000000..d832478831 --- /dev/null +++ b/.github/workflows/docs-omnibus.yml @@ -0,0 +1,48 @@ +name: Run an omnibus test script for the docs + +on: + push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] + paths: + - 'doc/test/*' + - 'doc/Makefile' + + pull_request: + # Run on pull requests that change the listed files + paths: + - 'doc/test/*' + - 'doc/Makefile' + + workflow_dispatch: + +jobs: + build-docs-omnibus-test: + # Don't run on forks, because part(s) of omnibus testing script will look for + # branch(es) that forks may not have. + if: ${{ github.repository == 'ESCOMP/CTSM' }} + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + lfs: true + + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules + run: | + bin/git-fleximod update -o + + # Set up conda + - name: Set up conda environment + uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 + with: + activate-environment: ctsm_pylib + environment-file: python/conda_env_ctsm_py.yml + channels: conda-forge + auto-activate-base: false + + - name: Text Sphinx builds with omnibus script + run: | + cd doc/test/ && ./testing.sh diff --git a/.github/workflows/docs-update-ctsm_pylib.yml b/.github/workflows/docs-update-ctsm_pylib.yml new file mode 100644 index 0000000000..4ea5af396c --- /dev/null +++ b/.github/workflows/docs-update-ctsm_pylib.yml @@ -0,0 +1,67 @@ +name: Docs tests to run when ctsm_pylib is updated + +on: + push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] + paths: + - 'python/conda_env_ctsm_py.txt' + - 'doc/ctsm-docs_container/requirements.txt' + - '.github/workflows/docs-common.yml' + - '.github/workflows/docs-update-dependency-common.yml' + + pull_request: + # Run on pull requests that change the listed files + paths: + - 'python/conda_env_ctsm_py.txt' + - 'doc/ctsm-docs_container/requirements.txt' + - '.github/workflows/docs-common.yml' + - '.github/workflows/docs-update-dependency-common.yml' + + schedule: + # 8 am every Monday UTC + - cron: '0 8 * * 1' + + workflow_dispatch: + +permissions: + contents: read +jobs: + test-build-docs-ctsm_pylib: + if: ${{ always() }} + name: Build with ctsm_pylib + uses: ./.github/workflows/docs-common.yml + with: + use_conda: true + conda_env_file: python/conda_env_ctsm_py.yml + conda_env_name: ctsm_pylib + + test-update-dependency: + if: ${{ always() }} + name: Docs dependency update tests + uses: ./.github/workflows/docs-update-dependency-common.yml + + # File an issue if the docs build failed during a scheduled run. + # The main thing we're concerned about in that case is something having + # changed outside the repository that's causing the ctsm_pylib setup to + # fail. Thus, we don't need this job to wait for BOTH the above jobs--- + # if one fails, they both will. + file-issue-on-failure: + if: | + failure() && + github.event_name == 'schedule' + needs: test-build-docs-ctsm_pylib + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Create issue + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + filename: .github/workflows/docs-ctsm_pylib.issue_template.md + update_existing: true + search_existing: open + + diff --git a/.github/workflows/docs-update-dependency-common.yml b/.github/workflows/docs-update-dependency-common.yml new file mode 100644 index 0000000000..7bd0fd8839 --- /dev/null +++ b/.github/workflows/docs-update-dependency-common.yml @@ -0,0 +1,77 @@ +name: Jobs shared by docs workflows that run when a dependency is updated + +on: + workflow_call: + inputs: + # Conda is always needed for both jobs in this workflow. Here, + # we set default values for the variables in case the calling + # workflow doesn't provide them. + conda_env_file: + required: false + type: string + default: "python/conda_env_ctsm_py.yml" + conda_env_name: + required: false + type: string + default: "ctsm_pylib" + secrets: {} + +jobs: + compare-docbuilder-vs-ctsmpylib: + name: Are both methods identical? + + # Don't run on forks, because test_container_eq_ctsm_pylib.sh uses + # build_docs_to_publish, which will look for branch(es) that forks + # may not have + if: ${{ github.repository == 'ESCOMP/CTSM' }} + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + lfs: true + + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules + run: | + bin/git-fleximod update -o + + - name: Set up conda environment + uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 + with: + activate-environment: ${{ inputs.conda_env_name }} + environment-file: ${{ inputs.conda_env_file }} + channels: conda-forge + auto-activate-base: false + + - name: Compare docs built with container vs. ctsm_pylib + run: | + cd doc/test/ + ./test_container_eq_ctsm_pylib.sh + + makefile-method: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + lfs: true + + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules + run: | + bin/git-fleximod update -o + + - name: Set up conda environment + uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 + with: + activate-environment: ${{ inputs.conda_env_name }} + environment-file: ${{ inputs.conda_env_file }} + channels: conda-forge + auto-activate-base: false + + - name: Check that Makefile method works + run: | + cd doc/test/ + conda run -n ${{ inputs.conda_env_name }} --no-capture-output ./test_makefile_method.sh diff --git a/.github/workflows/docs-update-doc-builder.yml b/.github/workflows/docs-update-doc-builder.yml new file mode 100644 index 0000000000..1194e1c153 --- /dev/null +++ b/.github/workflows/docs-update-doc-builder.yml @@ -0,0 +1,43 @@ +name: Docs tests to run when doc-builder is updated + +on: + push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] + paths: + - 'doc/doc-builder' + - '.github/workflows/docs-update-dependency-common.yml' + + pull_request: + # Run on pull requests that change the listed files + paths: + - 'doc/doc-builder' + - '.github/workflows/docs-update-dependency-common.yml' + + workflow_dispatch: + +permissions: + contents: read +jobs: + test-update-dependency: + + name: Tests to run when either docs dependency is updated + uses: ./.github/workflows/docs-update-dependency-common.yml + + test-rv-setup: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + lfs: true + + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules + run: | + bin/git-fleximod update -o + + - name: build_docs rv method + run: | + cd doc/test/ && ./test_build_docs_-r-v.sh docker diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8cc1b7c7c2..780ba31b64 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,12 +3,33 @@ name: Test building docs when they're updated on: push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] paths: - 'doc/**' + - '!doc/test/*' + - '!doc/*ChangeLog*' + - '!doc/*ChangeSum*' + - '!doc/UpdateChangelog.pl' + - '.github/workflows/docs-common.yml' + # Include all include::ed files outside doc/ directory! + - 'src/README.unit_testing' + - 'tools/README' + - 'doc/test/test_container_eq_ctsm_pylib.sh' pull_request: + # Run on pull requests that change the listed files paths: - 'doc/**' + - '!doc/test/*' + - '!doc/*ChangeLog*' + - '!doc/*ChangeSum*' + - '!doc/UpdateChangelog.pl' + - '.github/workflows/docs-common.yml' + # Include all include::ed files outside doc/ directory! + - 'src/README.unit_testing' + - 'tools/README' + - 'doc/test/test_container_eq_ctsm_pylib.sh' workflow_dispatch: @@ -30,13 +51,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Checkout doc-builder external + # Check out all submodules because we might :literalinclude: something from one + - name: Checkout all submodules run: | - bin/git-fleximod update doc-builder + bin/git-fleximod update -o - - name: Build docs using container + - name: Build docs using Docker (Podman has trouble on GitHub runners) id: build-docs run: | cd doc && ./build_docs -b ${PWD}/_build -c -d diff --git a/.github/workflows/fleximod_test.yaml b/.github/workflows/fleximod_test.yaml index 7f3e5a1404..d85958f972 100644 --- a/.github/workflows/fleximod_test.yaml +++ b/.github/workflows/fleximod_test.yaml @@ -3,7 +3,11 @@ name: git-fleximod test # Test git-fleximod update and cleanliness # Based closely on workflow from CESM repo # -on: [push, pull_request] +on: + push: + # Run when a change to any is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if no files are changed. + branches: ['*'] + pull_request: jobs: fleximod-test: @@ -18,7 +22,7 @@ jobs: uses: actions/checkout@v4 - id: run-fleximod run: | - $GITHUB_WORKSPACE/bin/git-fleximod update + $GITHUB_WORKSPACE/bin/git-fleximod update -o echo echo "Update complete, checking status" echo @@ -28,4 +32,4 @@ jobs: echo echo "Checking if git fleximod matches expected externals" echo - git diff --exit-code + git add . && git diff --exit-code && git diff --cached --exit-code diff --git a/.github/workflows/formatting_python.yml b/.github/workflows/formatting_python.yml deleted file mode 100644 index 6fffd4261b..0000000000 --- a/.github/workflows/formatting_python.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Check Python formatting - -on: - push: - paths: - - 'python/**' - - 'cime_config/SystemTests/**' - - 'cime_config/buildlib/**' - - 'cime_config/buildnml/**' - pull_request: - paths: - - 'python/**' - - 'cime_config/SystemTests/**' - - 'cime_config/buildlib/**' - - 'cime_config/buildnml/**' - -jobs: - lint-and-format-check: - runs-on: ubuntu-latest - steps: - # Checkout the code - - uses: actions/checkout@v4 - - # Set up the conda environment - - uses: conda-incubator/setup-miniconda@v3 - with: - activate-environment: ctsm_pylib - environment-file: python/conda_env_ctsm_py.yml - channels: conda-forge - auto-activate-base: false - - # Run pylint check - - name: Run pylint - run: | - cd python - conda run -n ctsm_pylib make lint - - # Run black check - - name: Run black - # Run this step even if previous step(s) failed - if: success() || failure() - run: | - cd python - conda run -n ctsm_pylib make black diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000000..79ad016da2 --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,67 @@ +name: Run Python tests + +on: + push: + # Run when a change to these files is pushed to any branch. Without the "branches:" line, for some reason this will be run whenever a tag is pushed, even if the listed files aren't changed. + branches: ['*'] + paths: + - 'python/**' + - 'cime_config/SystemTests/**' + - 'cime_config/buildlib/**' + - 'cime_config/buildnml/**' + pull_request: + # Run on pull requests that change the listed files + paths: + - 'python/**' + - 'cime_config/SystemTests/**' + - 'cime_config/buildlib/**' + - 'cime_config/buildnml/**' + +jobs: + python-unit-tests: + runs-on: ubuntu-latest + steps: + # Checkout the code + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Set up the conda environment + - uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 + with: + activate-environment: ctsm_pylib + environment-file: python/conda_env_ctsm_py.yml + channels: conda-forge + auto-activate-base: false + + # Run Python unit tests check + - name: Run Python unit tests + run: | + cd python + conda run -n ctsm_pylib ./run_ctsm_py_tests -u + + python-lint-and-black: + runs-on: ubuntu-latest + steps: + # Checkout the code + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Set up the conda environment + - uses: conda-incubator/setup-miniconda@2defc80cc6f4028b1780c50faf08dd505d698976 # v3 + with: + activate-environment: ctsm_pylib + environment-file: python/conda_env_ctsm_py.yml + channels: conda-forge + auto-activate-base: false + + # Run pylint check + - name: Run pylint + run: | + cd python + conda run -n ctsm_pylib make lint + + # Run black check + - name: Run black + # Run this step even if previous step(s) failed + if: success() || failure() + run: | + cd python + conda run -n ctsm_pylib make black diff --git a/.github/workflows/validate_xml.py b/.github/workflows/validate_xml.py new file mode 100644 index 0000000000..c854bf916b --- /dev/null +++ b/.github/workflows/validate_xml.py @@ -0,0 +1,67 @@ +""" +Check that all XML files in our repo (except for those in submodules) are well-formed. Error if not. +""" +import sys +import glob +import os +import subprocess +import xml.etree.ElementTree as ET + + +def get_submodule_paths(): + """ + Get list of submodules + """ + cmd = "git config --file .gitmodules --get-regexp path | awk '{ print $2 }'" + result = subprocess.run(cmd, capture_output=True, text=True, shell=True, check=True) + result_list = result.stdout.split("\n")[:-1] + result_list = [x for x in result_list if x] + return result_list + + +def is_in_submodule(file_path, submodule): + """ + Return True if file is in given submodule, False otherwise + """ + return os.path.commonpath([file_path, submodule]) == os.path.commonpath([submodule]) + + +def is_in_any_submodule(file_path, submodule_paths): + """ + Return True if file is in any submodule, False otherwise + """ + file_path = os.path.abspath(file_path) + submodule_paths = map(os.path.abspath, submodule_paths) + return any(is_in_submodule(file_path, submodule) for submodule in submodule_paths) + + +def validate_xml(file_path): + """ + Return True if XML file is well-formed, False otherwise + """ + try: + ET.parse(file_path) + except ET.ParseError: + print(f"❌ {file_path} is NOT well-formed") + return False + print(f"✅ {file_path} is well-formed") + return True + + +def main(): + # pylint: disable=missing-function-docstring + submodule_paths = get_submodule_paths() + all_valid = True + for xml_file in glob.glob("**/*.xml", recursive=True): + if is_in_any_submodule(xml_file, submodule_paths): + continue + if not validate_xml(xml_file): + all_valid = False + + if not all_valid: + print("\nUse xmllint to show problems in malformed files") + + return all_valid + + +sys.exit(0 if main() else 1) diff --git a/.github/workflows/xml-check.yml b/.github/workflows/xml-check.yml new file mode 100644 index 0000000000..d944222dfc --- /dev/null +++ b/.github/workflows/xml-check.yml @@ -0,0 +1,20 @@ +name: Check that XML files are well-formed +# Only check files in our repo that AREN'T in submodules +# Use a Python command to check each file because xmllint isn't available on GH runners + +on: [push, pull_request] # Trigger on push or pull request + +jobs: + check-xml: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.x' + + - name: Check XML files + run: python .github/workflows/validate_xml.py diff --git a/.gitignore b/.gitignore index 278c0957f0..a335b7107e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,16 @@ *.nc # but don't ignore netcdf files here: !/python/ctsm/test/testinputs/*.nc +!/python/ctsm/test/testinputs/**/*.nc # editor files *.swp *~ + +# MS VS Code .vscode/ +# but don't ignore the main .vscode directory here: +!/.vscode # vim files (from https://github.com/github/gitignore/blob/master/Global/Vim.gitignore) # Swap @@ -111,6 +116,7 @@ core.* *.pyc Depends -# Docs build output +# Docs build and testing output _build*/ - +_publish*/ +doc/.coverage diff --git a/.gitmodules b/.gitmodules index 1e20c73d71..94d7e74d97 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,7 @@ [submodule "fates"] path = src/fates url = https://github.com/NorESMhub/fates -fxtag = sci.1.85.1_api.40.0.0_nor_sci5_api1 +fxtag = sci.1.88.6_api.42.0.0_14pft_nor_sci1_api1 fxrequired = AlwaysRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/fates @@ -36,7 +36,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/fates [submodule "cism"] path = components/cism url = https://github.com/NorESMhub/CISM-wrapper -fxtag = cismwrap_2_2_007_noresm_v0 +fxtag = cismwrap_2_2_007_noresm_v1 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/CISM-wrapper @@ -44,7 +44,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/CISM-wrapper [submodule "rtm"] path = components/rtm url = https://github.com/ESCOMP/RTM -fxtag = rtm1_0_86 +fxtag = rtm1_0_89 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESCOMP/RTM @@ -52,14 +52,14 @@ fxDONOTUSEurl = https://github.com/ESCOMP/RTM [submodule "mosart"] path = components/mosart url = https://github.com/NorESMhub/MOSART -fxtag = mosart1.1.12_noresm_v0 +fxtag = mosart1.1.12_noresm_v1 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/MOSART [submodule "mizuRoute"] -path = components/mizuRoute -url = https://github.com/ESCOMP/mizuRoute + path = components/mizuroute + url = https://github.com/ESCOMP/mizuRoute fxtag = cesm-coupling.n03_v2.2.0 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed @@ -68,7 +68,7 @@ fxDONOTUSEurl = https://github.com/ESCOMP/mizuRoute [submodule "ccs_config"] path = ccs_config url = https://github.com/NorESMhub/ccs_config_noresm.git -fxtag = ccs_config_noresm0.0.49 +fxtag = ccs_config_noresm0.0.55 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/ccs_config_noresm @@ -76,7 +76,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/ccs_config_noresm [submodule "cime"] path = cime url = https://github.com/NorESMhub/cime -fxtag = cime6.1.73_noresm_v0 +fxtag = cime6.1.143_noresm_v1 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/cime @@ -84,7 +84,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/cime [submodule "cmeps"] path = components/cmeps url = https://github.com/NorESMhub/CMEPS.git -fxtag = cmeps1.0.39_noresm_v6 +fxtag = cmeps1.1.23_noresm_v1 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/CMEPS.git @@ -92,7 +92,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/CMEPS.git [submodule "cdeps"] path = components/cdeps url = https://github.com/NorESMhub/CDEPS.git -fxtag = cdeps1.0.70_noresm_v7 +fxtag = cdeps1.0.83_noresm_v2 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NorESMhub/CDEPS.git @@ -108,7 +108,7 @@ fxDONOTUSEurl = https://github.com/NorESMhub/NorESM_share [submodule "parallelio"] path = libraries/parallelio url = https://github.com/NCAR/ParallelIO -fxtag = pio2_6_3 +fxtag = pio2_6_5 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/NCAR/ParallelIO @@ -116,7 +116,7 @@ fxDONOTUSEurl = https://github.com/NCAR/ParallelIO [submodule "mpi-serial"] path = libraries/mpi-serial url = https://github.com/ESMCI/mpi-serial -fxtag = MPIserial_2.5.1 +fxtag = MPIserial_2.5.4 fxrequired = ToplevelRequired # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial @@ -124,7 +124,7 @@ fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial [submodule "doc-builder"] path = doc/doc-builder url = https://github.com/ESMCI/doc-builder -fxtag = v2.0.0 +fxtag = v2.2.6 fxrequired = ToplevelOptional # Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed fxDONOTUSEurl = https://github.com/ESMCI/doc-builder diff --git a/.lib/git-fleximod/git_fleximod/cli.py b/.lib/git-fleximod/git_fleximod/cli.py index ac9493cfc3..b5a0549422 100644 --- a/.lib/git-fleximod/git_fleximod/cli.py +++ b/.lib/git-fleximod/git_fleximod/cli.py @@ -1,38 +1,50 @@ from pathlib import Path -import argparse +import argparse, os, sys from git_fleximod import utils -__version__ = "0.9.3" +__version__ = "1.0.2" + +class CustomArgumentParser(argparse.ArgumentParser): + def print_help(self, file=None): + # First print the default help message + super().print_help(file) + + # Then append the contents of README.md + candidate_paths = [ + Path(sys.prefix) / "share" / "git_fleximod" / "README.md", + Path(__file__).resolve().parent.parent / "README.md", # fallback for dev + ] + for path in candidate_paths: + if os.path.exists(path): + with open(path) as f: + print( f.read(), file=file) + return + print( "README.md not found.", file=file) def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree which contains a file called filename """ - try: - root = utils.execute_subprocess(["git","rev-parse", "--show-toplevel"], - output_to_caller=True ).rstrip() - except: - d = Path.cwd() - root = Path(d.root) - dirlist = [] - dl = d - while dl != root: - dirlist.append(dl) - dl = dl.parent - dirlist.append(root) - dirlist.reverse() - - for dl in dirlist: - attempt = dl / filename - if attempt.is_file(): - return str(dl) - return None - return Path(root) + d = Path.cwd() + root = Path(d.root) + dirlist = [] + dl = d + while dl != root: + dirlist.append(dl) + dl = dl.parent + dirlist.append(root) + dirlist.reverse() + + for dl in dirlist: + attempt = dl / filename + if attempt.is_file(): + return str(dl) + return None def get_parser(): description = """ %(prog)s manages checking out groups of gitsubmodules with additional support for Earth System Models """ - parser = argparse.ArgumentParser( + parser = CustomArgumentParser( description=description, formatter_class=argparse.RawDescriptionHelpFormatter ) diff --git a/.lib/git-fleximod/git_fleximod/git_fleximod.py b/.lib/git-fleximod/git_fleximod/git_fleximod.py index 13f35df959..b3c4fece4e 100755 --- a/.lib/git-fleximod/git_fleximod/git_fleximod.py +++ b/.lib/git-fleximod/git_fleximod/git_fleximod.py @@ -9,6 +9,7 @@ import shutil import logging import textwrap +import asyncio from git_fleximod import utils from git_fleximod import cli from git_fleximod.gitinterface import GitInterface @@ -181,6 +182,8 @@ def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger): url = gitmodules.get(name, "url") assert path and url, f"Malformed .gitmodules file {path} {url}" tag = gitmodules.get(name, "fxtag") + if not tag: + tag = gitmodules.get(name, "hash") fxurl = gitmodules.get(name, "fxDONOTUSEurl") fxsparse = gitmodules.get(name, "fxsparse") fxrequired = gitmodules.get(name, "fxrequired") @@ -216,10 +219,10 @@ def git_toplevelroot(root_dir, logger): _, superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") return superroot -def submodules_update(gitmodules, root_dir, requiredlist, force): - for name in gitmodules.sections(): +async def submodules_update(gitmodules, root_dir, requiredlist, force): + async def update_submodule(name, requiredlist, force): submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) - + _, needsupdate, localmods, testfails = submod.status() if not submod.fxrequired: submod.fxrequired = "AlwaysRequired" @@ -237,11 +240,11 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): if "Optional" in fxrequired and "Optional" not in requiredlist: if fxrequired.startswith("Always"): print(f"Skipping optional component {name:>20}") - continue + return # continue to next submodule optional = "AlwaysOptional" in requiredlist if fxrequired in requiredlist: - submod.update() + await submod.update() repodir = os.path.join(root_dir, submod.path) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout @@ -250,8 +253,10 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): newrequiredlist = ["AlwaysRequired"] if optional: newrequiredlist.append("AlwaysOptional") + await submodules_update(gitsubmodules, repodir, newrequiredlist, force=force) - submodules_update(gitsubmodules, repodir, newrequiredlist, force=force) + tasks = [update_submodule(name, requiredlist, force) for name in gitmodules.sections()] + await asyncio.gather(*tasks) def local_mods_output(): text = """\ @@ -345,7 +350,7 @@ def main(): sys.exit(f"No submodule components found, root_dir={root_dir}") retval = 0 if action == "update": - submodules_update(gitmodules, root_dir, fxrequired, force) + asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force)) elif action == "status": tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True) if tfails + lmods + updates > 0: diff --git a/.lib/git-fleximod/git_fleximod/gitinterface.py b/.lib/git-fleximod/git_fleximod/gitinterface.py index fb20883cd0..022426d28c 100644 --- a/.lib/git-fleximod/git_fleximod/gitinterface.py +++ b/.lib/git-fleximod/git_fleximod/gitinterface.py @@ -2,6 +2,7 @@ import sys from . import utils from pathlib import Path +import asyncio class GitInterface: def __init__(self, repo_path, logger): @@ -47,6 +48,16 @@ def _init_git_repo(self): command = ("git", "-C", str(self.repo_path), "init") utils.execute_subprocess(command) + def _git_operation_command(self, operation, args): + newargs = [] + for a in args: + # Do not use ssh interface + if isinstance(a, str): + a = a.replace("git@github.com:", "https://github.com/") + newargs.append(a) + + return self._git_command(operation, *newargs) + # pylint: disable=unused-argument def git_operation(self, operation, *args, **kwargs): newargs = [] @@ -66,6 +77,25 @@ def git_operation(self, operation, *args, **kwargs): else: return 0, command + # pylint: disable=unused-argument + async def git_operation_async(self, operation, *args, **kwargs): + command = self._git_operation_command(operation, args) + if isinstance(command, list): + try: + process = await asyncio.create_subprocess_exec( + *command, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + status = process.returncode + output = stdout.decode().strip() if stdout else stderr.decode().strip() + return status, output + except Exception as e: + sys.exit(e) + else: + return 0, command + def config_get_value(self, section, name): if self._use_module: config = self.repo.config_reader() diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index 75d9dd4eb9..4c6c3accb3 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -119,27 +119,25 @@ def status(self): atag = atag[:-1] if atag == self.fxtag: break - - - #print(f"line is {line} ahash is {ahash} atag is {atag} {parts}") - # atag = git.git_operation("describe", "--tags", "--always") - # ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] - recurse = False if rurl != self.url: remote = self._add_remote(git) git.git_operation("fetch", remote) + # Asked for a tag and found that tag if self.fxtag and atag == self.fxtag: result = f" {self.name:>20} at tag {self.fxtag}" recurse = True testfails = False + # Asked for and found a hash elif self.fxtag and (ahash[: len(self.fxtag)] == self.fxtag or (self.fxtag.find(ahash)==0)): result = f" {self.name:>20} at hash {ahash}" recurse = True testfails = False + # Asked for and found a hash elif atag == ahash: result = f" {self.name:>20} at hash {ahash}" recurse = True + # Did not find requested tag or hash elif self.fxtag: result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}" testfails = True @@ -284,17 +282,18 @@ def sparse_checkout(self): if not os.path.isdir(infodir): os.makedirs(infodir) gitsparse = os.path.abspath(os.path.join(infodir, "sparse-checkout")) - if os.path.isfile(gitsparse): - self.logger.warning( - "submodule {} is already initialized {}".format(self.name, rootdotgit) - ) - return - - with utils.pushd(sprep_repo): + if os.path.isfile(gitsparse): + self.logger.warning( + "submodule {} is already initialized {}".format(self.name, rootdotgit) + ) + os.remove(gitsparse) + if os.path.isfile(self.fxsparse): - shutil.copy(self.fxsparse, gitsparse) - + else: + self.logger.warning( + "submodule {} could not find {}".format(self.name, self.fxsparse) + ) # Finally checkout the repo sprepo_git.git_operation("fetch", "origin", "--tags") @@ -303,11 +302,18 @@ def sparse_checkout(self): print(f"Error checking out {self.name:>20} at {self.fxtag}") else: print(f"Successfully checked out {self.name:>20} at {self.fxtag}") + status,f = sprepo_git.git_operation("status") + # Restore any files deleted from sandbox + for line in f.splitlines(): + if "deleted:" in line: + deleted_file = line.split("deleted:")[1].strip() + sprepo_git.git_operation("checkout", deleted_file) + rgit.config_set_value('submodule.' + self.name, "active", "true") rgit.config_set_value('submodule.' + self.name, "url", self.url) rgit.config_set_value('submodule.' + self.name, "path", self.path) - def update(self): + async def update(self): """ Updates the submodule to the latest or specified version. @@ -341,6 +347,9 @@ def update(self): # Look for a .gitmodules file in the newly checkedout repo if self.fxsparse: print(f"Sparse checkout {self.name} fxsparse {self.fxsparse}") + if not os.path.isfile(self.fxsparse): + self.logger.info("Submodule {} fxsparse file not found".format(self.name)) + self.sparse_checkout() else: if not repo_exists and self.url: @@ -378,19 +387,26 @@ def update(self): git.git_operation("submodule", "add", "--name", self.name, "--", self.url, self.path) if not repo_exists: - git.git_operation("submodule", "update", "--init", "--", self.path) + git.git_operation("submodule", "init", "--", self.path) + await git.git_operation_async("submodule", "update", "--", self.path) if self.fxtag: smgit = GitInterface(repodir, self.logger) newremote = self._add_remote(smgit) # Trying to distingush a tag from a hash - allowed = set(string.digits + 'abcdef') + allowed = set(string.digits + 'abcdef') + status = 0 if not set(self.fxtag) <= allowed: # This is a tag tag = f"refs/tags/{self.fxtag}:refs/tags/{self.fxtag}" - smgit.git_operation("fetch", newremote, tag) - smgit.git_operation("checkout", self.fxtag) - + status,_ = smgit.git_operation("fetch", newremote, tag) + if status == 0: + status,_ = smgit.git_operation("checkout", self.fxtag) + if status: + utils.fatal_error( + f"Failed to checkout {self.name} at tag or hash {self.fxtag} from {repodir}" + ) + if not os.path.exists(os.path.join(repodir, ".git")): utils.fatal_error( f"Failed to checkout {self.name} {repo_exists} {repodir} {self.path}" @@ -408,6 +424,18 @@ def update(self): if fxtag and fxtag not in tags: git.git_operation("fetch", newremote, "--tags") status, atag = git.git_operation("describe", "--tags", "--always") + status, files = git.git_operation("diff", "--name-only", "-z") + modfiles = [] + moddirs = [] + if files: + for f in files.split('\0'): + if f: + if os.path.exists(f): + git.git_operation("checkout",f) + elif os.path.isdir(f): + moddirs.append(f) + else: + modfiles.append(f) if fxtag and fxtag != atag: try: status, _ = git.git_operation("checkout", fxtag) @@ -419,6 +447,10 @@ def update(self): elif not fxtag: print(f"No fxtag found for submodule {self.name:>20}") + elif modfiles: + print(f"{self.name:>20} has modified files: {modfiles}") + elif moddirs: + print(f"{self.name:>20} has modified directories: {moddirs}") else: print(f"{self.name:>20} up to date.") diff --git a/.lib/git-fleximod/pyproject.toml b/.lib/git-fleximod/pyproject.toml index 1d0419ad20..029fd65e2b 100644 --- a/.lib/git-fleximod/pyproject.toml +++ b/.lib/git-fleximod/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.9.3" +version = "1.0.2" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] @@ -11,6 +11,7 @@ keywords = ["git", "submodule", "sparse-checkout"] packages = [ { include = "git_fleximod"}, { include = "doc"}, +{ include = "README.md"}, ] [tool.poetry.scripts] diff --git a/.lib/git-fleximod/tbump.toml b/.lib/git-fleximod/tbump.toml index b432206a54..f6fe1e88e2 100644 --- a/.lib/git-fleximod/tbump.toml +++ b/.lib/git-fleximod/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.9.3" +current = "1.0.2" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/.lib/git-fleximod/tests/conftest.py b/.lib/git-fleximod/tests/conftest.py index 1dd1b86f34..44d28e1788 100644 --- a/.lib/git-fleximod/tests/conftest.py +++ b/.lib/git-fleximod/tests/conftest.py @@ -32,7 +32,7 @@ def logger(): "submodule_name": "test_optional", "status1" : "test_optional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_optional at tag MPIserial_2.4.0", - "status3" : "test_optional not checked out, out of sync at tag MPIserial_2.5.1, expected tag is MPIserial_2.4.0 (optional)", + "status3" : "test_optional not checked out, out of sync at tag MPIserial_2.5.4, expected tag is MPIserial_2.4.0 (optional)", "status4" : "test_optional at tag MPIserial_2.4.0", "gitmodules_content": """ [submodule "test_optional"] @@ -46,7 +46,7 @@ def logger(): "submodule_name": "test_alwaysoptional", "status1" : "test_alwaysoptional MPIserial_2.3.0 is out of sync with .gitmodules e5cf35c", "status2" : "test_alwaysoptional at hash e5cf35c", - "status3" : "out of sync at tag MPIserial_2.5.1, expected tag is e5cf35c", + "status3" : "out of sync at tag MPIserial_2.5.4, expected tag is e5cf35c", "status4" : "test_alwaysoptional at hash e5cf35c", "gitmodules_content": """ [submodule "test_alwaysoptional"] diff --git a/.lib/git-fleximod/tests/test_c_required.py b/.lib/git-fleximod/tests/test_c_required.py index 89ab8d294d..2ac6614519 100644 --- a/.lib/git-fleximod/tests/test_c_required.py +++ b/.lib/git-fleximod/tests/test_c_required.py @@ -1,4 +1,5 @@ import pytest +import re from pathlib import Path def test_required(git_fleximod, test_repo, shared_repos): @@ -28,3 +29,15 @@ def test_required(git_fleximod, test_repo, shared_repos): assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") assert shared_repos["status4"] in status.stdout + + text = file_path.read_text() + new_value = "somethingelse" + pattern = r"(^\s*fxtag\s*=\s*).*$" + replacement = r"\1" + new_value + new_text = re.sub(pattern, replacement, text, flags=re.MULTILINE) + + # Write updated content back to file + file_path.write_text(new_text) + + result = git_fleximod(test_repo, f"update {repo_name}") + assert f'fatal: couldn\'t find remote ref' in result.stderr or 'error: pathspec \'somethingelse\' did not match any file(s) known to git' in result.stderr diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 0000000000..83801e43c2 --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,12 @@ +# Suggested Settings for Using MS Visual Studio Code with CTSM + +These are a few useful settings for using VS Code with CTSM. + +To enable the template for your local use... + +``` shell +cp settings_template.json settings.json +``` + +[!IMPORTANT] +If you already have a settings.json file in place, copy the code from the template into your local version. This could go here for this CTSM clone, or to your: User, Remote, or Workspace level. \ No newline at end of file diff --git a/.vscode/settings_template.json b/.vscode/settings_template.json new file mode 100644 index 0000000000..02133e4d4e --- /dev/null +++ b/.vscode/settings_template.json @@ -0,0 +1,11 @@ +{ + // Exclude all files from .gitignore from being shown in the VS Code explorer view + "explorer.excludeGitIgnore": true + // Treat .pf PFunit files as if they are Fortran + "files.associations": { + "*.pf": "fortran-modern" + // Path to fortls from the ctsm_pylib conda environment + // Correct the path for you to use + "fortran-ls.executablePath": "/glade/work/$USER/conda-envs/ctsm_pylib/bin/fortls", + } +} \ No newline at end of file diff --git a/README b/README index 99c6d8e9d4..b752d07660 100644 --- a/README +++ b/README @@ -66,7 +66,7 @@ components/cmeps -------------------- CESM top level driver (for NUOPC driver [w components/cdeps -------------------- CESM top level data model shared code (for NUOPC driver). components/cism --------------------- CESM Community land Ice Sheet Model. components/mosart ------------------- Model for Scale Adaptive River Transport -components/mizuRoute ---------------- Reached based river transport model for water routing +components/mizuroute ---------------- Reached based river transport model for water routing (allows both gridded river and Hydrologic Responce Unit river grids) components/rtm ---------------------- CESM River Transport Model. @@ -141,6 +141,7 @@ tools/contrib ----------------- Miscellansous useful scripts for pre and post pr as well as case management of CTSM. These scripts are contributed by users and may not be as well tested or supported as other tools. +.vscode ----------------------- Suggested settings for using MS Visual Studio code with CTSM. ============================================================================================= diff --git a/README.md b/README.md index 5e800a0b77..5ea26566c9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ http://www.cesm.ucar.edu/models/cesm2.0/land/ and -https://escomp.github.io/ctsm-docs/ +https://escomp.github.io/CTSM/ For help with how to work with CTSM in git, see diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index 712a5619aa..9e26e99a3f 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -811,7 +811,8 @@ sub setup_cmdl_fates_mode { "flandusepftdat","use_fates_potentialveg","use_fates_lupft","fates_history_dimlevel", "use_fates_daylength_factor", "fates_photosynth_acclimation", "fates_stomatal_model", "fates_stomatal_assimilation", "fates_leafresp_model", "fates_cstarvation_model", - "fates_regeneration_model", "fates_hydro_solver", "fates_radiation_model", "fates_electron_transport_model" + "fates_regeneration_model", "fates_hydro_solver", "fates_radiation_model", "fates_electron_transport_model", + "use_fates_managed_fire" ); # dis-allow fates specific namelist items with non-fates runs @@ -1005,6 +1006,13 @@ sub setup_cmdl_bgc { if ( &value_is_true($nl->get_value($var)) && $nl_flags->{'soil_decomp_method'} ne "CENTURYKoven2013" ) { $log->fatal_error("$var can only be on with CENTURYKoven2013 soil decomposition"); } + + # Set use_nvmovement + $var = "use_nvmovement"; + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, $var); + if ( &value_is_true($nl->get_value($var)) && !&value_is_true($nl_flags->{'use_nitrif_denitrif'}) ) { + $log->fatal_error("$var cannot be on with use_nitrif_denitrif = .false."); + } } # end bgc @@ -1018,15 +1026,24 @@ sub setup_cmdl_fire_light_res { my $val = $opts->{$var}; if ( &value_is_true($nl->get_value('use_cn')) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'fire_method'); - } else { - if ( &value_is_true($nl->get_value('use_fates')) ) { - # fates_spitfire_mode default has to get a default here and not in setup_logic_fates - if ( ! defined($nl->get_value('fates_spitfire_mode'))){ - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl,'fates_spitfire_mode', - 'use_fates'=>$nl_flags->{'use_fates'}, 'use_fates_sp'=>$nl_flags->{'use_fates_sp'}); + } + + # we have to process defaults for fates fire modes before and not in setup_logc_fates, however, they should not be defined for use_cn or other bgc + if ( &value_is_true($nl->get_value('use_fates')) ) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_fates_managed_fire', + 'use_fates'=>$nl_flags->{'use_fates'}, 'use_fates_sp'=>$nl_flags->{'use_fates_sp'} ); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'fates_spitfire_mode', 'use_fates'=>$nl_flags->{'use_fates'}, + 'use_fates_managed_fire'=>$nl->get_value('use_fates_managed_fire'), 'use_fates_sp'=>$nl_flags->{'use_fates_sp'} ); + # Check use_fates_managed_fire mode is running with spitfire on + if ( defined($nl->get_value('use_fates_managed_fire')) ) { + if ( &value_is_true($nl->get_value('use_fates_managed_fire')) ) { + if ( $nl->get_value('fates_spitfire_mode') == 0 ) { + $log->fatal_error("fates_spitfire_mode must be non-zero when use_fates_managed_fire is true"); + } } } } + my $fire_method = remove_leading_and_trailing_quotes( $nl->get_value('fire_method') ); if ( $val eq "default" ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, $var, @@ -1696,6 +1713,7 @@ sub process_namelist_inline_logic { setup_logic_cnmatrix($opts, $nl_flags, $definition, $defaults, $nl, $envxml_ref); setup_logic_spinup($opts, $nl_flags, $definition, $defaults, $nl); setup_logic_supplemental_nitrogen($opts, $nl_flags, $definition, $defaults, $nl); + setup_logic_c_isotope($opts, $nl_flags, $definition, $defaults, $nl); setup_logic_snowpack($opts, $nl_flags, $definition, $defaults, $nl); setup_logic_fates($opts, $nl_flags, $definition, $defaults, $nl); setup_logic_z0param($opts, $nl_flags, $definition, $defaults, $nl); @@ -1745,7 +1763,6 @@ sub process_namelist_inline_logic { # namelist group: ch4par_in # ############################### setup_logic_methane($opts, $nl_flags, $definition, $defaults, $nl); - setup_logic_c_isotope($opts, $nl_flags, $definition, $defaults, $nl); ############################### # namelist group: ndepdyn_nml # @@ -1811,6 +1828,7 @@ sub process_namelist_inline_logic { # NOTE: After setup_logic_dust_emis # ##################################### setup_logic_megan($opts, $nl_flags, $definition, $defaults, $nl); + setup_logic_megan_opts($opts, $nl_flags, $definition, $defaults, $nl); ################################## # namelist group: lai_streams # @@ -2762,6 +2780,9 @@ SIMYR: foreach my $sim_yr ( @sim_years ) { $log->fatal_error("Problem interpreting init_interp_attributes from the namelist_defaults file: $pair"); } } + # Add init_interp_fill_missing_urban_with_HD defaults as a function of sim_year and phys + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'init_interp_fill_missing_urban_with_HD', + 'sim_year'=>$settings{'sim_year'}, 'phys'=>$physv->as_string() ); } } else { $try = $done @@ -2875,7 +2896,7 @@ sub setup_logic_do_transient_pfts { } # if do_transient_pfts is .true. and any of these (n_dom_* or toosmall_*) - # are > 0 or collapse_urban = .true., then give fatal error + # are > 0 or collapse_urban = .true., or vars_1dwt_w_time = .false., then give fatal error if (&value_is_true($nl->get_value($var))) { if (&value_is_true($nl->get_value('collapse_urban'))) { $log->fatal_error("$var cannot be combined with collapse_urban"); @@ -2883,6 +2904,12 @@ sub setup_logic_do_transient_pfts { if ($n_dom_pfts > 0 || $n_dom_landunits > 0 || $toosmall_soil > 0 || $toosmall_crop > 0 || $toosmall_glacier > 0 || $toosmall_lake > 0 || $toosmall_wetland > 0 || $toosmall_urban > 0) { $log->fatal_error("$var cannot be combined with any of the of the following > 0: n_dom_pfts > 0, n_dom_landunit > 0, toosmall_soi > 0._r8, toosmall_crop > 0._r8, toosmall_glacier > 0._r8, toosmall_lake > 0._r8, toosmall_wetland > 0._r8, toosmall_urban > 0._r8"); } + + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'vars_1dwt_w_time', + 'do_transient_pfts'=>$nl_flags->{'do_transient_pfts'}); + if (!&value_is_true($nl->get_value('vars_1dwt_w_time'))) { + $log->fatal_error("vars_1dwt_w_time cannot be .false. if do_transient_pfts is .true."); + } } } @@ -2952,7 +2979,7 @@ sub setup_logic_do_transient_crops { } # if do_transient_crops is .true. and any of these (n_dom_* or toosmall_*) - # are > 0 or collapse_urban = .true., then give fatal error + # are > 0 or collapse_urban = .true., or vars_1dwt_w_time = .false., then give fatal error if (&value_is_true($nl->get_value($var))) { if (&value_is_true($nl->get_value('collapse_urban'))) { $log->fatal_error("$var cannot be combined with collapse_urban"); @@ -2960,6 +2987,12 @@ sub setup_logic_do_transient_crops { if ($n_dom_pfts > 0 || $n_dom_landunits > 0 || $toosmall_soil > 0 || $toosmall_crop > 0 || $toosmall_glacier > 0 || $toosmall_lake > 0 || $toosmall_wetland > 0 || $toosmall_urban > 0) { $log->fatal_error("$var cannot be combined with any of the of the following > 0: n_dom_pfts > 0, n_dom_landunit > 0, toosmall_soil > 0._r8, toosmall_crop > 0._r8, toosmall_glacier > 0._r8, toosmall_lake > 0._r8, toosmall_wetland > 0._r8, toosmall_urban > 0._r8"); } + + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'vars_1dwt_w_time', + 'do_transient_crops'=>$nl_flags->{'do_transient_crops'}); + if (!&value_is_true($nl->get_value('vars_1dwt_w_time'))) { + $log->fatal_error("vars_1dwt_w_time cannot be .false. if do_transient_crops is .true."); + } } my $dopft = "do_transient_pfts"; @@ -3029,7 +3062,7 @@ sub setup_logic_do_transient_lakes { } # if do_transient_lakes is .true. and any of these (n_dom_* or toosmall_*) - # are > 0 or collapse_urban = .true., then give fatal error + # are > 0 or collapse_urban = .true., or vars_1dwt_w_time = .false., then give fatal error if (&value_is_true($nl->get_value($var))) { if (&value_is_true($nl->get_value('collapse_urban'))) { $log->fatal_error("$var cannot be combined with collapse_urban"); @@ -3039,6 +3072,12 @@ sub setup_logic_do_transient_lakes { if ($n_dom_pfts > 0 || $n_dom_landunits > 0 || $toosmall_soil > 0 || $toosmall_crop > 0 || $toosmall_glacier > 0 || $toosmall_lake > 0 || $toosmall_wetland > 0 || $toosmall_urban > 0) { $log->fatal_error("$var cannot be combined with any of the of the following > 0: n_dom_pfts > 0, n_dom_landunit > 0, toosmall_soil > 0._r8, toosmall_crop > 0._r8, toosmall_glacier > 0._r8, toosmall_lake > 0._r8, toosmall_wetland > 0._r8, toosmall_urban > 0._r8"); } + + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'vars_1dwt_w_time', + 'do_transient_lakes'=>$nl_flags->{'do_transient_lakes'}); + if (!&value_is_true($nl->get_value('vars_1dwt_w_time'))) { + $log->fatal_error("vars_1dwt_w_time cannot be .false. if do_transient_lakes is .true."); + } } } @@ -3101,7 +3140,7 @@ sub setup_logic_do_transient_urban { } # if do_transient_urban is .true. and any of these (n_dom_* or toosmall_*) - # are > 0 or collapse_urban = .true., then give fatal error + # are > 0 or collapse_urban = .true., or vars_1dwt_w_time = .false., then give fatal error if (&value_is_true($nl->get_value($var))) { if (&value_is_true($nl->get_value('collapse_urban'))) { $log->fatal_error("$var cannot be combined with collapse_urban"); @@ -3111,6 +3150,12 @@ sub setup_logic_do_transient_urban { if ($n_dom_pfts > 0 || $n_dom_landunits > 0 || $toosmall_soil > 0 || $toosmall_crop > 0 || $toosmall_glacier > 0 || $toosmall_lake > 0 || $toosmall_wetland > 0 || $toosmall_urban > 0) { $log->fatal_error("$var cannot be combined with any of the of the following > 0: n_dom_pfts > 0, n_dom_landunit > 0, toosmall_soil > 0._r8, toosmall_crop > 0._r8, toosmall_glacier > 0._r8, toosmall_lake > 0._r8, toosmall_wetland > 0._r8, toosmall_urban > 0._r8"); } + + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'vars_1dwt_w_time', + 'do_transient_urban'=>$nl_flags->{'do_transient_urban'}); + if (!&value_is_true($nl->get_value('vars_1dwt_w_time'))) { + $log->fatal_error("vars_1dwt_w_time cannot be .false. if do_transient_urban is .true."); + } } } @@ -3183,10 +3228,11 @@ sub setup_logic_do_grossunrep { my $var = 'do_grossunrep'; - # Start by assuming a default value of '.true.'. Then check a number of + # Start by assuming a default value of '.false.'. Then check a number of # conditions under which do_grossunrep cannot be true. Under these - # conditions: (1) set default value to '.false.'; (2) make sure that the + # conditions: (1) set default value to '.false.' again; (2) make sure that the # value is indeed false (e.g., that the user didn't try to set it to true). + # Ideally the default value would be set in namelist_defaults my $default_val = ".false."; @@ -3711,64 +3757,86 @@ sub setup_logic_c_isotope { # my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c13', + 'bgc_mode'=>$nl_flags->{'bgc_mode'}, 'phys'=>$nl_flags->{'phys'}, + 'lnd_tuning_mode'=>$nl_flags->{'lnd_tuning_mode'}, ssp_rcp=>$nl_flags->{'ssp_rcp'} ); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c14', + 'bgc_mode'=>$nl_flags->{'bgc_mode'}, 'phys'=>$nl_flags->{'phys'}, + 'lnd_tuning_mode'=>$nl_flags->{'lnd_tuning_mode'}, ssp_rcp=>$nl_flags->{'ssp_rcp'} ); my $use_c13 = $nl->get_value('use_c13'); my $use_c14 = $nl->get_value('use_c14'); if ( $nl_flags->{'bgc_mode'} ne "sp" && $nl_flags->{'bgc_mode'} ne "fates" ) { if ( $nl_flags->{'bgc_mode'} ne "bgc" ) { - if ( defined($use_c13) && &value_is_true($use_c13) ) { + if ( &value_is_true($use_c13) ) { $log->warning("use_c13 is ONLY scientifically validated with the bgc=BGC configuration" ); } - if ( defined($use_c14) && &value_is_true($use_c14) ) { + if ( &value_is_true($use_c14) ) { $log->warning("use_c14 is ONLY scientifically validated with the bgc=BGC configuration" ); } } - if ( defined($use_c14) ) { - if ( &value_is_true($use_c14) ) { - my $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); - if ( defined($use_c14_bombspike) && &value_is_true($use_c14_bombspike) ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c14_filename', - 'use_c14'=>$use_c14, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c14_bombspike'=>$nl->get_value('use_c14_bombspike'), - 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); - } - } else { - if ( defined($nl->get_value('use_c14_bombspike')) || - defined($nl->get_value('atm_c14_filename')) ) { - $log->fatal_error("use_c14 is FALSE and use_c14_bombspike or atm_c14_filename set"); - } + my $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); + my $stream_fldfilename_atm_c14 = $nl->get_value('stream_fldfilename_atm_c14'); + my $atm_c14_filename = $nl->get_value('atm_c14_filename'); + if ( &value_is_true($use_c14) ) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c14_bombspike', 'use_c14'=>$use_c14 ); + $use_c14_bombspike = $nl->get_value('use_c14_bombspike'); + if ( &value_is_true($use_c14_bombspike) ) { + if ( defined($stream_fldfilename_atm_c14) ) { + setup_logic_c14_streams($opts, $nl_flags, $definition, $defaults, $nl); + } else { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c14_filename', + 'use_c14'=>$use_c14, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c14_bombspike'=>$nl->get_value('use_c14_bombspike'), + 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + } + $stream_fldfilename_atm_c14 = $nl->get_value('stream_fldfilename_atm_c14'); + $atm_c14_filename = $nl->get_value('atm_c14_filename'); + if ( defined($stream_fldfilename_atm_c14) && defined($atm_c14_filename) ) { + $log->fatal_error("Both stream_fldfilename_atm_c14 and atm_c14_filename set, only one should be set"); + } } } else { - if ( defined($nl->get_value('use_c14_bombspike')) || - defined($nl->get_value('atm_c14_filename')) ) { - $log->fatal_error("use_c14 NOT set to .true., but use_c14_bompspike/atm_c14_filename defined."); + if ( defined($use_c14_bombspike) || + defined($stream_fldfilename_atm_c14) || + defined($atm_c14_filename) ) { + $log->fatal_error("use_c14 is FALSE and use_c14_bombspike, stream_fldfilename_atm_c14 or atm_c14_filename set"); } } - if ( defined($use_c13) ) { - if ( &value_is_true($use_c13) ) { - my $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); - if ( defined($use_c13_timeseries) && &value_is_true($use_c13_timeseries) ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c13_filename', - 'use_c13'=>$use_c13, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c13_timeseries'=>$nl->get_value('use_c13_timeseries'), - 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); - } - } else { - if ( defined($nl->get_value('use_c13_timeseries')) || - defined($nl->get_value('atm_c13_filename')) ) { - $log->fatal_error("use_c13 is FALSE and use_c13_timeseries or atm_c13_filename set"); - } + my $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); + my $stream_fldfilename_atm_c13 = $nl->get_value('stream_fldfilename_atm_c13'); + my $atm_c13_filename = $nl->get_value('atm_c13_filename'); + if ( &value_is_true($use_c13) ) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_c13_timeseries', 'use_c13'=>$use_c13 ); + $use_c13_timeseries = $nl->get_value('use_c13_timeseries'); + if ( &value_is_true($use_c13_timeseries) ) { + if ( defined($stream_fldfilename_atm_c13) ) { + setup_logic_c13_streams($opts, $nl_flags, $definition, $defaults, $nl); + } else { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'atm_c13_filename', + 'use_c13'=>$use_c13, 'use_cn'=>$nl_flags->{'use_cn'}, 'use_c13_timeseries'=>$nl->get_value('use_c13_timeseries'), + 'ssp_rcp'=>$nl_flags->{'ssp_rcp'} ); + } + $stream_fldfilename_atm_c13 = $nl->get_value('stream_fldfilename_atm_c13'); + $atm_c13_filename = $nl->get_value('atm_c13_filename'); + if ( defined($stream_fldfilename_atm_c13) && defined($atm_c13_filename) ) { + $log->fatal_error("Both stream_fldfilename_atm_c13 and atm_c13_filename set, only one should be set"); + } } } else { if ( defined($nl->get_value('use_c13_timeseries')) || + defined($nl->get_value('stream_fldfilename_atm_c13')) || defined($nl->get_value('atm_c13_filename')) ) { - $log->fatal_error("use_c13 NOT set to .true., but use_c13_bompspike/atm_c13_filename defined."); + $log->fatal_error("use_c13 is FALSE and use_c13_timeseries, stream_fldfilename_atm_c13 or atm_c13_filename set"); } } } else { - if ( defined($use_c13) || - defined($use_c14) || - defined($nl->get_value('use_c14_bombspike')) || + if ( &value_is_true($use_c13) || + &value_is_true($use_c14) || + &value_is_true($nl->get_value('use_c14_bombspike')) || defined($nl->get_value('atm_c14_filename')) || - defined($nl->get_value('use_c13_timeseries')) || - defined($nl->get_value('atm_c13_filename')) ) { + defined($nl->get_value('stream_fldfilename_atm_c14')) || + &value_is_true($nl->get_value('use_c13_timeseries')) || + defined($nl->get_value('atm_c13_filename')) || + defined($nl->get_value('stream_fldfilename_atm_c13')) ) { $log->fatal_error("bgc=sp and C isotope namelist variables were set, both can't be used at the same time"); } } @@ -3776,6 +3844,24 @@ sub setup_logic_c_isotope { #------------------------------------------------------------------------------- +sub setup_logic_c13_streams { + my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # + # C13 stream file settings + # +} + +#------------------------------------------------------------------------------- + +sub setup_logic_c14_streams { + my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # + # C14 stream file settings + # +} + +#------------------------------------------------------------------------------- + sub setup_logic_nitrogen_deposition { my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; @@ -4049,12 +4135,13 @@ sub setup_logic_dry_deposition { my @list = ( "drydep_list", "dep_data_file"); if ($opts->{'drydep'} ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'drydep_list'); - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'dep_data_file'); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'drydep_list', "use_fates"=>$nl_flags->{'use_fates'}); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'dep_data_file', "use_fates"=>$nl_flags->{'use_fates'}); + &remove_newlines( $nl, $definition, "drydep_list" ); } # fates-sp will set use_fates_nocomp in the setup logic for fates earlier if ( &value_is_true( $nl_flags->{'use_fates'}) && - not &value_is_true($nl->get_value('use_fates_nocomp'))) { + ! &value_is_true($nl->get_value('use_fates_nocomp'))) { foreach my $var ( @list ) { if ( defined($nl->get_value($var)) ) { $log->warning("DryDeposition $var is being set and can NOT be on when FATES is also on unless FATES-NOCOMP mode is on.\n" . @@ -4080,7 +4167,16 @@ sub setup_logic_fire_emis { if ( &value_is_true( $nl_flags->{'use_fates'} ) ) { $log->warning("Fire emission option $var can NOT be on when FATES is also on.\n" . " DON'T use the '--fire_emis' option when '--bgc fates' is activated"); - } + } elsif ( ! &value_is_true( $nl_flags->{'use_cn'} ) ) { + $log->fatal_error("Fire emission option $var can NOT be on when BGC SP (i.e. Satellite Phenology) is also on.\n" . + " DON'T use the '--fire_emis' option when '--bgc sp' is activated"); + } elsif ( &value_is_true( $nl_flags->{'use_cn'}) ) { + my $fire_method = remove_leading_and_trailing_quotes( $nl->get_value('fire_method') ); + if ( $fire_method eq "nofire" ) { + $log->fatal_error("Fire emission option $var can NOT be on with BGC and fire_method=='nofire'.\n" . + " DON'T use the '--fire_emis' option when fire_method is nofire"); + } + } } } } @@ -4161,6 +4257,7 @@ sub setup_logic_dust_emis { sub setup_logic_megan { my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # Setup megan_emis_nl namelist for drv_flds_in my $var = "megan"; @@ -4174,12 +4271,40 @@ sub setup_logic_megan { } if ($nl_flags->{'megan'} ) { - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_specifier'); - add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_factors_file'); + if (&value_is_true($nl_flags->{"use_fates"}) && ! &value_is_true($nl->get_value('use_fates_nocomp'))) { + $log->fatal_error("Running MEGAN in fates bgc mode without use_fates_nocomp=.true. or use_fates_sp=.true. is not allowed"); + } + + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_specifier',"use_fates"=>$nl_flags->{'use_fates'}); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_factors_file',"use_fates"=>$nl_flags->{'use_fates'}); } if ( defined($nl->get_value('megan_specifier')) || defined($nl->get_value('megan_factors_file')) ) { check_megan_spec( $opts, $nl, $definition ); + &remove_newlines( $nl, $definition, "megan_specifier" ); + } +} + +#------------------------------------------------------------------------------- + +sub setup_logic_megan_opts { + my ($opts, $nl_flags, $definition, $defaults, $nl) = @_; + # Setup megan_opts namelist + # This should be set when megan is turned on by CTSM, but also when CAM has turned it on + + if ($nl_flags->{'megan'} ) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_use_gamma_sm'); + if ( &value_is_true( $nl->get_value('megan_use_gamma_sm') ) ) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'megan_min_gamma_sm'); + } elsif ( defined($nl->get_value('megan_min_gamma_sm')) ) { + $log->fatal_error("megan_min_gamma_sm should NOT be set when megan_use_gamma_sm NOT TRUE.\n" ); + } + } + else { + if ( defined($nl->get_value('megan_use_gamma_sm')) || + defined($nl->get_value('megan_min_gamma_sm')) ) { + $log->fatal_error("MEGAN options should NOT be set when MEGAN is NOT in use.\n" ); + } } } @@ -4239,7 +4364,7 @@ sub setup_logic_lai_streams { if ( &value_is_true($nl_flags->{'use_crop'}) && &value_is_true($nl->get_value('use_lai_streams')) ) { $log->fatal_error("turning use_lai_streams on is incompatable with use_crop set to true."); } - if ( $nl_flags->{'bgc_mode'} eq "sp" || ($nl_flags->{'bgc_mode'} eq "fates" && &value_is_true($nl->get_value('use_fates_sp')) )) { + if ( $nl_flags->{'bgc_mode'} eq "sp" || ($nl_flags->{'bgc_mode'} eq "fates" && &value_is_true($nl_flags->{'use_fates_sp'}) )) { if ( &value_is_true($nl->get_value('use_lai_streams')) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_lai_streams'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'lai_mapalgo', @@ -4482,6 +4607,10 @@ sub setup_logic_cngeneral { "(eg. don't use these options with SP mode)."); } } + if ( &value_is_true($nl->get_value('reseed_dead_plants')) && + &remove_leading_and_trailing_quotes($nl_flags->{'clm_start_type'}) eq "branch") { + $log->fatal_error("reseed_dead_plants MUST be .false. in a branch run"); + } } #------------------------------------------------------------------------------- @@ -4738,6 +4867,7 @@ sub setup_logic_fates { 'use_fates_lupft'=>$nl->get_value('use_fates_lupft'), 'use_fates_sp'=>$nl_flags->{'use_fates_sp'} ); + my $suplnitro = $nl->get_value('suplnitro'); my $parteh_mode = $nl->get_value('fates_parteh_mode'); if ( ($parteh_mode == 1) && ($suplnitro !~ /ALL/) && not &value_is_true( $nl_flags->{'use_fates_sp'}) ) { @@ -4749,29 +4879,26 @@ sub setup_logic_fates { # For FATES SP mode make sure no-competetiion, and fixed-biogeography are also set # And also check for other settings that can't be trigged on as well # - my $var = "use_fates_sp"; - if ( defined($nl->get_value($var)) ) { - if ( &value_is_true($nl->get_value($var)) ) { - my @list = ( "use_fates_nocomp", "use_fates_fixed_biogeog" ); - foreach my $var ( @list ) { - if ( ! &value_is_true($nl->get_value($var)) ) { - $log->fatal_error("$var is required when FATES SP is on (use_fates_sp)" ); - } - } - # spit-fire can't be on with FATES SP mode is active - if ( $nl->get_value('fates_spitfire_mode') > 0 ) { - $log->fatal_error('fates_spitfire_mode can NOT be set to greater than 0 when use_fates_sp is true'); - } + if ( &value_is_true($nl_flags->{'use_fates_sp'}) ) { + my @list = ( "use_fates_nocomp", "use_fates_fixed_biogeog" ); + foreach my $var ( @list ) { + if ( ! &value_is_true($nl->get_value($var)) ) { + $log->fatal_error("$var is required when FATES SP is on (use_fates_sp)" ); + } + } + # spit-fire can't be on with FATES SP mode is active + if ( $nl->get_value('fates_spitfire_mode') > 0 ) { + $log->fatal_error("fates_spitfire_mode can NOT be set to greater than 0 when use_fates_sp is true"); + } - # fates landuse can't be on with FATES SP mode is active - if ( &value_is_true($nl->get_value('use_fates_luh')) ) { - $log->fatal_error('use_fates_luh can NOT be true when use_fates_sp is true'); - } + # fates landuse can't be on with FATES SP mode is active + if ( &value_is_true($nl->get_value('use_fates_luh')) ) { + $log->fatal_error('use_fates_luh can NOT be true when use_fates_sp is true'); + } - # hydro isn't currently supported to work when FATES SP mode is active - if (&value_is_true( $nl->get_value('use_fates_planthydro') )) { - $log->fatal_error('fates sp mode is currently not supported to work with fates hydro'); - } + # hydro isn't currently supported to work when FATES SP mode is active + if (&value_is_true( $nl->get_value('use_fates_planthydro') )) { + $log->fatal_error('fates sp mode is currently not supported to work with fates hydro'); } } my $var = "use_fates_inventory_init"; @@ -4796,6 +4923,13 @@ sub setup_logic_fates { } } } + # Check that both FaTES-SP and FATES ST3 aren't both on + my $var = "use_fates_ed_st3"; + if ( defined($nl->get_value($var)) ) { + if ( &value_is_true($nl->get_value($var)) && &value_is_true($nl_flags->{'use_fates_sp'}) ) { + $log->fatal_error("$var can NOT also be true with use_fates_sp true" ); + } + } # check that fates landuse change mode has the necessary luh2 landuse timeseries data # and add the default if not defined. Do not add default if use_fates_potentialveg is true. # If fixed biogeography is on, make sure that flandusepftdat is avilable. @@ -5202,7 +5336,7 @@ sub write_output_files { @groups = qw(clm_inparm ndepdyn_nml popd_streams urbantv_streams light_streams soil_moisture_streams lai_streams atm2lnd_inparm lnd2atm_inparm clm_canopyhydrology_inparm cnphenology - cropcal_streams + cropcal_streams megan_opts clm_soilhydrology_inparm dynamic_subgrid cnvegcarbonstate finidat_consistency_checks dynpft_consistency_checks clm_initinterp_inparm century_soilbgcdecompcascade @@ -5237,6 +5371,9 @@ sub write_output_files { push @groups, "clm_canopy_inparm"; push @groups, "prigentroughness"; push @groups, "zendersoilerod"; + if ( &value_is_true($nl_flags->{'use_cn'}) ) { + push @groups, "carbon_isotope_streams"; + } if (remove_leading_and_trailing_quotes($nl->get_value('snow_cover_fraction_method')) eq 'SwensonLawrence2012') { push @groups, "scf_swenson_lawrence_2012_inparm"; } @@ -5749,7 +5886,21 @@ sub quote_string { $str = "\'$str\'"; } return $str; - } +} + +#------------------------------------------------------------------------------- + +sub remove_newlines { + # Check for and remove line returns in the string, so that it will validate later + my ($nl, $definition, $var) = @_; + + my $value = $nl->get_value($var); + if ( $value =~ /\n/) { + $value =~ s/\n//g; + my $group = $definition->get_group_name($var); + $nl->set_variable_value($group, $var, $value); + } +} #------------------------------------------------------------------------------- diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index 4b3f0227c4..a2c1471a54 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -110,6 +110,28 @@ attributes from the config_cache.xml file (with keys converted to upper-case). Medlyn2011 Ball-Berry1987 + +.false. +.false. +.false. +.false. + +.true. +.true. + + + +.true. +.true. +.true. +.true. + lnd/clm2/isotopes/atm_delta_C13_CMIP6_1850-2015_yearly_v2.0_c190528.nc lnd/clm2/isotopes/atm_delta_C13_CMIP6_SSP119_1850-2100_yearly_c181209.nc @@ -130,6 +152,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). .false. .true. +.true. .false. @@ -141,6 +164,12 @@ attributes from the config_cache.xml file (with keys converted to upper-case). 0 0 + + + +.false. +0.0d00 + NONE ALL @@ -148,7 +177,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). 0.50,0.30 0.60,0.40 - +0.60,0.40 ON_WASTEHEAT ON @@ -166,6 +195,10 @@ attributes from the config_cache.xml file (with keys converted to upper-case). .false. + +.false. +.true. + .false. .true. @@ -206,7 +239,14 @@ attributes from the config_cache.xml file (with keys converted to upper-case). .true. .false. -.true. + +.true. +.true. +.false. 0.006 @@ -229,6 +269,8 @@ attributes from the config_cache.xml file (with keys converted to upper-case). 0. 2. +0. +2. .true. @@ -484,13 +526,11 @@ attributes from the config_cache.xml file (with keys converted to upper-case). Jordan1991 Sturm1997 - Jordan1991 -Sturm1997 +Jordan1991 - Jordan1991 -Jordan1991 + -'single_at_atm_topo','UNSET','virtual','multiple' +'single_at_atm_topo','UNSET','virtual','virtual' 'single_at_atm_topo','UNSET','virtual','virtual' -lnd/clm2/paramdata/ctsm60_params.c250311.nc +lnd/clm2/paramdata/ctsm60_params_cal115_c250813.nc lnd/clm2/paramdata/ctsm60_params.5.3.045_noresm_v14_c251031.nc lnd/clm2/paramdata/clm50_params.c250311.nc lnd/clm2/paramdata/clm45_params.c250311.nc @@ -542,7 +582,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). -lnd/clm2/paramdata/fates_params_sci.1.85.1_api.40.0.0_14pft_nor_sci4_api1_c251128.nc +lnd/clm2/paramdata/fates_params_sci.1.88.6_api.42.0.0_14pft_nor_sci1_api1_c251204.nc @@ -830,6 +870,21 @@ attributes from the config_cache.xml file (with keys converted to upper-case). >hgrid=0.9x1.25 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.false. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.false. + +hgrid=0.9x1.25 maxpft=17 mask=tx2_3v2 use_cn=.false. use_crop=.false. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.true. + +hgrid=ne30np4.pg3 maxpft=17 mask=tx2_3v2 use_cn=.false. use_crop=.false. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.true. + + +hgrid=0.9x1.25 maxpft=17 mask=tx2_3v2 use_cn=.false. use_crop=.false. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.true. + +hgrid=ne30np4.pg3 maxpft=17 mask=tx2_3v2 use_cn=.false. use_crop=.false. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.true. + + hgrid=0.9x1.25 maxpft=17 mask=gx1v7 use_cn=.false. use_crop=.false. irrigate=.true. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.false. @@ -857,15 +912,15 @@ attributes from the config_cache.xml file (with keys converted to upper-case). hgrid=0.9x1.25 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.false. glc_nex=10 do_transient_pfts=.false. phys=clm6_0 use_excess_ice=.true. +>hgrid=0.9x1.25 maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. irrigate=.false. glc_nex=10 do_transient_pfts=.false. phys=clm6_0 use_excess_ice=.true. hgrid=0.9x1.25 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.false. glc_nex=10 do_transient_pfts=.false. lnd_tuning_mode=clm6_0_CRUJRA2024 use_excess_ice=.true. +>hgrid=0.9x1.25 maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. irrigate=.false. glc_nex=10 do_transient_pfts=.false. lnd_tuning_mode=clm6_0_CRUJRA2024 use_excess_ice=.true. mask=gx1v7 use_cn=.true. do_transient_pfts=.false. use_excess_ice=.true. use_crop=.false. irrigate=.false. +>mask=tx2_3v2 use_cn=.true. do_transient_pfts=.false. use_excess_ice=.true. use_crop=.false. irrigate=.false. hgrid=1.9x2.5 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.true. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.false. hgrid=0.9x1.25 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.true. glc_nex=10 do_transient_pfts=.false. use_excess_ice=.true. +>hgrid=0.9x1.25 maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. irrigate=.true. glc_nex=10 do_transient_pfts=.false. use_excess_ice=.true. hgrid=ne0np4.ARCTICGRIS.ne30x8 maxpft=17 mask=tx0.1v2 use_cn=.false. use_crop=.false. irrigate=.true. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.false. + +hgrid=ne0np4CONUS.ne30x8 maxpft=17 mask=tx0.1v2 use_cn=.false. use_crop=.false. irrigate=.true. glc_nec=10 do_transient_pfts=.false. use_excess_ice=.false. + + + +maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. glc_nec=10 use_excess_ice=.true. + +maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. glc_nec=10 use_excess_ice=.true. + + maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.true. glc_nec=10 use_excess_ice=.true. +>maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. glc_nec=10 use_excess_ice=.true. hgrid=0.9x1.25 maxpft=79 mask=gx1v7 use_cn=.true. use_crop=.true. irrigate=.true. glc_nec=10 use_excess_ice=.true. +>hgrid=0.9x1.25 maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. glc_nec=10 use_excess_ice=.true. maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. irrigate=.true. glc_nec=10 use_excess_ice=.true. +>maxpft=79 mask=tx2_3v2 use_cn=.true. use_crop=.true. glc_nec=10 use_excess_ice=.true. lnd/clm2/initdata_map/clmi.I1850Clm50SpCru.1706-01-01.0.9x1.25_gx1v7_simyr1850_c200806.nc + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_SP_f09_121_1850.clm2.r.0041-01-01-00000.nc + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_SP_ne30_120_1850.clm2.r.0041-01-01-00000.nc + + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_SP_f09_121_HIST.clm2.r.2000-01-01-00000.nc + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_SP_ne30_120_HIST.clm2.r.2000-01-01-00000.nc + + -lnd/clm2/initdata_esmf/ctsm5.3/ctsm53019_f09_BNF_pSASU.clm2.r.0161-01-01-00000.nc + ic_tod="0" glc_nec="10" use_crop=".true." + phys="clm6_0" use_init_interp=".true." +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115f09_118_pSASU.clm2.r.0161-01-01-00000.nc lnd/clm2/initdata_esmf/ctsm5.3/ctsm53019_f09_BNF_pSASU.clm2.r.0161-01-01-00000.nc +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_116_pSASU.clm2.r.0161-01-01-00000.nc -lnd/clm2/initdata_esmf/ctsm5.3/ctsm530_f19_g17_Bgc_exice_pSASU.clm60.r.0161-01-01.nc +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm53065_54surfdata_PPEcal115_115_pSASU.clm2.r.0161-01-01-00000.nc - +lnd/clm2/initdata_esmf/ctsm5.3/ctsm52026_f09_pSASU.clm2.r.0421-01-01-00000.nc +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm53065_54surfdata_PPEcal115_115_HIST.clm2.r.2000-01-01-00000.nc @@ -1351,7 +1449,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case). >lnd/clm2/initdata_esmf/ctsm5.2/clmi.I2000Clm50BgcCrop.2011-01-01.1.9x2.5_gx1v7_gl4_simyr2000_c240223.nc - + lnd/clm2/initdata_map/clmi.BHIST.2000-01-01.0.9x1.25_gx1v7_simyr1979_c200806.nc - + + lnd/clm2/initdata_map/clmi.BHIST.2000-01-01.0.9x1.25_gx1v7_simyr1979_c200806.nc + lnd/clm2/initdata_map/clmi.BHIST.2000-01-01.1.9x2.5_gx1v7_simyr1979_c200806.nc - + lnd/clm2/initdata_map/clmi.FHISTSp.1979-01-01.ARCTIC_ne30x4_mt12_simyr1979_c200806.nc - + -lnd/clm2/initdata_esmf/ctsm5.3/ctsm53019_f09_BNF_hist.clm2.r.2000-01-01-00000.nc + ic_tod="0" glc_nec="10" use_crop=".true." + phys="clm6_0" +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.2000-01-01-00000.nc lnd/clm2/initdata_esmf/ctsm5.3/ctsm53019_f09_BNF_hist.clm2.r.2000-01-01-00000.nc +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.2000-01-01-00000.nc + + + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.2010-01-01-00000.nc + +lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.2010-01-01-00000.nc @@ -1454,20 +1568,22 @@ attributes from the config_cache.xml file (with keys converted to upper-case). - +lnd/clm2/initdata_esmf/ctsm5.3/ctsm53n04ctsm52028_f09_g17_BgcCrop_exice_hist.clm60.r.1979-01-01.nc + ic_tod="0" glc_nec="10" use_crop=".true." + phys="clm6_0" +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.1979-01-01-00000.nc + lnd/clm2/initdata_esmf/ctsm5.3/ctsm53n04ctsm52028_ne30pg3t232_BgcCrop_exice_hist.clm60.r.1979-01-01.nc + ic_tod="0" glc_nec="10" use_crop=".true." + phys="clm6_0" +>lnd/clm2/initdata_esmf/ctsm5.4/ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.1979-01-01-00000.nc - + lnd/clm2/initdata_map/clmi.FHISTSp.1979-01-01.ARCTIC_ne30x4_mt12_simyr1979_c200806.nc - + + lnd/clm2/initdata_map/clmi.BHIST.2000-01-01.0.9x1.25_gx1v7_simyr1979_c200806.nc + lnd/clm2/initdata_map/clmi.BHIST.2000-01-01.1.9x2.5_gx1v7_simyr1979_c200806.nc - + lnd/clm2/initdata_map/clmi.FHISTSp.1979-01-01.ARCTIC_ne30x4_mt12_simyr1979_c200806.nc - + - + lnd/clm2/initdata_map/clmi.FHISTSp.1979-01-01.ARCTIC_ne30x4_mt12_simyr1979_c200806.nc - + lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_mpasa120_hist_2000_78pfts_c240908.nc + +lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne3np4_hist_2000_78pfts_c240925.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne3np4.pg3_hist_2000_78pfts_c240908.nc @@ -1721,6 +1841,8 @@ lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne30np4.pg2_hist_1850_78pfts_c240908.n lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne30np4.pg3_hist_1850_78pfts_c240908.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne3np4.pg3_hist_1850_78pfts_c240908.nc + +lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne3np4_hist_1850_78pfts_c240925.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_C96_hist_1850_78pfts_c240908.nc @@ -1731,8 +1853,6 @@ lnd/clm2/surfdata_esmf/ctsm5.3.0/synthetic/surfdata_1x1_cidadinhoBR_synth_hist_2 lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_1x1_brazil_hist_1850_78pfts_c240912.nc - -lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne3np4.pg3_hist_1850_78pfts_c240908.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/surfdata_ne16np4.pg3_hist_1850_78pfts_c240908.nc @@ -1803,6 +1923,8 @@ lnd/clm2/surfdata_esmf/NEON/ctsm5.3.0/surfdata_1x1_NEON_TOOL_hist_2000_78pfts_c2 lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_mpasa120_SSP2-4.5_1850-2100_78pfts_c240908.nc +lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_ne3np4_SSP2-4.5_1850-2100_78pfts_c240926.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_ne3np4.pg3_SSP2-4.5_1850-2100_78pfts_c240908.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_mpasa120_SSP2-4.5_1850-2100_78pfts_c240908.nc +lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_ne3np4_SSP2-4.5_1850-2100_78pfts_c240926.nc lnd/clm2/surfdata_esmf/ctsm5.3.0/landuse.timeseries_ne3np4.pg3_SSP2-4.5_1850-2100_78pfts_c240908.nc 2000 2000 -lnd/clm2/firedata/clmforc.Li_2017_HYDEv3.2_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2016_c180202.nc +lnd/clm2/firedata/clmforc.Li_2025_CMIP7_hdm_0.5x0.5_simyr1850-2025_c251013.nc lnd/clm2/firedata/clmforc.Li_2018_SSP1_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc lnd/clm2/firedata/clmforc.Li_2018_SSP1_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc @@ -2165,7 +2289,7 @@ lnd/clm2/surfdata_esmf/NEON/ctsm5.3.0/surfdata_1x1_NEON_TOOL_hist_2000_78pfts_c2 lnd/clm2/firedata/clmforc.Li_2018_SSP5_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc -lnd/clm2/firedata/clmforc.Li_2017_HYDEv3.2_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2016_c180202.nc +lnd/clm2/firedata/clmforc.Li_2025_CMIP7_hdm_0.5x0.5_simyr1850-2025_c251013.nc lnd/clm2/firedata/clmforc.Li_2018_SSP1_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc lnd/clm2/firedata/clmforc.Li_2018_SSP1_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc @@ -2404,6 +2528,7 @@ lnd/clm2/surfdata_esmf/NEON/ctsm5.3.0/surfdata_1x1_NEON_TOOL_hist_2000_78pfts_c2 .true. .false. .true. +.false. FvCB1980 @@ -2418,6 +2543,7 @@ lnd/clm2/surfdata_esmf/NEON/ctsm5.3.0/surfdata_1x1_NEON_TOOL_hist_2000_78pfts_c2 default twostream 2D_Picard +.false. .false. .false. .false. @@ -2452,6 +2578,11 @@ lnd/clm2/surfdata_esmf/NEON/ctsm5.3.0/surfdata_1x1_NEON_TOOL_hist_2000_78pfts_c2 .false. .false. .false. +.true. +.true. +.true. +.true. +.false. diff --git a/bld/namelist_files/namelist_defaults_drydep.xml b/bld/namelist_files/namelist_defaults_drydep.xml index e65dca474a..281a5f8153 100644 --- a/bld/namelist_files/namelist_defaults_drydep.xml +++ b/bld/namelist_files/namelist_defaults_drydep.xml @@ -15,15 +15,69 @@ attributes from the config_cache.xml file (with keys converted to upper-case). -'O3','NO2','HNO3','NO','HO2NO2','CH3OOH','CH2O','CO','H2O2','CH3COOOH','PAN','MPAN','C2H5OOH','ONIT','POOH','C3H7OOH','ROOH','CH3COCHO','CH3COCH3','Pb','ONITR','MACROOH','XOOH','ISOPOOH','CH3OH','C2H5OH','CH3CHO','GLYALD','HYAC','HYDRALD','ALKOOH','MEKOOH','TOLOOH','TERPOOH','CH3COOH','CB1','CB2','OC1','OC2','SOA','SO2','SO4','NH3','NH4NO3' +'BIGALK','C2H4','C2H5OH','C2H5OOH','C2H6','C3H6', + 'C3H7OOH','C3H8','CH2O','CH3CHO','CH3COCH3','CH3COCHO','CH3COOH', + 'CH3COOOH','CH3OH','CH3OOH','CO','DMS','EOOH','GLYALD','GLYOXAL', + 'H2O2','H2SO4','HNO3','HO2NO2','HYAC','HYDRALD','ISOP','ISOPOOH', + 'MACR','MACROOH','MPAN','MVK','N2O5','NH3','NH4','NO','NO2', + 'NOA','O3','O3S','ONITR','PAN','POOH','ROOH','SO2','SOAG','XOOH' +'O3','NO2','HNO3','NO','HO2NO2', + 'CH3OOH','CH2O','CO','H2O2','CH3COOOH', + 'PAN','MPAN','C2H5OOH','ONIT','POOH', + 'C3H7OOH','ROOH','CH3COCHO','CH3COCH3', + 'Pb','ONITR','MACROOH','XOOH','ISOPOOH', + 'CH3OH','C2H5OH','CH3CHO','GLYALD','HYAC', + 'HYDRALD','ALKOOH','MEKOOH','TOLOOH','TERPOOH', + 'CH3COOH','CB1','CB2','OC1','OC2','SOA','SO2','SO4','NH3','NH4NO3' + -atm/cam/chem/trop_mozart/dvel/dep_data_c201019.nc +atm/cam/chem/trop_mozart/dvel/dep_data_c20221208.nc +atm/cam/chem/trop_mozart/dvel/dep_data_c201019.nc + - + +'ISOP = isoprene', +'TERP = carene_3 + pinene_a + thujene_a + bornene + terpineol_4 + terpineol_a + terpinyl_ACT_a +', + ' myrtenal + sabinene + pinene_b + camphene + fenchene_a + limonene + phellandrene_a + terpinene_a +', + ' terpinene_g + terpinolene + phellandrene_b + linalool + ionone_b + geranyl_acetone + neryl_acetone +', + ' jasmone + verbenene + ipsenol + myrcene + ocimene_t_b + ocimene_al + ocimene_c_b + 2met_nonatriene +', + ' farnescene_a + caryophyllene_b + acoradiene + aromadendrene + bergamotene_a + bergamotene_b +', + ' bisabolene_a + bisabolene_b + bourbonene_b + cadinene_d + cadinene_g + cedrene_a + copaene_a +', + ' cubebene_a + cubebene_b + elemene_b + farnescene_b + germacrene_B + germacrene_D + gurjunene_b +', + ' humulene_a + humulene_g + isolongifolene + longifolene + longipinene + muurolene_a + muurolene_g +', + ' selinene_b + selinene_d + nerolidol_c + nerolidol_t', +'BIGALK = tricyclene + camphor + fenchone + thujone_a + thujone_b + cineole_1_8 + borneol + bornyl_ACT +', + ' cedrol + decanal + heptanal + heptane + hexane + nonanal + octanal + octanol + oxopentanal + pentane +', + ' hexanal + hexanol_1 + pentanal + heptanone', +'CH3OH = methanol', 'CH3COCH3 = acetone', 'CH3CHO = acetaldehyde', 'C2H5OH = ethanol', +'CH2O = formaldehyde', 'CH3COOH = acetic_acid', 'CO = carbon_monoxide', +'C2H6 = ethane', 'C2H4 = ethene', 'C3H8 = propane', 'C3H6 = propene', +'SOAE = 0.5954*isoprene + 5.1004*(carene_3 + pinene_a + thujene_a + bornene +', + ' terpineol_4 + terpineol_a + terpinyl_ACT_a + myrtenal + sabinene + pinene_b + camphene +', + ' fenchene_a + limonene + phellandrene_a + terpinene_a + terpinene_g + terpinolene +', + ' phellandrene_b + linalool + ionone_b + geranyl_acetone + neryl_acetone + jasmone +', + ' verbenene + ipsenol + myrcene + ocimene_t_b + ocimene_al + ocimene_c_b + 2met_nonatriene) + ', + ' 12.3942*(farnescene_a + caryophyllene_b + acoradiene + aromadendrene + bergamotene_a +', + ' bergamotene_b + bisabolene_a + bisabolene_b + bourbonene_b + cadinene_d + cadinene_g +', + ' cedrene_a + copaene_a + cubebene_a + cubebene_b + elemene_b + farnescene_b +', + ' germacrene_B + germacrene_D + gurjunene_b + humulene_a + humulene_g + isolongifolene +', + ' longifolene + longipinene + muurolene_a + muurolene_g + selinene_b + selinene_d +', + ' nerolidol_c + nerolidol_t)' + -'ISOP = isoprene', 'C10H16 = pinene_a + carene_3 + thujene_a', 'CH3OH = methanol', 'C2H5OH = ethanol', 'CH2O = formaldehyde', 'CH3CHO = acetaldehyde', 'CH3COOH = acetic_acid', 'CH3COCH3 = acetone' +'ISOP = isoprene', + 'C10H16 = pinene_a + carene_3 + thujene_a', + 'CH3OH = methanol', + 'C2H5OH = ethanol', + 'CH2O = formaldehyde', + 'CH3CHO = acetaldehyde', + 'CH3COOH = acetic_acid', 'CH3COCH3 = acetone' + atm/cam/chem/trop_mozart/emis/megan21_emis_factors_78pft_c20161108.nc atm/cam/chem/trop_mozart/emis/megan21_emis_factors_78pft_c20161108.nc diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 820975655d..7a032b901f 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -8,11 +8,11 @@ - Full pathname of initial conditions file. If blank CLM will startup from arbitrary initial conditions. @@ -29,30 +29,30 @@ creating the output file specified by finidat_interp_dest. This requires that finidat be non-blank. - Full pathname of master restart file for a branch run. (only used if RUN_TYPE=branch) (Set with RUN_REFCASE and RUN_REFDATE) - Component name to use in history and restart files - Full pathname of land fraction data file. @@ -78,8 +78,8 @@ Type of CO2 feedback. -Supplemental Nitrogen mode and for what type of vegetation it's turned on for. -In this mode Nitrogen is unlimited rather than prognosed and in general vegetation is +Supplemental Nitrogen mode and for what type of vegetation it's turned on for. +In this mode Nitrogen is unlimited rather than prognosed and in general vegetation is over-productive. NONE = No vegetation types get supplemental Nitrogen ALL = Supplemental Nitrogen is active for all vegetation types @@ -123,7 +123,7 @@ Otherwise use the fraction straight up (the default for CLM5.0) 10SL_3.5m = standard CLM4 and CLM4.5 version -23SL_3.5m = more vertical layers for permafrost simulations +23SL_3.5m = more vertical layers for permafrost simulations 49SL_10m = 49 layer soil column, 10m of soil, 5 bedrock layers 20SL_8.5m = 20 layer soil column, 8m of soil, 5 bedrock layers 4SL_2m = 4 layer soil column, 2m of soil, 0 bedrock layers @@ -427,7 +427,7 @@ Index of solution method of Richards equation. Change method for richards equation solution and boundary conditions. -CLM 4.5 - soilwater_movement_method = 0 (Zeng and Decker, 2009, method). +CLM 4.5 - soilwater_movement_method = 0 (Zeng and Decker, 2009, method). CLM 5.0 - soilwater_movement_method = 1 (adaptive time stepping moisture form from Martyn Clark). 1 (adaptive time stepping moisture form @@ -447,7 +447,7 @@ lower_boundary_condition = 2 : zero-flux lower boundary condition lower_boundary_condition = 3 : water table head-based lower boundary condition w/ aquifer layer. (use with soilwater_movement_method=adaptive time stepping) lower_boundary_condition = 4 : 11-layer solution w/ aquifer layer (only used with soilwater_movement_method=Zeng&Decker 2009) -TODO(bja, 2015-09) these should be strings so they have meaningful names instead of ints. +TODO(bja, 2015-09) these should be strings so they have meaningful names instead of ints. If TRUE, irrigation will be active. - + If TRUE, fsat will be set to zero for crop columns. - + @@ -731,7 +731,7 @@ feature on will result in more memory usage. + group="clm_inparm" valid_values="" value=".false."> Toggle to turn on the tree damage module in FATES (Only relevant if FATES is on) @@ -742,7 +742,7 @@ Turn on spitfire module to simulate fire by setting fates_spitfire_mode > 0. Allowed values are: 0 : Simulations of fire are off 1 : use a global constant lightning rate found in fates_params. - 2 : use an external lightning dataset. + 2 : use an external lightning dataset. 3 : use an external confirmed ignitions dataset (not available through standard CSEM dataset collection). 4 : use external lightning and population datasets to simulate both natural and anthropogenic 5 : use gross domestic production and population datasets to simulate anthropogenic fire supression @@ -750,21 +750,32 @@ ignitions. (Only relevant if FATES is on) + +Enable FATES managed fire mode. Requires that fates_spitfire_mode is on (in any mode). +This mode allows the FATES model to conduct fuel-load reduction through managed burns. +The boundary conditions in which a managed fire is allowed is set via the FATES parameter +file. The burned area fraction of a managed burn is defined through the FATES parameter +file as well. This mode works in conjunction with the SPITFIRE module to determine +whether a wildfire or managed fire takes place on a given patch. +(Only relevant if FATES is on) + + + group="clm_inparm" valid_values="" value=".false."> Toggle to turn on fixed biogeography mode (Only relevant if FATES is on) -Toggle to turn on no competition mode (only relevant if FATES is being used). + group="clm_inparm" valid_values="" value=".false."> +Toggle to turn on no competition mode (only relevant if FATES is being used). -Toggle to turn on FATES satellite phenology mode (only relevant if FATES is being used). + group="clm_inparm" valid_values="" value=".false."> +Toggle to turn on FATES satellite phenology mode (only relevant if FATES is being used). -Full pathname of fates landuse x pft association static data map. +Full pathname of fates landuse x pft association static data map. The file associates land use types with pfts across a static global map. -This file is necessary for running FATES with use_fates_luh, +This file is necessary for running FATES with use_fates_luh, use_fates_nocomp, and use_fates_fixedbiogeo engaged (note that use_fates_lupft is provided as a namelist option to engage all necessary options). The file is output by the FATES land use data tool (https://github.com/NGEET/tools-fates-landusedata) @@ -991,6 +1002,12 @@ How LUNA and Photosynthesis (if needed) will get Leaf nitrogen content lnc_opt = true get from leaf N from CN model lnc_opt = false get based on LAI and fixed CN ratio from parameter file + + use_nvmovement = true use soil nitrogen vertical movement + use_nvmovement = false do not use soil nitrogen vertical movement + use_nvmovement cannot be true while use_nitrif_denitrif is false + @@ -1014,12 +1031,12 @@ Full pathname of surface data file. Full pathname of hillslope data file. - SNICAR (SNow, ICe, and Aerosol Radiative model) optical data file name - SNICAR (SNow, ICe, and Aerosol Radiative model) snow aging data file name @@ -1167,7 +1184,7 @@ Per tape series maximum number of time samples. -Per tape series history file density (i.e. output precision) +Per tape series history file density (i.e. output precision) 1=double precision 2=single precision Default: 2,2,2,2,2,2,2,2,2,2 @@ -1175,7 +1192,7 @@ Per tape series history file density (i.e. output precision) -Per tape series history write frequency. +Per tape series history write frequency. positive means in time steps 0=monthly negative means hours @@ -1227,7 +1244,7 @@ If TRUE, urban traffic flux will be activated (Currently NOT implemented). group="clm_humanindex_inparm" valid_values="ALL,FAST,NONE" > Human heat stress indices: ALL = All indices will be calculated - FAST = A subset of indices will be calculated (will not include the computationally + FAST = A subset of indices will be calculated (will not include the computationally expensive wet bulb calculation and associated indices) NONE = No indices will be calculated @@ -1316,7 +1333,7 @@ nyr_SASU=1: the fastest SASU, but inaccurate; nyr_SASU=nyr_forcing(eg. 20): the -The restart file will be based on the average of all analytic solutions within the iloop_avg^th loop. +The restart file will be based on the average of all analytic solutions within the iloop_avg^th loop. eg. if nyr_forcing = 20, iloop_avg = 8, the restart file in yr 160 will be based on analytic solutions from yr 141 to 160. The number of the analytic solutions within one loop depends on ratio between nyr_forcing and nyr_SASU. eg. if nyr_forcing = 20, nyr_SASU = 5, number of analytic solutions is 20/5=4 @@ -1332,7 +1349,7 @@ Turn on methane model. Standard part of CLM45BGC model. -CLM Biogeochemistry mode : Carbon Nitrogen model (CN) +CLM Biogeochemistry mode : Carbon Nitrogen model (CN) (or CLM45BGC if phys=clm4_5, vsoilc_centbgc='on', and clm4me='on') @@ -1350,7 +1367,7 @@ Requires the CN model to work (either CN or CNDV). -Nitrification/denitrification splits the prognostic mineral N pool into two +Nitrification/denitrification splits the prognostic mineral N pool into two mineral N pools: NO3 and NH4, and includes the transformations between them. Turned on for BGC FATES currently allows it to be true or false, but will be hardwired to true later @@ -1390,17 +1407,17 @@ Toggle to turn on the prognostic crop model -Toggle to turn on the prognostic fertilizer for crop model +Toggle to turn on the prognostic fertilizer for crop model -Toggle to turn on the 1-year grain product pool in the crop model +Toggle to turn on the 1-year grain product pool in the crop model -Fraction of post-harvest crop residues (leaf and stem) to move to +Fraction of post-harvest crop residues (leaf and stem) to move to 1-year product pool instead of letting them fall as litter. Default: 0.0 @@ -1409,7 +1426,7 @@ Fraction of post-harvest crop residues (leaf and stem) to move to group="crop_inparm" valid_values="constant,varytropicsbylat" value="constant"> Type of mapping to use for base temperature for prognostic crop model constant = Just use baset from the PFT parameter file -varytropicsbylat = Vary the tropics by latitude +varytropicsbylat = Vary the tropics by latitude - Parameter to set the type of ozone vegetation stress method - unset = (default) ozone stress vegetation method is off + Parameter to set the type of ozone vegetation stress method + unset = (default) ozone stress vegetation method is off stress_lombardozzi2015 = ozone stress vegetation functions from Danica Lombardozzi 2015 stress_falk = ozone stress vegetation functions from Stefanie Falk (issue #1224) Default: "unset" - - + + Phenology onset depends on the vegetation type @@ -1631,7 +1648,7 @@ by getco2_historical.ncl - Aerosol deposition file name (only used for aerdepregrid.ncl) @@ -1779,14 +1796,59 @@ Colon delimited list of variables to read from the streams file for nitrogen dep + group="ndepdyn_nml" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from Nitrogen deposition input file to the model resolution bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation + + + + + +First year to loop over for atmospheric C14 isotope delta data + + + +Last year to loop over for data atmospheric C14 isotope delta data + + + +Simulation year that aligns with stream_year_first_atm_c14 value + + + +Filename of input stream data for atmospheric C14 isotope delta data + + + +First year to loop over for atmospheric C13 isotope delta data + + + +Last year to loop over for data atmospheric C13 isotope delta data + + + +Simulation year that aligns with stream_year_first_atm_c13 value + + + +Filename of input stream data for atmospheric C13 isotope delta data @@ -1808,21 +1870,33 @@ Filename of input stream data for aeolian roughness length (from Prigent's rough mesh filename of input stream data for aeolian roughness length (from Prigent's roughness dataset) + +Mapping method for the Prigent roughness input file to the model resolution +(Only used when use_prigent_roughness is TRUE and normally only needed with the Leung_2023 dust emission method) + bilinear = bilinear interpolation + nn = nearest neighbor + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation + + + group="zendersoilerod" valid_values="bilinear,nn,redist,consd,consf,none" > Option only applying for the Zender_2003 method for whether the soil erodibility file is handled here in CTSM, or in the ATM model. (only used when dust_emis_method is Zender_2003) bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation -Filename of input stream data for finundated inversion of observed (from Prigent dataset) +Filename of input stream data for finundated inversion of observed (from Prigent dataset) to hydrologic variables (either TWS or ZWT) -mesh filename of input stream data for finundated inversion of observed (from Prigent dataset) +mesh filename of input stream data for finundated inversion of observed (from Prigent dataset) to hydrologic variables (either TWS or ZWT) + +Mapping method for the finundated inversion input file to the model resolution +(Only used when use_ch4 is TRUE) + bilinear = bilinear interpolation + nn = nearest neighbor + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation + + @@ -1935,7 +2021,7 @@ Filename of input stream data for LAI -dtlimit (ratio of max/min stream delta times) for LAI streams, which allows for cycling over a year of data +dtlimit (ratio of max/min stream delta times) for LAI streams, which allows for cycling over a year of data @@ -1945,14 +2031,14 @@ Time interpolation method to use with LAI streams + group="lai_streams" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from LAI input file to the model resolution bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation @@ -2091,14 +2177,14 @@ Time interpolation method to use with Lightning streams + group="light_streams" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from Lightning input file to the model resolution bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation @@ -2138,14 +2224,14 @@ Time interpolation method to use with human population density streams + group="popd_streams" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from human population density input file to the model resolution bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation @@ -2185,14 +2271,14 @@ Time interpolation method to use with urban time varying streams + group="urbantv_streams" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from urban time varying input file to the model resolution bilinear = bilinear interpolation nn = nearest neighbor - nnoni = nearest neighbor on the "i" (longitude) axis - nnonj = nearest neighbor on the "j" (latitude) axis - spval = set to special value - copy = copy using the same indices + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization + none = no interpolation Land mask description for mksurfdata input files - + @@ -2233,7 +2319,7 @@ Resolution of finundated inversion streams dataset (stream_fldfilename_ch4finund to use for methane model (only applies when CN and methane model are turned on) - + Resolution of Lightning dataset to use for CN or FATES fire model @@ -2252,46 +2338,56 @@ Add a note to the output namelist about the options given to build-namelist -CLM run type. +CLM run type. 'default' use the default type of clm_start type for this configuration 'cold' is a run from arbitrary initial conditions 'arb_ic' is a run using initial conditions if provided, OR arbitrary initial conditions if no files can be found - 'startup' is an initial run with initial conditions provided. + 'startup' is an initial run with initial conditions provided. 'continue' is a restart run. 'branch' is a restart run in which properties of the output history files may be changed. -Shared Socioeconomic Pathway (SSP) and Representative Concentration Pathway (RCP) combination for future scenarios +Shared Socioeconomic Pathway (SSP) and Representative Concentration Pathway (RCP) combination for future scenarios The form is SSPn-m.m Where n is the SSP number and m.m is RCP radiative forcing at peak or 2100 in W/m^2 n is just the whole number of the specific SSP scenario. The lower numbers have higher mitigation - the higher numbers less mitigation, more than one SSP can result in the same RCP forcing hist means do NOT use a future scenario, just use historical data. - + Land mask description - + General configuration of model version and atmospheric forcing to tune the model to run under. -This sets the model to run with constants and initial conditions that were set to run well under +This sets the model to run with constants and initial conditions that were set to run well under the configuration of model version and atmospheric forcing. To run well constants would need to be changed to run with a different type of atmospheric forcing. (Some options for the newest physics will be based on previous tuning, and buildnml will let you know about this) - + -If 1, turn on the MEGAN model for BVOC's (Biogenic Volitile Organic Compounds) - +If 1, turn on the MEGAN model for BVOC's (Biogenic Volatile Organic Compounds) + + + +If TRUE, use activity factor for soil moisture for MEGAN isoprene emissions. + + + +Minimum activity factor for soil moisture for MEGAN isoprene emissions. + + + @@ -2353,8 +2449,8 @@ NOTE: THIS CORRESPONDS DIRECTLY TO THE env_run.xml VARIABLE OF THE SAME NAME. group="default_settings" valid_values="sp,bgc,fates" > Command line arguement for biogeochemistry mode for CLM4.5 sp = Satellitte Phenology - bgc = CLM4.5 BGC model with: - CENTURY model pools + bgc = CLM4.5 BGC model with: + CENTURY model pools Nitrification/De-nitrification Methane model Vertically resolved Carbon @@ -2372,8 +2468,8 @@ Flag for overriding the crash that should occur if user tries to start the model -Flag for setting the state of the Accelerated decomposition spinup state for the BGC model. - 0 = normal model behavior; +Flag for setting the state of the Accelerated decomposition spinup state for the BGC model. + 0 = normal model behavior; 1 = AD spinup (standard) 2 = AD spinup (accelerated spinup from Ricciuto, doesn't work for CNDV and not implemented for CN soil decomposition) Entering and exiting spinup mode occurs automatically by comparing the namelist and restart file values for this variable. @@ -2433,7 +2529,7 @@ Soil decomposition method CENTURYKoven2013 -- CENTURY model in CTSM from Koven et. al. 2013 MIMICSWieder2015 -- MIMICS model in CTSM from Wieder et. al. 2015 -An active soil decomposition method requires the BGC or FATES model to work +An active soil decomposition method requires the BGC or FATES model to work And both BGC and FATES models require an active soil decomposition model @@ -2581,7 +2677,7 @@ Minimum lake depth to increase non-molecular thermal diffusivities by the factor group="clm_inparm" valid_values="" > Factor to increase non-molecular thermal diffusivities for lakes deeper than deepmixing_depthcrit to account for unresolved 3D processes. -Set to 1 to +Set to 1 to -Allows user to tune the value of aereoxid. If set to FALSE, then use the value of aereoxid from +Allows user to tune the value of aereoxid. If set to FALSE, then use the value of aereoxid from the parameter file (set to 0.0, but may be tuned with values in the range {0.0,1.0}. If set to TRUE, then don't fix aere (see ch4Mod.F90). Default: .true. @@ -2640,7 +2736,7 @@ so the coupled system will NOT conserve carbon in this mode if the methane model -Inundated fraction method type to use for the CH4 submodel (possibly affecting soil +Inundated fraction method type to use for the CH4 submodel (possibly affecting soil heterotrophic respiration and denitrification depending on the configuration), h2osfc ----------- Use prognostic saturated fraction h2osfc value calculated in Soil Hydrology @@ -2779,6 +2875,15 @@ or more, which causes CAM to blow up. However, note that setting it to true will break water and energy conservation! + +A TRUE setting adds the time dimension to all 1dwt variables that appear in +files generated as a result of hist_dov2xy = .false. (e.g. pfts1d_wtcol). +Transient simulations (run_has_transient_landcover = .true.) have the same +outcome as vars_1dwt_w_time = .true.. +Use this flag if you wish to change FALSE to TRUE when run_has_transient_landcover = .false.. + + @@ -2817,7 +2922,7 @@ Values less than 5 are mainly useful for testing, and should not be used for sci -Maximum snow depth in mm H2O equivalent. Additional mass gains will be capped when this depth +Maximum snow depth in mm H2O equivalent. Additional mass gains will be capped when this depth is exceeded. Changes in this value should possibly be accompanied by changes in: - nlevsno: larger values of h2osno_max should be accompanied by increases in nlevsno @@ -2936,8 +3041,8 @@ differently in areas below and above reset_snow_glc_ela. Only relevant if reset_snow_glc is .true. When resetting snow pack over glacier columns, one can choose to do this over all glacier -columns, or only those below a certain elevation. A typical use case is to reset only those -columns that have a seasonal snow pack in the real world, i.e. SMB less than 0, also known as +columns, or only those below a certain elevation. A typical use case is to reset only those +columns that have a seasonal snow pack in the real world, i.e. SMB less than 0, also known as the equilibrium line altitude (ELA). This parameter sets a single global ELA value. By setting this parameter to a large value (i.e. 10000 m), all glacier columns will be reset. @@ -2984,7 +3089,7 @@ the related bulk quantities. If .true., run with water isotopes - + @@ -3012,7 +3117,7 @@ snow melt of Brock et al. (2006) group="clm_initinterp_inparm" valid_values="" > If FALSE (which is the default): If an output type cannot be found in the input for initInterp, code aborts -If TRUE: If an output type cannot be found in the input, fill with closest natural veg column +If TRUE: If a non-urban output type cannot be found in the input, fill with closest natural veg column (using bare soil for patch-level variables) NOTE: Natural vegetation and crop landunits always behave as if this were true. e.g., if @@ -3021,6 +3126,14 @@ always fill with the closest natural veg patch / column, regardless of the value flag. So interpolation from non-crop to crop cases can be done without setting this flag. + +If FALSE (which is the default): If an urban output type cannot be found in the input for initInterp, +code aborts +If TRUE: If an urban output type cannot be found in the input, fill with closest urban high density +(HD) landunit + + @@ -3073,7 +3186,7 @@ Initial soil temperature to use for gridcells with excess ice present during a r -Soil depth below which initial excess ice concentration will be applied during a run starting with coldstart (m). Value only applys if use_excess_ice is true. +Soil depth below which initial excess ice concentration will be applied during a run starting with coldstart (m). Value only applys if use_excess_ice is true. If this is set below depth of the soil depth, only the last soil layer will get excess ice. @@ -3096,10 +3209,13 @@ mesh filename of input stream data for excess ice + group="exice_streams" valid_values="bilinear,nn,redist,consd,consf,none" > Mapping method from excess ice input stream data to the model resolution bilinear = bilinear interpolation nn = nearest neighbor + redist = Redistributes data from source mesh to destination mesh + consd = First-order conservative interpolation + consf = Same as consd with fraction area normalization none = no interpolation diff --git a/bld/unit_testers/build-namelist_test.pl b/bld/unit_testers/build-namelist_test.pl index 2c95d2e624..cc747cdc5f 100755 --- a/bld/unit_testers/build-namelist_test.pl +++ b/bld/unit_testers/build-namelist_test.pl @@ -163,10 +163,10 @@ sub cat_and_create_namelistinfile { # # Figure out number of tests that will run # -my $ntests = 3264; +my $ntests = 3400; if ( defined($opts{'compare'}) ) { - $ntests += 1980; + $ntests += 2061; } plan( tests=>$ntests ); @@ -288,7 +288,7 @@ sub cat_and_create_namelistinfile { &make_config_cache($phys); my @mfiles = ( "lnd_in", "drv_flds_in", $tempfile ); my $mfiles = NMLTest::CompFiles->new( $cwd, @mfiles ); -foreach my $options ( "-drydep", "-megan", "-drydep -megan", "-fire_emis", "-drydep -megan -fire_emis" ) { +foreach my $options ( "-drydep --bgc sp", "-megan --bgc sp", "-drydep -megan --bgc bgc", "-fire_emis --bgc bgc", "-drydep -megan -fire_emis --bgc bgc" ) { &make_env_run(); eval{ system( "$bldnml -envxml_dir . $options > $tempfile 2>&1 " ); }; is( $@, '', "options: $options" ); @@ -323,6 +323,7 @@ sub cat_and_create_namelistinfile { "-res 0.9x1.25 -namelist '&a use_lai_streams=.true.,use_soil_moisture_streams=.true./'", "-res 0.9x1.25 -namelist '&a use_excess_ice=.true. use_excess_ice_streams=.true./'", "-res 0.9x1.25 --clm_start_type cold -namelist '&a use_excess_ice=.true. use_excess_ice_streams=.true./'", + "-res 0.9x1.25 --bgc bgc --namelist \"&a urbantvmapalgo='redist' ndepmapalgo='consd' popdensmapalgo='consf'\"", "-res 0.9x1.25 -use_case 1850_control", "-res 1x1pt_US-UMB -clm_usr_name 1x1pt_US-UMB -namelist '&a fsurdat=\"/dev/null\"/'", "-res 1x1_brazil", @@ -537,9 +538,9 @@ sub cat_and_create_namelistinfile { $mode = "-phys $phys CAM_SETS_DRV_FLDS"; &make_config_cache($phys); foreach my $options ( - "--res 1.9x2.5 --mask gx1v7 --bgc sp --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam6.0 --infile empty_user_nl_clm", - "--res 1.9x2.5 --mask gx1v7 --bgc sp --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", - "--res 1.9x2.5 --mask gx1v7 --bgc sp -no-crop --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", + "--res 1.9x2.5 --bgc sp --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam6.0 --infile empty_user_nl_clm", + "--res 1.9x2.5 --bgc sp --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", + "--res 1.9x2.5 --bgc sp -no-crop --use_case 20thC_transient --namelist '&a start_ymd=19790101/' --lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", "--res ne0np4.ARCTIC.ne30x4 --mask tx0.1v2 -bgc sp -use_case 20thC_transient -namelist '&a start_ymd=19790101/' -lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", "--res ne0np4.ARCTICGRIS.ne30x8 --mask tx0.1v2 -bgc sp -use_case 20thC_transient -namelist '&a start_ymd=19790101/' -lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", "--res ne0np4CONUS.ne30x8 --mask tx0.1v2 -bgc sp -use_case 20thC_transient -namelist '&a start_ymd=20130101/' -lnd_tuning_mode ${phys}_cam7.0 --infile empty_user_nl_clm", @@ -576,8 +577,8 @@ sub cat_and_create_namelistinfile { "--res 1.9x2.5 --bgc bgc --use_case 1850-2100_SSP2-4.5_transient --namelist '&a start_ymd=19101023/'", "-namelist \"&a dust_emis_method='Zender_2003', zender_soil_erod_source='lnd' /'\"", "-bgc bgc -use_case 2000_control -namelist \"&a fire_method='nofire'/\" -crop", - "-res 0.9x1.25 -bgc sp -use_case 1850_noanthro_control -drydep -fire_emis", - "-res 0.9x1.25 -bgc bgc -use_case 1850_noanthro_control -drydep -fire_emis -light_res 360x720", + "-res 0.9x1.25 -bgc sp -use_case 1850_noanthro_control -drydep", + "-res 0.9x1.25 -bgc bgc -use_case 1850_noanthro_control -drydep -fire_emis -megan -light_res 360x720", "--bgc bgc --light_res none --namelist \"&a fire_method='nofire'/\"", "--bgc fates --light_res 360x720 --no-megan --namelist \"&a fates_spitfire_mode=2/\"", "--bgc fates --light_res none --no-megan --namelist \"&a fates_spitfire_mode=1/\"", @@ -678,6 +679,10 @@ sub cat_and_create_namelistinfile { namelst=>"soil_decomp_method='None'", phys=>"clm5_0", }, + "reseed with branch" =>{ options=>"-clm_start_type branch -envxml_dir .", + namelst=>"reseed_dead_plants=.true.", + phys=>"clm6_0", + }, "reseed without CN" =>{ options=>" -envxml_dir . -bgc sp", namelst=>"reseed_dead_plants=.true.", phys=>"clm5_0", @@ -790,6 +795,30 @@ sub cat_and_create_namelistinfile { namelst=>"use_c14_bombspike=.true.", phys=>"clm5_0", }, + "bombspike file and stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c14=TRUE use_c14_bombspike=.true. stream_fldfilename_atm_c14='/dev/null', atm_c14_filename='/dev/null'", + phys=>"clm6_0", + }, + "c13 file and stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c13_timeseries=.true. stream_fldfilename_atm_c13='/dev/null', atm_c13_filename='/dev/null'", + phys=>"clm6_0", + }, + "c13 off, but stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c13=.false. stream_fldfilename_atm_c13='/dev/null'", + phys=>"clm6_0", + }, + "c14 off, but stream" =>{ options=>"-bgc bgc -envxml_dir .", + namelst=>"use_c14=.false. stream_fldfilename_atm_c14='/dev/null'", + phys=>"clm6_0", + }, + "sp, but c13 stream" =>{ options=>"-bgc sp -envxml_dir .", + namelst=>"stream_fldfilename_atm_c13='/dev/null'", + phys=>"clm6_0", + }, + "sp, but c14 stream" =>{ options=>"-bgc sp -envxml_dir .", + namelst=>"stream_fldfilename_atm_c14='/dev/null'", + phys=>"clm6_0", + }, "lightres no cn" =>{ options=>"-bgc sp -envxml_dir . -light_res 360x720", namelst=>"", phys=>"clm5_0", @@ -1093,6 +1122,10 @@ sub cat_and_create_namelistinfile { namelst=>"suplnitro='NONE'", phys=>"clm6_0", }, + "FATESwBothSpST3" =>{ options=>"--bgc fates --envxml_dir . --no-megan", + namelst=>"use_fates_sp = TRUE, use_fates_ed_st3 = TRUE", + phys=>"clm6_0", + }, "FireNoneButBGCfireon" =>{ options=>"-bgc bgc -envxml_dir . -light_res none", namelst=>"fire_method='li2021gswpfrc'", phys=>"clm6_0", @@ -1109,6 +1142,10 @@ sub cat_and_create_namelistinfile { namelst=>"fates_spitfire_mode=1,use_fates_sp=.true.", phys=>"clm5_0", }, + "managedfirenospitfire" =>{ options=>"-envxml_dir . --bgc fates", + namelst=>"fates_spitfire_mode=0,use_fates_managed_fire=.true.", + phys=>"clm5_0", + }, "usefatesspusefateshydro" =>{ options=>"-envxml_dir . --bgc fates", namelst=>"use_fates_sp=.true.,use_fates_planthydro=.true.", phys=>"clm5_0", @@ -1145,6 +1182,14 @@ sub cat_and_create_namelistinfile { namelst=>"", phys=>"clm4_5", }, + "useFIREEMISwithNOFIRE" =>{ options=>"--bgc bgc --envxml_dir . --fire_emis", + namelst=>"fire_method='nofire'", + phys=>"clm6_0", + }, + "useFIREEMISwithSP" =>{ options=>"--bgc sp --envxml_dir . --fire_emis", + namelst=>"", + phys=>"clm6_0", + }, "useDRYDEPwithFATES" =>{ options=>"--bgc fates --envxml_dir . --no-megan --drydep", namelst=>"", phys=>"clm4_5", @@ -1297,6 +1342,14 @@ sub cat_and_create_namelistinfile { namelst=>"use_lai_streams=.true.", phys=>"clm5_0", }, + "megan_opts_wo_megan" =>{ options=>"--envxml_dir . --bgc bgc --no-megan", + namelst=>"megan_use_gamma_sm=TRUE, megan_min_gamma_sm=1.0", + phys=>"clm6_0", + }, + "megan_min_wo_megan_use" =>{ options=>"--envxml_dir . --bgc bgc --megan", + namelst=>"megan_use_gamma_sm=FALSE, megan_min_gamma_sm=1.0", + phys=>"clm6_0", + }, "soil_erod_wo_Zender" =>{ options=>"--envxml_dir . --ignore_warnings", namelst=>"dust_emis_method='Leung_2023', stream_meshfile_zendersoilerod = '/dev/null'", phys=>"clm6_0", @@ -1494,7 +1547,7 @@ sub cat_and_create_namelistinfile { print "========================================================================\n"; # Check for ALL resolutions with CLM50SP -my @resolutions = ( "360x720cru", "10x15", "4x5", "0.9x1.25", "1.9x2.5", "ne3np4.pg3", "ne16np4.pg3", "ne30np4", "ne30np4.pg2", "ne30np4.pg3", "ne120np4.pg3", "ne0np4CONUS.ne30x8", "ne0np4.ARCTIC.ne30x4", "ne0np4.ARCTICGRIS.ne30x8", "C96", "mpasa480", "mpasa120" ); +my @resolutions = ( "360x720cru", "10x15", "4x5", "0.9x1.25", "1.9x2.5", "ne3np4", "ne3np4.pg3", "ne16np4.pg3", "ne30np4", "ne30np4.pg2", "ne30np4.pg3", "ne120np4.pg3", "ne0np4CONUS.ne30x8", "ne0np4.ARCTIC.ne30x4", "ne0np4.ARCTICGRIS.ne30x8", "C96", "mpasa480", "mpasa120" ); my @only2000_resolutions = ( "1x1_numaIA", "1x1_brazil", "1x1_mexicocityMEX", "1x1_vancouverCAN", "1x1_urbanc_alpha", "5x5_amazon", "0.125nldas2", "mpasa60", "mpasa15", "mpasa3p75" ); my @regional; foreach my $res ( @resolutions ) { @@ -1531,7 +1584,7 @@ sub cat_and_create_namelistinfile { print " Test important resolutions for BGC and historical\n"; print "==================================================\n"; -my @resolutions = ( "4x5", "10x15", "360x720cru", "ne30np4.pg3", "ne3np4.pg3", "1.9x2.5", "0.9x1.25", "C96", "mpasa120" ); +my @resolutions = ( "4x5", "10x15", "360x720cru", "ne30np4.pg3", "ne3np4", "ne3np4.pg3", "1.9x2.5", "0.9x1.25", "C96", "mpasa120" ); my @regional; my $nlbgcmode = "bgc"; my $mode = "$phys-$nlbgcmode"; @@ -1758,7 +1811,7 @@ sub cat_and_create_namelistinfile { &cleanup(); } -my @crop_res = ( "1x1_numaIA", "4x5", "10x15", "0.9x1.25", "1.9x2.5", "ne3np4.pg3", "ne30np4", "ne30np4.pg3", "C96", "mpasa120" ); +my @crop_res = ( "1x1_numaIA", "4x5", "10x15", "0.9x1.25", "1.9x2.5", "ne3np4", "ne3np4.pg3", "ne30np4", "ne30np4.pg3", "C96", "mpasa120" ); foreach my $res ( @crop_res ) { $options = "-bgc bgc -crop -res $res -envxml_dir ."; &make_env_run(); @@ -1847,7 +1900,7 @@ sub cat_and_create_namelistinfile { &cleanup(); } # Transient ssp_rcp scenarios that work -my @tran_res = ( "4x5", "0.9x1.25", "1.9x2.5", "10x15", "360x720cru", "ne3np4.pg3", "ne16np4.pg3", "ne30np4.pg3", "C96", "mpasa120" ); +my @tran_res = ( "4x5", "0.9x1.25", "1.9x2.5", "10x15", "360x720cru", "ne3np4", "ne3np4.pg3", "ne16np4.pg3", "ne30np4.pg3", "C96", "mpasa120" ); foreach my $usecase ( "1850-2100_SSP2-4.5_transient" ) { my $startymd = 20150101; foreach my $res ( @tran_res ) { @@ -1884,7 +1937,7 @@ sub cat_and_create_namelistinfile { "-bgc bgc -clm_demand flanduse_timeseries -sim_year 1850-2000 -namelist '&a start_ymd=18500101/'", "-bgc bgc -envxml_dir . -namelist '&a use_c13=.true.,use_c14=.true.,use_c14_bombspike=.true./'" ); foreach my $clmopts ( @clmoptions ) { - my @clmres = ( "10x15", "4x5", "360x720cru", "0.9x1.25", "1.9x2.5", "ne3np4.pg3", "ne16np4.pg3", "ne30np4.pg3", "C96", "mpasa120" ); + my @clmres = ( "10x15", "4x5", "360x720cru", "0.9x1.25", "1.9x2.5", "ne3np4", "ne3np4.pg3", "ne16np4.pg3", "ne30np4.pg3", "C96", "mpasa120" ); foreach my $res ( @clmres ) { $options = "-res $res -envxml_dir . "; &make_env_run( ); diff --git a/ccs_config b/ccs_config index 015ec04aae..62af5049e4 160000 --- a/ccs_config +++ b/ccs_config @@ -1 +1 @@ -Subproject commit 015ec04aae4a11dded7b03ddb3edb0b52b26e8b3 +Subproject commit 62af5049e4fbea2168e3807bc9a97c32841fbe7f diff --git a/cime b/cime index 3012a954e0..ab73a7eb70 160000 --- a/cime +++ b/cime @@ -1 +1 @@ -Subproject commit 3012a954e08fde0b67ea07216f8ddc9e6a8cc59f +Subproject commit ab73a7eb703c81b172644db455486c28b47fc0c2 diff --git a/cime_config/SystemTests/fsurdatmodifyctsm.py b/cime_config/SystemTests/fsurdatmodifyctsm.py index 03e437d5c4..b2cc756e47 100644 --- a/cime_config/SystemTests/fsurdatmodifyctsm.py +++ b/cime_config/SystemTests/fsurdatmodifyctsm.py @@ -21,19 +21,27 @@ class FSURDATMODIFYCTSM(SystemTestsCommon): - def __init__(self, case): + def setup_phase(self, clean=False, test_mode=False, reset=False, keep=False, disable_git=False): """ - initialize an object interface to the SMS system test + override default SMS system test setup phase """ - SystemTestsCommon.__init__(self, case) + + # Default SMS behavior + self.setup_indv( + clean=clean, + test_mode=test_mode, + reset=reset, + keep=keep, + disable_git=disable_git, + ) if not os.path.exists( os.path.join(self._get_caseroot(), "done_FSURDATMODIFYCTSM_setup.txt") ): # Create out-of-the-box lnd_in to obtain fsurdat_in - case.create_namelists(component="lnd") + self._case.create_namelists(component="lnd") # If fsurdat_in does not exist, download it from the server - case.check_all_input_data() + self._case.check_all_input_data() lnd_in_path = os.path.join(self._get_caseroot(), "CaseDocs/lnd_in") with open(lnd_in_path, "r") as lnd_in: diff --git a/cime_config/SystemTests/lreprstruct.py b/cime_config/SystemTests/lreprstruct.py index a03fb1815b..baf172fffe 100644 --- a/cime_config/SystemTests/lreprstruct.py +++ b/cime_config/SystemTests/lreprstruct.py @@ -16,6 +16,8 @@ """ +import re + from CIME.SystemTests.system_tests_compare_two import SystemTestsCompareTwo from CIME.XML.standard_module_setup import * from CIME.SystemTests.test_utils.user_nl_utils import append_to_user_nl_files @@ -53,13 +55,16 @@ def _case_one_setup(self): user_nl_clm_path = os.path.join(self._get_caseroot(), "user_nl_clm") with open(user_nl_clm_path) as f: user_nl_clm_text = f.read() - for grain_output in re.findall("GRAIN\w*", user_nl_clm_text): - user_nl_clm_text = user_nl_clm_text.replace( - grain_output, + + def replace_grain(match): + grain_output = match.group() + return ( grain_output.replace("GRAIN", "REPRODUCTIVE1") + "', '" - + grain_output.replace("GRAIN", "REPRODUCTIVE2"), + + grain_output.replace("GRAIN", "REPRODUCTIVE2") ) + + user_nl_clm_text = re.sub(r"GRAIN\w*", replace_grain, user_nl_clm_text) with open(user_nl_clm_path, "w") as f: f.write(user_nl_clm_text) diff --git a/cime_config/SystemTests/rxcropmaturityinst.py b/cime_config/SystemTests/rxcropmaturityinst.py deleted file mode 100644 index bf8bf7750b..0000000000 --- a/cime_config/SystemTests/rxcropmaturityinst.py +++ /dev/null @@ -1,6 +0,0 @@ -from rxcropmaturity import RXCROPMATURITYSHARED - - -class RXCROPMATURITYINST(RXCROPMATURITYSHARED): - def run_phase(self): - self._run_phase(h1_inst=True) diff --git a/cime_config/SystemTests/rxcropmaturityskipgeninst.py b/cime_config/SystemTests/rxcropmaturityskipgeninst.py deleted file mode 100644 index 4cab9bd7c0..0000000000 --- a/cime_config/SystemTests/rxcropmaturityskipgeninst.py +++ /dev/null @@ -1,6 +0,0 @@ -from rxcropmaturity import RXCROPMATURITYSHARED - - -class RXCROPMATURITYSKIPGENINST(RXCROPMATURITYSHARED): - def run_phase(self): - self._run_phase(skip_gen=True, h1_inst=True) diff --git a/cime_config/SystemTests/setparamfile.py b/cime_config/SystemTests/setparamfile.py new file mode 100644 index 0000000000..f106a9505a --- /dev/null +++ b/cime_config/SystemTests/setparamfile.py @@ -0,0 +1,95 @@ +""" +CTSM-specific test that first runs the set_paramfile tool and then ensures that CTSM does not fail +using the just-generated parameter file +""" + +import os +import sys +import logging +import re +from CIME.SystemTests.system_tests_common import SystemTestsCommon + +# In case we need to import set_paramfile later +_CTSM_PYTHON = os.path.join( + os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, "python" +) +sys.path.insert(1, _CTSM_PYTHON) + +logger = logging.getLogger(__name__) + + +class SETPARAMFILE(SystemTestsCommon): + def __init__(self, case): + """ + initialize an object interface to the SMS system test + """ + SystemTestsCommon.__init__(self, case) + + # Create out-of-the-box lnd_in to obtain paramfile + case.create_namelists(component="lnd") + + # Find the paramfile to modify + lnd_in_path = os.path.join(self._get_caseroot(), "CaseDocs", "lnd_in") + self._paramfile_in = None + with open(lnd_in_path, "r", encoding="utf-8") as lnd_in: + for line in lnd_in: + paramfile_in = re.match(r" *paramfile *= *'(.*)'", line) + if paramfile_in: + self._paramfile_in = paramfile_in.group(1) + break + if not self._paramfile_in: + raise RuntimeError(f"paramfile not found in {lnd_in_path}") + + # Get the output file + self.paramfile_out = os.path.join(self._get_caseroot(), "paramfile.nc") + + # Define set_paramfile command + self.set_paramfile_cmd = [ + "set_paramfile", + "-i", + self._paramfile_in, + "-o", + self.paramfile_out, + # Change two parameters for one PFT + "-p", + "needleleaf_deciduous_boreal_tree", + "rswf_min=0.35", + "rswf_max=0.7", + ] + + def build_phase(self, sharedlib_only=False, model_only=False): + """ + Run set_paramfile and then build the model + """ + + # Run set_paramfile. + # build_phase gets called twice: + # - once with sharedlib_only = True and + # - once with model_only = True + # Because we only need set_paramfile run once, we only do it for the sharedlib_only call. + # We could also check for the existence of the set_paramfile outputs, but that might lead to + # a situation where the user expects set_paramfile to be called but it's not. Better to run + # unnecessarily (e.g., if you fixed some FORTRAN code and just need to rebuild). + if sharedlib_only: + self._run_set_paramfile() + + # Do the build + self.build_indv(sharedlib_only=sharedlib_only, model_only=model_only) + + def _run_set_paramfile(self): + """ + Run set_paramfile + """ + # Import set_paramfile. Do it here rather than at top because otherwise the import will + # be attempted even during RUN phase. + # pylint: disable=wrong-import-position,import-outside-toplevel + from ctsm.param_utils.set_paramfile import main as set_paramfile + + # Run set_paramfile + sys.argv = self.set_paramfile_cmd + set_paramfile() + + # Append + user_nl_clm_path = os.path.join(self._get_caseroot(), "user_nl_clm") + with open(user_nl_clm_path, "a", encoding="utf-8") as user_nl_clm: + user_nl_clm.write(f"paramfile = '{self.paramfile_out}'\n") diff --git a/cime_config/SystemTests/sspmatrixcn.py b/cime_config/SystemTests/sspmatrixcn.py index 17ac8abd74..87c4ab2e80 100644 --- a/cime_config/SystemTests/sspmatrixcn.py +++ b/cime_config/SystemTests/sspmatrixcn.py @@ -14,6 +14,7 @@ """ import shutil, glob, os, sys +from pathlib import Path from datetime import datetime if __name__ == "__main__": @@ -205,9 +206,9 @@ def run_indv(self, nstep, st_archive=True): restdir = os.path.join(rest_r, rundate) os.mkdir(restdir) rpoint = os.path.join(restdir, "rpointer.clm." + rundate) - os.mknod(rpoint) + Path.touch(rpoint) rpoint = os.path.join(restdir, "rpointer.cpl." + rundate) - os.mknod(rpoint) + Path.touch(rpoint) def run_phase(self): "Run phase" diff --git a/cime_config/SystemTests/subsetdata.py b/cime_config/SystemTests/subsetdata.py new file mode 100644 index 0000000000..63855a8ca4 --- /dev/null +++ b/cime_config/SystemTests/subsetdata.py @@ -0,0 +1,93 @@ +""" +Parent class for CTSM-specific tests that first run the subset_data tool and then ensure +that CTSM does not fail using the just-generated input files +""" + +import os +import sys +import logging +from CIME.SystemTests.system_tests_common import SystemTestsCommon +from CIME.user_mod_support import apply_user_mods +from CIME.XML.standard_module_setup import * + +# In case we need to import subset_data later +_CTSM_PYTHON = os.path.join( + os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, "python" +) +sys.path.insert(1, _CTSM_PYTHON) + +logger = logging.getLogger(__name__) + + +class SUBSETDATASHARED(SystemTestsCommon): + def __init__(self, case, subset_data_cmd): + """ + initialize an object interface to the SMS system test + """ + SystemTestsCommon.__init__(self, case) + + # Check the test setup + if not self._case.get_value("LND_GRID") == "CLM_USRDAT": + raise RuntimeError("SUBSETDATA tests require resolution CLM_USRDAT") + if "serial" not in self._case.get_value("MPILIB"): + raise RuntimeError("SUBSETDATA tests require a serial MPILIB") + if "BGC-CROP" not in self._case.get_value("COMPSET"): + raise RuntimeError("SUBSETDATA tests require a BGC-CROP compset") + + # Add standard subset_data arguments + out_dir = os.path.join(self._get_caseroot(), "subset_data_output") + self.usermods_dir = os.path.join(out_dir, "user_mods") + self.subset_data_cmd = subset_data_cmd + [ + "--create-user-mods", + "--outdir", + out_dir, + "--user-mods-dir", + self.usermods_dir, + "--overwrite", + ] + + def setup_phase(self, clean=False, test_mode=False, reset=False, keep=False, disable_git=False): + """ + override default SMS system test setup phase + """ + + # Default SMS behavior + self.setup_indv( + clean=clean, + test_mode=test_mode, + reset=reset, + keep=keep, + disable_git=disable_git, + ) + + # Run subset_data, if needed. + # It's needed during SETUP and/or NLCOMP phases if comparing/generating a baseline because + # the namelist comparison will require generating a namelist, and that will fail if we + # haven't specified our custom fsurdat and other stuff. By calling self._run_subset_data() + # only if the usermods directory doesn't yet exist, we avoid it being called every time the + # test class is initialized (which happens, e.g., in RUN phase). + if not os.path.exists(self.usermods_dir): + self._run_subset_data() + + def _run_subset_data(self): + """ + Run subset_data + """ + # Import subset_data. Do it here rather than at top because otherwise the import will + # be attempted even during RUN phase. + # pylint: disable=wrong-import-position,import-outside-toplevel + from ctsm.subset_data import main as subset_data + + # Run subset_data + sys.argv = self.subset_data_cmd + subset_data() + + # Required so that CTSM doesn't fail + user_nl_clm_path = os.path.join(self.usermods_dir, "user_nl_clm") + with open(user_nl_clm_path, "a", encoding="utf-8") as user_nl_clm: + user_nl_clm.write("\ncheck_dynpft_consistency = .false.\n") + + # Apply the user mods + self._case.flush(flushall=True) + apply_user_mods(self._get_caseroot(), self.usermods_dir) + self._case.read_xml() diff --git a/cime_config/SystemTests/subsetdatapoint.py b/cime_config/SystemTests/subsetdatapoint.py new file mode 100644 index 0000000000..6b735809cd --- /dev/null +++ b/cime_config/SystemTests/subsetdatapoint.py @@ -0,0 +1,38 @@ +""" +CTSM-specific test that first runs the subset_data point tool and then ensures +that CTSM does not fail using the just-generated input files +""" + +from subsetdata import SUBSETDATASHARED + + +class SUBSETDATAPOINT(SUBSETDATASHARED): + def __init__(self, case): + """ + initialize an object interface to the SMS system test + """ + + lat = 45.402252 + lon = -92.798085 + + # Don't need to include things that are added during SUBSETDATASHARED.__init__() + subset_data_cmd = [ + "tools/site_and_regional/subset_data", + "point", + "--lat", + str(lat), + "--lon", + str(lon), + "--create-surface", + "--crop", + "--create-landuse", + "--surf-year", + "1850", + "--create-datm", + "--datm-syr", + "1901", + "--datm-eyr", + "1901", + ] + + super().__init__(case, subset_data_cmd) diff --git a/cime_config/SystemTests/subsetdataregion.py b/cime_config/SystemTests/subsetdataregion.py new file mode 100644 index 0000000000..e0c8144b3a --- /dev/null +++ b/cime_config/SystemTests/subsetdataregion.py @@ -0,0 +1,41 @@ +""" +CTSM-specific test that first runs the subset_data region tool and then ensures +that CTSM does not fail using the just-generated input files +""" + +from subsetdata import SUBSETDATASHARED + + +class SUBSETDATAREGION(SUBSETDATASHARED): + def __init__(self, case): + """ + initialize an object interface to the SMS system test + """ + + lat1 = -9 + lat2 = -7 + lon1 = 291 + lon2 = 293 + + # Don't need to include things that are added during SUBSETDATASHARED.__init__() + subset_data_cmd = [ + "tools/site_and_regional/subset_data", + "region", + "--lat1", + str(lat1), + "--lat2", + str(lat2), + "--lon1", + str(lon1), + "--lon2", + str(lon2), + "--create-mesh", + "--create-domain", + "--create-surface", + "--crop", + "--create-landuse", + "--surf-year", + "1850", + ] + + super().__init__(case, subset_data_cmd) diff --git a/cime_config/config_archive.xml b/cime_config/config_archive.xml index c219a1d1ef..052a7f1f2f 100644 --- a/cime_config/config_archive.xml +++ b/cime_config/config_archive.xml @@ -1,49 +1,32 @@ r - rh\d? + rh\da + rh\di h\d*.*\.nc$ lilac_hi.*\.nc$ lilac_atm_driver_h\d*.*\.nc$ e locfnh - rpointer.lnd$NINST_STRING + rpointer.lnd$NINST_STRING.$DATENAME ./$CASE.clm2$NINST_STRING.r.$DATENAME.nc - rpointer.lnd - rpointer.lnd_9999 + rpointer.lnd.1976-01-01-00000 + rpointer.lnd_9999.1976-01-01-00000 casename.clm2.r.1976-01-01-00000.nc - casename.clm2.rh4.1976-01-01-00000.nc - casename.clm2.h0.1976-01-01-00000.nc + casename.clm2.rh4a.1976-01-01-00000.nc + casename.clm2.rh4i.1976-01-01-00000.nc + casename.clm2.h0a.1976-01-01-00000.nc + casename.clm2.h0i.1976-01-01-00000.nc casename.clm2.lilac_hi.1976-01-01-00000.nc casename.clm2.lilac_atm_driver_h0.0001-01.nc - casename.clm2.h0.1976-01-01-00000.nc.base + casename.clm2.h0a.1976-01-01-00000.nc.base + casename.clm2.h0i.1976-01-01-00000.nc.base casename.clm2_0002.e.postassim.1976-01-01-00000.nc casename.clm2_0002.e.preassim.1976-01-01-00000.nc - anothercasename.clm2.i.1976-01-01-00000.nc - - - - r - rh\d? - h\d*.*\.nc$ - e - locfnh - - rpointer.lnd$NINST_STRING - ./$CASE.ctsm$NINST_STRING.r.$DATENAME.nc - - - rpointer.lnd - rpointer.lnd_9999 - casename.ctsm.r.1976-01-01-00000.nc - casename.ctsm.rh4.1976-01-01-00000.nc - casename.ctsm.h0.1976-01-01-00000.nc - casename.ctsm.h0.1976-01-01-00000.nc.base - casename.ctsm_0002.e.postassim.1976-01-01-00000.nc - casename.ctsm_0002.e.preassim.1976-01-01-00000.nc + anothercasename.clm2.r.1976-01-01-00000.nc diff --git a/cime_config/config_component.xml b/cime_config/config_component.xml index 0eba609c26..79186e6dc9 100644 --- a/cime_config/config_component.xml +++ b/cime_config/config_component.xml @@ -240,13 +240,25 @@ char - - -bgc sp - -bgc bgc - -bgc bgc -crop - -bgc fates -nomeg - -bgc fates - -bgc fates + + -bgc sp + -bgc bgc + -bgc bgc -crop + --bgc fates --no-fire_emis --no-megan --no-drydep + --bgc fates --no-fire_emis + --bgc fates --no-fire_emis + + + --bgc sp --no-megan --no-drydep --no-fire_emis + --bgc bgc --no-megan --no-drydep --no-fire_emis + --bgc bgc --crop --no-megan --no-drydep --no-fire_emis + --bgc fates --no-megan --no-drydep --no-fire_emis + -bgc bgc -dynamic_vegetation diff --git a/cime_config/config_compsets.xml b/cime_config/config_compsets.xml index 1906abfa27..656d8a4962 100644 --- a/cime_config/config_compsets.xml +++ b/cime_config/config_compsets.xml @@ -476,7 +476,7 @@ ISSP585Clm60BgcCropCrujra - SSP585_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV + SSP585_DATM%CRUJRA2024_CLM60%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV @@ -495,7 +495,7 @@ ISSP245Clm60BgcCropCrujra - SSP245_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV + SSP245_DATM%CRUJRA2024_CLM60%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV @@ -504,7 +504,7 @@ ISSP370Clm60BgcCropCrujra - SSP370_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV + SSP370_DATM%CRUJRA2024_CLM60%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV diff --git a/cime_config/config_pes.xml b/cime_config/config_pes.xml index f3d82dd5fe..ff3c12f527 100644 --- a/cime_config/config_pes.xml +++ b/cime_config/config_pes.xml @@ -1115,7 +1115,7 @@ - none + default ne120 layout for any machine -16 -16 @@ -1148,6 +1148,196 @@ + + + + + + eXtra-Large Derecho ne120 layout + + -1 + -44 + -44 + -44 + -44 + -44 + -44 + -44 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + Large Derecho ne120 layout + + -1 + -22 + -22 + -22 + -22 + -22 + -22 + -22 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + Medium Derecho ne120 layout + + -1 + -11 + -11 + -11 + -11 + -11 + -11 + -11 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + Small Derecho ne120 layout + + -1 + -6 + -6 + -6 + -6 + -6 + -6 + -6 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + eXtra-Small Derecho ne120 layout + + -1 + -3 + -3 + -3 + -3 + -3 + -3 + -3 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + @@ -1751,7 +1941,7 @@ - none + Derecho mpasa15 layout -1 -36 @@ -1786,6 +1976,242 @@ + + + + + Large Derecho mpasa15 layout + + -1 + -72 + -72 + -72 + -72 + -72 + -72 + -72 + -72 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + Small Derecho mpasa15 layout + + -1 + -18 + -18 + -18 + -18 + -18 + -18 + -18 + -18 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + + eXtra-Small Derecho mpasa15 layout + + -1 + -9 + -9 + -9 + -9 + -9 + -9 + -9 + -9 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + -1 + -1 + -1 + -1 + -1 + -1 + -1 + + + + + + + + none + + -80 + -80 + -80 + -80 + -80 + -80 + -80 + -80 + -80 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + Large mpasaa3p75 PE layout + + -300 + -300 + -300 + -300 + -300 + -300 + -300 + -300 + -300 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + Large mpasaa3p75 PE layout with high memory by using only 80 tasks per node + + + -480 + -480 + -480 + -480 + -480 + -480 + -480 + -480 + -480 + + + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + 80 + + + diff --git a/cime_config/config_tests.xml b/cime_config/config_tests.xml index ee80087a08..c5c6749392 100644 --- a/cime_config/config_tests.xml +++ b/cime_config/config_tests.xml @@ -35,6 +35,26 @@ This defines various CTSM-specific system tests $STOP_N + + Run CTSM with files generated by the subset_data point tool + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + + + Run CTSM with files generated by the subset_data region tool + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + CTSM Land model test to ensure that we can allocate and use a second grain pool 1 @@ -145,16 +165,6 @@ This defines various CTSM-specific system tests $STOP_N - - As RXCROPMATURITY but ensure instantaneous h1. Can be removed once instantaneous and other variables are on separate files. - 1 - FALSE - FALSE - never - $STOP_OPTION - $STOP_N - - As RXCROPMATURITY but don't actually generate GDDs. Allows short testing with existing GDD inputs. 1 @@ -165,8 +175,8 @@ This defines various CTSM-specific system tests $STOP_N - - As RXCROPMATURITYSKIPGEN but ensure instantaneous h1. Can be removed once instantaneous and other variables are on separate files. + + Modify a copy of the paramfile and run with it. 1 FALSE FALSE diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index e4d9f89d4d..a4fa443e40 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -29,6 +29,42 @@ + + + FAIL + #3311 + Requires finidat with c13/c14 to PASS + + + + + FAIL + #3311 + Requires finidat with c13/c14 to PASS + + + + + FAIL + #3311 + Requires finidat with c13/c14 to PASS + + + + + FAIL + #3311 + Requires finidat with c13/c14 to PASS + + + + + FAIL + #3311 + Requires finidat with c13/c14 to PASS + + + FAIL @@ -62,6 +98,38 @@ + + + FAIL + #3453 + + + + + FAIL + #3454 + + + + + FAIL + #3454 + + + + + FAIL + #3252 + Works with finidat = 'ctsm53041_54surfdata_snowTherm_100_pSASU.clm2.r.0161-01-01-00000.nc' and fails with finidat = 'ctsm53041_54surfdata_snowTherm_100_pSASU.clm2.r.0161-01-01-00000_64bitoffset.nc'. + + + + + FAIL + #3252 + Works with finidat = 'ctsm53041_54surfdata_snowTherm_100_pSASU.clm2.r.0161-01-01-00000.nc' and fails with finidat = 'ctsm53041_54surfdata_snowTherm_100_pSASU.clm2.r.0161-01-01-00000_64bitoffset.nc'. + + FAIL @@ -138,6 +206,17 @@ + + + FAIL + #3182 + + + FAIL + #3182 + + + FAIL @@ -209,6 +288,15 @@ + + FAIL @@ -246,13 +334,13 @@ - + FAIL - #3097 + FATES#1089 - + FAIL FATES#1089 @@ -300,6 +388,50 @@ + + + + + FAIL + #3316 + + + + + + + + FAIL + #3383 + + + + + + FAIL + #3383 + + + + + FAIL + #3494 + + + + + + FAIL + #3496 + + + + + FAIL + #3507 + + + @@ -317,5 +449,11 @@ + + + FAIL + ESCOMP/CTSM#3494 + + diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 885221cee6..6b0fe2b194 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -13,6 +13,10 @@ rxcropmaturity: Short tests to be run during development related to prescribed crop calendars matrixcn: Tests exercising the matrix-CN capability aux_clm_mpi_serial: aux_clm tests using mpi-serial. Useful for redoing tests that failed due to https://github.com/ESCOMP/CTSM/issues/2916, after having replaced libraries/mpi-serial with a fresh copy. + subset_data: Tests exercising the subset_data tool and running CTSM with its output + decomp_init: Initialization tests specifically for examining the PE layout decomposition initialization + uhr_decomp_init: Initialization tests at Ultra High Resolution - specifically for examining the PE layout decomposition initialization + interim_restart: Tests having to do with interim restart capability. --> @@ -97,6 +101,15 @@ + + + + + + + + + @@ -151,7 +164,7 @@ - + @@ -173,6 +186,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -220,7 +262,7 @@ - + @@ -229,7 +271,7 @@ - + @@ -407,7 +449,7 @@ - + @@ -416,8 +458,18 @@ + + + + + + + + + + - + @@ -426,7 +478,7 @@ - + @@ -435,7 +487,7 @@ - + @@ -444,7 +496,7 @@ - + @@ -454,7 +506,7 @@ - + @@ -463,7 +515,7 @@ - + @@ -472,7 +524,7 @@ - + @@ -481,7 +533,7 @@ - + @@ -547,20 +599,38 @@ - + - + + + + + + + + + + + + + + + + + + + @@ -568,7 +638,7 @@ - + @@ -1262,7 +1332,7 @@ - + @@ -1290,7 +1360,7 @@ - + @@ -1299,7 +1369,7 @@ - + @@ -1308,7 +1378,7 @@ - + @@ -1317,7 +1387,7 @@ - + @@ -1340,13 +1410,14 @@ - + + @@ -1424,9 +1495,12 @@ + + + @@ -1471,6 +1545,8 @@ + + @@ -1593,7 +1669,7 @@ - + @@ -1650,15 +1726,6 @@ - - - - - - - - - @@ -1734,7 +1801,6 @@ - @@ -1759,7 +1825,7 @@ - + @@ -1777,7 +1843,7 @@ - + @@ -1820,6 +1886,7 @@ + @@ -1828,7 +1895,7 @@ - + @@ -1848,7 +1915,7 @@ - + @@ -1857,7 +1924,7 @@ - + @@ -2008,6 +2075,7 @@ + @@ -2270,7 +2338,7 @@ - + @@ -2279,7 +2347,7 @@ - + @@ -2319,7 +2387,7 @@ - + @@ -2331,7 +2399,7 @@ - + @@ -2340,7 +2408,6 @@ - @@ -2349,7 +2416,6 @@ - @@ -2447,47 +2513,49 @@ - + - + + - + - - - + + - + - + - - + + + - + - + + - + @@ -2517,17 +2585,18 @@ - + - + + - + @@ -2575,7 +2644,17 @@ - + + + + + + + + + + + @@ -2681,13 +2760,14 @@ - + + - + @@ -2712,7 +2792,7 @@ - + @@ -2722,6 +2802,7 @@ + @@ -2733,7 +2814,7 @@ - + @@ -2855,7 +2936,7 @@ - + @@ -2863,7 +2944,7 @@ - + @@ -2886,6 +2967,7 @@ + @@ -2904,7 +2986,7 @@ - + @@ -2919,7 +3001,7 @@ - + @@ -2971,6 +3053,16 @@ + + + + + + + + + + @@ -2984,12 +3076,13 @@ + - + @@ -3059,14 +3152,14 @@ - + - + @@ -3105,15 +3198,65 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3173,9 +3316,10 @@ - + + @@ -3217,7 +3361,7 @@ - + @@ -3261,6 +3405,7 @@ + @@ -3292,7 +3437,7 @@ - + @@ -3341,7 +3486,7 @@ - + @@ -3383,9 +3528,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3395,6 +3571,7 @@ + @@ -3405,7 +3582,6 @@ - @@ -3533,7 +3709,7 @@ - + @@ -3543,9 +3719,10 @@ - + + @@ -3553,7 +3730,7 @@ - + @@ -3755,6 +3932,15 @@ + + + + + + + + + @@ -3803,6 +3989,7 @@ + @@ -3810,24 +3997,25 @@ - - - - - - - - - - - - - - - - - - + @@ -3861,6 +4049,7 @@ + @@ -3923,10 +4112,10 @@ - + - + @@ -3949,6 +4138,8 @@ + + @@ -3961,6 +4152,8 @@ + + @@ -4014,12 +4207,6 @@ - - - - - - @@ -4036,9 +4223,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4058,6 +4274,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4108,6 +4419,7 @@ + @@ -4146,17 +4458,6 @@ - - - - - - - - - - - @@ -4169,17 +4470,6 @@ - - - - - - - - - - - @@ -4294,7 +4584,7 @@ - + @@ -4304,7 +4594,7 @@ - + @@ -4365,6 +4655,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4529,7 +4893,7 @@ - + @@ -4544,7 +4908,7 @@ - + @@ -4553,7 +4917,7 @@ - + diff --git a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStartup_output_sp_exice/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStartup_output_sp_exice/include_user_mods index 142522f5b3..eccf97ff8e 100644 --- a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStartup_output_sp_exice/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStartup_output_sp_exice/include_user_mods @@ -1,2 +1,3 @@ +../nofireemis ../monthly ../../../../usermods_dirs/clm/output_sp_exice diff --git a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/Fates/shell_commands b/cime_config/testdefs/testmods_dirs/clm/Fates/shell_commands index 5c06a9b93d..61b47ddb39 100644 --- a/cime_config/testdefs/testmods_dirs/clm/Fates/shell_commands +++ b/cime_config/testdefs/testmods_dirs/clm/Fates/shell_commands @@ -1 +1,4 @@ ./xmlchange BFBFLAG="TRUE" + +# The following is done as documented in #3184 where more memory is needed for FATES single processor cases +./xmlchange MEM_PER_TASK=20 diff --git a/cime_config/testdefs/testmods_dirs/clm/Fates/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/Fates/user_nl_clm index 2bfe512b38..427f19bd3b 100644 --- a/cime_config/testdefs/testmods_dirs/clm/Fates/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/Fates/user_nl_clm @@ -23,6 +23,6 @@ hist_fincl1 = 'FATES_NCOHORTS', 'FATES_TRIMMING', 'FATES_AREA_PLANTS', 'FATES_DEMOTION_CARBONFLUX', 'FATES_PROMOTION_CARBONFLUX', 'FATES_MORTALITY_CFLUX_CANOPY', 'FATES_MORTALITY_CFLUX_USTORY', 'FATES_NEP', 'FATES_HET_RESP', 'FATES_FIRE_CLOSS', 'FATES_FIRE_FLUX_EL', -'FATES_CBALANCE_ERROR', 'FATES_ERROR_EL', 'FATES_LEAF_ALLOC', +'FATES_CBALANCE_ERROR', 'FATES_LEAF_ALLOC', 'FATES_SEED_ALLOC', 'FATES_STEM_ALLOC', 'FATES_FROOT_ALLOC', -'FATES_CROOT_ALLOC', 'FATES_STORE_ALLOC' +'FATES_CROOT_ALLOC', 'FATES_STORE_ALLOC', 'FCO2', 'TOT_WOODPRODC_LOSS', 'FATES_INTERR_LIVEVEG_EL' diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm index 40af29b7ba..777c0ee298 100644 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdAllVars/user_nl_clm @@ -22,8 +22,8 @@ hist_fincl1 = 'FATES_TLONGTERM', 'FATES_STORE_ALLOC_SZPF','FATES_DDBH_SZPF','FATES_GROWTHFLUX_SZPF','FATES_GROWTHFLUX_FUSION_SZPF', 'FATES_DDBH_CANOPY_SZPF','FATES_DDBH_USTORY_SZPF','FATES_BASALAREA_SZPF','FATES_VEGC_ABOVEGROUND_SZPF', 'FATES_NPLANT_SZPF','FATES_NPLANT_ACPF','FATES_MORTALITY_BACKGROUND_SZPF','FATES_MORTALITY_HYDRAULIC_SZPF', -'FATES_MORTALITY_CSTARV_SZPF','FATES_MORTALITY_IMPACT_SZPF','FATES_MORTALITY_FIRE_SZPF', -'FATES_MORTALITY_CROWNSCORCH_SZPF','FATES_MORTALITY_CAMBIALBURN_SZPF','FATES_MORTALITY_TERMINATION_SZPF', +'FATES_MORTALITY_CSTARV_SZPF','FATES_MORTALITY_IMPACT_SZPF','FATES_MORTALITY_WILDFIRE_SZPF', +'FATES_MORTALITY_WILDFIRE_CROWN_SZPF','FATES_MORTALITY_WILDFIRE_CAMBIAL_SZPF','FATES_MORTALITY_TERMINATION_SZPF', 'FATES_MORTALITY_LOGGING_SZPF','FATES_MORTALITY_FREEZING_SZPF','FATES_MORTALITY_SENESCENCE_SZPF', 'FATES_MORTALITY_AGESCEN_SZPF','FATES_MORTALITY_AGESCEN_ACPF','FATES_MORTALITY_CANOPY_SZPF', 'FATES_M3_MORTALITY_CANOPY_SZPF','FATES_M3_MORTALITY_USTORY_SZPF', diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/include_user_mods new file mode 100644 index 0000000000..5417dbaa1c --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/include_user_mods @@ -0,0 +1 @@ +../FatesCold \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/shell_commands b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/shell_commands new file mode 100644 index 0000000000..7dd25a08bf --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/shell_commands @@ -0,0 +1,5 @@ +#!/bin/bash + +./xmlchange LND_TUNING_MODE="clm6_0_cam7.0" +./xmlchange ROF_NCPL='$ATM_NCPL' + diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/user_nl_clm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/user_nl_datm b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/user_nl_datm new file mode 100644 index 0000000000..c35d3fd9d4 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdCamLndTuningMode/user_nl_datm @@ -0,0 +1,2 @@ +iradsw = -1 +nextsw_cday_calc = "cam7" diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/include_user_mods new file mode 100644 index 0000000000..14f7591b72 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/include_user_mods @@ -0,0 +1 @@ +../FatesCold diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/user_nl_clm new file mode 100644 index 0000000000..05085483d3 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdManagedFire/user_nl_clm @@ -0,0 +1,34 @@ +use_fates_managed_fire = .true. +fates_spitfire_mode = 1 +hist_ndens = 2,2 +hist_fincl1 = 'FATES_NCOHORTS', 'FATES_TRIMMING', 'FATES_AREA_PLANTS', +'FATES_AREA_TREES', 'FATES_COLD_STATUS', 'FATES_GDD', +'FATES_NCHILLDAYS', 'FATES_NCOLDDAYS', 'FATES_DAYSINCE_COLDLEAFOFF','FATES_DAYSINCE_COLDLEAFON', +'FATES_CANOPY_SPREAD', 'FATES_NESTEROV_INDEX', 'FATES_IGNITIONS', 'FATES_FDI', +'FATES_ROS','FATES_EFFECT_WSPEED', 'FATES_FUELCONSUMED', 'FATES_FIRE_INTENSITY', +'FATES_FIRE_INTENSITY_BURNFRAC', 'FATES_BURNFRAC', 'FATES_FUEL_MEF', +'FATES_FUEL_BULKD', 'FATES_FUEL_EFF_MOIST', 'FATES_FUEL_SAV', +'FATES_FUEL_AMOUNT', 'FATES_LITTER_IN', 'FATES_LITTER_OUT', +'FATES_SEED_BANK', 'FATES_SEEDS_IN', 'FATES_STOREC', 'FATES_VEGC', +'FATES_SAPWOODC', 'FATES_LEAFC', 'FATES_FROOTC', 'FATES_REPROC', +'FATES_STRUCTC', 'FATES_NONSTRUCTC', 'FATES_VEGC_ABOVEGROUND', +'FATES_CANOPY_VEGC', 'FATES_USTORY_VEGC', 'FATES_PRIMARY_PATCHFUSION_ERR', +'FATES_HARVEST_WOODPROD_C_FLUX', 'FATES_DISTURBANCE_RATE_FIRE', +'FATES_DISTURBANCE_RATE_LOGGING', 'FATES_DISTURBANCE_RATE_TREEFALL', +'FATES_STOMATAL_COND', 'FATES_LBLAYER_COND', 'FATES_NPP', 'FATES_GPP', +'FATES_AUTORESP', 'FATES_GROWTH_RESP', 'FATES_MAINT_RESP', 'FATES_GPP_CANOPY', +'FATES_AUTORESP_CANOPY', 'FATES_GPP_USTORY', 'FATES_AUTORESP_USTORY', +'FATES_DEMOTION_CARBONFLUX', 'FATES_PROMOTION_CARBONFLUX', +'FATES_MORTALITY_CFLUX_CANOPY', 'FATES_MORTALITY_CFLUX_USTORY', +'FATES_NEP', 'FATES_HET_RESP', 'FATES_FIRE_CLOSS', 'FATES_FIRE_FLUX_EL', +'FATES_CBALANCE_ERROR', 'FATES_LEAF_ALLOC', +'FATES_SEED_ALLOC', 'FATES_STEM_ALLOC', 'FATES_FROOT_ALLOC', +'FATES_CROOT_ALLOC', 'FATES_STORE_ALLOC', +'FATES_WILDFIRE_INTENSITY','FATES_WILDFIRE_INTENSITY_BURNFRAC','FATES_RXFIRE_INTENSITY', +'FATES_RXFIRE_INTENSITY_BURNFRAC','FATES_WILDFIRE_BURNFRAC','FATES_RXFIRE_BURNFRAC', +'FATES_RXFIRE_BURNABLE_FUEL','FATES_RXFIRE_BURNABLE_FI','FATES_RXFIRE_BURNABLE_FINAL', +'FATES_WILDFIRE_BURNFRAC_AP','FATES_WILDFIRE_INTENSITY_BURNFRAC_AP', +'FATES_RXFIRE_BURNFRAC_AP','FATES_RXFIRE_INTENSITY_BURNFRAC_AP','FATES_MORTALITY_WILDFIRE_SZPF', +'FATES_MORTALITY_WILDFIRE_CROWN_SZPF','FATES_MORTALITY_WILDFIRE_CAMBIAL_SZPF', +'FATES_MORTALITY_RXFIRE_SZPF','FATES_MORTALITY_RXCROWN_SZPF','FATES_MORTALITY_RXCAMBIAL_SZPF', +'FATES_MORTALITY_RXFIRE_SZ' diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganNoComp/shell_commands b/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganNoComp/shell_commands deleted file mode 100644 index 749af3486f..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganNoComp/shell_commands +++ /dev/null @@ -1,2 +0,0 @@ -./xmlchange CLM_BLDNML_OPTS='--megan' --append -./xmlchange CLM_BLDNML_OPTS="--ignore_warnings" --append diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganSatPhen/shell_commands b/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganSatPhen/shell_commands deleted file mode 100644 index 749af3486f..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdMeganSatPhen/shell_commands +++ /dev/null @@ -1,2 +0,0 @@ -./xmlchange CLM_BLDNML_OPTS='--megan' --append -./xmlchange CLM_BLDNML_OPTS="--ignore_warnings" --append diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdST3/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdST3/user_nl_clm index 860656e8d8..eca76c4b9c 100644 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdST3/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdST3/user_nl_clm @@ -1,2 +1 @@ use_fates_ed_st3= .true. -hist_fexcl1 = 'FATES_ERROR_EL' diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/include_user_mods new file mode 100644 index 0000000000..33ca1de12e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/include_user_mods @@ -0,0 +1 @@ +../FatesColdSatPhen diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/shell_commands b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/shell_commands new file mode 100644 index 0000000000..7dd25a08bf --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/shell_commands @@ -0,0 +1,5 @@ +#!/bin/bash + +./xmlchange LND_TUNING_MODE="clm6_0_cam7.0" +./xmlchange ROF_NCPL='$ATM_NCPL' + diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_clm new file mode 100644 index 0000000000..f8887cc415 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_clm @@ -0,0 +1,2 @@ +use_fates_sp = .true. +fates_radiation_model = 'twostream' diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_datm b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_datm new file mode 100644 index 0000000000..c35d3fd9d4 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSatPhenCamLndTuningMode/user_nl_datm @@ -0,0 +1,2 @@ +iradsw = -1 +nextsw_cday_calc = "cam7" diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/shell_commands b/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/shell_commands index db5a1f8672..4eb555a0e7 100644 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/shell_commands +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/shell_commands @@ -9,3 +9,5 @@ $FATESDIR/tools/modify_fates_paramfile.py --O --fin $FATESPARAMFILE --fout $FATE $FATESDIR/tools/modify_fates_paramfile.py --O --fin $FATESPARAMFILE --fout $FATESPARAMFILE --var fates_seed_dispersal_max_dist --val 2500000 --allpfts $FATESDIR/tools/modify_fates_paramfile.py --O --fin $FATESPARAMFILE --fout $FATESPARAMFILE --var fates_seed_dispersal_pdf_scale --val 1e-05 --allpfts $FATESDIR/tools/modify_fates_paramfile.py --O --fin $FATESPARAMFILE --fout $FATESPARAMFILE --var fates_seed_dispersal_pdf_shape --val 0.1 --allpfts + +echo "fates_paramfile = '$FATESPARAMFILE'" >> $CASEDIR/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/user_nl_clm index ecd1dc8b57..7b736a1511 100644 --- a/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/FatesColdSeedDisp/user_nl_clm @@ -1,3 +1,2 @@ -fates_paramfile = '$CASEROOT/fates_params_seeddisp_4x5.nc' fates_seeddisp_cadence = 1 hist_fincl1 = 'FATES_SEEDS_IN_GRIDCELL_PF', 'FATES_SEEDS_OUT_GRIDCELL_PF' diff --git a/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods new file mode 100644 index 0000000000..5f7ca15ec0 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/include_user_mods @@ -0,0 +1 @@ +../ciso_monthly_2013Start diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm new file mode 100644 index 0000000000..df0189c2e6 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_cmip7_monthly_2013Start/user_nl_clm @@ -0,0 +1,6 @@ + stream_fldfilename_atm_c13 = '$DIN_LOC_ROOT/lnd/clm2/isotopes/ctsmforc.Graven.atm_delta_C13_CMIP7_global_1700-2023_yearly_v3.0_c251013.nc' + stream_fldfilename_atm_c14 = '$DIN_LOC_ROOT/lnd/clm2/isotopes/ctsmforc.Graven.atm_delta_C14_CMIP7_4x1_global_1700-2023_yearly_v3.0_c251013.nc' + stream_year_first_atm_c14 = 2013 + stream_model_year_align_atm_c14 = 2013 + stream_year_first_atm_c13 = 2013 + stream_model_year_align_atm_c13 = 2013 diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods new file mode 100644 index 0000000000..2cc5720115 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/include_user_mods @@ -0,0 +1 @@ +../ciso_monthly diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands new file mode 100644 index 0000000000..035842f982 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/shell_commands @@ -0,0 +1 @@ +./xmlchange RUN_STARTDATE=2013-01-01 diff --git a/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm new file mode 100644 index 0000000000..b0129f7f6e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/ciso_monthly_2013Start/user_nl_clm @@ -0,0 +1,3 @@ +! Add C13/C14 output to validate the incoming data +hist_fincl1 += 'RC13_CANAIR', 'RC14_CANAIR' +hist_fincl2 = 'RC13_CANAIR', 'RC14_CANAIR' diff --git a/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods index acdaa462fc..821b73c2e0 100644 --- a/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods @@ -1 +1,2 @@ +../nofireemis ../decStart diff --git a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm index 8ad588381e..56d4696774 100644 --- a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm @@ -17,4 +17,5 @@ hist_fincl3 = 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GRAINC_TO hist_nhtfrq = -24,-8,-24 hist_mfilt = 1,1,1 hist_type1d_pertape(3) = 'PFTS' +hist_avgflag_pertape(3) = 'I' hist_dov2xy = .true.,.false.,.false. diff --git a/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/datm_rcp45_anom_forc/include_user_mods similarity index 100% rename from cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/include_user_mods rename to cime_config/testdefs/testmods_dirs/clm/datm_rcp45_anom_forc/include_user_mods diff --git a/cime_config/testdefs/testmods_dirs/clm/datm_rcp45_anom_forc/user_nl_datm b/cime_config/testdefs/testmods_dirs/clm/datm_rcp45_anom_forc/user_nl_datm new file mode 100644 index 0000000000..a1e2523cca --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/datm_rcp45_anom_forc/user_nl_datm @@ -0,0 +1 @@ +anomaly_forcing = 'Anomaly.Forcing.cmip5.rcp45' diff --git a/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm b/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm deleted file mode 100644 index d8ee13a339..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm +++ /dev/null @@ -1 +0,0 @@ -anomaly_forcing = 'Anomaly.Forcing.Temperature' diff --git a/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm_streams b/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm_streams deleted file mode 100644 index 34ca8a96ae..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/datm_ssp126_anom_forc/user_nl_datm_streams +++ /dev/null @@ -1,22 +0,0 @@ -Anomaly.Forcing.Temperature:year_first=2015 -Anomaly.Forcing.Temperature:year_last=2100 -Anomaly.Forcing.Temperature:year_align=2015 -Anomaly.Forcing.Temperature:meshfile =$DIN_LOC_ROOT/share/meshes/fv0.9x1.25_141008_polemod_ESMFmesh.nc -! List of Data types to use -! Remove the variables you do NOT want to include in the Anomaly forcing: -! pr is preciptiation -! tas is temperature -! huss is humidity -! uas and vas are U and V winds -! rsds is solare -! rlds is LW down -Anomaly.Forcing.Temperature:datavars = pr Faxa_prec_af, \ - tas Sa_tbot_af, \ - ps Sa_pbot_af, \ - huss Sa_shum_af, \ - uas Sa_u_af, \ - vas Sa_v_af, \ - rsds Faxa_swdn_af, \ - rlds Faxa_lwdn_af -Anomaly.Forcing.Temperature:datafiles =$DIN_LOC_ROOT/atm/datm7/anomaly_forcing/CMIP6-SSP1-2.6/af.allvars.CESM.SSP1-2.6.2015-2100_c20220628.nc - diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/include_user_mods new file mode 100644 index 0000000000..fe0e18cf88 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/include_user_mods @@ -0,0 +1 @@ +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/user_nl_clm new file mode 100644 index 0000000000..499b6026ea --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_FillMissingW_Urban/user_nl_clm @@ -0,0 +1,6 @@ +! NOTE: Using an initial file that does NOT have TBD on it and 5.4 landuse timeseries dataset that has TBD on it +fsurdat = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.4.0/surfdata_0.9x1.25_hist_1850_78pfts_c250428.nc' +flanduse_timeseries = '$DIN_LOC_ROOT/lnd/clm2/surfdata_esmf/ctsm5.4.0/landuse.timeseries_0.9x1.25_hist_1850-2023_78pfts_c250428.nc' +finidat = '$DIN_LOC_ROOT/lnd/clm2/initdata_esmf/ctsm5.4/ctsm53041_54surfdata_snowTherm_100_pSASU.clm2.r.0161-01-01-00000.nc' +init_interp_fill_missing_urban_with_HD = .true. +use_init_interp = .true. diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/README b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/README new file mode 100644 index 0000000000..2037eb7100 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/README @@ -0,0 +1,6 @@ +This test runs different stream mapalgo and tintalgo options for various streams to make sure they work. + +Tests with it need to be at f09 resolution so that the redist option can be tried + + +NOTE: The none option doesn't seem to work, which likely means it only works with data for a single grid point EBK 7/23/2025 diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/include_user_mods new file mode 100644 index 0000000000..fe0e18cf88 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/include_user_mods @@ -0,0 +1 @@ +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/shell_commands b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/shell_commands new file mode 100644 index 0000000000..d8434d61ea --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/shell_commands @@ -0,0 +1,2 @@ +# -ignore_warnings is needed because we are turning on the Prigent streams even though they aren't needed +./xmlchange --append CLM_BLDNML_OPTS=-ignore_warnings diff --git a/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/user_nl_clm new file mode 100644 index 0000000000..c46d115579 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/f09_ObscureStreamOpts/user_nl_clm @@ -0,0 +1,30 @@ +! Turn Zender on so that the streams file will be used, no matter the compset +dust_emis_method = 'Zender_2003' +zender_soil_erod_source = 'lnd' + +! Make sure excess ice streams are on, no matter the compset +use_excess_ice = .true. +use_excess_ice_streams = .true. + +! Don't turn on prescribed LAI or soil-moisture to reduce complexity here and keep it in the prescribed testmod + +! Turn on any optional streams though + +use_prigent_roughness = .true. + +! Try all the different mapalgo options in the various stream files +! NOTE: The options with redist assume that the data grid is the same resolution as the model +! Which is why the testmod name starts with f09 +ndepmapalgo = 'redist' +ch4finundatedmapalgo = 'redist' +zendersoilerod_mapalgo = 'nn' +lightngmapalgo = 'consf' +popdensmapalgo = 'consd' +urbantvmapalgo = 'redist' +stream_mapalgo_exice = 'nn' +prigentroughnessmapalgo = 'consf' +! Likewise try all the different tintalgo options +ndep_tintalgo = 'lower' +lightng_tintalgo = 'nearest' +popdens_tintalgo = 'linear' ! The default for this is nearest +urbantv_tintalgo = 'upper' diff --git a/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/include_user_mods deleted file mode 100644 index 4fbf11b334..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/include_user_mods +++ /dev/null @@ -1 +0,0 @@ -../flexCN_FUN diff --git a/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/user_nl_clm deleted file mode 100644 index 8084f982e1..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/flexCN_FUN_BNF/user_nl_clm +++ /dev/null @@ -1,2 +0,0 @@ - nfix_method = 'Bytnerowicz' - diff --git a/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/README b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/README new file mode 100644 index 0000000000..5d30cc0d4e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/README @@ -0,0 +1,8 @@ +The purpose of this testmod directory is to setup for running fast testing +of initialization. So it bypasses the run phase and exits early. + +We use cold start so that we can get through initialization faster, + +I/O is turned off as much as possible. + +And physics options that make the model run faster are used. diff --git a/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/shell_commands b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/shell_commands new file mode 100755 index 0000000000..0d8a5d36e1 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/shell_commands @@ -0,0 +1,28 @@ +#!/bin/bash +./xmlchange CLM_FORCE_COLDSTART="on" + +# We use this testmod in a _Ln1 test; this requires forcing the ROF coupling frequency to same frequency as DATM +./xmlchange ROF_NCPL='$ATM_NCPL' + +# Turn off ROF model when used with compsets that have them +./xmlchange RTM_MODE='NULL' + +# Turn MEGAN off to run faster +./xmlchange CLM_BLDNML_OPTS='--no-megan' --append + +# Use fast structure and NWP configuration for speed +./xmlchange CLM_STRUCTURE="fast" +./xmlchange CLM_CONFIGURATION="nwp" + +# Restarts aren't allowed for these tests, and turn off CPL history +# First change in env_test.xml, then in the standard one so it won't complain there +./xmlchange --force REST_OPTION="never" --file env_test.xml +./xmlchange --force HIST_OPTION="never" --file env_test.xml +./xmlchange REST_OPTION="never" +./xmlchange HIST_OPTION="never" + +# Timer settings +./xmlchange TIMER_DETAIL="2" +./xmlchange SAVE_TIMING="TRUE" +./xmlchange CHECK_TIMING="TRUE" +./xmlchange ESMF_PROFILING_LEVEL="10" diff --git a/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm new file mode 100644 index 0000000000..c2a2d14793 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/for_testing_fastsetup_bypassrun/user_nl_clm @@ -0,0 +1,7 @@ +! Turn off history, restarts, and output +hist_empty_htapes = .true. +use_noio = .true. + +! Turn off urban options, and only do urban in gridcells that are majority urban +urban_hac = 'OFF' +toosmall_urban = 98.0d00 ! Minimize urban in gridcells diff --git a/cime_config/testdefs/testmods_dirs/clm/mpasa3p75/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/mpasa3p75/user_nl_clm new file mode 100644 index 0000000000..d9023871b5 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/mpasa3p75/user_nl_clm @@ -0,0 +1,6 @@ +! Settings currently required to run at the mpasa3p75 grid +! urbantv files at that resolution and use a redistribution mapping + +stream_fldfilename_urbantv = '$DIN_LOC_ROOT/lnd/clm2/urbandata/CTSM52_tbuildmax_OlesonFeddema_2020_mpasa3p75_fromf09_simyr1849-2106_c20240502.nc' +stream_meshfile_urbantv = '$DIN_LOC_ROOT/lnd/clm2/urbandata/CTSM52_tbuildmax_Oleson_2020_mpasa3p75_ESMFmesh_cdf5_c202405021.nc' +urbantvmapalgo = 'redist' diff --git a/cime_config/testdefs/testmods_dirs/clm/noAnomalyForcing/user_nl_datm b/cime_config/testdefs/testmods_dirs/clm/noAnomalyForcing/user_nl_datm new file mode 100644 index 0000000000..e609bf1b7a --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/noAnomalyForcing/user_nl_datm @@ -0,0 +1,2 @@ +anomaly_forcing = 'none' + diff --git a/cime_config/testdefs/testmods_dirs/clm/nvmovement/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/nvmovement/include_user_mods new file mode 100644 index 0000000000..fe0e18cf88 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/nvmovement/include_user_mods @@ -0,0 +1 @@ +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/nvmovement/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/nvmovement/user_nl_clm new file mode 100644 index 0000000000..258d3bbdf4 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/nvmovement/user_nl_clm @@ -0,0 +1 @@ + use_nvmovement = .true. diff --git a/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/README b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/README index 938dffbe6f..56457840bf 100644 --- a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/README +++ b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/README @@ -1,5 +1,8 @@ -The purpose of this testmod directory is to trigger the runtime -self-tests. This runs a suite of unit/integration tests. +The purpose of this testmod directory is to trigger runtime +initialization self-tests. This runs a set of unit/integration tests +that apply at initialization. -We use cold start so that we can get through initialization faster, -since how we initialize the model is unimportant for these self-tests. +We inherit the test-mod that sets up for testing and bypassing as much as possible for speed. + +There are other self_tests that need to be exercised in the model time stepping +and are done outside of these. diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/include_user_mods new file mode 100644 index 0000000000..cdf5cc9c81 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/include_user_mods @@ -0,0 +1 @@ +../for_testing_fastsetup_bypassrun diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands deleted file mode 100755 index d426269206..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/shell_commands +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -./xmlchange CLM_FORCE_COLDSTART="on" - -# We use this testmod in a _Ln1 test; this requires forcing the ROF coupling frequency to every time step -./xmlchange ROF_NCPL=48 diff --git a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm index 6187386336..9e8e0fcd04 100644 --- a/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/run_self_tests/user_nl_clm @@ -1 +1,5 @@ for_testing_run_ncdiopio_tests = .true. + +! Turn off history, restarts, and output +hist_empty_htapes = .true. +use_noio = .true. diff --git a/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/usermods_dirs/clm/NEON/FATES/defaults/user_nl_datm b/cime_config/usermods_dirs/clm/NEON/FATES/defaults/user_nl_datm new file mode 100644 index 0000000000..ec9f6cbcc7 --- /dev/null +++ b/cime_config/usermods_dirs/clm/NEON/FATES/defaults/user_nl_datm @@ -0,0 +1 @@ +anomaly_forcing = 'none' diff --git a/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm b/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm new file mode 100644 index 0000000000..ec9f6cbcc7 --- /dev/null +++ b/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm @@ -0,0 +1 @@ +anomaly_forcing = 'none' diff --git a/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm_streams b/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm_streams index bae77db6b5..36f1e72b3a 100644 --- a/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm_streams +++ b/cime_config/usermods_dirs/clm/NEON/defaults/user_nl_datm_streams @@ -37,4 +37,3 @@ preso3.SSP3-7.0:year_first=2018 preso3.SSP3-7.0:year_last=2022 preso3.SSP3-7.0:year_align=2018 preso3.SSP3-7.0:dtlimit=30 - diff --git a/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm new file mode 100644 index 0000000000..ec9f6cbcc7 --- /dev/null +++ b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm @@ -0,0 +1 @@ +anomaly_forcing = 'none' diff --git a/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm_streams b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm_streams index 29a8c675ac..cacc92f688 100644 --- a/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm_streams +++ b/cime_config/usermods_dirs/clm/PLUMBER2/defaults/user_nl_datm_streams @@ -38,4 +38,3 @@ presndep.SSP3-7.0:datafiles = $DIN_LOC_ROOT/lnd/clm2/ndepdata/fndep_clm_SSP370_b presndep.SSP3-7.0:dtlimit=30 co2tseries.SSP3-7.0:datafiles = $DIN_LOC_ROOT/atm/datm7/CO2/fco2_datm_globalSSP3-7.0_simyr_1750-2501_CMIP6_c201101.nc - diff --git a/cime_config/usermods_dirs/clm/reduced_output_fates/shell_commands b/cime_config/usermods_dirs/clm/reduced_output_fates/shell_commands index 2957effd77..b9d63a5d57 100644 --- a/cime_config/usermods_dirs/clm/reduced_output_fates/shell_commands +++ b/cime_config/usermods_dirs/clm/reduced_output_fates/shell_commands @@ -2,7 +2,7 @@ compset=`./xmlquery COMPSET --value` echo "fates_history_dimlevel = 1,2" >> user_nl_clm if [[ $compset =~ .*CLM[0-9]+%[^_]*FATES-SP.* ]]; then - echo "hist_fexcl1= 'FATES_CBALANCE_ERROR','FATES_COLD_STATUS','FATES_BURNFRAC','FATES_CROOTMAINTAR','FATES_CROOT_ALLOC','FATES_DAYSINCE_COLDLEAFOFF','FATES_DAYSINCE_COLDLEAFON','FATES_DEMOTION_CARBONFLUX','FATES_DISTURBANCE_RATE_FIRE','FATES_DISTURBANCE_RATE_LOGGING','FATES_DISTURBANCE_RATE_TREEFALL','FATES_EFFECT_WSPEED','FATES_EXCESS_RESP','FATES_FDI','FATES_FIRE_CLOSS','FATES_FIRE_INTENSITY','FATES_FIRE_INTENSITY_BURNFRAC','FATES_FROOT_ALLOC','FATES_FUELCONSUMED','FATES_FUEL_AMOUNT','FATES_FUEL_BULKD','FATES_FUEL_EFF_MOIST','FATES_GDD','FATES_FUEL_SAV','FATES_HARVEST_DEBT','FATES_HARVEST_DEBT_SEC','FATES_HARVEST_WOODPROD_C_FLUX','FATES_HET_RESP','FATES_IGNITIONS','FATES_LEAF_ALLOC','FATES_LITTER_IN','FATES_LITTER_OUT','FATES_LSTEMMAINTAR','FATES_LUCHANGE_WOODPROD_C_FLUX','FATES_MAINT_RESP_UNREDUCED','FATES_MORTALITY_CFLUX_CANOPY','FATES_MORTALITY_CFLUX_USTORY','FATES_NCHILLDAYS','FATES_NCOLDDAYS','FATES_NEP','FATES_NESTEROV_INDEX','FATES_NONSTRUCTC','FATES_NPP','FATES_PRIMARY_PATCHFUSION_ERR','FATES_PRIMARY_PATCHFUSION_ERR','FATES_PROMOTION_CARBONFLUX','FATES_REPROC','FATES_ROS','FATES_SAPWOODC','FATES_STEM_ALLOC','FATES_STOREC','FATES_STOREC_TF','FATES_STORE_ALLOC','FATES_STRUCTC','FATES_TRIMMING','FATES_UNGERM_SEED_BANK','FATES_USTORY_VEGC','FATES_VEGC_ABOVEGROUND','FATES_WOOD_PRODUCT','FATES_SEEDLING_POOL','FATES_SEEDS_IN','FATES_SEEDS_IN_LOCAL','FATES_SEED_ALLOC','FATES_SEED_BANK','FSH_R','HEAT_FROM_AC','HIA_R','HIA_U','HUMIDEX_R','HUMIDEX_U','MORTALITY_CROWNAREA_CANOPY','MORTALITY_CROWNAREA_UNDERSTORY','QIRRIG_FROM_GW_CONFINED','QIRRIG_FROM_GW_UNCONFINED','QIRRIG_FROM_SURFACE','SWBGT_R','SWBGT_U',,'WASTEHEAT','WBT','FATES_FUEL_MEF','FATES_GPP_USTORY','HUMIDEX','LNC','TBUILD','URBAN_AC','URBAN_HEAT','VENTILATION','WBT_R','WBT_U'" >> user_nl_clm + echo "hist_fexcl1= 'FATES_CBALANCE_ERROR','FATES_COLD_STATUS','FATES_BURNFRAC','FATES_CROOTMAINTAR','FATES_CROOT_ALLOC','FATES_DAYSINCE_COLDLEAFOFF','FATES_DAYSINCE_COLDLEAFON','FATES_DEMOTION_CARBONFLUX','FATES_DISTURBANCE_RATE_FIRE','FATES_DISTURBANCE_RATE_LOGGING','FATES_DISTURBANCE_RATE_TREEFALL','FATES_EFFECT_WSPEED','FATES_EXCESS_RESP','FATES_FDI','FATES_FIRE_CLOSS','FATES_FIRE_INTENSITY','FATES_FIRE_INTENSITY_BURNFRAC','FATES_FROOT_ALLOC','FATES_FUELCONSUMED','FATES_FUEL_AMOUNT','FATES_FUEL_BULKD','FATES_FUEL_EFF_MOIST','FATES_GDD','FATES_FUEL_SAV','FATES_HARVEST_DEBT','FATES_HARVEST_DEBT_SEC','FATES_HARVEST_WOODPROD_C_FLUX','FATES_HET_RESP','FATES_IGNITIONS','FATES_LEAF_ALLOC','FATES_LITTER_IN','FATES_LITTER_OUT','FATES_LSTEMMAINTAR','FATES_LUCHANGE_WOODPROD_C_FLUX','FATES_MAINT_RESP_UNREDUCED','FATES_MORTALITY_CFLUX_CANOPY','FATES_MORTALITY_CFLUX_USTORY','FATES_NCHILLDAYS','FATES_NCOLDDAYS','FATES_NEP','FATES_NESTEROV_INDEX','FATES_NONSTRUCTC','FATES_NPP','FATES_PRIMARY_PATCHFUSION_ERR','FATES_PRIMARY_PATCHFUSION_ERR','FATES_PROMOTION_CARBONFLUX','FATES_REPROC','FATES_ROS','FATES_SAPWOODC','FATES_STEM_ALLOC','FATES_STOREC','FATES_STOREC_TF','FATES_STORE_ALLOC','FATES_STRUCTC','FATES_TRIMMING','FATES_UNGERM_SEED_BANK','FATES_USTORY_VEGC','FATES_VEGC_ABOVEGROUND','FATES_SEEDLING_POOL','FATES_SEEDS_IN','FATES_SEEDS_IN_LOCAL','FATES_SEED_ALLOC','FATES_SEED_BANK','FSH_R','HEAT_FROM_AC','HIA_R','HIA_U','HUMIDEX_R','HUMIDEX_U','MORTALITY_CROWNAREA_CANOPY','MORTALITY_CROWNAREA_UNDERSTORY','QIRRIG_FROM_GW_CONFINED','QIRRIG_FROM_GW_UNCONFINED','QIRRIG_FROM_SURFACE','SWBGT_R','SWBGT_U',,'WASTEHEAT','WBT','FATES_FUEL_MEF','FATES_GPP_USTORY','HUMIDEX','LNC','TBUILD','URBAN_AC','URBAN_HEAT','VENTILATION','WBT_R','WBT_U'" >> user_nl_clm else echo "hist_fexcl1='ACTUAL_IMMOB','BTRANMN','EFLXBUILD','EFLX_DYNBAL','EFLX_GRND_LAKE','FATES_DEMOTION_CARBONFLUX','FATES_EXCESS_RESP','FATES_MAINT_RESP_UNREDUCED','FATES_PRIMARY_PATCHFUSION_ERR','FATES_PROMOTION_CARBONFLUX','FATES_SEEDS_IN_LOCAL','FATES_UNGERM_SEED_BANK','HEAT_FROM_AC','HIA','HIA_R','HIA_U','HUMIDEX','HUMIDEX_R','HUMIDEX_U','LAKEICEFRAC_SURF','LAKEICETHICK','LNC','MORTALITY_CROWNAREA_CANOPY','MORTALITY_CROWNAREA_UNDERSTORY','QIRRIG_FROM_GW_CONFINED','QIRRIG_FROM_GW_UNCONFINED','QIRRIG_FROM_SURFACE','RSSHA','RSSUN','SWBGT','SWBGT_U','SWBGT_R','TBUILD','URBAN_AC','URBAN_HEAT','WASTEHEAT','WBT','WBT_R','WBT_U','FPG','FSH_R','F_DENIT','F_N2O_DENIT','F_N2O_NIT','F_NIT','GROSS_NMIN','HEAT_FROM_AC','HIA_R','HIA_U','HUMIDEX_R','HUMIDEX_U','LITTERC_HR','LIT_CEL_N','LIT_LIG_N','LIT_LIG_C','LIT_MET_C','LIT_MET_N','MORTALITY_CROWNAREA_CANOPY','MORTALITY_CROWNAREA_UNDERSTORY','NDEP_TO_SMINN','NET_NMIN','NFIX_TO_SMINN','POTENTIAL_IMMOB','POT_F_DENIT','POT_F_NIT','QIRRIG_FROM_GW_CONFINED','QIRRIG_FROM_GW_UNCONFINED','QIRRIG_FROM_SURFACE','SMINN_TO_PLANT','SMIN_NH4','SMIN_NO3','SMIN_NO3_LEACHED','SMIN_NO3_RUNOFF','SOM_ACT_C','SOM_ACT_N','SOM_C_LEACHED','SOM_PAS_C','SOM_PAS_N','SOM_SLO_C','SUPPLEMENT_TO_SMINN','SWBGT_R','SWBGT_U','TOTCOLC','TOTCOLN','TOTECOSYSC','TOTECOSYSN','TOTLITC','TOTLITC_1m','TOTLITN','TOTLITN_1m','TOTSOMC','TOTSOMN','TOT_WOODPRODC','TOT_WOODPRODC_LOSS','TOT_WOODPRODN','TOT_WOODPRODN_LOSS','WASTEHEAT','WBT','WBT_U','WBT_R','DWT_WOODPRODN_GAIN','VENTILATION','LIT_LIG_N_vr','LIT_LIG_N_vr','LIT_MET_N_vr','LIT_CEL_N_vr','SOILN_vr','SOM_ACT_N_vr','SOM_PAS_N_vr','SOM_SLO_N_vr','SMINN_vr','SMIN_NO3_vr','DWT_WOODPRODC_GAIN','EFLX_LH_TOT_R','LAISHA','LAISUN','SMINN','TOTSOMN_1m','SMIN_NH4_vr'" >> user_nl_clm fi diff --git a/components/cdeps b/components/cdeps index 24369b135b..7ee2cf7954 160000 --- a/components/cdeps +++ b/components/cdeps @@ -1 +1 @@ -Subproject commit 24369b135b100d8c6a0816012b5ed0a79d6c9703 +Subproject commit 7ee2cf7954b1c18181652ed9b6ec26a8c31cb147 diff --git a/components/cism b/components/cism index 47f49ff8ed..8db0a2f005 160000 --- a/components/cism +++ b/components/cism @@ -1 +1 @@ -Subproject commit 47f49ff8ed1e002da0c57a1a0ad8d19b3609ba1e +Subproject commit 8db0a2f005cc42647b640c9f401083806a22bfa6 diff --git a/components/cmeps b/components/cmeps index f822a7d019..bdafcecac1 160000 --- a/components/cmeps +++ b/components/cmeps @@ -1 +1 @@ -Subproject commit f822a7d019a162cd25183bc822db0ccaebce511c +Subproject commit bdafcecac1c76c5143fd7dae136e02f85417de85 diff --git a/components/mizuroute b/components/mizuroute new file mode 160000 index 0000000000..362bee329b --- /dev/null +++ b/components/mizuroute @@ -0,0 +1 @@ +Subproject commit 362bee329bd6bf1fd45c8f36e006b9c4294bb8ca diff --git a/components/mosart b/components/mosart index a150d66335..a19a0f3956 160000 --- a/components/mosart +++ b/components/mosart @@ -1 +1 @@ -Subproject commit a150d663358e067208a52cbef7fc6c4f0e37f02e +Subproject commit a19a0f3956c4fc64603a36bb680d20b77a0d1383 diff --git a/components/rtm b/components/rtm index 26e96f500b..1165b4ff0c 160000 --- a/components/rtm +++ b/components/rtm @@ -1 +1 @@ -Subproject commit 26e96f500b9500b32a870db20eed6b1bd37587ea +Subproject commit 1165b4ff0c015369d350478c5833cf366acada9f diff --git a/doc/ChangeLog b/doc/ChangeLog index 49caf507b8..655b56af19 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,4 +1,3290 @@ =============================================================== +Tag name: ctsm5.3.085 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Fri Nov 14 11:08:54 AM MST 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + + PRs + #3581 by Bill Sacks: Generalize some paths so unit testing works in a CESM checkout, previously assumed standalone CTSM checkout. + #3561 by Erik Kluzek: Start adding streams infrastructure for carbon isotopes. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #2837 + Resolves #3502 + Design notes in #3546 + Some work on #3346 + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: + No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3609 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.084 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Fri 31 Oct 2025 01:12:26 PM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Bring changes on b4b-dev to master + +- Fix FUNIT testing on Mac's +- Some fixes to set_paramfile +- Don't auto build documentation on personal forks, only on ESCOMP + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixes #3571 -- set_paramfile ordering of PFT's from user doesn't have to match paramfile order + Fixes #3559 -- Correct Ndims error + Fixes #3369 -- Docs build and deploy only runs on ESCOMP not personal forks + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: + Some changes to set_paramfile documentation + +Notes of particular relevance for developers: +--------------------------------------------- + +Testing summary: regular +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-for-bit + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + + -- #3577 unit testing on Mac + -- #3572 set_paramfile ordering + -- #3560 Fix Ndim error in set_paramfile + -- #3557 doc/build/run/deploy only on ESCOMP + +=============================================================== +=============================================================== +Tag name: ctsm5.3.083 +Originator(s): rgknox (Ryan Knox,LAWRENCE BERKELEY NATIONAL LABORATORY,510-495-2153) +Date: Wed 29 Oct 2025 03:35:50 PM MDT +One-line Summary: Changes to coupling of supplementation status with FATES. + +Purpose and description of changes +---------------------------------- + +Supplementation status is now passed to FATES as a run-time boundary condition. This set of changes is synchronized with FATES-side changes that were oriented around how supplementation status impacts whether or not fine-root proportions are allowed to change during CNP runs. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? No + +Bugs fixed +---------- + +No bugs were fixed. + +Notes of particular relevance for users +--------------------------------------- + +When CTSM-FATES has nutrient coupling, note that FATES plants will default to not changing L2FR when supplementing nitrogen. + +Caveats for users (e.g., need to interpolate initial conditions): None + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): None + +Changes made to namelist defaults (e.g., changed parameter values): None + +Changes to the datasets (e.g., parameter, surface or initial files): None + +Changes to documentation: None necessary. + +Substantial timing or memory changes: None + +Notes of particular relevance for developers: +--------------------------------------------- + +We had been passing a stealth namelist variable from CTSM to FATES, it gave FATES information about what types of mineralized nutrient were available (ie NO3 or NH4), but FATES has never used this information, so it was removed. + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): None + +Changes to tests or testing: None + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: (give name of baseline if different from CTSM tagname, normally fates baselines are fates--) + derecho ----- OK + izumi ------- OK + + +Answer changes +-------------- + +Changes answers relative to baseline: No answer changes with one exception. Some FATES-specific variables related to radiation diagnostics and error tracking changed with updating the FATES tag. These were diagnostic only changes, and not related to FATES internals changing in any way. + +ther details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/CTSM/pull/3348 + + +=============================================================== +=============================================================== +Tag name: ctsm5.3.082 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Fri 24 Oct 2025 11:17:41 AM MDT +One-line Summary: Update to CMIP7 population density file for non-SSP cases + +Purpose and description of changes +---------------------------------- + + Update two lines in namelist_defaults_ctsm.xml. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3545 Update to official CMIP7 pop. density data + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + Updated pop. density file for non-SSP cases. + +Changes to the datasets (e.g., parameter, surface or initial files): + Updated pop. density file for non-SSP cases. + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: non-SSP + - what platforms/compilers: all + - nature of change: larger than roundoff/same climate + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3563 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.081 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Wed 22 Oct 2025 05:31:52 PM MDT +One-line Summary: Change defaults for when Carbon isotopes are turned on, and turn on irrigate for Sp/Bgc cases for clm6_0 historical transient cases + +Purpose and description of changes +---------------------------------- + +Only turn C13/C14 for clm6_0 and BGC, and also require the DATM forcing to be CRUJRA2024 or CAM7 and for the historical period. So for example SSP compsets won't have C13/C14 on, single point compsets won't, or for using older forcing (like CAM6, or GSWP3v1, Qian, or CRUv7). Also whenever use_c13 or use_c14 is on turn on the corresponding time series file. + +Also change default for irrigate to on for historical SP/BGC cases. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixes #3527 -- irrigation settings for control vs historical cases + Fixes #3551 -- Turn on relevent Carbon isotope time-series when use_c13/c14 is on + Some work on #3346 -- CMIP7 Carbon isotope data + +Notes of particular relevance for users +--------------------------------------- + +Caveats for users (e.g., need to interpolate initial conditions): + This just changes the defaults for when Carbon isotopes are turned on, to make it less often + Users can still turn it on by hand for the cases that they want. + +Changes made to namelist defaults (e.g., changed parameter values): + Carbon isotope timeseries files are turned on whenever use_c13/use_c14 is turned on so it uses + the historical + Turn irrigate on for clm6_0 historical + Carbon isotopes only turned on by default for clm6_0 when CRUJRA2024 or CAM7 forcing is used + and only for historical cases and not SSP's + +Testing summary: regular +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK (differences in namelists are all as expected) + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: previous + + +Answer changes +-------------- + +Changes answers relative to baseline: HistClm60 compsets and tests with the ciso testmod + + Summarize any changes to answers, i.e., + - what code configurations: HistClm60Sp/HistClm60Bgc now with irriage on + cases with use_c13/use_c14 now have c13/c14 isotope timeseries files on + - what platforms/compilers: all + - nature of change (roundoff; larger than roundoff/same climate; new climate): + Carbon Isotopes -- are diagnostic and only change fields on history files + Cases that now have C13/C14 isotopes on now use timeseries files rather + than the default pre-industrial so change answers for C13/C14 fields + Irrigate turned on for HistClm60Bgc/HistClm60Sp compsets will change answers + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + +https://github.com/ESCOMP/CTSM/pull/3549 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.080 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Thu Oct 16 13:41:08 MDT 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Merge b4b-dev to master. Includes these PRs: +- [ESCOMP/CTSM Pull Request #3193: resolve issue #103: Example 1.7 typo by linniahawkins](https://github.com/ESCOMP/CTSM/pull/3193) +- [ESCOMP/CTSM Pull Request #3518: Update of the leaching doc by jinmuluo](https://github.com/ESCOMP/CTSM/pull/3518) +- [ESCOMP/CTSM Pull Request #3548: Finishing adding RC14_CANAIR history output by ekluzek](https://github.com/ESCOMP/CTSM/pull/3548) + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed in the included PRs: +- [ESCOMP/CTSM Issue #103: Example 1.7 typo](https://github.com/ESCOMP/CTSM/issues/103) +- [ESCOMP/CTSM Issue #3488: Add RC14_CANAIR as optional to history output so that C14 time series data can be verified](https://github.com/ESCOMP/CTSM/issues/3488) +- [ESCOMP/CTSM Issue #3490: Change history averaging of RC13_CANAIR and RC14_CANAIR so that zero's aren't averaged in](https://github.com/ESCOMP/CTSM/issues/3490) + + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: +- Updated leaching documentation +- Typo fix + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- [ESCOMP/CTSM Pull Request #3553: b4b-dev merge 2025-10-16 by samsrabin](https://github.com/ESCOMP/CTSM/pull/3553) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.079 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Mon Oct 6 21:41:11 MDT 2025 +One-line Summary: Update submodules to match versions in cesm3_0_alpha07e + +Purpose and description of changes +---------------------------------- + +Update submodules to match versions in cesm3_0_alpha07e, plus: +- Update CMEPS further +- Update git-fleximod + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[X] clm6_0 + +[X] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: Compsets without stub glacier model + - what platforms/compilers: All + - nature of change (roundoff; larger than roundoff/same climate; new climate): Larger than roundoff, at least + + +Other details +------------- + +List any git submodules updated: +- ccs_config: ccs_config_cesm1.0.56 to ccs_config_cesm1.0.61 +- CDEPS: cdeps1.0.79 to cdeps1.0.81 +- CIME: cime6.1.113 to cime6.1.128 +- CISM: cismwrap_2_2_006 to cismwrap_2_2_010 +- CMEPS: cmeps1.1.5 to cmeps1.1.20 + +Pull Requests that document the changes: +- [ESCOMP/CTSM Pull Request #3523: ctsm5.3.079: Update submodules to match versions in cesm3_0_alpha07e (+ CMEPS)](https://github.com/ESCOMP/CTSM/pull/3523) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.078 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Fri 03 Oct 2025 09:31:06 AM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + + PR #3516 by Matvey: Add failing test as reminder for mosart history issue + PR #3511 by Erik: Decompinit timer updates + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: +Makes visible #3494 ERI_Ld90 tests fail with mosart.h0a having diffs +Fixes #3387 Bring in more timers for initialization +Some work in #3448 Improve design and error checking in decompMod and decomInitMod +Fixes #3508 Add decompMod_clean method +Fixes #3509 Internal subroutines for decompInit_lnd + +Notes of particular relevance for developers: +--------------------------------------------- +Changes to tests or testing: + New failing test added as reminder for mosart history issue + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3517 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.077 +Originator(s): rgknox (Ryan Knox,LAWRENCE BERKELEY NATIONAL LABORATORY) +Date: Thu 02 Oct 2025 09:39:48 AM MDT +One-line Summary: Adjustment of albedo timestep filtering with FATES. + +Purpose and description of changes +---------------------------------- + +A logical filter is used (doalb) during the main timestep loop to prevent +calculations of surface albedo when it is unnecessary. This includes when +coupling with an atmosphere and boundary conditions are available at a different +frequency as the land-timestep. This set of changes seeks to get FATES using +this filter (intead of all the time), while also enabling continuity on restarts +and not breaking existing behavior. + +These changes relied heavily on contributions from Matvey Debolskiy and +consultation from Mariana Vertenstein. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? + +This set of changes has NO scientifically meaningful changes to answers in any supported +configuration. + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +Addresses #3043 "Fates gives errsol BalanceCheck error with cam7". + +FATES PR 1397 is also required to address similar issues related to radiation +error diagnostics: https://github.com/NGEET/fates/pull/1397 + + +Notes of particular relevance for developers: +--------------------------------------------- + +Changes to tests or testing: +* New tests (ERI FatesLandTuningMode) have been added and expected fails have been added where appropriate. + +Testing summary: +---------------- + +regular tests (aux_clm): + + derecho ----- OK + izumi ------- OK + +fates tests: + derecho ----- OK + izumi ----- OK + + +Answer changes +-------------- + +For non-SP FATES configurations, some small round-off level changes are introduced due to an update in the FATES tag sci.87.2_api.41.0.0. + +Other details +------------- +Pull Requests that document the changes (include PR ids): +* [Pull Request #3051: ctsm5.3.077: Removing non doalb call of wrap_canopy_radiation for fates by rgknox](https://github.com/ESCOMP/CTSM/pull/3051) + + +=============================================================== +=============================================================== +Tag name: ctsm5.3.076 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Wed 24 Sep 2025 01:12:39 AM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Merge b4b-dev into master. + +Add dask and fortls to ctsm_pylib. Fortls added for using MS VS Code there's also a .vscode directory added with a sample settings template with some recommend settings to use when using VS Code with CTSM. See the README.md file in .vscode for how to use. + +Dask is added to lower memory needs with chunking and also allow CTSM tools to run faster by allowing multiple threads to be used on compute. + +Add some more PE layouts for mpasa3p75 and tests for them to the uhr_decomp_init test list. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): + +Fixes #3489 -- PE layouts for mpasa3p75 +Fixes #3380 -- mesh plotter instructions wrong about dask +Fixes #3381 -- Add dask to ctsm_pylib + +Notes of particular relevance for users +--------------------------------------- + +Caveats for users (e.g., need to interpolate initial conditions): + You'll need to recreate your ctsm_pylib environment to take advantage of these updates + + ./py_env_create --overwrite --yes + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + New ctsm_pylib environment + +Changes to documentation: Some updates around dask and mesh-plotter + +Notes of particular relevance for developers: +--------------------------------------------- +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + The decomp_init and uhr_decomp_init testlists use clm4_5 physics as a way to get fast test cases + This could be changed to use more recent physics + +Changes to tests or testing: New tests added to hr_decomp_init testlist + +Testing summary: regular +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-for-bit + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + +#3492 -- mpasa3p75 PE layouts and tests +#3473 -- dask and documentation around it +#3470 -- fortls and .vscode + +=============================================================== +=============================================================== +Tag name: ctsm5.3.075 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Tue 09 Sep 2025 03:12:17 PM MDT +One-line Summary: Change default glcmec_downscale_longwave from true to false for clm6 + +Purpose and description of changes +---------------------------------- + + Why: Adam Herrington, Bill Lipscomb, Gunter Leguy found that turning off the LW downscaling improves the melt and runoff biases. + + How: Change glcmec_downscale_longwave to false for clm6 in namelist_defaults_ctsm.xml. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3467 Turn off LW downscaling over glacier elevation classes by default + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + As explained above. + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: clm6 + - what platforms/compilers: all + - nature of change: larger than roundoff/same climate + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3475 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.074 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Mon 08 Sep 2025 06:04:38 PM MDT +One-line Summary: Update ctsm6 default paramfile and finidat files + +Purpose and description of changes +---------------------------------- + + - Copied the new files to /inputdata and rimported them to the svn repository + - Updated namelist_defaults_ctsm.xml + + New paramfile /glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/paramdata/ctsm60_params_cal115_c250813.nc + + New finidat files in /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/initdata_esmf/ctsm5.4: + f19 2000 Bgc ctsm53065_54surfdata_PPEcal115_115_HIST.clm2.r.2000-01-01-00000.nc + f19 1850 Bgc ctsm53065_54surfdata_PPEcal115_115_pSASU.clm2.r.0161-01-01-00000.nc + + ne30 1979 BgcCrop ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.1979-01-01-00000.nc + ne30 2000 BgcCrop ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.2000-01-01-00000.nc + ne30 2010 BgcCrop ctsm5.4_5.3.068_PPEcal115_116_HIST.clm2.r.2010-01-01-00000.nc + ne30 1850 BgcCrop ctsm5.4_5.3.068_PPEcal115_116_pSASU.clm2.r.0161-01-01-00000.nc + + f09 1979 BgcCrop ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.1979-01-01-00000.nc + f09 2000 BgcCrop ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.2000-01-01-00000.nc + f09 2010 BgcCrop ctsm5.4_5.3.068_PPEcal115f09_118_HIST.clm2.r.2010-01-01-00000.nc + f09 1850 BgcCrop ctsm5.4_5.3.068_PPEcal115f09_118_pSASU.clm2.r.0161-01-01-00000.nc + + f09 1850 Sp ctsm5.4_5.3.068_PPEcal115_SP_f09_121_1850.clm2.r.0041-01-01-00000.nc + f09 2000 Sp ctsm5.4_5.3.068_PPEcal115_SP_f09_121_HIST.clm2.r.2000-01-01-00000.nc + ne30 1850 Sp ctsm5.4_5.3.068_PPEcal115_SP_ne30_120_1850.clm2.r.0041-01-01-00000.nc + ne30 2000 Sp ctsm5.4_5.3.068_PPEcal115_SP_ne30_120_HIST.clm2.r.2000-01-01-00000.nc + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + Updated default files as listed above + +Changes to the datasets (e.g., parameter, surface or initial files): + Same comment + +Notes of particular relevance for developers: +--------------------------------------------- +Changes to tests or testing: + bld/unit_testers/build-namelist_test.pl + I removed "--mask gx1v7" from a few tests to allow them to pass when the mask has been updated to tx2_3v2 + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK (expected failures reported in #3050) + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: ctsm6 + - what platforms/compilers: all + - nature of change: larger than roundoff/same climate + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3460 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.073 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Thu 28 Aug 2025 01:10:16 PM MDT +One-line Summary: Update .gitmodules to cesm3_0_alpha07c + +Purpose and description of changes +---------------------------------- + Update ccs_config_cesm1.0.48 to ccs_config_cesm1.0.56. + Update cime6.1.112 to cime6.1.113. + Answers change in gnu and nvhpc tests on derecho (details below). + New bugs found and reported in issues + #3453 FAIL MKSURFDATAESMF_...intel NLCOMP + #3454 FAIL SUBSETDATA* tests NLCOMP + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3411 Update cime and ccs_config versions to those used in cesm3_0_alpha07c + Resolves #3180 nvhpc module setup problem in ctsm5.3.050 with ccs_config1.0.43 + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what platforms/compilers: gnu and nvhpc on derecho + - nature of change: roundoff + + Answers change due to the following: + ccs_config1.0.49 -- Moves the NVHPC compiler version back a bit on Derecho, which changes answers + ccs_config1.0.52 -- Updates the GNU compiler version on Derecho, which changes answers + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + ccs_config_cesm1.0.48 --> ccs_config_cesm1.0.56 + cime6.1.112 --> cime6.1.113 + +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3422 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.072 +Originator(s): jinmuluo (Jinmu Luo, Cornell University) +Date: Tue 26 Aug 2025 02:53:26 PM MDT +One-line Summary: New vertical movement scheme for soil nitrate + +Purpose and description of changes +---------------------------------- + + Introducing a vertical movement scheme for soil nitrate but setting its use to .false. by default. + Details about the new scheme in the PR #2992. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + No corresponding github issue + +Notes of particular relevance for users +--------------------------------------- +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + use_nvmovement can be set by user + +Changes made to namelist defaults (e.g., changed parameter values): + use_nvmovement defaults to .false. + +Changes to documentation: + Planned for Jinmu's NSF-NCAR visit this Fall + +Notes of particular relevance for developers: +--------------------------------------------- +Changes to tests or testing: + New tests with nvmovement testmods, one with matrixcnOn and one with it off + ERP_D_Ld5.f10_f10_mg37.I1850Clm60BgcCrop.derecho_intel.clm-nvmovement--clm-matrixcnOn + ERP_D.f10_f10_mg37.IHistClm60Bgc.derecho_intel.clm-decStart--clm-nvmovement + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS with all commits up to and including 1cf9859 + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- +Changes answers relative to baseline: No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/2992 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.071 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Fri Aug 22 13:49:25 MDT 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Merge b4b-dev to master. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3375: Enabling running an initialization test at mpasa3p75](https://github.com/ESCOMP/CTSM/issues/3375) +- [Issue #3402: py_env_create and a test broken after Derecho updates to conda/mamba](https://github.com/ESCOMP/CTSM/issues/3402) +- [Issue #3417: Use shr_abort_mod for endrun, and add optional arguments for file and line](https://github.com/ESCOMP/CTSM/issues/3417) +- [Issue #3420: Don't have endrun abort on bad subgrid_level](https://github.com/ESCOMP/CTSM/issues/3420) + + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: +- Docs added for new query_paramfile and set_paramfile tools. + + +Notes of particular relevance for developers: +--------------------------------------------- + +Changes to tests or testing: +- New SETPARAMFILE SystemTest, one of which has been added to aux_clm and clm_pymods +- Adds a test with resolution mpasa3p75_mpasa3p75_mt13 to new uhr_decomp_init suite (of which it's the only member) +- run_self_tests testmod changed; replaced in some tests by new for_testing_fastsetup_bypassrun testmod. + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- DIFF + izumi ------- OK + + +Answer changes +-------------- + +Changes answers relative to baseline: +- SMS_D_Ln1.f10_f10_mg37.I2000Clm50BgcCropQianRs.derecho_intel.clm-run_self_tests changes answers due to differences in user_nl_clm and shell_commands. + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3403: Fix py_env_create and tests by samsrabin](https://github.com/ESCOMP/CTSM/pull/3403) +- [Pull Request #3390: Make npcropmin/max private to pftconMod by samsrabin](https://github.com/ESCOMP/CTSM/pull/3390) +- [Pull Request #3418: Endrun work by ekluzek](https://github.com/ESCOMP/CTSM/pull/3418) +- [Pull Request #3408: Logging improvements for GDD-generation workflow by samsrabin](https://github.com/ESCOMP/CTSM/pull/3408) +- [Pull Request #3397: New tools: query_paramfile and set_paramfile by samsrabin](https://github.com/ESCOMP/CTSM/pull/3397) +- [Pull Request #3413: Add a test for Mpasa3p75 by ekluzek](https://github.com/ESCOMP/CTSM/pull/3413) +- [Pull Request #3431: b4b-dev merge 2025-08-22 by samsrabin](https://github.com/ESCOMP/CTSM/pull/3431) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.070 +Originator(s): glemieux (Gregory Lemieux, LBNL, glemieux@lbl.gov) +Date: Fri 22 Aug 2025 02:29:15 AM MDT +One-line Summary: Update default FATES parameter file and add FATES managed fire namelist option + +Purpose and description of changes +---------------------------------- + +This updates the FATES tag and associated default parameter file. The tag update includes a +new managed fire feature which is controlled by a new namelist option. The majority of +the parameter file updates are associated with this new feature, although there is also +a smaller set of minor fixes and removal of depricated parameters. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + Added a new option, use_fates_managed_fire + +Changes to tests or testing: + Added a new testmod to test use_fates_managed_fire. + Added a unit test associated to make sure SPITFIRE mode is enabled when managed fire option is on. + +Testing summary: +---------------- + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - pASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: (give name of baseline if different from CTSM tagname, normally fates baselines are fates--) + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: yes, only for FATES testmods + + Summarize any changes to answers, i.e., + - what code configurations: For FATES landuse (LUH2) modes only + - what platforms/compilers: all + - nature of change: larger than roundoff + + Answer changes are confined to FATES landuse only mode due to the + parameter file fix with FATES pull request 1412. + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + fates: sci.1.84.0_api.40.0.0 -> sci.1.87.0_api.41.0.0 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + +https://github.com/ESCOMP/CTSM/pull/3372 +https://github.com/NGEET/fates/pull/1444 +https://github.com/NGEET/fates/pull/1419 +https://github.com/NGEET/fates/pull/1412 +https://github.com/NGEET/fates/pull/1360 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.069 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Tue Aug 12 09:52:59 MDT 2025 +One-line Summary: Add SystemTests to run subset_data and then CTSM + +Purpose and description of changes +---------------------------------- + +Adds SUBSETDATAPOINT and SUBSETDATAREGION tests to aux_clm (and new subset_data suite). These run subset_data for either a point or a six-cell region, then run CTSM with the outputs. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- Resolves [Issue #1491: Add two new tests that run subset_data and then run a case from it](https://github.com/ESCOMP/CTSM/issues/1491) + + +Notes of particular relevance for developers: +--------------------------------------------- +NOTE: Be sure to review the steps in README.CHECKLIST.master_tags as well as the coding style in the Developers Guide +[Remove any lines that don't apply. Remove entire section if nothing applies.] + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + +Changes to tests or testing: +- Adds test SUBSETDATAPOINT_Ld5_D_Mmpi-serial.CLM_USRDAT.I2000Clm60BgcCropCrujra.derecho_intel.clm-default +- Adds test SUBSETDATAREGION_Ld5_D_Mmpi-serial.CLM_USRDAT.I2000Clm60BgcCropCrujra.derecho_intel.clm-default +- Adds new subset_data suite with those tests in it +- Those tests are also in aux_clm + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - Unit, black, and pylint checks pass. Two system tests fail due to recent conda and mamba updates on Derecho; these have been fixed on b4b-dev with [Pull Request #3403: Fix py_env_create and tests by samsrabin](https://github.com/ESCOMP/CTSM/pull/3403) + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3292: ctsm5.3.069: Add SystemTests to run subset_data and then CTSM by samsrabin](https://github.com/ESCOMP/CTSM/pull/3292) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.068 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Mon 11 Aug 2025 02:27:39 PM MDT +One-line Summary: Change megan_use_gamma_sm to default false + +Purpose and description of changes +---------------------------------- + + One line change in namelist_defaults_ctsm.xml + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3016 Low megan emissions in cesm3 + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + Change megan_use_gamma_sm to default false + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: only tests with MEGAN + - what platforms/compilers: all + - nature of change: larger than roundoff for MEGAN fields only, otherwise bit-for-bit + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3384 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.067 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Fri 08 Aug 2025 02:49:25 PM MDT +One-line Summary: (Mostly) fix interim restarts + +Purpose and description of changes +---------------------------------- + +Various changes that fix CLM's behavior with `DOUT_S_SAVE_INTERIM_RESTART_FILES=TRUE`. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue ESCOMP/CTSM#3355: config_archive.xml needs fix for st_archive](https://github.com/ESCOMP/CTSM/issues/3355) +- [Issue ESCOMP/CTSM#3356: CMEPS needs updating to fix st_archive](https://github.com/ESCOMP/CTSM/issues/3356) +- [Issue ESCOMP/CTSM#3357: CIME needs updating to fix st_archive](https://github.com/ESCOMP/CTSM/issues/3357) +- [Issue ESCOMP/CTSM#3358: MOSART needs updating to fix st_archive](https://github.com/ESCOMP/CTSM/issues/3358) +- [Issue ESCOMP/CTSM#3359: Add ERR test to aux_clm](https://github.com/ESCOMP/CTSM/issues/3359) +- [Issue ESCOMP/CTSM#3310: Unrevert rpointer changes in #3067](https://github.com/ESCOMP/CTSM/issues/3310) +- [Issue ESCOMP/CTSM#3098: st_archive issues in ctsm5.3.041 with our testing](https://github.com/ESCOMP/CTSM/issues/3098) +- [Issue ESCOMP/CTSM#3374: CIME needs updating for reliable ERR and IRT tests](https://github.com/ESCOMP/CTSM/issues/3374) +- [Issue ESCOMP/CTSM#3377: Interim restarts work: MOSART doesn't save last restart?](https://github.com/ESCOMP/CTSM/issues/3377) + + +Notes of particular relevance for developers: +--------------------------------------------- +NOTE: Be sure to review the steps in README.CHECKLIST.master_tags as well as the coding style in the Developers Guide +[Remove any lines that don't apply. Remove entire section if nothing applies.] + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + +Changes to tests or testing: +- Adds interim_restart suite with 5 short derecho gnu tests. 3 of those are also in aux_clm; another 1 is in ctsm_sci. +- 2 of the new aux_clm tests are expected failures; see [Issue ESCOMP/CTSM#3383: Interim restart work: ERR...clm-default tests fail COMPARE_base_rest (missing/wrong files)](https://github.com/ESCOMP/CTSM/issues/3383). + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + any other testing (give details below): + + interim_restart + derecho ---- OK + + +Other details +------------- +[Remove any lines that don't apply. Remove entire section if nothing applies.] + +List any git submodules updated: +- CIME: cime6.1.107 to cime6.1.111 +- CMEPS: cmeps1.1.2 to cmeps1.1.5 +- MOSART: mosart1.1.10 to mosart1.1.12 +- RTM: rtm1_0_88 to rtm1_0_89 + +Pull Requests that document the changes (include PR ids): +- [Pull Request ESCOMP/CTSM#3400: ctsm5.3.067: (Mostly) fix interim restarts by samsrabin](https://github.com/ESCOMP/CTSM/pull/3400) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.066 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Fri 08 Aug 2025 09:19:34 AM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + + PR #3395 making this tag includes: + #3352 Add Fang Li's abm and popden .ncl scripts to /tools/contrib + #3395 Add to README.CHECKLIST.master_tags a new reminder: + "When izumi’s baseline is ready, open read permissions to all" + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Addresses part of #2701 New population dataset for streams files + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK (running aux_clm was redundant given the type of code changes) + izumi ------- OK + +Answer changes +-------------- +Changes answers relative to baseline: No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3395 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.065 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Mon 28 Jul 2025 09:30:35 AM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Implement namelist options for soil moisture activation factor used in MEGAN isoprene emissions. + +Adds a GitHub issue template for things related to documentation. + +Fixes a self-test. + +Adds a broadcast of the namelist value for a few mapalgo items after reading it in so it is consistent across all ranks when not using the default value. Also update the mapalgo options list to be consistent with the NUOPC options, the MCT list was in place before this. + +Update submodules so we are using the latest in cesm development for cesm3_0_alpha07b. + +Harden github actions to improve security (use a hash, rather than a tag, reduce permissions to readonly when possible) + +Update SpinupStability scripts for h0->h0a transition (backwards compatible). +Update run_clm_historical script for nuopc, extension through 2023, produces restart files for 1979 and 2000, and h0->h0a transition (backwards compatible). + +Remove flexCN_FUN_BNF test and testmods as redundant + +Fix string replacements in lreprstruct test: +The previous logic caused problems if "GRAIN" appeared in two (or more) strings, where one was a substring of the other. For example, in LREPRSTRUCT_Ly1_P128x1.f10_f10_mg37.I1850Clm50BgcCrop.derecho_gnu.clm-ciso--clm-cropMonthOutput, before this replacement, one line contained 'GRAINN_TO_FOOD' and a later line contained (among other things) "'GRAINN_TO_FOOD_PERHARV', 'GRAINN_TO_FOOD_ANN'". This was problematic because the first replacement of GRAINN_TO_FOOD incorrectly led to replacements in the later strings as well. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixes #3316 Fix mpas15a self test + Work on #3016 low MEGAN emissions + Fixes #2533 Fix broadcast of urbantvmapalgo + Fixes #3345 Fix broadcast of other mapalgo namelist items + Fixes #1912 Fix list of valid options for mapalgo namelist items + Fixes #3303 Update submodules to cesm3_0_alpha07b + Fixes #2983 Remove redundant test + Fixes #3316 self test fails for mpasa15 + Fixes #3316 Problem with LREPR* tests + +Notes of particular relevance for users +--------------------------------------- + +Caveats for users (e.g., need to interpolate initial conditions): + Changes to megan_opts namelist only happen if megan is on + You can only set megan_min_gamma_sm if megan_use_gamma_sm is TRUE + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + Two new CTSM megan_opts namelist items: + megan_use_gamma_sm + megan_min_gamma_sm + + Add ch4finundatedmapalgo to namelist XML, was in the code but not in the XML + + Fix the list of options for the mapalgo namelist items: + Remove nnonj, nnoni, spval, copy + Add: consf, consd, none + +Changes made to namelist defaults (e.g., changed parameter values): + Sets defaults for megan_opts namelist to reproduce answers + +Notes of particular relevance for developers: +--------------------------------------------- + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + LREPR test was fixed, but didn't have a unit tester for it + +Changes to tests or testing: + Add f09_ObscureStreamOpts testmod and test, and remove a BNF test and testmod + + +Testing summary: regular +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - (I still get #3205 when running the testing) + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-for-bit + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): rtm, mostart, ccs_config, cime, cmeps, cdeps, pio + In general update to cesm3_0_alpha07b submodules + RTM to rtm1_0_88 (fixes history metadata) + MOSART to mosart1.1.10 (fixes history metadata) + ccs_config to ccs_config_cesm1.0.48 + CIME to cime6.1.107 + CMEPS to cmeps1.1.2 + CDEPS to cdeps1.0.79 + PIO to pio2_6_6 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + - #3309 + - #3144 + - #3318 + - #2534 + - #3334 + - #3343 + - #3338 + - #3329 + - #3314 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.064 +Originator(s): slevis (Samuel Levis) +Date: Thu 24 Jul 2025 01:13:00 PM MDT +One-line Summary: Add time dimension to 1d_wt fields in transient runs + +Purpose and description of changes +---------------------------------- + + 1d_wt fields (e.g. pfts1d_wtgcell) appear in history when hist_dov2xy = .false. + This PR: + - adds the time dimension to these variables in Hist runs because these variables change with time + - adds new namelist variable vars_1dwt_w_time to let users add the time dimension when it is not added by default, i.e. in non-transient runs + - throws a build namelist error when the new namelist variable is .false. in a transient simulation + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3307 Updating vector history file output... + +Notes of particular relevance for users +--------------------------------------- +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + New namelist variable vars_1dwt_w_time; user will get an error if they set it to .false. when any of the following is .true.: + do_transient_pfts, do_transient_crops, do_transient_lakes, do_transient_urban + +Changes made to namelist defaults (e.g., changed parameter values): + vars_1dwt_w_time defaults to true when any of the following is .true.: + do_transient_pfts, do_transient_crops, do_transient_lakes, do_transient_urban + else vars_1dwt_w_time defaults to .false. + +Changes to documentation: + Added vars_1dwt_w_time to namelist_definition_ctsm.xml with an explanation of its use + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + - 1d_wt fields with the time dimension will display different answers in transient simulations, though this is diagnostic in nature. + - Only one test is affected in aux_clm: RXCROPMATURITYSKIPGEN_Ld1097.f10_f10_mg37.IHistClm60BgcCrop.derecho_intel.clm-cropMonthOutput + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3328 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.063 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Thu Jul 10 12:28:36 MDT 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Regular merge of b4b-dev branch to master. See "Bugs fixed" and "Other details" for more information. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed: +- [Issue #2985: Fix need/checks for inputdata path in subset_data and Python testing](https://github.com/ESCOMP/CTSM/issues/2985) +- [Issue #2986: Avoid use of os.mknod() in Python testing for portability](https://github.com/ESCOMP/CTSM/issues/2986) +- [Issue #2984: Python unit tests aren't portable](https://github.com/ESCOMP/CTSM/issues/2984) +- [Issue #3279: subset_data still having trouble with Longitude](https://github.com/ESCOMP/CTSM/issues/3279) +- [Issue #2911: Docs: Specify that snow/ice units are liquid water equivalent](https://github.com/ESCOMP/CTSM/issues/2911) +- [Issue #3312: Add some PE layout test sizes for some resolutions to facilitate testing decompInit time testing for different problem sizes / task counts](https://github.com/ESCOMP/CTSM/issues/3312) +- [Issue #3313: Hist fields REPRODUCTIVE1N_TO_FOOD_PERHARV and _ANN lose their suffixes in LREPR* tests](https://github.com/ESCOMP/CTSM/issues/3313) +- [Issue #3110: Initialization of historical using CTSM5.4 surface datasets fails](https://github.com/ESCOMP/CTSM/issues/3110) + + +Notes of particular relevance for users +--------------------------------------- + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): +- New init_interp_fill_missing_urban_with_HD option (default `.false.`). See [Pull Request #3132: Fix #3110 (Initialization of historical using CTSM5.4 surface datasets fails) by olyson](https://github.com/ESCOMP/CTSM/pull/3132). + +Changes to documentation: +- Tech Note now specifies that snow/ice units are liquid water equivalent. + + +Notes of particular relevance for developers: +--------------------------------------------- + +Changes to tests or testing: +- Adds SMS_D_Ld10.f09_f09_mt232.IHistClm60BgcCrop.derecho_intel.clm-f09_FillMissingW_Urban test to aux_clm +- Adds various tests to new decomp_init test suite ("Initialization tests specifically for examining the PE layout decomposition initialization") +- Adds various Python unit and system tests + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: (give name of baseline if different from CTSM tagname, normally fates baselines are fates--) + * Will run fates suite after aux_clm, just to generate fates-sci.1.84.0_api.40.0.0-ctsm5.3.063 baseline. Will not compare against any baseline or even check for errors. + + +Other details +------------- + +Pull Requests that document the changes: +- [Pull Request #3238: Add GitHub workflow for Python unit tests by samsrabin](https://github.com/ESCOMP/CTSM/pull/3238) +- [Pull Request #3286: subset_data: Fix conversion of Longitude to string by samsrabin](https://github.com/ESCOMP/CTSM/pull/3286) +- [Pull Request #3247: add notes to specify snow/ice units are liquid water equivalent by sy-li](https://github.com/ESCOMP/CTSM/pull/3247) +- [Pull Request #3315: Add decomp_init testlist and some extra PE layouts for some grids by ekluzek](https://github.com/ESCOMP/CTSM/pull/3315) +- [Pull Request #3314: Fix string replacements in lreprstruct test by billsacks](https://github.com/ESCOMP/CTSM/pull/3314) +- [Pull Request #3132: Fix #3110 (Initialization of historical using CTSM5.4 surface datasets fails) by olyson](https://github.com/ESCOMP/CTSM/pull/3132) +- [Pull Request #3240: tips-for-working-with-rst.md: Add common errors, cheatsheet links. by samsrabin](https://github.com/ESCOMP/CTSM/pull/3240) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.062 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Wed 09 Jul 2025 09:03:55 AM MDT +One-line Summary: Put instantaneous and non-inst. fields on separate hist files + +Purpose and description of changes +---------------------------------- + Following ctsm5.3.018 "Change history time to be the middle of the time bounds" + the current change intends to prevent confusion associated with the time corresponding to instantaneous history fields by putting them on separate files than non-instantaneous fields. The result is + + 1) two history files per clm, mosart, and rtm history tape: + tape h0 becomes h0a and h0i + tape h1 becomes h1a and h1i + ... + tape hX becomes hXa and hXi + + 2) two history restart files per history restart tape: + rh0 becomes rh0a and rh0i + rh1 becomes rh1a and rh1i + ... + rhX becomes rhXa and rhXi + + The clm handles empty history (and corresponding history restart) files by not generating them, while rtm and mosart give an error. Instead of refactoring rtm and mosart to behave like the clm (considered out of scope), I have introduced one active instantaneous field in mosart and one in rtm to bypass the "empty file" error. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #1059 Don't allow inst. fields and averaged fields to be on the same history file + Resolves ESCOMP/RTM#32 + Resolves ESCOMP/MOSART#52 + +Notes of particular relevance for users +--------------------------------------- +Caveats for users (e.g., need to interpolate initial conditions): + History tapes have new extensions, e.g. h0 becomes h0a and h0i + History restart tapes have new extensions, e.g. rh0 becomes rh0a and rh0i + +Changes to documentation: + Not, yet: + - clm documentation + - Adam Phillips' cmip documentation + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: (-c fates-sci.1.84.0_api.40.0.0-ctsm5.3.061 -g fates-sci.1.84.0_api.40.0.0-ctsm5.3.062) + derecho ----- OK + izumi ------- OK + + any other testing (give details below): + + ctsm_sci (-c ctsm_sci-ctsm5.3.059 -g ctsm_sci-ctsm5.3.062) + derecho ---- FAIL and I will open an issue and mark EXPECTED FAILURE as the problem originates in ctsm5.3.060 + + mosart tests: (-c mosart1.1.08-ctsm5.3.061 -g mosart1.1.09-ctsm5.3.062) + derecho ----- OK + izumi ------- OK + + rtm tests: (-c rtm1_0_86-ctsm5.3.061 -g rtm1_0_87-ctsm5.3.062) + derecho ----- OK + izumi ------- OK + + crop_calendars tests: (tested while in ctsm5.3.058 and again in ctsm5.3.061) + derecho ----- OK + izumi ------- OK + + ssp tests: (tested while in ctsm5.3.058 and again in ctsm5.3.061) + derecho ----- OK + + hillslope tests: (tested while in ctsm5.3.058 and again in ctsm5.3.061) + derecho ----- OK + + fire tests: (tested while in ctsm5.3.058 and again in ctsm5.3.061) + derecho ----- OK + +Answer changes +-------------- + +Changes answers relative to baseline: No, but read caveat: + h0 files become h0a (containing non-instantaneous fields) and h0i (containing instantaneous fields): + - I spot-checked clm, mosart, and rtm files and confirmed no bitwise change in answers. + - I ran Sam Rabin's comparison tool written specifically to compare hX files against hXa + hXi files: + ~samrabin/pr_2445_baseline_compare/pr_2445_baseline_compare.py -1 /glade/campaign/cgd/tss/ctsm_baselines/ctsm5.3.061 tests_0701-173109de + and it returned a single DIFF that appears to be a false positive. + +Other details +------------- +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + mosart1.1.08 --> mosart1.1.09 + rtm1_0_86 --> rtm1_0_87 + +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/2445 + https://github.com/ESCOMP/MOSART/pull/117 + https://github.com/ESCOMP/RTM/pull/61 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.061 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Thu 26 Jun 2025 11:28:43 AM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- +PR #3231 Clean up docs workflows +Resolves #3160 +Resolves #3213 + +PR #3272 Throw error if reseed_dead_plants = .true. in a branch simulation +Resolves #3257 + +PR #3264 Fix plumber2_surf_wrapper +Resolves #3262 + +PR #3259 subset_data point: Fix --create-datm and Longitude TypeErrors +Resolves #3258 +Resolves #3260 +Resolves #3197 +Resolves #2960 + +PR #3227 Docs docs: Update Windows instructions +Resolves #3185 + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Listed along with corresponding PRs in "Purpose and description of changes" above + +Notes of particular relevance for users +--------------------------------------- +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + #3272 Throw error if reseed_dead_plants = .true. in a branch simulation + +Changes to documentation: + #3227 Docs docs: Update Windows instructions + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- +Changes answers relative to baseline: No + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3283 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.060 +Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310) +Date: Tue 24 Jun 2025 02:13:05 PM MDT +One-line Summary: Preliminary update of ctsm54 defaults (answer changing) + +Purpose and description of changes +---------------------------------- + + Brings to master some of the work done in #3206, which I merged to the ctsm5.4 alpha branch recently as tag alpha-ctsm5.4.CMIP7.02.ctsm5.3.055. + + Allows Cecile to run coupled without having to adjust clm things manually: updates namelist defaults and IC files that have been limited to the ctsm5.4 branch so far. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[x] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3116 modify snow thermal conductivity defaults + Resolves #3005 ctsm54 initial condition files + +Notes of particular relevance for users +--------------------------------------- +Changes made to namelist defaults (e.g., changed parameter values): + a3ce6a7 changes the default snow thermal conductivity schemes over lakes and glaciers in clm6 cases + +Changes to the datasets (e.g., parameter, surface or initial files): + a75e488 introduces new paramfile to clm6 cases + 3a8c432 introduces new f09 and ne30 finidat files for 1850 and 2000 clm6 cases + Reverted preexisting changes to the default raw datasets that came in with #3206 (from the ctsm54 branch) + Reverted changes to the f09 and ne30 fsurdat/landuse files that came in with b1890ac + +Changes to documentation: + None, yet + +Notes of particular relevance for developers: +--------------------------------------------- +Changes to tests or testing: + The next 2 tests are now labeled EXPECTED FAILURE in the RUN phase, to be addressed in issue #3252: + LII2FINIDATAREAS_D_P256x2_Ld1.f09_g17.I1850Clm50BgcCrop.derecho_intel.clm-default--clm-matrixcnOn_ignore_warnings + LII2FINIDATAREAS_D_P256x2_Ld1.f09_g17.I1850Clm50BgcCrop.derecho_intel.clm-default + +Testing summary: +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK (see Changes to tests above) + izumi ------- OK + + fates tests: (-c fates-sci.1.84.0_api.40.0.0-ctsm5.3.051 -g fates-sci.1.84.0_api.40.0.0-ctsm5.3.060) + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: Yes + + Summarize any changes to answers, i.e., + - what code configurations: various + - what platforms/compilers: all + - nature of change: larger than roundoff/same climate + + See above in changes to namelist defaults and datasets for the sources of change. + +Other details +------------- +Pull Requests that document the changes (include PR ids): + https://github.com/ESCOMP/ctsm/pull/3268 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.059 +Originator(s): erik (Erik Kluzek) +Date: Mon 23 Jun 2025 01:39:37 AM MDT +One-line Summary: Bring in various cleanup efforts found in previous testing after the chill changes came in + +Purpose and description of changes +---------------------------------- + +Various updates for testing and other problems identified in the +cesm3_0_beta04 tag. So fixes and cleanup for usability. +Including the following: + +- Fix SHR_ASSERT so single-point matrix test passes +- ne3np4 to namelist_defaults_ctsm.xml and Makefile for PTS mode and add ability +- Fixes warm starts in PTS_MODE so that SCAM can use restart files +- f19 + f45 16pft fsurdat/landuse files to namelist_defaults_ctsm + Makefile +- Changes in the FORTRAN code to properly abort when fire-emission is asked for + it can't be provided. Added unit testing for this. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + + Fixes #2868 -- Custom crop calendar instructions + Fixes #2791 -- f19 16 pft for PPE work + Fixes #2768 -- ne3np4 added in + Fixes #2780 -- CN matrix single point + Fixes #2762 -- Don't allow FATES, SP, or nofire to turn fire-emis on + Fixes #3073 -- Wrong order for testmods with nofireemis + Some of #2810 -- 16pft f45 landuse.timeseries for FATES + CTSM namelist checking for: + https://github.com/NGEET/fates/issues/1356 -- FatesSp and FATES ST3 on at same time + Some work on https://github.com/ESCOMP/CTSM/issues/2643 -- standarize logical settings for FATES + +Notes of particular relevance for users +--------------------------------------- +Caveats for users (e.g., need to interpolate initial conditions): + Turning on fire-emissions can NOT be turned on now for configurations that + don't allow it. + - FATES + - Sp + - Bgc but with nofire + + Also now CTSM fire-emiss, drydep and MEGAN are turned off for CTSM when coupled to CAM + This means that CAM will be the one that sets each of these + They can be added and turned on for CTSM I compsets though + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + FATES-SP and FATES-ST3 can't be both on at the same time + +Changes made to namelist defaults (e.g., changed parameter values): + Changes to some of the finidat file settings for 2013 at ne0np4CONUS.ne30x8 + +Changes to the datasets (e.g., parameter, surface or initial files): + New fsurdat/flanduse_timeseries for ne3np4 needed for SCAM + +Notes of particular relevance for developers: +--------------------------------------------- +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + Path for mizuroute changed to lowercase so that it matches CESM checkouts + +Changes to tests or testing: + Many tests now need to explicitly set nofireemis. Some testmods that assume + Sp include that explicitly. Other tests also include --clm-nofireemis + PTS_MODE testing changed from f45 to ne3np4 for SCAM + Add more tests for CAM VR grids + Add more namelist testing for ne3np4 and for fire-emission options and + FatesSp and FATES ST3 mode fails correctly + + +Testing summary: regular, ctsm_sci +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - Fails, but ctsm5.3.058 does as well + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + any other testing (give details below): + + ctsm_sci + derecho ---- OK + cesm_testing: + SMS_D_Ln9.f19_f19_mg17.FWma2000climo.derecho_intel.cam-outfrq9s_waccm_ma_mam4 + SMS_D_Ln9.ne0ARCTICne30x4_ne0ARCTICne30x4_mt12.FHIST.derecho_intel.cam-outfrq9s + SMS_D_Ln9_P1280x1.ne0CONUSne30x8_ne0CONUSne30x8_mt12.FCHIST.derecho_intel.cam-outfrq9s + SMS_D_Ln9_P1280x1.ne0CONUSne30x8_ne0CONUSne30x8_mt12.FCnudged.derecho_intel.cam-outfrq9s + SMS_D_Ln9_P5120x1.ne0ARCTICne30x4_ne0ARCTICne30x4_mt12.FHIST.derecho_intel.cam-outfrq9s + +Answer changes +-------------- + +Changes answers relative to baseline: no-bit-for-bit + Some tests that allowed fire-emissions to be on, have a different field list now + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + Change name of components/mizuroute directory so the same as used in CESM + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + #2840 -- Fix single point matrixcn fails + #2835 -- n3np4 + warm start fixes for PTS_MODE + #2834 -- f19 + f45 16 pft datasets + #2844 -- Fortran code abort when fire-emission asked for and can't be provided + +=============================================================== +=============================================================== +Tag name: ctsm5.3.058 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Mon Jun 16 11:43:52 MDT 2025 +One-line Summary: Fix clm6 compset aliases + +Purpose and description of changes +---------------------------------- + +The following clm60 compset aliases were actually returning long names with CLM50 physics: + +``` + ISSP245Clm60BgcCropCrujra : SSP245_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV + ISSP370Clm60BgcCropCrujra : SSP370_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV + ISSP585Clm60BgcCropCrujra : SSP585_DATM%CRUJRA2024_CLM50%BGC-CROP_SICE_SOCN_MOSART_SGLC_SWAV +``` + +This tag fixes them. It also adds a GitHub workflow to prevent this from happening again, at least for clm6. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[X] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3244: ISSP CLM60 Crujra compset aliases have longnames that use CLM50, not CLM60](https://github.com/ESCOMP/CTSM/issues/3244) +- [Issue #3254: aux_clm has no Clm60 ISSP tests](https://github.com/ESCOMP/CTSM/issues/3254) + + +Notes of particular relevance for developers: +--------------------------------------------- + +Changes to tests or testing: +- Three ISSP tests in aux_clm changed from Clm50 to their Clm60 equivalents. +- Other ISSP Clm50 tests are untouched. +- Adds a GitHub workflow to check that Clm6 compset aliases return CLM6 longnames. + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Answer changes +-------------- + +Changes answers relative to baseline: + + [ If a tag changes answers relative to baseline comparison the + following should be filled in (otherwise remove this section). + And always remove these three lines and parts that don't apply. ] + + Summarize any changes to answers, i.e., + - what code configurations: ISSP245, ISSP370, and ISSP585 compsets + - what platforms/compilers: All + - nature of change (roundoff; larger than roundoff/same climate; new climate): + Larger than roundoff/same climate, since it only affects land-only (I) cases. + + Specifically, the following compsets now actually receive Clm60 physics instead of Clm50: + - ISSP245Clm60BgcCropCrujra + - ISSP370Clm60BgcCropCrujra + - ISSP585Clm60BgcCropCrujra + + +Other details +------------- + +Pull Requests that document the changes: +- [Pull Request #3248: ctsm5.3.058: Fix clm6 compset aliases by samsrabin](https://github.com/ESCOMP/CTSM/pull/3248) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.057 +Originator(s): glemieux (Gregory Lemieux, LBNL, glemieux@lbl.gov) +Date: Fri Jun 13 17:00:00 MDT 2025 +One-line Summary: Fix PEM test for on-the-fly parameter file generation + +Purpose and description of changes +---------------------------------- + +This resolves an issue in which PEM tests that are used in conjunction with +testmods that build the FATES parameter file on-the-fly might result in RUN +failure due to the file not being generated for the second case. This +addresses the issue by simply by setting the fates_paramfile namelist setting +to the full path for the primary test case directory. + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves #3097 Missing on-the-fly built fates parameter file for test types that have a "case2" subdirectory + +Notes of particular relevance for developers: +--------------------------------------------- +Changes to tests or testing: + PEM_D_Ld20.5x5_amazon.I2000Clm50FatesRs.derecho_gnu.clm-FatesColdSeedDisp is now added to the aux_clm test suite + Note that this test fails as expected on the COMPARE_mod_pes step + +Testing summary: +---------------- + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +Answer changes +-------------- + +Changes answers relative to baseline: No + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) +[ctsm5.3.057: Fix PEM test for FATES testmod that builds an on-the-fly parameter file](https://github.com/ESCOMP/CTSM/pull/3243) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.056 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Thu 12 Jun 2025 01:43:46 PM MDT +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Several updates to documentation, from the documentation hackathon. +Also remove /glade from paths for mksurfdata_esmf paths + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Resolves Improve headings in single-point docs #3008 + Resolves Docs needed: Supported towers #3175 + Resolves Docs needed: NEON #3167 + Documentation part of run_neon base case must be of same run type as a requested clone #1926 + Resolves run_tower documentation needed #2997 + Resolves Various subset_data and related docs needed #3000 + Resolves Docs docs: Inline code rendered as italics #3164 + Resolves Fix some missing equation references in Snow Hydrology chapter of Tech Note #3202 + Resolves Fix Equation number label 2.5.119 in technical note #3196 + Resolves Remove hardcoded /glade/campaign/cesm/cesmdata/inputdata/ paths to support --rawdata-dir flexibility #3031 + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: Yes! + +Notes of particular relevance for developers: None +--------------------------------------------- + +Testing summary: regular +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - I ran it and there were fails but previous versions had this as well + One problem was with the NEON server, so outside our control + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-for-bit + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + + Merge b4b-dev to master #3242 + Updates to run_tower/single point documentation #3194 + fixed italics to be in-line code #3198 + Fix some equation references in the Snow Hydrology chapter of the Tech Note #3203 + Fix typo in Chapter 5 of technical note #3195 + Remove hardcoded paths in gen_mksurfdata_namelist.xml #3162 + Merge ctsm5.3.050 to b4b-dev #3200 + +=============================================================== +=============================================================== +Tag name: ctsm5.3.055 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Thu Jun 5 13:59:20 MDT 2025 +One-line Summary: Remove FTorch + +Purpose and description of changes +---------------------------------- + +Incorrect CMEPS version causes a build failure. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3210: build-and-deploy failure at ctsm5.3.053](https://github.com/ESCOMP/CTSM/issues/3210) +- [Issue #3214: Build fails with FTorch checked out](https://github.com/ESCOMP/CTSM/issues/3214) + + +Testing summary: +---------------- + +Model now builds successfully on Izumi with all optional submodules checked out. + + +Other details +------------- +[Remove any lines that don't apply. Remove entire section if nothing applies.] + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): +- FTorch removed + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3211: ctsm5.3.055: Remove broken FTorch submodule by samsrabin](https://github.com/ESCOMP/CTSM/pull/3211) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.054 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Mon Jun 2 11:39:50 MDT 2025 +One-line Summary: CDEPS: Allow anomaly forcings with any DATM + +Purpose and description of changes +---------------------------------- + +Updates CDEPS to version cdeps1.0.75, allowing anomaly forcings with any datm_mode. CRU-JRA thus now works with anomalies. + +Also brings in cdeps1.0.74: "In DATM with cplhist mode, make 1st timestep same as cam7 when using the cam7 option." + + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3209: [CDEPS] Allow anomaly forcings with any datm](https://github.com/ESCOMP/CTSM/issues/3209) + + +Testing summary: +---------------- + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + ctsm_sci (just three tests that were failing before) + derecho ---- PASS + + +Answer changes +-------------- + +Changes answers relative to baseline: Yes (although not in tests) + + Summarize any changes to answers, i.e., + - what code configurations: CPLHIST + - what platforms/compilers: All + - nature of change (roundoff; larger than roundoff/same climate; new climate): Roundoff + + Only affects first timestep under lnd_tuning_mode clm6_0_cam7.0. + + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): +- cdeps updated from cdeps1.0.73 to cdeps1.0.75 + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3212: ctsm5.3.054: CDEPS: Allow anomaly forcings with any DATM by samsrabin](https://github.com/ESCOMP/CTSM/pull/3212) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.053 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Fri May 30 14:56:48 MDT 2025 +One-line Summary: Fix and improve anomaly forcings for ISSP cases + +Purpose and description of changes +---------------------------------- + +Fixes a bug where all SSP cases would get RCP45 anomalies. Also makes it much simpler to run land-only SSP cases. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[X] clm6_0 + +[X] clm5_0 + +[X] ctsm5_0-nwp + +[X] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- Fixes [Issue #2301: SSP cases get rcp45 anomalies](https://github.com/ESCOMP/CTSM/issues/2301) +- Resolves [Issue #922: Turn on anomaly forcing for ISSP compsets](https://github.com/ESCOMP/CTSM/issues/922) +- Resolves [Issue #1730: Example, user_nl_datm_streams file for anomaly forcing for NUOPC coupler](https://github.com/ESCOMP/CTSM/issues/1730) + + +Notes of particular relevance for users +--------------------------------------- + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): +- Greatly simplifies the setup of ISSP cases where you want to use the CESM2 anomaly forcings. See updated documentation in User's Guide + +Changes to documentation: +- User's Guide section on running with anomaly forcings + + +Testing summary: +---------------- + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + any other testing (give details below): + ssp test suite ---- PASS + +Answer changes +-------------- + +Changes answers relative to baseline: + + Summarize any changes to answers, i.e., + - what code configurations: ISSP cases other than ssp245 with anomaly forcings, if the user didn't manually specify all the anomaly forcing files + - what platforms/compilers: all + - nature of change (roundoff; larger than roundoff/same climate; new climate): larger than roundoff/same climate + + If this tag changes climate describe the run(s) done to evaluate the new + climate (put details of the simulations in the experiment database) + - n/a: This only affects ISSP cases + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- [Pull Request #2686: ctsm5.3.053: Fix and improve anomaly forcings for ISSP cases by samsrabin](https://github.com/ESCOMP/CTSM/pull/2686) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.052 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Fri 30 May 2025 12:44:17 AM MDT +One-line Summary: Changes to MEGAN needed for coupled cases + +Purpose and description of changes +---------------------------------- + +Corrections on how MEGAN coefficients are applied. + +Depends on the cmeps tag cmeps1.1.1 + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixes #3016 -- low MEGAN emissions + +Notes of particular relevance for users +--------------------------------------- + +Notes of particular relevance for developers: +--------------------------------------------- + +Testing summary: regular +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + +Answer changes +-------------- + +Changes answers relative to baseline: Just for MEGAN + + Summarize any changes to answers, i.e., + - what code configurations: when MEGAN turned on + - what platforms/compilers: all + - nature of change: + change to climate in MEGAN results so that low emissions are improved + Just for VOC field 015 in the coupler + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + cmeps to cmeps1.0.48 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + #3065 -- MEGAN coefs fix, answer changes for MEGAN + +=============================================================== +=============================================================== +Tag name: ctsm5.3.051 +Originator(s): erik (Erik Kluzek,UCAR/TSS,303-497-1326) +Date: Fri 30 May 2025 12:19:19 AM MDT +One-line Summary: Update submodules to cesm3_0_beta06 versions and update MEGAN/drydep test namelist + +Purpose and description of changes +---------------------------------- + +Update submodules to versions from cesm3_0_beta06 including some needed updates in cime and ccs_config. +Also update the MEGAN and drydep test namelists so they are testing what's used in coupled simualtions. + +Add FTorch as an optional library that can be checked out with git-fleximod. + +This also brings in updates on derecho_intel so that intel-oneapi is used, which changes answers. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + + Fixes #2710 MEGAN/drydep test namelists + Fixes #2476 Update to intel-oneapi + Fixes #3135 FTorch + Fixes #3108 derecho_gnu mpi-serial tests + Address some things in #3156 General testing changes to make + +Notes of particular relevance for users +--------------------------------------- + +Caveats for users (e.g., need to interpolate initial conditions): + Single point cases may fail due to lack of memory -- especially FATES cases + See: https://github.com/ESCOMP/CTSM/issues/3181 + +Notes of particular relevance for developers: +--------------------------------------------- + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + +Changes to tests or testing: + Change some of the tests for CESM: prealpha, prebeta, and aux_cime_baseliens + Increase memory per task for FATES tests so will work for single point cases + +Testing summary: regular, fates, ctsm_sci +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS (but just before tagging I saw problems with this and previous tags) + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + fates tests: fates-sci.1.84.0_api.40.0.0-ctsm5.3.050 + + derecho ----- OK + izumi ------- OK + + any other testing (give details below): + + ctsm_sci + derecho ---- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: Yes, MEGAN/drydep namelist different, derecho_intel/dercho_nvhpc change answers + + Summarize any changes to answers, i.e., + - what code configurations: When MEGAN/drydep test namelist turned on, also future scenario SSP cases + - what platforms/compilers: derecho_intel or derecho_nvhpc + - nature of change: + compiler changes should be roundoff + MEGAN/drydep and SSP cases will be greater than that + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): + +cism to cismwrap_2_2_006 + +fxtag = ccs_config_cesm1.0.23 +ccs_config to ccs_config_cesm1.0.43 + +CIME to cime6.1.102 + +CMEPS to cmeps1.0.47 + +CDEPS to cdeps1.0.73 + +PIO to pio2_6_4 + +MPISerial to MPIserial_2.5.4 + + FTorch to v0.0.5 + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + https://github.com/ESCOMP/CTSM/pull/3111 -- bring in the answer changing part for derecho_intel + https://github.com/ESCOMP/CTSM/pull/3159 -- update to cesm3_0_alpha06f externals + https://github.com/ESCOMP/CTSM/pull/3125 -- update to cesm3_0_alpha06g externals and needed fixes + +=============================================================== +=============================================================== +Tag name: ctsm5.3.050 +Originator(s): samrabin (Sam Rabin) +Date: Thu May 29 11:26:21 MDT 2025 +One-line Summary: Fix Linux Podman; prefer Linux Docker; update docs docs. + +Purpose and description of changes +---------------------------------- + +- Fixes Podman builds on Ubuntu (mostly; see ESMCI/doc-builder#27) +- Docker now preferred on non-Mac systems +- Improves docs docs + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3163: Docs docs: Extraneous variable in a command](https://github.com/ESCOMP/CTSM/issues/3163) + +Notes of particular relevance for users +--------------------------------------- + +User's Guide section on docs updated with new instructions. + + +Notes of particular relevance for developers: +--------------------------------------------- + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): +- If you're running doc/testing.sh locally, you'll need both Docker and Podman installed. + + +Testing summary: +---------------- + +All required testing happened in GitHub workflows on the PR. + + +Other details +------------- + +List any git submodules updated: doc-builder + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3184: ctsm5.3.050: Doc infrastructure and docs: fixes and improvements by samsrabin](https://github.com/ESCOMP/CTSM/pull/3184) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.049 +Originator(s): samrabin (Sam Rabin) +Date: Tue May 27 12:49:00 MDT 2025 +One-line Summary: Switch docs to use Podman + +Purpose and description of changes +---------------------------------- + +Updates doc-builder to a version that prefers to use podman instead of docker. Also updates documentation to instruct docs-writers to use Podman instead of Docker, along with some other improvements. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- +[Remove any lines that don't apply. Remove entire section if nothing applies.] + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + +Notes of particular relevance for users +--------------------------------------- + +User's Guide section on docs updated with new instructions. + + +Testing summary: +---------------- + +All required testing happened in GitHub workflows on the PR. + + +Other details +------------- + +List any git submodules updated: doc-builder + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3153: ctsm5.3.049: Preferentially use Podman for ctsm-docs container by samsrabin](https://github.com/ESCOMP/CTSM/pull/3153) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.048 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Mon May 26 18:30:59 MDT 2025 +One-line Summary: Automatically publish docs to this repo + +Purpose and description of changes +---------------------------------- + +Enables automatic publication of documentation to this repo's GitHub Pages, making ctsm-docs repo obsolete. Also updates documentation documentation. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #2541: Some links in User's Guide sidebar are broken](https://github.com/ESCOMP/CTSM/issues/2541) +- [Issue #2839: Automatically publish updated docs](https://github.com/ESCOMP/CTSM/issues/2839) +- [Issue #3121: Fix capital letter in doc filenames](https://github.com/ESCOMP/CTSM/issues/3121) + +Notes of particular relevance for users +--------------------------------------- + +Changes to documentation: +- Canonical URL for User's Guide and Tech Note will be changing from https://escomp.github.io/ctsm-docs to https://escomp.github.io/ctsm. +- Updates to "Working with the CTSM documentation" section + + +Notes of particular relevance for developers: +--------------------------------------------- + +We're going to get rid of the ctsm-docs repo! + + +Testing summary: +---------------- + +None, as no code is changing aside from the documentation infrastructure, which is tested in GitHub Workflows. + +Other details +------------- + +List any git submodules updated (cime, rtm, mosart, cism, fates, etc.): +- doc-builder + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3146: ctsm5.3.048: Automatically publish docs to this repo by samsrabin](https://github.com/ESCOMP/CTSM/pull/3146/files) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.047 +Originator(s): samrabin (Sam Rabin, UCAR/TSS) +Date: Mon May 26 16:30:10 MDT 2025 +One-line Summary: Merge b4b-dev to master + +Purpose and description of changes +---------------------------------- + +Merge b4b-dev to master. Includes PRs: +- [Pull Request #3056: QOL improvements in mksurfdata_esmf makefile by samsrabin](https://github.com/ESCOMP/CTSM/pull/3056) +- [Pull Request #3100: Fix Longitude comparison error for regional subset_data by samsrabin](https://github.com/ESCOMP/CTSM/pull/3100) +- [Pull Request #3122: Fix a couple of things in documentation conf.py by samsrabin](https://github.com/ESCOMP/CTSM/pull/3122) +- [Pull Request #3128: Fix ctsm-docs container version tagging by samsrabin](https://github.com/ESCOMP/CTSM/pull/3128) +- [Pull Request #3138: Don't build docs for ChangeLog/Sum by samsrabin](https://github.com/ESCOMP/CTSM/pull/3138) +- [Pull Request #3143: Don't run workflows on plain tag creation by samsrabin](https://github.com/ESCOMP/CTSM/pull/3143) +- [Pull Request #3144: Add docs issue template by samsrabin](https://github.com/ESCOMP/CTSM/pull/3144) + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description): +- [Issue #3093: subset_data: Longitude comparison error](https://github.com/ESCOMP/CTSM/issues/3093) +- [Issue #3126: Increment docs container version](https://github.com/ESCOMP/CTSM/issues/3126) +- [Issue #3142: Workflows run on new tags even w/o file changes](https://github.com/ESCOMP/CTSM/issues/3142) + + +Testing summary: +---------------- + + + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - PASS + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +- [Pull Request #3154: Merge b4b-dev 2025-05-24 by samsrabin](https://github.com/ESCOMP/CTSM/pull/3154) + +=============================================================== +=============================================================== +Tag name: ctsm5.3.046 +Originator(s): rgknox (Ryan Knox) +Date: Mon 26 May 2025 02:45:52 AM MDT +One-line Summary: For FATES set itype to ispval and a few unused variables to nan to help prevent future problems + +Purpose and description of changes +---------------------------------- + + +On FATES patches, there should be no notion of pft associated with the patch, and therefore patch%itype should always be invalid. +Even for FATES-SP, the patch should be associated with the FATES pft, which is not associated with itype. In this set of changes, +itype is set to spval on fates patches. This will help prevent bugs in the future, because its use in a fates context should trigger +errors, particularly in debug mode. + +When FATES with itype tset o ispval which when used in DEBUG mode will show up as a bounds overflow. +For example in issue 2932, where the use of Meier 2022 was turned on in FATES and shouldn't have been. + +For FATES also set the fields downreg, leafn, froot, and croot t NaN, so when used in DEBUG mode their use will result in a floting +point exception. + + +Significant changes to scientifically-supported configurations +-------------------------------------------------------------- + +Does this tag change answers significantly for any of the following physics configurations? +(Details of any changes will be given in the "Answer changes" section below.) + + [Put an [X] in the box for any configuration with significant answer changes.] + +[ ] clm6_0 + +[ ] clm5_0 + +[ ] ctsm5_0-nwp + +[ ] clm4_5 + + +Bugs fixed +---------- + +List of CTSM issues fixed (include CTSM Issue # and description) [one per line]: + Fixed #2933 -- setting patch%itype to invalid for FATES patches + +Notes of particular relevance for users +--------------------------------------- + +Changes to CTSM's user interface (e.g., new/renamed XML or namelist variables): + Dissallow drydep or MEGAN for all FATES types including FatesSp and die with + a fatal_error, rather than a warning + +Notes of particular relevance for developers: +--------------------------------------------- + +Caveats for developers (e.g., code that is duplicated that requires double maintenance): + Unfrtunately setting itype doesn't fix all possible problems with + See discussin #2149 + +Changes to tests or testing: + Disable FatesSp drydep and MEGAN tests + +Testing summary: regular fates +---------------- + [PASS means all tests PASS; OK means tests PASS other than expected fails.] + + build-namelist tests (if CLMBuildNamelist.pm has changed): + + derecho - OK + + python testing (if python code has changed; see instructions in python/README.md; document testing done): + + derecho - PASS + + regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing): + + derecho ----- OK + izumi ------- OK + +If the tag used for baseline comparisons was NOT the previous tag, note that here: + + +Answer changes +-------------- + +Changes answers relative to baseline: No bit-fr-bit + +Other details +------------- + +Pull Requests that document the changes (include PR ids): +(https://github.com/ESCOMP/ctsm/pull) + https://github.com/ESCOMP/CTSM/pull/2935 + +=============================================================== +=============================================================== Tag name: ctsm5.3.045 Originator(s): glemieux (Gregory Lemieux, LBNL, glemieux@lbl.gov) Date: Tue May 20 11:56:15 MDT 2025 diff --git a/doc/ChangeSum b/doc/ChangeSum index 75660abd7b..135ae3ac13 100644 --- a/doc/ChangeSum +++ b/doc/ChangeSum @@ -1,5 +1,45 @@ Tag Who Date Summary ============================================================================================================================ + ctsm5.3.085 slevis 11/14/2025 Merge b4b-dev to master + ctsm5.3.084 erik 10/31/2025 Merge b4b-dev to master + ctsm5.3.083 rgknox 10/29/2025 Changes to coupling of supplementation status with FATES. + ctsm5.3.082 slevis 10/24/2025 Update to CMIP7 population density file for non-SSP cases + ctsm5.3.081 erik 10/22/2025 Change defaults for when Carbon isotopes are turned on, and turn on irrigate for Sp/Bgc cases for clm6_0 historical transient cases + ctsm5.3.080 samrabin 10/16/2025 Merge b4b-dev to master + ctsm5.3.079 samrabin 10/06/2025 Update submodules to match versions in cesm3_0_alpha07e + ctsm5.3.078 slevis 10/03/2025 Merge b4b-dev to master + ctsm5.3.077 rgknox 10/02/2025 Adjustment of timestep albedo filtering with FATES + ctsm5.3.076 erik 09/24/2025 Merge b4b-dev to master + ctsm5.3.075 slevis 09/10/2025 Change default glcmec_downscale_longwave from true to false for clm6 + ctsm5.3.074 slevis 09/09/2025 Update ctsm6 default paramfile and finidat files + ctsm5.3.073 slevis 08/28/2025 Update .gitmodules to cesm3_0_alpha07c + ctsm5.3.072 jinmuluo 08/26/2025 New vertical movement scheme for soil nitrate + ctsm5.3.071 samrabin 08/22/2025 Merge b4b-dev to master + ctsm5.3.070 glemieux 08/22/2025 Update default FATES parameter file and add FATES managed fire namelist option + ctsm5.3.069 samrabin 08/12/2025 Add SystemTests to run subset_data and then CTSM + ctsm5.3.068 slevis 08/11/2025 Change megan_use_gamma_sm to default false + ctsm5.3.067 samrabin 08/08/2025 (Mostly) fix interim restarts + ctsm5.3.066 slevis 08/08/2025 Merge b4b-dev to master + ctsm5.3.065 erik 07/28/2025 Merge b4b-dev to master + ctsm5.3.064 slevis 07/24/2025 Add time dimension to 1d_wt fields in transient runs + ctsm5.3.063 samrabin 07/10/2025 Merge b4b-dev to master + ctsm5.3.062 slevis 07/09/2025 Put inst. and non-inst. fields on separate hist files + ctsm5.3.061 slevis 06/26/2025 Merge b4b-dev to master + ctsm5.3.060 slevis 06/24/2025 Preliminary update of ctsm54 defaults (answer changing) + ctsm5.3.059 erik 06/23/2025 Bring in various cleanup efforts found in previous testing after the chill changes came in + ctsm5.3.058 samrabin 06/16/2025 Fix clm6 compset aliases + ctsm5.3.057 glemieux 06/13/2025 Fix PEM test for on-the-fly parameter file generation + ctsm5.3.056 erik 06/12/2025 Merge b4b-dev to master + ctsm5.3.055 samrabin 06/05/2025 Remove FTorch + ctsm5.3.054 samrabin 06/02/2025 CDEPS: Allow anomaly forcings with any DATM + ctsm5.3.053 samrabin 05/30/2025 Fix and improve anomaly forcings for ISSP cases + ctsm5.3.052 erik 05/30/2025 Changes to MEGAN needed for coupled cases + ctsm5.3.051 erik 05/28/2025 Update submodules to cesm3_0_beta06 versions and update MEGAN/drydep test namelist + ctsm5.3.050 samrabin 05/29/2025 Fix Linux Podman; prefer Linux Docker; update docs docs. + ctsm5.3.049 samrabin 05/27/2025 Switch docs to use Podman + ctsm5.3.048 samrabin 05/26/2025 Automatically publish docs to this repo + ctsm5.3.047 samrabin 05/26/2025 Merge b4b-dev to master + ctsm5.3.046 rgknox 05/26/2025 For FATES set itype to ispval and a few unused variables to nan to help prevent future problems ctsm5.3.045 glemieux 05/20/2025 FATES default parameter update for API 40 ctsm5.3.044 slevis 05/14/2025 Introduce time-evolving LEAFCN_TARGET as function of leafcn param ctsm5.3.043 slevis 05/09/2025 Merge b4b-dev diff --git a/doc/Makefile b/doc/Makefile index 7e74b6e545..d24e5e31aa 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -6,11 +6,12 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = clmdoc SOURCEDIR = source +DIRWITHCONFPY = doc-builder BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" -c "$(DIRWITHCONFPY)" $(SPHINXOPTS) $(O) # 'make fetch-images' should be run before building the documentation. (If building via # the build_docs command, this is run automatically for you.) This is needed because we @@ -33,9 +34,9 @@ fetch-images: # # The use of $(0) is as in the catch-all target. latexpdf: - $(SPHINXBUILD) -M $@ "$(SOURCEDIR)/tech_note" "$(BUILDDIR)" -c "$(SOURCEDIR)" -D numfig_secnum_depth=1 $(SPHINXOPTS) $(O) + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)/tech_note" "$(BUILDDIR)" -c "$(DIRWITHCONFPY)" -D numfig_secnum_depth=1 $(SPHINXOPTS) $(O) # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" -c "$(DIRWITHCONFPY)" $(SPHINXOPTS) $(O) diff --git a/doc/README.CHECKLIST.master_tags b/doc/README.CHECKLIST.master_tags index 51386c4238..5e6d44ad14 100644 --- a/doc/README.CHECKLIST.master_tags +++ b/doc/README.CHECKLIST.master_tags @@ -86,6 +86,7 @@ if you did step 7 above using git commands that require this step) ---- NOTES ---- -(3) -- Always test on your fork with a feature-branch so that we can change tag order if needed. Put +(3a) -- When izumi’s baseline is ready, manually open read permissions to all. +(3b) -- Always test on your fork with a feature-branch so that we can change tag order if needed. Put baselines in the next tag name, as we can easily change afterwards if needed. diff --git a/doc/__init__.py b/doc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/build_docs_to_publish b/doc/build_docs_to_publish new file mode 100755 index 0000000000..6804311c64 --- /dev/null +++ b/doc/build_docs_to_publish @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -e + +script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +if [ ! -f doc-builder/build_docs_to_publish ]; then + "${script_dir}"/../bin/git-fleximod update doc-builder +fi + +cd "${script_dir}" + +echo "Running: make fetch-images" +make fetch-images + +echo "Running: ./doc-builder/build_docs_to_publish $@" +pwd +./doc-builder/build_docs_to_publish "$@" + +exit 0 \ No newline at end of file diff --git a/doc/ctsm-docs_container/Dockerfile b/doc/ctsm-docs_container/Dockerfile index 90e3a6160e..3a24e1d4a4 100644 --- a/doc/ctsm-docs_container/Dockerfile +++ b/doc/ctsm-docs_container/Dockerfile @@ -28,4 +28,5 @@ WORKDIR /home/user CMD ["/bin/bash", "-l"] LABEL org.opencontainers.image.title="Container for building CTSM documentation" -LABEL org.opencontainers.image.source=https://github.com/ESCOMP/CTSM \ No newline at end of file +LABEL org.opencontainers.image.source=https://github.com/ESCOMP/CTSM +LABEL org.opencontainers.image.version="v1.0.2e" diff --git a/doc/ctsm-docs_container/README.md b/doc/ctsm-docs_container/README.md index bdd4eb3fa7..bff10aada2 100644 --- a/doc/ctsm-docs_container/README.md +++ b/doc/ctsm-docs_container/README.md @@ -1,55 +1,87 @@ # The ctsm-docs container -This directory and its Dockerfile are used to build a Docker container for building the CTSM documentation. Unless you're a developer working on the container, you probably don't need to care about anything in here. +This directory and its Dockerfile are used to build a container for building the CTSM documentation. Unless you're a developer working on the container, you probably don't need to care about anything in here. ## Introduction -This Readme tells you how to update the ctsm-docs Docker container if a need to do so arises—for example, adding a Python module that brings new functionality in the build. After you've followed all these instructions, you will probably want to push an update to [doc-builder](https://github.com/ESMCI/doc-builder) that updates `DEFAULT_DOCKER_IMAGE` in [build_commands.py](https://github.com/ESMCI/doc-builder/blob/master/doc_builder/build_commands.py) to point to the new tag. +This Readme tells you how to update the ctsm-docs container if a need to do so arises—for example, adding a Python module that brings new functionality in the build. After you've followed all these instructions, you will probably want to push an update to [doc-builder](https://github.com/ESMCI/doc-builder) that updates `DEFAULT_IMAGE` in [build_commands.py](https://github.com/ESMCI/doc-builder/blob/master/doc_builder/build_commands.py) to point to the new tag. ## Building -If you actually want to build the container, make sure Docker is running. In the Docker Desktop settings, make sure you've enabled the [`continerd` image store](https://docs.docker.com/desktop/features/containerd/), which allows multi-platform builds. Then do: +If you actually want to build the container, you will need to have Podman installed. We previously used Docker for this, but had to move away from it due to licensing issues. Note that these issues have to do specifically with Docker Desktop, which is required to get the Docker Engine on some platforms. The Docker Engine itself is open-source, so our ctsm-docs container testing and publishing workflows are fine to continue using it. We are also fine to use the Docker Engine via Podman for local builds, which is what's described here. + +Once you have Podman installed, you can do: ```shell -docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/escomp/ctsm/ctsm-docs . +podman build --no-cache -t ctsm-docs . ``` -To use your new version for local testing, you'll need to tell doc-builder to use that image. Call `docker images`, which should return something like this: +To use your new version for local testing, you'll need to tell doc-builder to use that image. Call `podman images`, which should return something like this: ```shell -REPOSITORY TAG IMAGE ID CREATED SIZE -ghcr.io/escomp/ctsm/ctsm-docs latest ab51446519a4 3 seconds ago 233MB +REPOSITORY TAG IMAGE ID CREATED SIZE +localhost/ctsm-docs latest 6464f26339bc 22 seconds ago 241 MB ... ``` -To test, you can tell `build_docs` to use your new version by adding `--docker-image IMAGE_ID` to your call, where in the example above `IMAGE_ID` is `ab51446519a4`. +To test, you can tell `build_docs` to use your new version by adding `--container-image IMAGE_ID` to your call, where in the example above `IMAGE_ID` is `6464f26339bc`. + +## Publishing automatically + +The `docker-image-build-publish.yml` workflow makes it so that new versions of the workflow will be published to the GitHub Container Registry whenever changes to the container setup are merged to CTSM's `master` branch. This will fail (as will a similar, no-publish workflow that happens on PRs) unless you specify exactly one new version number in the Dockerfile. This version number will be used as a tag that can be referenced by, e.g., doc-builder. + +Lots of container instructions tell you to use the `latest` tag, and indeed the workflow will add that tag automatically. However, actually _using_ `latest` can lead to support headaches as users think they have the right version but actually don't. Instead, you'll make a new version number incremented from the [previous one](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs/versions), in the `vX.Y.Z` format. + +Here's where you need to specify the version number in the Dockerfile: +```docker +LABEL org.opencontainers.image.version="vX.Y.Z" +``` +The string there can technically be anything as long as (a) it starts with a lowercase `v` and (b) it hasn't yet been used on a published version of the container. + +You can check the results of the automatic publication on the [container's GitHub page](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs). + +### Updating doc-builder +After the new version of the container is published, you will probably want to tell [doc-builder](https://github.com/ESMCI/doc-builder) to use the new one. Open a PR where you change the tag (the part after the colon) in the definition of `DEFAULT_IMAGE` in `doc_builder/build_commands.py`. Remember, **use the version number**, not "latest". + +## Publishing manually (NOT recommended) + +It's vastly preferable to let GitHub build and publish the new repo using the `docker-image-build-publish.yml` workflow as described above. However, if you need to publish manually for some reason, here's how. + +### Building the multi-architecture version -## Publishing +When publishing our container, we need to make sure it can run on either arm64 or amd64 processor architecture. This requires a special build process: +```shell +podman manifest create ctsm-docs-manifest +podman build --platform linux/amd64,linux/arm64 --manifest ctsm-docs-manifest . +``` ### Pushing to GitHub Container Registry If you want to publish the container, you first need a [GitHub Personal Access Token (Classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-tokens-classic) with the `write:packages` permissions. You can see your existing PAT(C)s [here](https://github.com/settings/tokens). If you don't have one with the right permissions, [this link](https://github.com/settings/tokens/new?scopes=write:packages) should start the setup process for you. Once you have a PAT(C), you can authenticate in your shell session like so: -```shell - echo YOUR_PERSONAL_ACCESS_TOKEN_CLASSIC | docker login ghcr.io -u YOUR_USERNAME --password-stdin +```bash +# bash: Make it so that, in this session, commands with a leading space are not saved to terminal history +export HISTCONTROL=ignoreboth + +# Include leading spaces so that your secret PAT(C) isn't included in terminal history + echo YOUR_PERSONAL_ACCESS_TOKEN_CLASSIC | podman login ghcr.io -u YOUR_USERNAME --password-stdin ``` -The leading spaces are intended to prevent this command, which contains your secret PAT(C), from being written to your shell's history file. That at least works in bash... sometimes. To be extra safe, in bash you can do `history -c` and it will clear the session's history entirely. ### Tagging -You'll next need to tag the image. Lots of Docker instructions tell you to use the `latest` tag, and Docker may actually do that for you. However, `latest` can lead to support headaches as users think they have the right version but actually don't. Instead, you'll make a new version number incremented from the [previous one](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs/versions), in the `vX.Y.Z` format. +You'll next need to tag the image. Lots of container instructions tell you to use the `latest` tag, and Podman may actually add that for you. However, `latest` can lead to support headaches as users think they have the right version but actually don't. Instead, you'll make a new version number incremented from the [previous one](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs/versions), in the `vX.Y.Z` format. -Copy the relevant image ID (see `docker images` instructions above) and tag it with your version number like so: +Copy the relevant image ID (see `podman images` instructions above) and tag it with your version number like so: ```shell -docker tag ab51446519a4 ghcr.io/escomp/ctsm/ctsm-docs:vX.Y.Z +podman tag 6464f26339bc ghcr.io/escomp/ctsm/ctsm-docs:vX.Y.Z ``` Push to the repo: ```shell -docker push ghcr.io/escomp/ctsm/ctsm-docs:vX.Y.Z +podman manifest push --all ctsm-docs-manifest ghcr.io/escomp/ctsm/ctsm-docs:vX.Y.Z ``` Then browse to the [container's GitHub page](https://github.com/ESCOMP/CTSM/pkgs/container/ctsm%2Fctsm-docs) to make sure this all worked and the image is public. ### Updating doc-builder -Since you've updated the container, you will probably want to tell [doc-builder](https://github.com/ESMCI/doc-builder) to use the new one. Open a PR where you change the tag (the part after the colon) in the definition of `DEFAULT_DOCKER_IMAGE` in `doc_builder/build_commands.py`. Remember, **use the version number**, not "latest". +See "Updating doc-builder" in the "Publishing automatically" section above. ## See also diff --git a/doc/ctsm-docs_container/get_version.sh b/doc/ctsm-docs_container/get_version.sh new file mode 100755 index 0000000000..485e720eff --- /dev/null +++ b/doc/ctsm-docs_container/get_version.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -e +cd doc/ctsm-docs_container + +# Extract version from Dockerfile +version="$(grep "org.opencontainers.image.version" Dockerfile | cut -d'"' -f2 | sort |uniq)" +n_found=$(echo $version | wc -w) + +# Error if anything other than exactly one version tag was found +if [[ ${n_found} -gt 1 ]]; then + echo -e "Multiple version tags found:\n${version}" >&2 + exit 2 +elif [[ ${n_found} -lt 1 ]]; then + echo "Expected 1 but found 0 version tags" >&2 + exit -1 +fi + +# Error if version doesn't start with v +if [[ "${version}" != "v"* ]]; then + echo "Version '${version}' doesn't start with v" >&2 + exit 22 +fi + +echo ${version} + +exit 0 diff --git a/doc/doc-builder b/doc/doc-builder index 9a25c4a20d..3ab6d06971 160000 --- a/doc/doc-builder +++ b/doc/doc-builder @@ -1 +1 @@ -Subproject commit 9a25c4a20db152f57f334c2eb86ea5246b406674 +Subproject commit 3ab6d06971e508f2886f0079db37156ab93c2b07 diff --git a/doc/source/__init__.py b/doc/source/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/source/_static/css/custom.css b/doc/source/_static/css/custom.css deleted file mode 100644 index 10abb45722..0000000000 --- a/doc/source/_static/css/custom.css +++ /dev/null @@ -1,17 +0,0 @@ -/* Make equation numbers float to the right */ -.eqno { - margin-left: 5px; - float: right; -} -/* Hide the link... */ -.math .headerlink { - display: none; - visibility: hidden; -} -/* ...unless the equation is hovered */ -.math:hover .headerlink { - display: inline-block; - visibility: visible; - /* Place link in margin and keep equation number aligned with boundary */ - margin-right: -0.7em; -} diff --git a/doc/source/_templates/footer.html b/doc/source/_templates/footer.html deleted file mode 100644 index a7c22a302a..0000000000 --- a/doc/source/_templates/footer.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "!footer.html" %} -{% block extrafooter %} - {{ super() }} - -{% endblock %} diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index fc88b1e359..0000000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8 -*- -# -# clmdoc documentation build configuration file, created by -# sphinx-quickstart on Thu Feb 23 17:14:30 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -import sphinx_rtd_theme - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.intersphinx', - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.githubpages', - 'sphinx_mdinclude', - ] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] -# source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'ctsm' -copyright = u'2020, UCAR' -author = u'' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'CTSM1' -# The full version, including alpha/beta/rc tags. -release = u'CTSM master' -# CTSM-specific: version label used at the top of some pages. -version_label = 'the latest development code' - -# List of versions to populate version picker dropdown menu -version_list = ["latest", "release-clm5.0"] - -# version_label is not a standard sphinx variable, so we need some custom rst to allow -# pages to use it. We need a separate replacement for the bolded version because it -# doesn't work to have variable replacements within formatting. -rst_epilog = """ -.. |version_label| replace:: {version_label} -.. |version_label_bold| replace:: **{version_label}** -""".format(version_label=version_label) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'clmdocdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - 'preamble': '\\usepackage{hyperref}', - - 'fncychap': '\\usepackage[Conny]{fncychap}', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [(master_doc, 'clmdoc.tex', u'CLM5 Documentation', '', 'manual'),] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'clmdoc', u'clmdoc Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'clmdoc', u'clmdoc Documentation', - author, 'clmdoc', 'One line description of project.', - 'Miscellaneous'), -] - - - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/', None)} - -numfig = True -numfig_format = {'figure': 'Figure %s', - 'table': 'Table %s', - 'code-block': 'Code %s', - 'section': '%s', - } -numfig_secnum_depth = 2 - -def setup(app): - app.add_css_file('css/custom.css') - -try: - html_context -except NameError: - html_context = dict() - -html_context["display_lower_left"] = True - -html_context["current_language"] = language - -html_context["current_version"] = os.environ.get("current_version") - -html_context["versions"] = [] -for this_version in version_list: - html_context["versions"].append([this_version, f"../../../versions/{this_version}/html"]) diff --git a/doc/source/tech_note/External_Nitrogen_Cycle/CLM50_Tech_Note_External_Nitrogen_Cycle.rst b/doc/source/tech_note/External_Nitrogen_Cycle/CLM50_Tech_Note_External_Nitrogen_Cycle.rst index f594778562..d1c476099d 100644 --- a/doc/source/tech_note/External_Nitrogen_Cycle/CLM50_Tech_Note_External_Nitrogen_Cycle.rst +++ b/doc/source/tech_note/External_Nitrogen_Cycle/CLM50_Tech_Note_External_Nitrogen_Cycle.rst @@ -133,6 +133,45 @@ where :math:`{WS}_{tot\_soil}` (kgH\ :sub:`2`\ O m\ :sup:`-2`) is the total mass NF_{leached} =\min \left(NF_{leached} ,\frac{NS_{sminn} sf}{\Delta t} \right). +Alternative way of evaluating the Leaching Losses of Nitrogen +-------------------------------------------------------------- + +The previous leaching mechanism is not designed for describing the vertical transport of :math:`{NO}_{3}^{-}` in soil, an alternative way to evaluate the vertical convective, diffusive, and dispersive of dissolved :math:`{NO}_{3}^{-}` in soil is provided in (:ref:`Luo et al. 2025 `). +To obtain the vertical profile of soil mineral N after vertical movement of each timestep, the vertical transport equation is summarized in :eq:`22.20`. + +.. math:: + :label: 22.20 + + \frac{\partial NS_{sminn}}{\partial t} = \frac{\partial J}{\partial z} + S + +where :math:`NS_{sminn} (gN m^{-3})` is the soil :math:`{NO}_{3}^{-}` concentration in each layer, :math:`J (gN m^{-2} s^{-1})` is the different vertical transport terms (:math:`J_{convective}, J_{diffusive}, J_{dispersive}`) between two soil layers, and :math:`S` is the sources or sinks fluxes. +Different transport terms are explained below + +.. math:: + :label: 22.21 + + J_{convective} = sf \frac{SN_{sminn}q_{out}}{\theta} + +where :math:`q_{out} (mH_{2}Os^{-1})` is the darcy flow of water, :math:`\theta (m^3H_{2}O m^{-3}soil)` is the soil water content. + +.. math:: + :label: 22.22 + + J_{diffusive} = -D_{aq} \frac{\theta^{7/3}}{\phi^{2}} \frac{\partial SN_{sminn}}{ \partial z} + +where :math:`\partial SN_{sminn}/ \partial z` is the concentration gradient, :math:`D_{aq}` is the nitrate aqueous diffusion coefficient which is taken as :math:`1.7*10^{-9} m^{2}s^{-1}`, and :math:`\phi (m^3m^{-3})` is soil porosity. + +.. math:: + :label: 22.23 + + J_{dispersive} = -D_{dis} \frac{\theta^{7/3}}{\phi^{2}} \frac{\partial SN_{sminn}}{ \partial z} + +where :math:`D_{dis}` is the dispersion coefficient, which equal to :math:`L_{dis} q_{out} \theta ^{-1}`, for simplicity reasons, :math:`L_{dis}` is taken as 0.1 meter. + +Finally, the classical convective-diffusion algorithm described in (:ref:`Patankar.2018 `) is used to discrete and solve the :math:`{NO}_{3}^{-}` vertical transport :eq:`22.20` in soils. +The advantage of this leaching mechanism is the soil :math:`{NO}_{3}^{-}` is able to move vertically (both upward or downward) with soil water movement, the mass of :math:`{NO}_{3}^{-}` reaches bedrock layer is finally taken as the :math:`NF_{leached}`. + + Losses of Nitrogen Due to Fire ----------------------------------- diff --git a/doc/source/tech_note/Fluxes/CLM50_Tech_Note_Fluxes.rst b/doc/source/tech_note/Fluxes/CLM50_Tech_Note_Fluxes.rst index 05b35d8b34..2eef99666f 100644 --- a/doc/source/tech_note/Fluxes/CLM50_Tech_Note_Fluxes.rst +++ b/doc/source/tech_note/Fluxes/CLM50_Tech_Note_Fluxes.rst @@ -1215,7 +1215,7 @@ The numerical solution for vegetation temperature and the fluxes of momentum, se #. Leaf boundary layer resistance :math:`r_{b}` (:eq:`5.122` ) -#. Aerodynamic resistances :math:`r_{ah} ^{{'} }` and :math:`r_{aw} ^{{'} }`(:eq:`5.116` ) +#. Aerodynamic resistances :math:`r_{ah} ^{{'} }` and :math:`r_{aw} ^{{'} }` (:eq:`5.116` ) #. Sunlit and shaded stomatal resistances :math:`r_{s}^{sun}` and :math:`r_{s}^{sha}` (Chapter :numref:`rst_Stomatal Resistance and Photosynthesis`) diff --git a/doc/source/tech_note/Introduction/CLM50_Tech_Note_Introduction.rst b/doc/source/tech_note/Introduction/CLM50_Tech_Note_Introduction.rst index bb0ab9eef5..b931dc8fed 100644 --- a/doc/source/tech_note/Introduction/CLM50_Tech_Note_Introduction.rst +++ b/doc/source/tech_note/Introduction/CLM50_Tech_Note_Introduction.rst @@ -152,6 +152,8 @@ Introduction The purpose of this document is to fully describe the biogeophysical and biogeochemical parameterizations and numerical implementation of version 5.0 of the Community Land Model (CLM5.0). Scientific justification and evaluation of these parameterizations can be found in the referenced scientific papers (:ref:`rst_References`). This document and the CLM5.0 User's Guide together provide the user with the scientific description and operating instructions for CLM. +.. _model-history: + Model History --------------- diff --git a/doc/source/tech_note/References/CLM50_Tech_Note_References.rst b/doc/source/tech_note/References/CLM50_Tech_Note_References.rst index eafd44e8f4..a006105baa 100644 --- a/doc/source/tech_note/References/CLM50_Tech_Note_References.rst +++ b/doc/source/tech_note/References/CLM50_Tech_Note_References.rst @@ -843,6 +843,10 @@ Lowe, P.R. 1977. An approximating polynomial for the computation of saturation v Luo, Y., Hui, D., and Zhang, D. 2006. Elevated CO2 stimulates net accumulations of carbon and nitrogen in land ecosystems: a meta-analysis. Ecology 87:53-63. +.. _Luoetal2025: + +Luo J, Hess P G, Hall S, et al. Agricultural emissions of reactive nitrogen gases from constrained simulations using the Community Land Model. Authorea Preprints, 2025. + .. _Magilletal1997: Magill, A.H. et al., 1997. Biogeochemical response of forest ecosystems to simulated chronic nitrogen deposition. Ecological Applications, 7: 402-415. @@ -1048,6 +1052,10 @@ Parton, W. et al. 1996. Generalized model for N2 and N2O production from nitrifi Parton, W.J. et al. 2001. Generalized model for NOx and N2O emissions from soils. J. Geophys. Res. 106(D15):17403-17419. +.. _Patankaretal2018: + +Patankar S., 2018. Numerical heat transfer and fluid flow[M]. CRC press., Boca Raton, section 5.2, 80-95 pp + .. _Paterson1994: Paterson, W.S.B., 1994. The Physics of Glaciers. Elsevier Science Inc., New York, 480 pp. diff --git a/doc/source/tech_note/Snow_Hydrology/CLM50_Tech_Note_Snow_Hydrology.rst b/doc/source/tech_note/Snow_Hydrology/CLM50_Tech_Note_Snow_Hydrology.rst index fdc559e1c2..acd0ae3e22 100644 --- a/doc/source/tech_note/Snow_Hydrology/CLM50_Tech_Note_Snow_Hydrology.rst +++ b/doc/source/tech_note/Snow_Hydrology/CLM50_Tech_Note_Snow_Hydrology.rst @@ -15,6 +15,21 @@ Shown are three snow layers, :math:`i=-2`, :math:`i=-1`, and :math:`i=0`. The la The state variables for snow are the mass of water :math:`w_{liq,i}` (kg m\ :sup:`-2`), mass of ice :math:`w_{ice,i}` (kg m\ :sup:`-2`), layer thickness :math:`\Delta z_{i}` (m), and temperature :math:`T_{i}` (Chapter :numref:`rst_Soil and Snow Temperatures`). The water vapor phase is neglected. Snow can also exist in the model without being represented by explicit snow layers. This occurs when the snowpack is less than a specified minimum snow depth (:math:`z_{sno} < 0.01` m). In this case, the state variable is the mass of snow :math:`W_{sno}` (kg m\ :sup:`-2`). +.. note:: + In CLM, all water-related state variables, including snow and ice, are reported in **liquid water equivalent** units. This means that quantities such as snow water equivalent (SWE), soil ice content, and snowmelt are expressed in terms of the depth of liquid water that would result if the frozen material melted completely. + + For example: + + - ``H2OSNO`` represents the total snow water equivalent in mm. + - ``H2OSOI_ICE`` is the soil ice content in mm. + - ``QSNOMELT`` is the snow melt rate in mm/s. + + In contrast, some glaciological or cryosphere models (e.g., PISM, RACMO2, Crocus) may output variables in **ice-equivalent** units, depending on the modeling context. When necessary, conversion from ice equivalent to water equivalent should account for the density of ice versus liquid water (:numref:`Table Physical Constants`): + + .. math:: + + \text{Water equivalent} = \text{Ice equivalent} \times \frac{\rho_\text{ice}}{\rho_\text{liq}} + Section :numref:`Snow Covered Area Fraction` describes the calculation of fractional snow covered area, which is used in the surface albedo calculation (Chapter :numref:`rst_Surface Albedos`) and the surface flux calculations (Chapter :numref:`rst_Momentum, Sensible Heat, and Latent Heat Fluxes`). The following two sections (:numref:`Ice Content` and :numref:`Water Content`) describe the ice and water content of the snow pack assuming that at least one snow layer exists. Section :numref:`Black and organic carbon and mineral dust within snow` describes how black and organic carbon and mineral dust particles are represented within snow, including meltwater flushing. See Section :numref:`Initialization of snow layer` for a description of how a snow layer is initialized. .. _Snow Covered Area Fraction: @@ -585,7 +600,7 @@ The maximum snow layer thickness, :math:`\Delta z_{\max }`, depends on the numbe Subdivision ''''''''''''''''''' -The snow layers are subdivided when the layer thickness exceeds the prescribed maximum thickness :math:`\Delta z_{\max }` with lower and upper bounds that depend on the number of snow layers (:numref:`Table snow layer thickness`). For example, if there is only one layer, then the maximum thickness of that layer is 0.03 m, however, if there is more than one layer, then the maximum thickness of the top layer is 0.02 m. Layers are checked sequentially from top to bottom for this limit. If there is only one snow layer and its thickness is greater than 0.03 m (:numref:`Table snow layer thickness`), the layer is subdivided into two layers of equal thickness, liquid water and ice contents, and temperature. If there is an existing layer below the layer to be subdivided, the thickness :math:`\Delta z_{i}`, liquid water and ice contents, :math:`w_{liq,\; i}` and :math:`w_{ice,\; i}`, and temperature :math:`T_{i}` of the excess snow are combined with the underlying layer according to equations -. If there is no underlying layer after adjusting the layer for the excess snow, the layer is subdivided into two layers of equal thickness, liquid water and ice contents. The vertical snow temperature profile is maintained by calculating the slope between the layer above the splitting layer (:math:`T_{1}` ) and the splitting layer (:math:`T_{2}` ) and constraining the new temperatures (:math:`T_{2}^{n+1}`, :math:`T_{3}^{n+1}` ) to lie along this slope. The temperature of the lower layer is first evaluated from +The snow layers are subdivided when the layer thickness exceeds the prescribed maximum thickness :math:`\Delta z_{\max }` with lower and upper bounds that depend on the number of snow layers (:numref:`Table snow layer thickness`). For example, if there is only one layer, then the maximum thickness of that layer is 0.03 m, however, if there is more than one layer, then the maximum thickness of the top layer is 0.02 m. Layers are checked sequentially from top to bottom for this limit. If there is only one snow layer and its thickness is greater than 0.03 m (:numref:`Table snow layer thickness`), the layer is subdivided into two layers of equal thickness, liquid water and ice contents, and temperature. If there is an existing layer below the layer to be subdivided, the thickness :math:`\Delta z_{i}`, liquid water and ice contents, :math:`w_{liq,\; i}` and :math:`w_{ice,\; i}`, and temperature :math:`T_{i}` of the excess snow are combined with the underlying layer according to equations :eq:`8.55` - :eq:`8.58`. If there is no underlying layer after adjusting the layer for the excess snow, the layer is subdivided into two layers of equal thickness, liquid water and ice contents. The vertical snow temperature profile is maintained by calculating the slope between the layer above the splitting layer (:math:`T_{1}` ) and the splitting layer (:math:`T_{2}` ) and constraining the new temperatures (:math:`T_{2}^{n+1}`, :math:`T_{3}^{n+1}` ) to lie along this slope. The temperature of the lower layer is first evaluated from .. math:: :label: 8.62 @@ -602,5 +617,5 @@ then adjusted as, T_{2}^{n+1} = T_{2}^{n} +\left(\frac{T_{1}^{n} -T_{2}^{n} }{{\left(\Delta z_{1} +\Delta z_{2}^{n} \right)\mathord{\left/ {\vphantom {\left(\Delta z_{1} +\Delta z_{2}^{n} \right) 2}} \right.} 2} } \right)\left(\frac{\Delta z_{2}^{n+1} }{2} \right) & \qquad T'_{3} `_ gives a synopsis of the changes to CLM since the CLM4.5 release. More details are given in the CLM ChangeLog files: +The :ref:`model history section ` section gives a synopsis of the changes to CLM in each release. More details are given in the CLM ChangeLog files: - `CLM 3.0 ChangeLog file `_ - `CLM 4.0 ChangeLog file `_ @@ -62,8 +62,6 @@ In :ref:`running-special-cases-section`, again for the expert user, we give deta :ref:`running-single-points` outlines how to do single-point or regional simulations using |version|. This is useful to either compare |version| simulations with point observational stations, such as tower sites (which might include your own atmospheric forcing), or to do quick simulations with CLM for example to test a new parameterization. There are several different ways given on how to perform single-point simulations which range from simple sampling of existing inputs to more complex where you create all your own datasets, tying into :ref:`using-clm-tools-section` and also :ref:`adding-new-resolutions-section` to add the files into the build-namelist XML database. -There is also :ref:`pts_mode`, which is useful for running single points as part of the Single Column Atmospheric Model (SCAM). - :ref:`troubleshooting-index` gives some guidance on trouble-shooting problems when using |version|. It doesn't cover all possible problems with CLM, but gives you some guidelines for things that can be done for some common problems. :ref:`testing_section` goes over the automated testing scripts for validating that the CLM is working correctly. The test scripts run many different configurations and options with CLM4.0 physics as well and |version| physics making sure that they work, as well as doing automated testing to verify restarts are working correctly, and testing at many different resolutions. In general this is an activity important only for a developer of |version|, but could also be used by users who are doing extensive code modifications and want to ensure that the model continues to work correctly. diff --git a/doc/source/users_guide/running-single-points/running-single-point-subset-data.rst b/doc/source/users_guide/running-single-points/generic-single-point-regional.rst similarity index 72% rename from doc/source/users_guide/running-single-points/running-single-point-subset-data.rst rename to doc/source/users_guide/running-single-points/generic-single-point-regional.rst index fc9d011e93..7e0b1e72fd 100644 --- a/doc/source/users_guide/running-single-points/running-single-point-subset-data.rst +++ b/doc/source/users_guide/running-single-points/generic-single-point-regional.rst @@ -1,15 +1,18 @@ .. include:: ../substitutions.rst -.. _single_point_subset_data: +.. _generic_single_point_runs: **************************************** -Running a single point using global data +Generic single-point runs **************************************** -``subset_data`` enables you to run the model using global datasets, but just picking a single point from those datasets and operating on it. It can be a very quick way to do fast simulations and get a quick turnaround. +While there are capabilities to run single-point cases at specific tower sites with forcing data from those observations (see :ref:`supported-tower-sites`), users can also run CTSM at a single lat/lon point of their choosing using the instructions below. -Subset the data ------------------- +============ +subset_data: +============ + +``subset_data`` enables you to run the model using global datasets, but just picking a single point from those datasets and operating on it. It can be a very quick way to do fast simulations and get a quick turnaround. This can also be done for regional simulations in the next section but first we will describe how to use subset_data for a single point. For single-point cases, you need to subset a surface dataset and (optionally) DATM data. The Python script to subset this data can be found in the CTSM repository at ``tools/site_and_regional/subset_data``. @@ -30,18 +33,23 @@ To subset surface data and climate forcings (DATM) for a single point, use the c - ``$my_lon_type``: 180 if your longitude is in the [-180, 180] format (i.e., centered at the Prime/0th Meridian); 360 if it's in the [0, 360] format (i.e., centered at the 180th Meridian). Note that ``--lon-type $my_lon_type`` is not necessary if your longitude is unambiguous---i.e., it's only needed if your longitude is in the range [0, 180]. - ``$my_site_name``: name of site, *used for file naming* - ``$my_start_year``: start year for DATM data to subset, *default between 1901 and 2014* -- ``$my_end_year``: end year for DATM data to subset, *default between 1901 and 2014; the default CRUJRA2024 DATM data ends in 2023, while the old default GSWP3 ends in 2015; see note below about switching the default DATM data* +- ``$my_end_year``: end year for DATM data to subset, *default between 1901 and 2014; the default CRUJRA2024 DATM data ends in 2023, while the old default GSWP3 ends in 2014; see note below about switching the default DATM data* - ``$my_output_dir``: output directory to place the subset data and user_mods directory. This should be something specific to *just* your data for ``$my_site_name``. -You can also have the script subset land-use data. See the help (``tools/site_and_regional/subset_data --help``) for all argument options. +You can also have the script subset land-use data. See the help (``tools/site_and_regional/subset_data --help``) for all argument options. For example, depending on your application, it may be helpful to specify a dominant PFT using ``--dompft`` and ``--pctpft`` flags. This allows you to control the PFTs that are present on your surface dataset .. note:: - This script defaults to subsetting specific surface, domain, and land-use files and the CRUJRA2024 DATM data, and can currently only be run as-is on Derecho. If you're not on Derecho, use ``--inputdata-dir`` to specify where the top level of your CESM input data is. Also, to subset GSWP3 instead of CRUJRA2024 DATM data, you currently need to hardwire ``datm_type = "datm_gswp3"`` (instead of the default ``"datm_crujra"``) in ``python/ctsm/subset_data.py``. + This script defaults to subsetting specific surface data, land-use timeseries, and the CRUJRA2024 DATM data. It can currently only be run as-is on Derecho. If you're not on Derecho, use ``--inputdata-dir`` to specify where the top level of your CESM input data is. + + Using ``--create-datm`` with GSWP3 data is no longer supported; see `CTSM issue #3269 `_. + + -The ``--create-user-mods`` command tells the script to set up a user mods directory in your specified ``$my_output_dir`` and to specify the required ``PTS_LAT`` and ``PTS_LON`` settings. You can then use this user mods directory to set up your CTSM case, as described below. +The ``--create-user-mods`` command tells the script to set up a user mods directory in your specified ``$my_output_dir`` and to specify the required ``PTS_LAT`` and ``PTS_LON`` settings. You can then use this user mods directory to set up your CTSM case, as described below. ``subset_data`` will default to subsetting surface data and land-use timeseries from the default, nominal one-degree resolution (f09) datasets. +================ Create the case ------------------- +================ You can use the user mods directory set up in the previous subset data step to tell CIME/CTSM where your subset files are located. diff --git a/doc/source/users_guide/running-single-points/index.rst b/doc/source/users_guide/running-single-points/index.rst index d5ece00ec9..1b503acc87 100644 --- a/doc/source/users_guide/running-single-points/index.rst +++ b/doc/source/users_guide/running-single-points/index.rst @@ -14,8 +14,8 @@ Running Single Point and Regional Cases .. toctree:: :maxdepth: 2 - single-point-and-regional-grid-configurations.rst - running-single-point-subset-data.rst - running-single-point-configurations.rst - running-pts_mode-configurations.rst + intro-to-single-pt-regional.rst + supported-tower-sites.rst + generic-single-point-regional.rst + predefined-single-point-regional-resolutions.rst diff --git a/doc/source/users_guide/running-single-points/intro-to-single-pt-regional.rst b/doc/source/users_guide/running-single-points/intro-to-single-pt-regional.rst new file mode 100644 index 0000000000..23279aac23 --- /dev/null +++ b/doc/source/users_guide/running-single-points/intro-to-single-pt-regional.rst @@ -0,0 +1,54 @@ +.. include:: ../substitutions.rst + +.. _single-point-regional-configurations: + +***************************************************** +Introduction to Single-Point and Regional Grid Setups +***************************************************** + +CTSM is designed to support a wide range of spatial scales, ranging from global simulations to regional runs to highly resolved single-point cases. Setting up and running single-point and regional simulations is useful for a variety of purposes including: running quick cases for testing, evaluating specific vegetation types, or running with observed data from a specific site to generate and test hypotheses. + +Single-point cases allow users to run CTSM at a specific location such as a flux tower or ecological field site. Single-point runs are especially useful where high-resolution meteorological forcing data and site-specific observations are available and require minimal computational resources. + +Regional configurations support simulations over broader geographic areas defined by a user-specified domain. Regional runs require additional input data such as meteorological forcing for the region. You can either extract regional subsets from global datasets or create custom datasets for your region of interest. + +.. _options-for-single-points: + +========================================= + Choosing the right single point options +========================================= + +There are several different ways to set up single-point and regional cases. + +For supported tower sites: +-------------------------- + +You can run at a supported tower site if one of the supported single-point/regional datasets is your site of interest (see :ref:`supported-tower-sites`). All the datasets are created for you, and you can easily select one and run it out of the box using a supported resolution from the top level of the CESM scripts. You can also use this method for your own datasets, but you have to create the datasets, and add them to the XML database in scripts, CLM and to the DATM. This is worthwhile if you want to repeat many multiple cases for a given point or region. + +Next, using ``subset_data`` is the best way to setup cases quickly where you can use a simple tool to create your own datasets (see :ref:`generic_single_point_runs`). With this method you don't have to change DATM or add files to the XML database. ``subset_data`` will create a usermod directory where you can store your files and the files needed to directly run a case. + +For unsupported tower sites: +---------------------------- + +If you have meteorology data that you want to force your CLM simulations with, you'll need to setup cases as described in :ref:`pre-defined-single-pt-regional-resolutions`. You'll need to create CLM datasets either according to ``CLM_USRDAT_NAME``. You may also need to modify DATM to use your forcing data. And you'll need to change your forcing data to be in a format that DATM can use. + +================ +Spinning up CTSM +================ + +We make steady state assumptions about the initial state of ecosystem properties including temperature water, snow, ice, carbon & nitrogen. This is the equilibrium state of the model, given the forcing data. Spinning up the model brings internal state variables into equilibrium with environmental forcing conditions so that the results are not influenced by the initial conditions of state variables (such as soil C). In runs with active biogoechemistry, we need to get the ecosystem carbon and nitrogen pools with long turnover times into steady state. + +Specifically, spinning up CTSM consists of 3 parts including: + +1. AD, or accelerated decomposition: The turnover and decomposition of the slow pools of C and N that normally have a long residence time in ecosystems is mathematically accelerated, where we make the slow pools spin up more quickly by increasing their turnover time. This includes: +-Accelerating turnover of wood, litter and soil pools +-Accelerating advection and diffusion terms +-Calculating this as a function of latitude so that spinup is more accelerated in high latitude regions. + +2. postAD, which occurs after AD spinup: During postAD runs we take away accelerated decomposition and let the ecosystem settle into its equilibrium, or steady-state under 'normal conditions'. Pools are increased by the same degree their turnover was increased (e.g., turnover 10x faster means the pool must be 10x larger). During AD and postAD spinup we cycle over several years of input data and hold other inputs constant (e.g., atmospheric CO2 concentrations, N deposition, etc.). For transient runs these inputs also change over time. + +3. transient: Transient runs are used to compare with observations, and include high frequency output that we can compare with flux tower measurements. The end of the spinup simulation is used as the initial conditions for a transient simulation, set in the user_nl_clm file. + + + + diff --git a/doc/source/users_guide/running-single-points/running-pts_mode-configurations.rst b/doc/source/users_guide/running-single-points/predefined-single-point-regional-resolutions.rst similarity index 91% rename from doc/source/users_guide/running-single-points/running-pts_mode-configurations.rst rename to doc/source/users_guide/running-single-points/predefined-single-point-regional-resolutions.rst index fb61397321..4ad8995511 100644 --- a/doc/source/users_guide/running-single-points/running-pts_mode-configurations.rst +++ b/doc/source/users_guide/running-single-points/predefined-single-point-regional-resolutions.rst @@ -1,13 +1,13 @@ .. include:: ../substitutions.rst -.. _pts_mode: +.. _pre-defined-single-pt-regional-resolutions: **************************************************** -Running a single point using global data - PTS_MODE +Pre-defined single-point and regional resolutions **************************************************** .. warning:: - ``PTS_MODE`` has been mostly deprecated in favor of ``subset_data`` (Sect. :numref:`single_point_subset_data`). You should only consider using it if you are using the Single Column Atmospheric Model (SCAM). + ``PTS_MODE`` has been mostly deprecated in favor of ``subset_data`` (Sect. :numref:`generic_single_point_runs`). You should only consider using it if you are using the Single Column Atmospheric Model (SCAM). ``PTS_MODE`` enables you to run the model using global datasets, but just picking a single point from those datasets and operating on it. It can be a very quick way to do fast simulations and get a quick turnaround. diff --git a/doc/source/users_guide/running-single-points/running-single-point-configurations.rst b/doc/source/users_guide/running-single-points/running-single-point-configurations.rst deleted file mode 100644 index 56cad6a11e..0000000000 --- a/doc/source/users_guide/running-single-points/running-single-point-configurations.rst +++ /dev/null @@ -1,215 +0,0 @@ -.. include:: ../substitutions.rst - -.. _running-single-point-datasets: - -****************************************** - Running Single Point Configurations -****************************************** - -In addition to running with the outputs of ``subset_data`` (Sect. :numref:`single_point_subset_data`), CLM supports running using single-point or regional datasets that are customized to a particular region. CLM supports a a small number of out-of-the-box single-point and regional datasets. However, users can create their own dataset. - -To get the list of supported dataset resolutions do this: -:: - - > cd $CTSMROOT/doc - > ../bld/build-namelist -res list - -Which results in the following: -:: - - CLM build-namelist - valid values for res (Horizontal resolutions - Note: 0.5x0.5, 5x5min, 10x10min, 3x3min and 0.33x0.33 are only used for CLM tools): - Values: default 512x1024 360x720cru 128x256 64x128 48x96 32x64 8x16 94x192 \ - 0.23x0.31 0.47x0.63 0.9x1.25 1.9x2.5 2.5x3.33 4x5 10x15 5x5_amazon 1x1_tropicAtl \ - 1x1_vancouverCAN 1x1_mexicocityMEX 1x1_asphaltjungleNJ 1x1_brazil 1x1_urbanc_alpha 1x1_numaIA \ - 1x1_smallvilleIA 0.5x0.5 3x3min 5x5min 10x10min 0.33x0.33 ne4np4 ne16np4 ne30np4 ne60np4 \ - ne120np4 ne240np4 wus12 us20 - Default = 1.9x2.5 - (NOTE: resolution and mask and other settings may influence what the default is) - -The resolution names that have an underscore in them ("_") are all single-point or regional resolutions. - -.. note:: When running a single point, the number of processors is automatically set to one, which is the only value allowed. - -.. warning:: - Just like running with the outputs from ``subset_data`` (Sect. :numref:`single_point_subset_data`), by default these setups sometimes run with ``MPILIB=mpi-serial`` (in the ``env_build.xml`` file) turned on, which allows you to run the model interactively. On some machines this mode is NOT supported and you may need to change it to FALSE before you are able to build. - -.. _single-point-global-climate: - -Single-point runs with global climate forcings -============================================== - -Example: Use global forcings at a site without its own special forcings ------------------------------------------------------------------------ - -This example uses the single-point site in Brazil. -:: - - > cd cime/scripts - > set SITE=1x1_brazil - > ./create_newcase -case testSPDATASET -res $SITE -compset I2000Clm50SpGs - > cd testSPDATASET - -Then setup, build and run normally. - -Example: Use global forcings at a site WITH its own special forcings --------------------------------------------------------------------- - -The urban Mexico City test site has its own atmosphere forcing data (see Sect. :numref:`single-point-with-own-forcing`). To ignore that and run it with the default global forcing data, but over the period for which its own forcing data is provided, do the following: - -:: - - > cd cime/scripts - # Set a variable to the site you want to use (as it's used several times below) - > set SITE=1x1_mexicocityMEX - > ./create_newcase -case testSPDATASET -res $SITE -compset I1PtClm50SpGs - > cd testSPDATASET - -(Note the use of ``I1Pt`` instead of ``I2000`` as in the example above.) Then setup, build and run normally. - -.. _single-point-with-own-forcing: - -Supported single-point runs for sites with their own atmospheric forcing -======================================================================== - -Of the supported single-point datasets we have three that also have atmospheric forcing data that go with them: Mexico City (Mexico), Vancouver, (Canada, British Columbia), and ``urbanc_alpha`` (test data for an Urban inter-comparison project). Mexico city and Vancouver also have namelist options in the source code for them to work with modified urban data parameters that are particular to these locations. To turn on the atmospheric forcing for these datasets, you set the ``env_run.xml DATM_MODE`` variable to ``CLM1PT``, and then the atmospheric forcing datasets will be used for the point picked. If you use one of the compsets that has "I1Pt" in the name that will be set automatically. - -.. todo:: - Update the below, as ``queryDefaultNamelist.pl`` no longer exists. - -When running with datasets that have their own atmospheric forcing you need to be careful to run over the period that data is available. If you have at least one year of forcing it will cycle over the available data over and over again no matter how long of a simulation you run. However, if you have less than a years worth of data (or if the start date doesn't start at the beginning of the year, or the end date doesn't end at the end of the year) then you won't be able to run over anything but the data extent. In this case you will need to carefully set the ``RUN_STARTDATE``, ``START_TOD`` and ``STOP_N/STOP_OPTION`` variables for your case to run over the entire time extent of your data. For the supported data points, these values are in the XML database and you can use the ``queryDefaultNamelist.pl`` script to query the values and set them for your case (they are set for the three urban test cases: Mexicocity, Vancouver, and urbanc_alpha). - -Example: Use site-specific atmospheric forcings ------------------------------------------------ -In this example, we show how to use the atmospheric forcings specific to the Vancouver, Canada point. -:: - - > cd cime/scripts - - # Set a variable to the site you want to use (as it's used several times below) - > set SITE=1x1_vancouverCAN - - # Create a case at the single-point resolutions with their forcing - > ./create_newcase -case testSPDATASETnAtmForcing -res $SITE -compset I1PtClm50SpGs - > cd testSPDATASETnAtmForcing - - # Figure out the start and end date for this dataset - # You can do this by examining the datafile. - > set STOP_N=330 - > set START_YEAR=1992 - > set STARTDATE=${START_YEAR}-08-12 - > @ NDAYS = $STOP_N / 24 - > ./xmlchange RUN_STARTDATE=$STARTDATE,STOP_N=$STOP_N,STOP_OPTION=nsteps - - # Set the User namelist to set the output frequencies of the history files - # Setting the stdurbpt use-case option create three history file streams - # The frequencies and number of time-samples needs to be set - > cat << EOF > user_nl_clm - hist_mfilt = $NDAYS,$STOP_N,$STOP_N - hist_nhtfrq = -1,1,1 - EOF - - > ./case.setup - -.. warning:: If you don't set the start-year and run-length carefully as shown above the model will abort with a "dtlimit error" in the atmosphere model. Since, the forcing data for this site (and the MexicoCity site) is less than a year, the model won't be able to run for a full year. The ``1x1_urbanc_alpha`` site has data for more than a full year, but neither year is complete hence, it has the same problem (see the problem for this site above). - -.. _creating-your-own-singlepoint-dataset: - -Creating your own single-point dataset -=================================================== - -The following provides an example of setting up a case using ``CLM_USRDAT_NAME`` where you rename the files according to the ``CLM_USRDAT_NAME`` convention. We have an example of such datafiles in the repository for a specific region over Alaska (actually just a sub-set of the global f19 grid). - -Example: Using CLM_USRDAT_NAME to run a simulation using user datasets for a specific region over Alaska ------------------------------------------------------------------------------------------------------------------------ -:: - - > cd cime/scripts - > ./create_newcase -case my_userdataset_test -res CLM_USRDAT -compset I2000Clm50BgcCruGs - > cd my_userdataset_test/ - > set GRIDNAME=13x12pt_f19_alaskaUSA - > set LMASK=gx1v6 - > ./xmlchange CLM_USRDAT_NAME=$GRIDNAME,CLM_BLDNML_OPTS="-mask $LMASK" - > ./xmlchange ATM_DOMAIN_FILE=domain.lnd.${GRIDNAME}_$LMASK.nc - > ./xmlchange LND_DOMAIN_FILE=domain.lnd.${GRIDNAME}_$LMASK.nc - - # Make sure the file exists in your $CSMDATA or else use svn to download it there - > ls $CSMDATA/lnd/clm2/surfdata_map/surfdata_${GRIDNAME}_simyr2000.nc - - # If it doesn't exist, comment out the following... - #> setenv SVN_INP_URL https://svn-ccsm-inputdata.cgd.ucar.edu/trunk/inputdata/ - #> svn export $SVN_INP_URL/lnd/clm2/surfdata_map/surfdata_${GRIDNAME}_simyr2000.nc $CSMDATA/lnd/clm2/surfdata_map/surfdata_${GRIDNAME}_simyr2000.nc - > ./case.setup - -The first step is to create the domain and surface datasets using the process outlined in :ref:`using-clm-tools-section`. Below we show an example of the process. - -Example: Creating a surface dataset for a single point ---------------------------------------------------------------------- -.. todo:: - Update the below, as ``mksurfdata.pl`` no longer exists and domain files aren't needed with nuopc. - -:: - - # set the GRIDNAME and creation date that will be used later - > setenv GRIDNAME 1x1_boulderCO - > setenv CDATE `date +%y%m%d` - # Create the SCRIP grid file for the location and create a unity mapping file for it. - > cd $CTSMROOT/tools/mkmapdata - > ./mknoocnmap.pl -p 40,255 -n $GRIDNAME - # Set pointer to MAPFILE just created that will be used later - > setenv MAPFILE `pwd`/map_${GRIDNAME}_noocean_to_${GRIDNAME}_nomask_aave_da_${CDATE}.nc - # create the mapping files needed by mksurfdata_esmf. - > cd ../.././mkmapdata - > setenv GRIDFILE ../mkmapgrids/SCRIPgrid_${GRIDNAME}_nomask_${CDATE}.nc - > ./mkmapdata.sh -r $GRIDNAME -f $GRIDFILE -t regional - # create the domain file - > cd ../../../../tools/mapping/gen_domain_files/src - > ../../../scripts/ccsm_utils/Machines/configure -mach cheyenne -compiler intel - > gmake - > cd .. - > setenv OCNDOM domain.ocn_noocean.nc - > setenv ATMDOM domain.lnd.{$GRIDNAME}_noocean.nc - > ./gen_domain -m $MAPFILE -o $OCNDOM -l $ATMDOM - # Save the location where the domain file was created - > setenv GENDOM_PATH `pwd` - # Finally create the surface dataset - > cd ../../../../lnd/clm/tools/|version|/mksurfdata_esmf/src - > gmake - > cd .. - > ./mksurfdata.pl -r usrspec -usr_gname $GRIDNAME -usr_gdate $CDATE - -The next step is to create a case that points to the files you created above. We will still use the ``CLM_USRDAT_NAME`` option as a way to get a case setup without having to add the grid to scripts. - -Example: Setting up a case from the single-point surface dataset just created --------------------------------------------------------------------------------------------- - -.. todo:: - Change this to provide instructions for a CTSM checkout instead of a CESM one. - -.. todo:: - Update the below, as domain files aren't needed with nuopc. - -:: - - # First setup an environment variable that points to the top of the CESM directory. - > setenv CESMROOT - # Next make sure you have a inputdata location that you can write to - # You only need to do this step once, so you won't need to do this in the future - > setenv MYCSMDATA $HOME/inputdata # Set env var for the directory for input data - > ./link_dirtree $CSMDATA $MYCSMDATA - # Copy the file you created above to your new $MYCSMDATA location following the CLMUSRDAT - # naming convention (leave off the creation date) - > cp $CESMROOT/$CTSMROOT/tools/mksurfdata_esmf/surfdata_${GRIDNAME}_simyr1850_$CDATE.nc \ - $MYCSMDATA/lnd/clm2/surfdata_map/surfdata_${GRIDNAME}_simyr1850.nc - > cd $CESMROOT/cime/scripts - > ./create_newcase -case my_usernldatasets_test -res CLM_USRDAT -compset I1850Clm50BgcCropCru \ - -mach cheyenne_intel - > cd my_usernldatasets_test - > ./xmlchange DIN_LOC_ROOT=$MYCSMDATA - # Set the path to the location of gen_domain set in the creation step above - > ./xmlchange ATM_DOMAIN_PATH=$GENDOM_PATH,LND_DOMAIN_PATH=$GENDOM_PATH - > ./xmlchange ATM_DOMAIN_FILE=$ATMDOM,LND_DOMAIN_FILE=$ATMDOM - > ./xmlchange CLM_USRDAT_NAME=$GRIDNAME - > ./case.setup - -.. note:: With this and previous versions of the model we recommended using ``CLM_USRDAT_NAME`` as a way to identify your own datasets without having to enter them into the XML database. This has the down-side that you can't include creation dates in your filenames, which means you can't keep track of different versions by date. It also means you HAVE to rename the files after you created them with ``mksurfdata.pl``. Now, since ``user_nl`` files are supported for ALL model components, and the same domain files are read by both CLM and DATM and set using the envxml variables: ``ATM_DOMAIN_PATH``, ``ATM_DOMAIN_FILE``, ``LND_DOMAIN_PATH``, and ``LND_DOMAIN_FILE`` -- you can use this mechanism (``user_nl_clm`` and ``user_nl_datm`` and those envxml variables) to point to your datasets in any location. In the future we will deprecate ``CLM_USRDAT_NAME`` and recommend ``user_nl_clm`` and ``user_nl_datm`` and the ``DOMAIN`` envxml variables. diff --git a/doc/source/users_guide/running-single-points/single-point-and-regional-grid-configurations.rst b/doc/source/users_guide/running-single-points/single-point-and-regional-grid-configurations.rst deleted file mode 100644 index d16dfa6f5e..0000000000 --- a/doc/source/users_guide/running-single-points/single-point-and-regional-grid-configurations.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. include:: ../substitutions.rst - -.. _single-point-configurations: - -***************************************** -Single and Regional Grid Configurations -***************************************** - -CLM allows you to set up and run cases with a single-point or a local region as well as global resolutions. This is often useful for running quick cases for testing, evaluating specific vegetation types, or land-units, or running with observed data for a specific site. - -There are two different ways to do this for normal-supported site - -``subset_data`` - runs for a single point using global datasets. - -``CLM_USRDAT_NAME`` - runs using your own datasets (single-point or regional). - -.. _options-for-single-points: - -========================================= - Choosing the right single point options -========================================= - -Running for a *normal supported site* is a great solution, if one of the supported single-point/regional datasets, is your region of interest (see :ref:`running-single-point-datasets`). All the datasets are created for you, and you can easily select one and run, out of the box with it using a supported resolution from the top level of the CESM scripts. The problem is that there is a very limited set of supported datasets. You can also use this method for your own datasets, but you have to create the datasets, and add them to the XML database in scripts, CLM and to the DATM. This is worthwhile if you want to repeat many multiple cases for a given point or region. - -In general :ref:`single_point_subset_data` is the quick and dirty method that gets you started, but it has limitations. It's good for an initial attempt at seeing results for a point of interest, but since you can NOT restart with it, its usage is limited. It is the quickest method as you can create a case for it directly from ``cime/scripts/create_newcase``. Although you can't restart, running a single point is very fast, and you can run for long simulation times even without restarts. - -Next, ``CLM_USRDAT_NAME`` using ``subset_data`` is the best way to setup cases quickly where you have a simple tool to create your own datasets (see :ref:`single_point_subset_data`). With this method you don't have to change DATM or add files to the XML database. ``subset_data`` will create a usermod directory where you can store your files and the files needed to directly run a case. - -Finally, if you also have meteorology data that you want to force your CLM simulations with you'll need to setup cases as described in :ref:`creating-your-own-singlepoint-dataset`. You'll need to create CLM datasets either according to ``CLM_USRDAT_NAME``. You may also need to modify DATM to use your forcing data. And you'll need to change your forcing data to be in a format that DATM can use. - diff --git a/doc/source/users_guide/running-single-points/supported-tower-sites.rst b/doc/source/users_guide/running-single-points/supported-tower-sites.rst new file mode 100644 index 0000000000..3e080a3ee1 --- /dev/null +++ b/doc/source/users_guide/running-single-points/supported-tower-sites.rst @@ -0,0 +1,82 @@ +.. include:: ../substitutions.rst + +.. _supported-tower-sites: + +******************************************** +Supported tower sites for single-point runs +******************************************** + +CTSM has functionality within the ``run_tower`` tool for running single-point cases at particular supported tower sites using forcing data from those sites. + +This tool was developed as a collaboration between NCAR's modeling capabilities and NEON's measurement network that could drive scientific discovery at the confluence of geosciences and biological sciences. The tool was then expanded to include PLUMBER sites to support a wider variety of ecological research projects. + +Broadly, this tool can be used to probe questions such as: + + * What biases in NCAR models can current observations address? + * How can NCAR models inform observational data streams? + * What new hypotheses of atmospheric science and macroscale ecology can be tested with observations and NCAR models to increase our understanding of the biosphere-atmosphere system and its response to global environmental change? + * Can Earth system prediction be extended to ecological forecasts? + +==================================================== +General Information on Running Supported Tower Sites +==================================================== + +The ``run_tower`` capability allows users to run Community Land Model (CLM) simulations at NEON and PLUMBER tower sites in a streamlined manner by setting up the appropriate model configurations, datasets, and initial conditions. This script can run for one or more (NEON or PLUMBER) tower sites. It will do the following: + + 1) Create a generic base case for cloning. + 2) Make the case for the specific neon or plumber site(s). + 3) Make changes to the case, for + a. AD spinup + b. post-AD spinup + c. transient + d. SASU or Matrix spinup + 4) Build and submit the case. + +The available options, a description of those options, and details on default values can be shown by running ``run_tower --help``. + +A `tutorial `_ on running and evaluating data from ``run_tower`` is also available. + +.. warning:: Note that the run_tower base case must be of same run type as a requested clone, as described by this `issue ticket `_. + +========================================= +NEON Tower Single Point Simulations +========================================= + +With this tool, CLM uses gap-filled meteorology from NEON tower sites, the dominant plant species is mapped to the appropriate model plant functional type (PFT), and soil characteristics used in the simulations are updated to match observations from NEON's soil megapits. Gap-filled NEON tower flux data are also available for model evaluation. Additionally, all the commands to run the model are combined into a script that you can easily call from a single line of code. + +Currently supported NEON sites can be found by running ``run_tower --help``. + +.. note:: If you choose to run ``all``, single point simulations at all NEON sites will be run. This is a useful feature, but we recommend testing out running just one site first. + +Information on the specific sites can be found on the `NEON webpage `_. + +.. note:: For NEON tower site simulations, the default run type is ``transient``. + +To run CTSM at a NEON site, change directories to where the run_tower tool is located, and then run the ``run_tower`` command. You can also add any additional arguments as described by the ``help`` options. These steps will look something like this:: + + cd CTSM/tools/site_and_regional + run_tower --neon-sites ABBY + +When a simulation completes, the data are stored in the archive directory under ``CTSM/tools/site_and_regional/archive``. In this directory you will find files that include data for every day of the simulation, as well as files that average model variables monthly. The output file names are automatically generated and are composed of the simulation name, which includes the site name, type of simulation (eg, ``transient``), and the date of simulated data. +The tower simulations generate two types of files: + +1) ``h0`` Variables that are averaged monthly. One file is available for every month of the simulation. These files include hundreds of variables. + +2) ``h1`` Variables that are recorded every 30 minutes. Values are aggregated into one file for each day of the simulation. Each file includes 48 data points for selected variables. + +========================================= +PLUMBER Tower Single Point Simulations +========================================= + +.. note:: A few important notes regarding the PLUMBER tower site simulations are that the default run type is ``ad``; additionally, PLUMBER cases all start in different years. + +Currently supported PLUMBER Sites can be found by running ``run_tower --help``. + +Information on the specific sites can be found `here `_. + +To run CTSM at a PLUMBER site, change directories to where the run_tower tool is located, and then run the ``run_tower`` command. You can also add any additional arguments as described by the ``help`` options. These steps will look something like this:: + + cd CTSM/tools/site_and_regional + run_tower --plumber-sites AR-SLu + +The output for a PLUMBER case will be set up similarly to the output for a NEON case, as described above. diff --git a/doc/source/users_guide/running-special-cases/Running-with-anomaly-forcing.rst b/doc/source/users_guide/running-special-cases/Running-with-anomaly-forcing.rst index 2efa65893d..a8197d836c 100644 --- a/doc/source/users_guide/running-special-cases/Running-with-anomaly-forcing.rst +++ b/doc/source/users_guide/running-special-cases/Running-with-anomaly-forcing.rst @@ -5,41 +5,60 @@ ============================== Running with anomaly forcing ============================== -Because performing fully coupled climate simulations is computationally expensive, an alternate method of running land-only simulations forced by future climate projections was developed for CTSM called 'anomaly forcing'. The anomaly forcing method uses a previously completed fully coupled simulation to create monthly anomalies, relative to the present day, of near-surface atmospheric states and fluxes. These anomalies, representing the evolution of future climate projections, are applied to a repeating cycle of present day atmospheric forcing data, either as an additive (for states) or multiplicative (for fluxes) quantity. Thus, high-frequency variability is obtained from the present day atmospheric forcing data, while the long-term evolution of the climate is determined by the anomaly forcing dataset. - -To enable anomaly forcing in a CTSM simulation, the following namelist variable can be added to the user\_nl\_datm file: - - anomaly\_forcing = 'Anomaly.Forcing.Precip','Anomaly.Forcing.Temperature','Anomaly.Forcing.Pressure','Anomaly.Forcing.Humidity','Anomaly.Forcing.Uwind','Anomaly.Forcing.Vwind','Anomaly.Forcing.Shortwave','Anomaly.Forcing.Longwave' - -Any combination or subset of forcing variables can be used, e.g. to modify only a single atmospheric forcing variable, one could use: - - anomaly\_forcing = 'Anomaly.Forcing.Temperature' - -which will only adjust the temperature (TBOT). - -After the namelist has been created, the run directory will be populated with files such as these: - - datm.streams.txt.Anomaly.Forcing.Temperature - -which will contain the location of the default anomaly forcing datasets. To use alternative data, copy these files to the case directory with the 'user\_' prefix, and modify the 'user\_*' files accordingly, e.g.: - - user\_datm.streams.txt.Anomaly.Forcing.Temperature - - For example, one could use the user\_datm.streams.txt.Anomaly.Forcing.* files to point to these SSP-derived anomaly forcing datasets: - - /glade/p/cesmdata/cseg/inputdata/atm/datm7/anomaly\_forcing/CMIP6-SSP3-7.0 - - af.huss.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - af.pr.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - af.ps.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - af.rlds.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - af.rsds.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - af.tas.cesm2.SSP3-7.0.2015-2100\_c20200329.nc - -Users may wish to also update files such as the landuse\_timeseries and aerosol and Ndepostion files to correspond to the appropriate SSP. - -For single point simulations, the global anomaly forcing files can be used, but the map_algo namelist variable should be appended with nearest neighbor values for each of the anomaly forcing fields, e.g. - - mapalgo = 'nn','nn','nn','nn','nn','nn','nn','nn','nn','nn','nn','nn','nn' (the number of 'nn' values will depend on the number of original streams plus the number of anomaly forcing streams) - -The cycling of the present-day (base) climate is controlled through the DATM\_YR\_START and DATM\_YR\_END variables in env\_run.xml. +Because performing fully coupled climate simulations is computationally expensive, an alternate method of running land-only simulations forced by future climate projections was developed for CTSM called "anomaly forcing." The anomaly forcing method uses a previously-completed, fully-coupled simulation to create monthly anomalies, relative to the present day, of near-surface atmospheric states and fluxes. These anomalies, representing the evolution of future climate projections, are applied to a repeating cycle of present day atmospheric forcing data, either as an additive (for states) or multiplicative (for fluxes) quantity. Thus, high-frequency variability is obtained from the present day atmospheric forcing data, while the long-term evolution of the climate is determined by the anomaly forcing dataset. + +Anomaly climate forcings are automatically enabled for ``ISSP`` compsets (e.g., ``ISSP585``). After the namelist has been created, ``CaseDocs/datm.streams.xml`` will have an entry like this pointing to the anomaly forcing file being used: + +:: + + + ... + + /glade/campaign/cesm/cesmdata/inputdata/atm/datm7/anomaly_forcing/CMIP6-SSP5-8.5/af.allvars.CESM.SSP5-8.5.2015-2100_c20220628.nc + + + huss Sa_shum_af + pr Faxa_prec_af + ps Sa_pbot_af + rlds Faxa_lwdn_af + rsds Faxa_swdn_af + tas Sa_tbot_af + uas Sa_u_af + vas Sa_v_af + + ... + + +To use alternative data, add a ``user_nl_datm_streams`` namelist file to your case with contents like so: + +:: + + Anomaly.Forcing.cmip6.ssp585:datafiles = /path/to/your/datafile + Anomaly.Forcing.cmip6.ssp585:meshfile = /path/to/meshfile/for/your/datafile + + ! List of Data types to use + ! Remove the variables you do NOT want to include in the anomaly forcing: + ! pr is precipitation + ! tas is temperature + ! huss is humidity + ! uas and vas are U and V winds + ! rsds is solare + ! rlds is LW down + Anomaly.Forcing.cmip6.ssp585:datavars = pr Faxa_prec_af, \ + tas Sa_tbot_af, \ + ps Sa_pbot_af, \ + huss Sa_shum_af, \ + uas Sa_u_af, \ + vas Sa_v_af, \ + rsds Faxa_swdn_af, \ + rlds Faxa_lwdn_af + +To instead disable anomaly forcing in ``ISSP`` compsets, the following can be added to the ``user_nl_datm`` file: + +:: + + anomaly_forcing = 'none' + +Note that other inputs are also set automatically for ``ISSP`` compsets, including CO2 (``co2tseries``), ozone (``preso3``), N deposition (``presndep``), and aerosols (``presaero``). + +The first and last years over which the present-day (base) climate should cycle are set through the ``DATM_YR_START`` and ``DATM_YR_END`` XML variables. diff --git a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-configuration.rst b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-configuration.rst index ec28a0d624..7b1a4d8ad0 100644 --- a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-configuration.rst +++ b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-configuration.rst @@ -352,13 +352,13 @@ When ``-irrig on`` is used ``build-namelist`` will try to find surface datasets Update the below, as ``queryDefaultNamelist.pl`` no longer exists. ``CLM_USRDAT_NAME`` - Provides a way to enter your own datasets into the namelist. The files you create must be named with specific naming conventions outlined in :ref:`creating-your-own-singlepoint-dataset`. To see what the expected names of the files are, use the ``queryDefaultNamelist.pl`` to see what the names will need to be. For example if your ``CLM_USRDAT_NAME`` will be "1x1_boulderCO", with a "navy" land-mask, constant simulation year range, for 1850, the following will list what your filenames should be: + Provides a way to enter your own datasets into the namelist. The files you create must be named with specific naming conventions outlined in :ref:`generic_single_point_runs`. To see what the expected names of the files are, use the ``queryDefaultNamelist.pl`` to see what the names will need to be. For example if your ``CLM_USRDAT_NAME`` will be "1x1_boulderCO", with a "navy" land-mask, constant simulation year range, for 1850, the following will list what your filenames should be: :: > cd $CTSMROOT/bld > queryDefaultNamelist.pl -usrname "1x1_boulderCO" -options mask=navy,sim_year=1850,sim_year_range="constant" -csmdata $CSMDATA - An example of using ``CLM_USRDAT_NAME`` for a simulation is given in Example :numref:`creating-your-own-singlepoint-dataset`. + An example of using ``CLM_USRDAT_NAME`` for a simulation is given in Example :numref:`generic_single_point_runs`. ``CLM_CO2_TYPE`` sets the type of input CO2 for either "constant", "diagnostic" or prognostic". If "constant" the value from ``CCSM_CO2_PPMV`` will be used. If "diagnostic" or "prognostic" the values MUST be sent from the atmosphere model. diff --git a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst index 2d6f58f317..a4333a2b29 100644 --- a/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst +++ b/doc/source/users_guide/setting-up-and-running-a-case/customizing-the-clm-namelist.rst @@ -186,7 +186,7 @@ Example: user_nl_clm namelist outputting some files in 1D Vector format hist_fincl5 = 'TG' hist_fincl6 = 'TG' hist_dov2xy = .true., .false., .false., .false. - hist_type2d_pertape = ' ', 'GRID', 'COLS', ' ' + hist_type1d_pertape = ' ', 'GRID', 'COLS', ' ' hist_nhtfrq = 0, -24, -24, -24 .. warning:: ``LAND`` and ``COLS`` are also options to the pertape averaging, but currently there is a bug with them and they fail to work. diff --git a/doc/source/users_guide/using-clm-tools/index.rst b/doc/source/users_guide/using-clm-tools/index.rst index 9b5318e5a2..b721b3e7d6 100644 --- a/doc/source/users_guide/using-clm-tools/index.rst +++ b/doc/source/users_guide/using-clm-tools/index.rst @@ -22,3 +22,4 @@ Using CLM tools creating-domain-files.rst observational-sites-datasets.rst cprnc.rst + paramfile-tools.md diff --git a/doc/source/users_guide/using-clm-tools/observational-sites-datasets.rst b/doc/source/users_guide/using-clm-tools/observational-sites-datasets.rst index 82169e8238..71f4783b92 100644 --- a/doc/source/users_guide/using-clm-tools/observational-sites-datasets.rst +++ b/doc/source/users_guide/using-clm-tools/observational-sites-datasets.rst @@ -9,6 +9,6 @@ Observational Sites Datasets .. todo:: Update this. -There are two ways to customize datasets for a particular observational site. The first is to customize the input to the tools that create the dataset, and the second is to overwrite the default data after you've created a given dataset. Depending on the tool it might be easier to do it one way or the other. In Table :numref:`reqd-files-table` we list the files that are most likely to be customized and the way they might be customized. Of those files, the ones you are most likely to customize are: ``fatmlndfrc``, ``fsurdat``, ``faerdep`` (for DATM), and ``stream_fldfilename_ndep``. Note ``mksurfdata_esmf`` as documented previously has options to overwrite the vegetation and soil types. For more information on this also see :ref:`creating-your-own-singlepoint-dataset`. +There are two ways to customize datasets for a particular observational site. The first is to customize the input to the tools that create the dataset, and the second is to overwrite the default data after you've created a given dataset. Depending on the tool it might be easier to do it one way or the other. In Table :numref:`reqd-files-table` we list the files that are most likely to be customized and the way they might be customized. Of those files, the ones you are most likely to customize are: ``fatmlndfrc``, ``fsurdat``, ``faerdep`` (for DATM), and ``stream_fldfilename_ndep``. Note ``mksurfdata_esmf`` as documented previously has options to overwrite the vegetation and soil types. For more information on this also see :ref:`generic_single_point_runs`. -Another aspect of customizing your input datasets is customizing the input atmospheric forcing datasets; see :ref:`creating-your-own-singlepoint-dataset` for more information on this. +Another aspect of customizing your input datasets is customizing the input atmospheric forcing datasets; see :ref:`generic_single_point_runs` for more information on this. diff --git a/doc/source/users_guide/using-clm-tools/paramfile-tools.md b/doc/source/users_guide/using-clm-tools/paramfile-tools.md new file mode 100644 index 0000000000..aaee301bcb --- /dev/null +++ b/doc/source/users_guide/using-clm-tools/paramfile-tools.md @@ -0,0 +1,73 @@ + +# Tools for working with parameter files + +This guide describes the features and usage of the `query_paramfile` and `set_paramfile` tools, located in `tools/param_utils/`. These utilities help users inspect and modify CLM parameter files. + +Note that you need to have the `ctsm_pylib` conda environment activated to use these tools. See Sect. :numref:`using-ctsm-pylib` for more information. + +## `query_paramfile` +**Purpose:** Print the values of one or more parameters from a CTSM parameter file (NetCDF format). + +**Features:** +- Print values for specified parameters or all. +- Optionally filter output by Plant Functional Types (PFTs) for PFT-specific parameters. + +For more information, do `tools/param_utils/query_paramfile --help`. + + +### Example usage + +Print all variables in a parameter file: +```bash +tools/param_utils/query_paramfile -i paramfile.nc +``` + +Print specific variables: +```bash +tools/param_utils/query_paramfile -i paramfile.nc jmaxha jmaxhd +``` + +Print values for specific PFTs: +```bash +tools/param_utils/query_paramfile -i paramfile.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept medlynslope +``` + +## `set_paramfile` +**Purpose:** Change values of one or more parameters in a CTSM parameter file (NetCDF format). + +**Features:** +- Modify parameter values for all or selected PFTs. +- Optionally drop PFTs not specified. +- Set parameter values to fill (missing) values using `nan`. +- Ensures safe file handling and checks for argument validity. + +Note that the output file must not already exist. + +For more information, do `tools/param_utils/set_paramfile --help`. + +### Example usage + +Change a scalar parameter: +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc jmaxha=51000 +``` + +Change a one-dimensional parameter (`mimics_fmet` has the `segment` dimension, length 4): +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mimics_fmet=0.1,0.2,0.3,0.4 +``` + +Change a one-dimensional parameter to be all one value (`mxmat` has the `pft` dimension, length 79): +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc mxmat=360 +``` + +Change a parameter for specific PFTs: +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass medlynintercept=99.9,100.1 medlynslope=2.99,1.99 mxmat=199 +``` + +Set a parameter to the fill value: +```bash +tools/param_utils/set_paramfile -i paramfile.nc -o output.nc -p needleleaf_evergreen_temperate_tree,c4_grass fleafcn=nan,nan +``` diff --git a/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.md b/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.md deleted file mode 100644 index 738af6b8ba..0000000000 --- a/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.md +++ /dev/null @@ -1,18 +0,0 @@ -.. _building-docs-multiple-versions: - -# Building multiple versions of the documentation - -There is a menu in the lower left of the webpage that lets readers switch between different versions of the documentation. Populating this menu involves a few steps. - -First, look at the `version_list` line in `docs/conf.py`. Edit that list as needed so it contains the name of each version you want. - -Next, you will need to build the documentation once for each version. To build a version called `latest`, you would first check out the corresponding version of the docs, then do: - -```shell -cd doc -./build_docs -r $HOME/path/to/build-dir -d -v latest -``` - -This will build the documentation in `$HOME/path/to/build-dir/versions/latest`. Open `$HOME/path/to/build-dir/versions/latest/html/index.html` to see the result. - -You can also leave off the `-v latest`, in which case the current Git branch name will be used as the version name. Note, though, that in this case you will need to manually create the `$HOME/path/to/build-dir/versions/branch-name/` directory if it doesn't already exist. diff --git a/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.rst b/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.rst new file mode 100644 index 0000000000..895dbf2a65 --- /dev/null +++ b/doc/source/users_guide/working-with-documentation/building-docs-multiple-versions.rst @@ -0,0 +1,29 @@ +.. _building-docs-multiple-versions: + +Building multiple versions of the documentation +=============================================== + +There is a menu in the lower left of the webpage that lets readers switch between different versions of the documentation. To build a website with this menu properly set up—so that all our versions appear and all the links work—you need to use ``docs/build_docs_to_publish`` instead of ``docs/build_docs``. + +Note that this is not necessary in order for you to contribute an update to the documentation. GitHub will test this automatically when you open a PR. But if you'd like to try, this will generate a local site for you in ``_publish/`` and then open it: + +.. literalinclude:: ../../../test/test_container_eq_ctsm_pylib.sh + :start-at: ./build_docs_to_publish + :end-before: VERSION LINKS WILL NOT RESOLVE + :append: CMD _publish/index.html # where CMD is open for Mac or wslview for Windows (Ubuntu VM) + +**Note:** This is not yet supported with Podman on Linux (including Ubuntu VM on Windows). See `doc-builder Issue #27: build_docs_to_publish fails on Linux (maybe just Ubuntu?) with Podman `_. It does work with Docker on Linux, though. + + +How this works +-------------- + +``build_docs_to_publish`` loops through the ``VERSION_LIST`` variable in ``doc/version_list.py``: + +.. literalinclude:: ../../../version_list.py + :start-at: version of certain files we want to preserve + :end-before: End version definitions + +For each member of ``VERSION_LIST``, ``build_docs_to_publish`` checks out its ``ref``, then builds the documentation in a build directory. (``LATEST_REF`` is set because some files, folders, and submodules are important for how the build works and need to stay the same for each build.) Once the build is complete, ``build_docs_to_publish`` should reset your local repo copy (CTSM clone) to how it was before you called ``build_docs_to_publish``. + +Next, ``build_docs_to_publish`` moves the HTML files from the build directory to the publish directory. The publish directory has a structure that matches the paths in the version dropdown menu's links. If a member of ``VERSION_LIST`` has ``landing_version=True``, its HTML will be at the top level. That makes it simple for people to find the default version of the docs at https://escomp.github.io/CTSM, rather than having to drill down further into something like ``https://escomp.github.io/CTSM/versions/latest``. diff --git a/doc/source/users_guide/working-with-documentation/building-docs-original-wiki.md b/doc/source/users_guide/working-with-documentation/building-docs-original-wiki.md index 20a739847f..63acab53a7 100644 --- a/doc/source/users_guide/working-with-documentation/building-docs-original-wiki.md +++ b/doc/source/users_guide/working-with-documentation/building-docs-original-wiki.md @@ -2,518 +2,9 @@ # ⚠️ Original docs documentation from the GitHub Wiki -.. todo:: +.. warning:: ⚠️⚠️⚠️WARNING⚠️⚠️⚠️ - This page contains documentation that (a) is more complicated than you probably require and (b) has not been fully checked for accuracy with the latest documentation setup. Unless you have a very good reason, you should probably go to :ref:`docs-intro-and-recommended`. + The linked page contains documentation that (a) is more complicated than you probably require and (b) has not been fully checked for accuracy with the latest documentation setup. Unless you have a very good reason, you should probably go to :ref:`docs-intro-and-recommended`. - -## Table of contents - -* [Intro to the CTSM documentation](#intro-to-the-ctsm-documentation) -* [What do I, as a contributor, need to do to update the tech note for my new feature?](#what-do-i-as-a-contributor-need-to-do-to-update-the-tech-note-for-my-new-feature) -* [Quick start to building the documentation](#quick-start-to-building-the-documentation) -* [Overview of the recommended build method and alternative methods](#overview-of-the-recommended-build-method-and-alternative-methods) -* [One-time setup needed for a given machine](#one-time-setup-needed-for-a-given-machine) - * [Prerequisites](#prerequisites) - * [Install Docker and download the required container](#install-docker-and-download-the-required-container) - * [Install git-lfs](#install-git-lfs) -* [Management of image files](#management-of-image-files) - * [Obtaining the image files](#obtaining-the-image-files) - * [Seeing what files are tracked by git-lfs](#seeing-what-files-are-tracked-by-git-lfs) - * [Adding a new image file type](#adding-a-new-image-file-type) -* [Procedure for building the html documentation](#procedure-for-building-the-html-documentation) - * [Recommended directory structure for these directions](#recommended-directory-structure-for-these-directions) - * [Initial steps for building the documentation](#initial-steps-for-building-the-documentation) - * [Building a test version of the documentation for your own review](#building-a-test-version-of-the-documentation-for-your-own-review) - * [Previewing the built documentation](#previewing-the-built-documentation) - * [Updating the official html documentation](#updating-the-official-html-documentation) - * [Dealing with errors](#dealing-with-errors) - * [Input/output error](#inputoutput-error) - * [Adding a new version](#adding-a-new-version) -* [Building a pdf of the tech note](#building-a-pdf-of-the-tech-note) -* [Resources for learning markup with reStructuredText and using Sphinx.](#resources-for-learning-markup-with-restructuredtext-and-using-sphinx) -* [Appendix: Other build methods](#appendix-other-build-methods) - * [Running build_docs from all of these methods](#running-build_docs-from-all-of-these-methods) - * [Relying more heavily on the Docker container](#relying-more-heavily-on-the-docker-container) - * [Launching the Docker image](#launching-the-docker-image) - * [Building the documentation](#building-the-documentation) - * [Viewing the built documentation](#viewing-the-built-documentation) - * [Committing to git repositories](#committing-to-git-repositories) - * [Installing required software on your desktop/laptop (instructions for Mac)](#installing-required-software-on-your-desktoplaptop-instructions-for-mac) - * [Prerequisites / assumptions](#prerequisites--assumptions) - * [Installing Sphinx and the necessary Sphinx theme](#installing-sphinx-and-the-necessary-sphinx-theme) - * [Installing latexmk](#installing-latexmk) - * [Optionally installing components needed to build the PDF](#optionally-installing-components-needed-to-build-the-pdf) - * [Building the documentation on cheyenne](#building-the-documentation-on-cheyenne) -* [Appendix: Editing tips](#appendix-editing-tips) - -.. _intro-to-the-ctsm-documentation: - -## Intro to the CTSM documentation - -The CTSM documentation is written using [reStructuredText markup language](http://www.sphinx-doc.org/en/stable/rest.html). ReStructuredText is a markup language, like HTML, markdown or latex. (See below for more resources on learning reStructuredText.) - -[Sphinx](http://www.sphinx-doc.org/en/stable/index.html) is a python tool for publishing reStructuredText documents in other formats such as HTML and PDF. - -The CTSM documentation source is stored in the `doc/source` directory of the main CTSM repository. The built documentation is stored in the gh-pages branch of the [ESCOMP/ctsm-docs repository](https://github.com/escomp/ctsm-docs). The Sphinx-generated HTML pages are accessible from URL [https://escomp.github.io/ctsm-docs/](https://escomp.github.io/ctsm-docs/) - -.. _what-do-i-as-a-contributor-need-to-do-to-update-the-tech-note-for-my-new-feature: - -## What do I, as a contributor, need to do to update the tech note for my new feature? - -As a contributor to CTSM, you don't necessarily need to know all of the information laid out below. If you need to update the tech note for some changes you have made, you should add or edit the necessary rst files in `doc/source/tech_note` on your CTSM branch. Then simply commit your changes to these rst files just as you would do for changes to Fortran source files, and include these rst changes in your Pull Request. Ideally these documentation changes will be on the same branch and the same Pull Request as the related source file changes, but they can also be made later, on a separate branch, and submitted to us via a separate Pull Request. In either case, the CTSM maintainers will review the documentation changes in the Pull Request and merge these changes to CTSM's master branch when ready. - -You do *not* need to open a Pull Request for the built documentation: one of the CTSM maintainers will rebuild the documentation later. However, if you have made more than minor edits to the documentation, we prefer if you have tested the documentation build to make sure there are no errors in the edited rst files and that the html documentation appears how you intended. But you do *not* need to send us or point us to your rebuilt documentation: One of the CTSM maintainers will build the documentation themselves to review the build. - -If you need to build the documentation, and/or if you need to change or add any images, you will need to keep reading for instructions on installing and using `git lfs`. - -.. _quick-start-to-building-the-documentation: - -## Quick start to building the documentation - -This documentation gives detailed explanations of a number of workflows that can be used in building the documentation. This section briefly summarizes a single, recommended workflow, so that you can get started quickly. - -First, as one-time setup, you will need to install: -- A recent version of git -- A recent version of python (python3.5 or later), available as `python3` -- [Docker](https://www.docker.com/products/docker-desktop) -- [Git LFS](https://git-lfs.github.com/), including running `git lfs install` (required if you will be adding or editing any image files, otherwise you can skip Git LFS for now) You should then launch Docker's desktop application, and run `docker pull escomp/base`. - -Then (if you haven't already done so), clone the CTSM repository (this example assumes that it appears in a path like this: `~/ctsm-repos/ctsm`). Check out the branch from which you want to build the documentation, then run `./bin/git-fleximod update --optional`. - -Then build the documentation like this: - -```shell -mkdir ~/ctsm-repos/ctsm-docs -cd ~/ctsm-repos/ctsm/doc -./build_docs -b ~/ctsm-repos/ctsm-docs -d -``` - -and view it by opening the file `~/ctsm-repos/ctsm-docs/html/index.html`. - -.. _overview-of-the-recommended-build-method-and-alternative-methods: - -## Overview of the recommended build method and alternative methods - -The directions here assume use of a documentation build method where you are working on your local desktop / laptop, and have some basic software installed there, but use a Docker container-based method for doing the actual documentation build. Other methods are documented in an [Appendix below](#appendix-other-build-methods). We prefer the Docker-based method because it avoids the complexity of installing multiple packages on your local machine, and ensures that all people building the documentation are using the same versions of Sphinx and other utilities. The main method described here leverages this Docker container while still allowing you to remain in the comfort of your own local environment. However, some people may prefer [the more Docker-centric method described in the Appendix](#relying-more-heavily-on-the-docker-container), because it has even fewer initial installation requirements. **That solution may also work more smoothly on a Windows system, though we have run the primary method successfully on Windows after ensuring that we have an executable named python3 installed.** - -.. _one-time-setup-needed-for-a-given-machine: - -## One-time setup needed for a given machine - -.. _prerequisites: - -### Prerequisites - -These instructions assume that you have the following available on your machine: - -- A recent version of git, ideally configured according to the recommendations in . - -- A recent version of python (python3.5 or later) - - Note that this must be available as `python3` (not just `python`). If your python installation did not create a `python3` command, you will need to create an alias or symbolic link so that python can be invoked via `python3`. On Windows, one developer reports success after copying `C:\Users\USERNAME\AppData\Local\Programs\Python\Python39\python.exe` to `python3.exe` (in the same location). - -.. _install-docker-and-download-the-required-container: - -### Install Docker and download the required container - -Download Docker to your personal desktop or laptop from [Docker](https://www.docker.com/products/docker-desktop) and follow the instructions. Then launch the Docker application. - -You can then obtain the necessary Docker container by running: - -```shell -docker pull escomp/base -``` - -Note: For some versions of Docker on a Mac, Docker's CPU usage can remain at 100% even after the documentation build process exits. This seems to be connected with the issue reported in . The issue probably arises because the `build_docs` script mounts your entire home directory in the docker container. You can work around this issue by quitting Docker when you are done building the docs, or by unchecking the option, "Use gRPC FUSE for file sharing" in Docker's preferences. However, note that unchecking that option might have some negative consequences, so you may want to re-enable it when you are done building the documentation if you use Docker for other purposes as well. - -.. _install-git-lfs: - -### Install git-lfs - -Git-LFS (Large File Support) is needed if you will be adding or editing any image files. **If there is even a chance that you will be doing so, it is best to go ahead and install this now, while you are thinking about it. This will prevent you from accidentally committing an image file directly to the repository, which is something we try very hard to avoid.** This is also needed for building the documentation if *not* using the Docker-based method. - -Follow the instructions on the [Git LFS page](https://git-lfs.github.com/) for installing git-lfs on your platform. (On a Mac using homebrew, this can be done with `brew install git-lfs`.) - -**Although the actual installation only needs to be done once per machine, each user who wants to use it must then run the following command to set up git-lfs for your user account on this machine:** - -```shell -git lfs install -``` - -.. _management-of-image-files: - -## Management of image files - -**If you are adding or changing any image files, be sure you have installed git-lfs according to the [above instructions](#Install-git-lfs), including running** `git lfs install`. If you are not sure whether you have already run `git lfs install`, it is safe to rerun that command to be sure. - -Image files are tracked using Git LFS (Large File Support). The images are stored somewhere on GitHub, but aren't actually part of the main repository. Instead, the repository contains small text files that direct git-lfs to the appropriate storage location on GitHub. - -For the most part, you can remain blissfully unaware of the details of how this works. For example, if you want to add a new image file to the repository, the process is just like adding any other file to the repository, as long as you have installed git-lfs. In addition, building the documentation does not require any additional steps - again, as long as you have installed git-lfs. But here are some notes that may help you: - -.. _obtaining-the-image-files: - -### Obtaining the image files - -We have configured git-lfs for CTSM so that it does *not* pull down any images by default, since these images are only rarely needed. If you have cloned CTSM or updated an existing clone and want to get the latest version of the image files, you can run: - -```shell -git lfs pull --exclude="" --include="" -``` - -Note that this is done automatically when building the documentation via the `build_docs` command, as described below. - -.. _seeing-what-files-are-tracked-by-git-lfs: - -### Seeing what files are tracked by git-lfs - -To see what file types (extensions) are tracked by git-lfs, run the following from within your CTSM clone: - -```shell -git lfs track -``` - -To see every file currently being managed by git-lfs, run the following from within your CTSM clone: - -```shell -git lfs ls-files --exclude="" --include="" -``` - -.. _adding-a-new-image-file-type: - -### Adding a new image file type - -If you want to add a new image file type, with an extension not currently being managed by git-lfs, use this process **before** adding the file to the git repository. Note that this assumes that you are in the top-level directory of your CTSM clone: - -```shell -git lfs track "*.extension" -git add .gitattributes -git commit -m "Use git-lfs for *.extension files" -``` - -Then you can check that it worked by running `git lfs track`. Finally, you can add your new file with standard `git add` and `git commit` commands. - -.. _procedure-for-building-the-html-documentation: - -## Procedure for building the html documentation - -.. _recommended-directory-structure-for-these-directions: - -### Recommended directory structure for these directions - -In order to give concrete examples, we assume a particular directory structure for these directions: We assume you have a directory in your home directory named `ctsm-repos`, and that you clone the main CTSM repository and the ctsm-docs repository inside that directory. So you will have something like: - -``` -~/ctsm-repos/ctsm -~/ctsm-repos/ctsm-docs -``` - -It's fine for your `ctsm` directory to be named something more specific, for example, including the branch name as is recommended [on the CTSM GitHub wiki](https://github.com/ESCOMP/CTSM/wiki/Quick-start-to-CTSM-development-with-git). It is also fine to use a different directory organization, but **if you are using the preferred, Docker container-based method, then both your main CTSM clone and the ctsm-docs clone must reside somewhere under your home directory.** - -.. _initial-steps-for-building-the-documentation: - -### Initial steps for building the documentation - -The following procedure assumes you have a clone of CTSM that is checked out at the branch from which you want to build documentation. - -Before building the documentation, you need to do the following: - -First, run `./bin/git-fleximod update --optional` to update your submodules; this is needed both to get `PTCLM` (which has some files referenced by the documentation build) and to get the `doc-builder` submodule that is used to do the build. **Note the use of the** `--optional` **flag here; this is needed because** `doc-builder` **is an optional submodule.** - -Next, if it's not already running, launch the Docker desktop application. - -Finally, if you haven't updated the `escomp/base` image in a while, you may want to update to the latest version with: - -```shell -docker pull escomp/base -``` - -.. _building-a-test-version-of-the-documentation-for-your-own-review: - -### Building a test version of the documentation for your own review - -If you are just building a test version of the documentation for your own review, then you don't need to do anything with the `ctsm-docs` GitHub repository. Instead, you can create a directory according to the [above recommendations for directory structure](#Recommended-directory-structure-for-these-directions). - -(However, you may want to follow a procedure similar to the one [documented below for updating the official html documentation](#Updating-the-official-html-documentation): That allows you to do an incremental build rather than building the whole documentation from scratch, which can take about 1/2 hour. You can just avoid committing and pushing when following that procedure, if all you want is to preview the documentation for your own review.) - -If you are using the [above recommendations for directory structure](#Recommended-directory-structure-for-these-directions), do the following from the `doc` directory of your ctsm clone: - -```shell -mkdir ~/ctsm-repos/ctsm-docs -./build_docs -b ~/ctsm-repos/ctsm-docs -d -``` - -(where the `-d` flag instructs `build_docs` to use the `escomp/base` Docker container to do the build). - -.. _previewing-the-built-documentation: - -### Previewing the built documentation - -You can then view your changes in a local browser window using a command like one of these: - -```shell -open ~/ctsm-repos/ctsm-docs/html/index.html -firefox ~/ctsm-repos/ctsm-docs/html/index.html -konqueror ~/ctsm-repos/ctsm-docs/html/index.html -``` - -**Note that the version dropdown menu will not work in these local previews.** - -.. _updating-the-official-html-documentation: - -### Updating the official html documentation - -If you want to update the official html documentation, follow the below procedure. (You can also use this procedure if you only intend to build a test version for your own review. This can be helpful because this procedure allows for an incremental build rather than building the entire documentation from scratch.) First clone the ctsm-docs repository to somewhere outside of your main CTSM repository. Here we'll assume that you are using the [above recommendations for directory structure](#Recommended-directory-structure-for-these-directions). - -```shell -cd ~/ctsm-repos -git clone https://github.com/ESCOMP/ctsm-docs.git -``` - -Or, if you already have a copy of ctsm-docs, make sure it is up-to-date as follows: - -```shell -cd ~/ctsm-repos/ctsm-docs -git checkout -- . -git pull -``` - -Next, `cd` to the `doc` directory of your main CTSM clone: - -```shell -cd ~/ctsm-repos/ctsm/doc -``` - -Then, to perform an incremental build (only updating files that need to be updated), run the following. **Note that the following assumes you are building the documentation for the master branch; if you are building the documentation for one of the release branches, replace** `master` **with the appropriate version.** - -```shell -./build_docs -b ~/ctsm-repos/ctsm-docs/versions/master -d -``` - -(where the `-d` flag instructs `build_docs` to use the `escomp/base` Docker container to do the build). - -However, if there have been significant changes to the documentation since the last documentation build, and you are intending to push the new build back to GitHub, it's probably best to first clean out the old build in order to remove any no-longer-relevant files. The downside of doing this is that the full documentation rebuild will take about 1/2 hour: - -```shell -./build_docs -b ~/ctsm-repos/ctsm-docs/versions/master -d -c -``` - -(where the `-c` flag instructs `build_docs` to first run `make clean` in the specified directory). - -You can preview the documentation as noted [above](#Previewing-the-built-documentation), but now the `index.html` file will be in `~/ctsm-repos/ctsm-docs/versions/master/html/index.html`. **Note that the version dropdown menu appear when you preview the documentation locally, but if you haven't built all the versions, some version links will lead to nonexistent files.** - -Then commit and push the rebuilt documentation as follows: - -```shell -cd ~/ctsm-repos/ctsm-docs -git add . -git commit -m "YOUR MESSAGE" -git push origin gh-pages -``` - -**Note if you're using a Windows machine: You may see a lot of warnings like** `warning: LF will be replaced by CRLF in path/to/some/file`. **These seem safe to ignore in this case.** - -Within a few minutes, the official documentation at [https://escomp.github.io/ctsm-docs/](https://escomp.github.io/ctsm-docs/) will be updated automatically. Note that you can also fork the ctsm-docs repository and push the gh-pages branch to your fork to allow others to preview the documentation before you overwrite the official documentation. - -.. _dealing-with-errors: - -### Dealing with errors - -.. _inputoutput-error: - -#### Input/output error - -Some people have reported an error like this: - -```shell -Exception occurred: - File "/usr/lib64/python3.6/shutil.py", line 205, in copystat - follow_symlinks=follow) -OSError: [Errno 5] Input/output error -The full traceback has been saved in /tmp/sphinx-err-hhpnw_mt.log, if you want to report the issue to the developers. -``` - -Rerunning the build command will restart it where it left off, and our experience is that that will let you complete the build - though you may need to rerun the build command a few times before it finally finishes successfully. - -.. _adding-a-new-version: - -### Adding a new version - -If you want to add a new version of the built documentation, so that it appears in the dropdown menu, follow this process: - -- In the ctsm-docs repository, create a new directory under `versions` with the name of the new version. Our convention is that this name should be the same as the name of the branch in the main CTSM repository that contains this version of the documentation source. -- In the ctsm-docs repository, add a line in the file `versions/versions.json`. Note that each line is a colon-delimited mapping; the name before the colon is the directory name (i.e., the name of the directory you just created, which is the same as the branch name in the CTSM repository), and the name after the colon is the version name that you want to appear in the dropdown menu. Make sure there is a comma at the end of every line except for the last in this file. -- In the main CTSM repository, check out the branch from which you want to build the documentation. -- In the main CTSM repository, edit the file `doc/source/conf.py`: edit the `version` and `release` variables (these should probably be the same as the version name that will appear in the dropdown menu) and anything else that needs to be changed for this release (`copyright`, etc.) -- Build the documentation as described above, being sure to specify your new directory for `BUILDDIR`. - -.. _building-a-pdf-of-the-tech-note: - -## Building a pdf of the tech note - -To build a pdf of the tech note (note that we currently do not support building a pdf of the user's guide), do the following from the `doc` directory of your CTSM clone: - -```shell -mkdir ~/ctsm-repos/ctsm-docs-pdf -./build_docs -b ~/ctsm-repos/ctsm-docs-pdf -d -t latexpdf -``` - -The pdf will appear at `~/ctsm-repos/ctsm-docs-pdf/latex/clmdoc.pdf`. - -.. _resources-for-learning-markup-with-restructuredtext-and-using-sphinx: - -## Resources for learning markup with reStructuredText and using Sphinx. - -* [reStructuredText Primer](http://www.sphinx-doc.org/en/stable/rest.html) -* [ReST Syntax](https://wiki.typo3.org/ReST_Syntax) -* [Sphinx (including how to get Sphinx)](http://www.sphinx-doc.org/en/stable/) -* [reStructured syntax](http://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html#tables) - -.. _appendix-other-build-methods: - -## Appendix: Other build methods - -.. _running-build_docs-from-all-of-these-methods: - -### Running build_docs from all of these methods - -With all of these alternative methods, you can still use `build_docs` commands similar to those given elsewhere in these instructions, **but without the** `-d` **argument**. - -.. _relying-more-heavily-on-the-docker-container: - -### Relying more heavily on the Docker container - -The above instructions still require you to have recent versions of python, git and git-lfs. Alternatively, you can use a method that relies more heavily on the Docker container, avoiding the need for other software to be installed. **This is a very reasonable alternative method, which some people may prefer - especially if you are already comfortable using Docker containers.** - -To start, [follow the instructions for installing Docker and downloading the required container](#Install-Docker-and-download-the-required-container). Then use the instructions below. - -.. _launching-the-docker-image: - -#### Launching the Docker image - -Assuming that both your main CTSM repository and ctsm-docs repository are cloned somewhere within your home directory, you can launch the Docker image like this: - -```shell -docker run -i -t --rm -v ${HOME}:/home/user/mounted_home escomp/base -``` - -This tells docker to *run* the container (`docker run`), interactively (`-i`) with a terminal (`-t`), to clean up the container after running (`--rm`), and most importantly, to map your local `${HOME}` directory into the `/home/user/mounted_home` directory inside the container (`-v ${HOME}:/home/user/mounted_home`). - -After running this command, you will see a command prompt, but now you will be in the Docker environment. This means: -- You will be able to see directories and files contained in your local `${HOME}` directory, but nothing else. `${HOME}/mounted_home` in the Docker environment refers to `${HOME}` in your native system environment. Changed or added files here will be visible in your native system, in the appropriate subdirectory of your `${HOME}` directory. This will become important later in these directions, since you will view the generated documentation from your native environment. -- Any commands you run from this environment will use the commands bundled with the Docker image, *not* the commands in your native system environment. - -When you are done using this Docker image, simply type `exit` from the command prompt. - -For far more information on running Docker containers, you can look at [Docker's documentation](https://docs.docker.com/engine/reference/run/). - -.. _building-the-documentation: - -#### Building the documentation - -You can use `build_docs` commands like those given elsewhere in these instructions, **but without the** `-d` **argument** (the `-d` argument launches a new Docker image, but in this case, you are already inside a Docker image, so this is unnecessary). Keep in mind that you will need to specify the path to the documentation build directory according to the Docker file system, not your native file system. - -.. _viewing-the-built-documentation: - -#### Viewing the built documentation - -You can view the built documentation [as described above](#Previewing-the-built-documentation). However, in order to find the `index.html` file, keep in mind the mapping between directories in Docker's environment and those in your native environment. - -.. _committing-to-git-repositories: - -#### Committing to git repositories - -Any local git configuration information is *not* picked up by the Docker image. Therefore, if you want to make any git commits from the Docker terminal session, you will need to do one of the following: -- After doing the `docker run` command as above, copy `/home/user/mounted_home/.gitconfig` to `/home/user/.gitconfig`. -- Or run the `git config` command to set your name and email, as described [here](https://github.com/ESCOMP/CTSM/wiki/Recommended-git-setup#required-settings) - -.. _installing-required-software-on-your-desktoplaptop-instructions-for-mac: - -### Installing required software on your desktop/laptop (instructions for Mac) - -If you choose not to use the Docker-based method described above, you can instead install Sphinx and some related packages locally. **We do not recommend this method, because it can be more challenging to set up and maintain than the method described above. In addition, the instructions here are not regularly tested, so may no longer work. Finally, the use of this method can mean that your Sphinx version differs from the one used to generate the official documentation, which can potentially cause problems.** However, if you cannot use the Docker method for some reason, then you can try this alternative. - -.. _prerequisites--assumptions: - -#### Prerequisites / assumptions - -The following documentation assumes that you have the following available on your machine: - -- A recent version of git, ideally configured according to the recommendations in . - -- A recent version of python (python3.5 or later) - -- If using a Mac, the [Homebrew package manager](https://brew.sh/) - -.. _installing-sphinx-and-the-necessary-sphinx-theme: - -#### Installing Sphinx and the necessary Sphinx theme - -Sphinx and the necessary Sphinx theme are python packages that can be installed with `pip install`: - -```shell -pip install sphinx -pip install git+https://github.com/esmci/sphinx_rtd_theme.git@version-dropdown-with-fixes -``` - -For more details on installing Sphinx, see the [Sphinx installation docs](http://www.sphinx-doc.org/en/stable/install.html). - -*You may then need to update your PATH environment variable to include the path to the* `sphinx-build` *script.* - -.. _installing-latexmk: - -#### Installing latexmk - -`latexmk` is needed both for building the PDF and for creating the equations in the html documentation. On a Mac using homebrew, this can be installed with: - -```shell -brew cask install mactex -``` - -Note that you will need to open a new terminal window for the latexmk tool to be added to your path (it is in `/Library/TeX/texbin/latexmk`, which should have been added to your path by the above `brew` command). - -.. _optionally-installing-components-needed-to-build-the-pdf: - -#### Optionally installing components needed to build the PDF - -If you want to build the PDF (not just the html web pages), you *may* also need rst2pdf. This can be installed with: - -```shell -pip install rst2pdf -``` - -.. _building-the-documentation-on-cheyenne: - -### Building the documentation on cheyenne - -Finally, although we haven't figured out a way to view the built documentation on cheyenne, you can test the build there, and then transfer the files to your local machine for viewing. - -To do so, first ensure that you are using the `git` and `python` modules on cheyenne rather than the default system versions, by doing `module load git` and `module load python`. (Note that the default git module has `git lfs` bundled with it.) - -Then, the first time you build the documentation, do this one-time setup: - -```shell -pip install --user sphinx -pip install --user sphinxcontrib-programoutput -pip install --user git+https://github.com/esmci/sphinx_rtd_theme.git@version-dropdown-with-fixes -``` - -and add the following to your `.bashrc` or similar file if using bash: - -```bash -export PATH=/glade/u/home/$USER/.local/bin:$PATH -``` - -or, if you're using tcsh, add to your `.tcshrc`: - -```tcsh -setenv PATH /glade/u/home/$USER/.local/bin:$PATH -``` - -.. _appendix-editing-tips: - -## Appendix: Editing tips -- Please don't add manual line breaks when writing text, as this harms searchability. (Note that it's fine to do this in multi-line `:math:` blocks.) For more information, see [text-linebreaks](https://github.com/ESCOMP/CTSM/issues/2135#issuecomment-1764999337). -- You can write the degree symbol ° with Opt-Shift-8 on Mac or Alt+0176 on Windows. Note that this is different from the [masculine ordinal indicator](https://en.wikipedia.org/wiki/Ordinal_indicator) º (typed with Opt-0 on Mac). This is much cleaner and more searchable than using RestructuredText syntax to write a superscript-o! -- Whenever possible, please give equations meaningful labels. E.g., for [eq. 2.26.2](https://escomp.github.io/ctsm-docs/versions/release-clm5.0/html/tech_note/Crop_Irrigation/CLM50_Tech_Note_Crop_Irrigation.html#equation-25-2), `:label: gdds_for_cfts`) instead of numbers (`:label: 25.2`). Numeric labels become obsolete—as you can see in that example!—whenever new equations and/or sections are added. (Note that the equation numbering in the rendered HTML is automatic.) -- Tables defined with the [`:table:` directive](https://docutils.sourceforge.io/docs/ref/rst/directives.html#table) can be annoying because they're very sensitive to the cells inside them being precisely the right widths, as defined by the first `====` strings. If you don't get the widths right, you'll see "Text in column margin" errors. Instead, define your tables using the [`:list-table:`](https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table) directive. \ No newline at end of file +If you're really sure you want to look at the old documentation instructions, they are preserved [here](https://github.com/ESCOMP/CTSM/wiki/Directions-for-editing-CLM-documentation-on-github-and-sphinx/af29e2b37c07581a8ebacaa49e7a9b0ecf6bb7f3). diff --git a/doc/source/users_guide/working-with-documentation/building-docs-prereqs-mac.md b/doc/source/users_guide/working-with-documentation/building-docs-prereqs-mac.md new file mode 100644 index 0000000000..c5629ba0ea --- /dev/null +++ b/doc/source/users_guide/working-with-documentation/building-docs-prereqs-mac.md @@ -0,0 +1,123 @@ +.. _building-docs-prereqs-mac: + +# Initial setup: Mac + +Note that you may need administrator privileges on your Mac for the installation steps detailed here. + +.. _building-docs-git-tools: + +## Python +To test whether you already have the required Python version, open a Terminal window and try the following: +```shell +python3 --version +``` + +If python3 is already set up, you'll see a version number. If that version is 3.7 or later, you should be ready as far as Python goes; continue to :ref:`additional-reqs`. + +If not, recent versions of macOS should print a messsage saying, "xcode-select: No developer tools were found, requesting install." A dialog box will then pop up that says, "The 'python3' command requires the command line developer tools. Would you like to install the tools now?" Press Install and go through the installation process. This will take a while; once it's done, test by doing ``python3 --version`` again. (You may need to open a new Terminal window.) If the printed version number looks good, continue to :ref:`additional-reqs`. + +.. + The paragraph above was tested 2025-04-25 on a fresh-ish installation of macOS 15.3.2. + +If instead `python3` gives "command not found," or the version is less than 3.7, you might need to install Python; continue to :ref:`aliasing-python3-to-python`. Otherwise, continue to :ref:`additional-reqs`. + +.. _aliasing-python3-to-python: + +### Aliasing `python3` to `python` +Try the same command as above, but instead of `python3` just do `python` (no number). If that version is 3.7 or later, you can tell your Mac that when you say `python3` you want it to use `python`: +```bash +echo alias python3="$(which python)" >> ~/.bashrc +echo alias python3="$(which python)" >> ~/.zshrc +``` + +This will make it so that bash scripts, like what we use to build our docs, know what to do for `python3`. `python3` will also be available in new Terminal sessions if your shell is `zsh` (the default since macOS 10.15) or `bash`. + +If you were able to do this, you can continue to :ref:`additional-reqs`. If not, continue to the next section. + +### Conda +If your `python` doesn't exist or is too old, we suggest using Python via Conda. First, check whether you already have Conda installed: :ref:`do-i-already-have-conda` If not, install Conda (:ref:`installing-conda-for-docs`), then come back here. + +Try this to check the Python version in the `base` Conda environment: +```shell +conda run -n base python3 --version +``` + +Repeat with all your Conda environments as needed until you find one that's Python 3.7 or later. Let's say your `ENVNAME` environment works. In that case, just make sure to do `conda activate ENVNAME` before running the commands in the documentation-building instructions. + +.. _additional-reqs: + +## Additional requirements + +.. _container-or-conda-mac: + +### Container software or Conda environment +We recommend building the software in what's called a container—basically a tiny little operating system with just some apps and utilities needed by the doc-building process. This is nice because, if we change the doc-building process in ways that require new versions of those apps and utilities, that will be completely invisible to you. You won't need to manually do anything to update your setup to work with the new process; it'll just happen automatically. + +We recommend using the container software Podman, which you can install with Homebrew. (:ref:`install-homebrew-mac`) + +1. Install Podman with `brew install podman`. +1. Set up and start a Podman "virtual machine" with `podman machine init --now`. +1. Test your installation by doing `podman run --rm hello-world`. If it worked, you should see ASCII art of the Podman logo. + +You may not be able to install Podman or any other containerization software, so there is an alternative method: a Conda environment. + +1. Install Conda, if needed (see :ref:`installing-conda-for-docs`). +1. Follow the instructions for setting up the `ctsm_pylib` Conda environment in Sect. :numref:`using-ctsm-pylib`. + +.. _docs-git-tools: + +### Git tools +Note: Do this section after handling Python, because the Python installation process might bring the Git tools with it. + +To test whether you have the required Git tools already, open a Terminal window and try the following: +```shell +git --version +git-lfs --version +``` + +If either of those fail with "command not found," you'll need to install them. The recommended way is with Homebrew. (:ref:`install-homebrew-mac`) + +2. Use Homebrew to [install Git](https://formulae.brew.sh/formula/git#default), if needed. +3. Use Homebrew to [install Git LFS](https://formulae.brew.sh/formula/git-lfs#default), if needed. + +## Frequently-asked questions + +.. _what-kind-of-mac-chip: + +### What kind of chip does my Mac have? +For certain steps in this installation process, you may need to know whether your Mac has an Intel (`x86_64`) or an Apple Silicon (`arm64`) chip. If you don't know, visit Apple's [Mac computers with Apple silicon](https://support.apple.com/en-us/116943) page for instructions. + +.. _install-homebrew-mac: + +### How do I install Homebrew? +1. Install Homebrew using the instructions at https://brew.sh/. Make sure to follow the instructions during this process for adding Homebrew to your path. +1. Check your installation by making sure that `brew --version` doesn't error. + +.. _do-i-already-have-conda: + +### Do I already have Conda installed? +You can check whether you have Conda installed like so: +```shell +conda env list +``` + +If that shows you something like +``` +# conda environments: +# +base /Users/you/... +another_env /Users/you/.../... +... +``` + +instead of the "command not found" error, then you do have conda installed! (Note that the second column doesn't really matter.) + +.. _installing-conda-for-docs: + +### How do I install Conda? +We suggest installing Conda, if needed, via Miniforge: + +1. [Download Miniforge](https://conda-forge.org/download/) and install it. (:ref:`what-kind-of-mac-chip`) You can also [install Miniforge via Homebrew](https://formulae.brew.sh/cask/miniforge#default), if you already have that installed. (:ref:`install-homebrew-mac`) +2. Activate Conda permanently in your shell by opening a new Terminal window and doing `conda init "$(basename $SHELL)"`. + +You should now have `conda` and an up-to-date version of `python3` available, although will need to open another new Terminal window for it to work. \ No newline at end of file diff --git a/doc/source/users_guide/working-with-documentation/building-docs-prereqs-windows.md b/doc/source/users_guide/working-with-documentation/building-docs-prereqs-windows.md new file mode 100644 index 0000000000..ceb701b5cf --- /dev/null +++ b/doc/source/users_guide/working-with-documentation/building-docs-prereqs-windows.md @@ -0,0 +1,130 @@ +.. _building-docs-prereqs-windows: + +# Initial setup: Windows + +Note that you may need administrator privileges on your PC (or approval from your IT department) for various steps here. + +.. _install-wsl: + +## Install Linux subsystem + +We don't support building our documentation in the native Windows command-line environment. Thus, you will need to install a little version of Linux inside a virtual machine (VM) to use instead. The process for doing this varies depending on how tightly the installation process is controlled on your computer. + +### NCAR computers + +Please follow the [Windows Subsystem for Linux (WSL) setup instructions](https://wiki.ucar.edu/pages/viewpage.action?pageId=514032264&spaceKey=CONFIGMGMT&title=Setup) on the UCAR Wiki. In the step about installing a Linux distribution, choose Ubuntu. + +Feel free to peruse the [overall WSL documentation](https://wiki.ucar.edu/spaces/CONFIGMGMT/pages/514032242/Windows+Subsystem+for+Linux) on and linked from the UCAR Wiki for additional information. + +### Non-NCAR computers + +If your computer is managed by an organization other than NCAR, please check with your IT department or equivalent for instructions on installing Windows Subsystem for Linux (WSL) and Ubuntu. Otherwise, follow these instructions: + +1. Download and install Ubuntu from the Microsoft Store. +1. Restart your computer. +1. Open Ubuntu. + +If Ubuntu opens in that last step but you see an error, you may need to manually enable Windows Subsystem for Linux (WSL). To do so: Open Control Panel, go to "Programs" > "Programs and Features" > "Turn Windows features on or off". Check the box next to "Windows Subsystem for Linux" and click OK. + +Once Ubuntu is working and open, you'll be asked to create a new UNIX username and password. This doesn't have to match your Windows username and password, but do make sure to save this information somewhere secure. + +.. _windows-docs-ubuntu-utilities: + +## Install utilities +Enter the following commands **into your Ubuntu terminal** to install any missing utilities we need (the `which ... ||` should make it so that no installation happens if you already have it): +```shell +# Refresh the list of available software +sudo apt-get update + +# make: Part of the docs-building process +which make || sudo apt-get -y install make + +# git and git-lfs, needed for getting and contributing to the CTSM code and docs +which git || sudo apt-get -y install git +which git-lfs || sudo apt-get -y install git-lfs + +# WSL utilities, which will give us the wslview command for opening HTML pages in a Windows browser +which wslview || sudo apt-get -y install wslu +``` + +.. _container-or-conda-windows: + +## Install container software or Conda environment + +We recommend building the software in what's called a container—basically a tiny little operating system with just some apps and utilities needed by the doc-building process. This is nice because, if we change the doc-building process in ways that require new versions of those apps and utilities, that will be completely invisible to you. You won't need to manually do anything to update your setup to work with the new process; it'll just happen automatically. + +For builds in WSL (Ubuntu), we recommend using the container software Docker. You can install it in Ubuntu like so: + +```shell +# If needed, download and run the Docker installation script. +# Ignore the message saying "We recommend using Docker Desktop for Windows." +# The script will make you wait 20 seconds to make sure this is want you want, +# and then it should continue automatically. +which docker || curl -fsSL https://get.docker.com -o get-docker.sh +which docker || sudo sh ./get-docker.sh + +# Set up the docker "group," if needed, and add your username to it. +sudo groupadd docker # Create docker group if it doesn't exist +sudo usermod -aG docker $USER # Add your user to the docker group +newgrp docker # Apply the new group membership (avoids needing to log out and back in) + +# Make sure it worked: This should print a "Hello from Docker!" message +docker run hello-world +``` + +You may not be able to install Docker or any other containerization software, so there is an alternative method: a Conda environment. + +1. Check whether you already have Conda installed by doing `which conda`. If that doesn't print anything, [install Miniconda](https://www.anaconda.com/docs/getting-started/miniconda/install#linux). +1. Follow the instructions for setting up the `ctsm_pylib` Conda environment in Sect. :numref:`using-ctsm-pylib`. + +.. _editing-text-files-wsl: + +## Editing documentation files +If you prefer using an old-school text editor like `vim`, it's probably already installed in your Ubuntu VM, or can be installed with `sudo apt-get -y install EDITOR_NAME`. If you prefer a more user-friendly interface, there are several options. Note that **all commands in this section are to be run in your Ubuntu VM, not a Windows terminal**. + +### In a Windows app (recommended) +If you installed `wslview` in the instructions above, you can edit files by doing +```shell +wslview path/to/file_i_want_to_edit.rst +``` +If not, you can do +```shell +explorer.exe $(wslpath -w path/to/file_i_want_to_edit.rst) +``` +These both do the same thing, but the `wslview` method is simpler. Either way, at least the first time you do this, it will open a window asking which app you'd like to open the file in. Choose whatever you're most comfortable with. At the bottom of the window, you can then choose whether you always want to open HTML files using the selected app or just this once. + +You may also be able to open files in Windows apps by using the name of the Windows executable. For Notepad, for instance, you would do +```shell +notepad.exe $(wslpath -w path/to/file_i_want_to_edit.rst) +``` + +If you use [VS Code](https://code.visualstudio.com/), you can install the [WSL VS Code extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl). Then (after closing and re-opening Ubuntu) you can open any documentation file **or folder** by doing +```shell +code path/to/file-or-folder +``` + +### In an Ubuntu app (not recommended) + +You can also install a user-friendly text editor in Ubuntu. This may be slower and have unexpected differences in behavior from what you expect from Windows apps, but it does work. For example: +- [gedit](https://gedit-text-editor.org/): `sudo apt-get install -y gedit` +- [Kate](https://kate-editor.org/): `sudo apt-get install -y kate` +- [VS Code](https://code.visualstudio.com/) (if you don't already have it installed on Windows): `sudo snap install code --classic` + +You can use all of those to open and edit files, but Kate and VS Code let you open entire folders, which can be convenient. In any case, you'd do `EDITOR_NAME path/to/thing/youre/editing` to open it, where `EDITOR_NAME` is `gedit`, `kate`, or `code`, respectively. + +## Troubleshooting + +### "Permission denied" error + +If you get this error, it may be a result of opening Ubuntu as an administrator (e.g., by right-clicking on its icon and choosing "Run as administrator.") Try not doing that, although this will result in you needing to get a new copy of CTSM to work in. + +If that's not feasible or doesn't solve the problem, you may need to remind Linux that you do actually own your files. **In your Ubuntu terminal**, do: +```shell +chown -R $USER:$USER $HOME +``` + +If that also gives a permission error, you may need to put `sudo` at the start of the command. + +### "The host 'wsl$' was not found in the list of allowed hosts" + +You may see this warning in a dialog box after trying to open a file with `wslview`, `explorer.exe`, or something else. Check "Permanently allow host 'wsl$'" and then press "Allow". diff --git a/doc/source/users_guide/working-with-documentation/caveats-for-working-with-markdown.md b/doc/source/users_guide/working-with-documentation/caveats-for-working-with-markdown.md deleted file mode 100644 index 843e0630f4..0000000000 --- a/doc/source/users_guide/working-with-documentation/caveats-for-working-with-markdown.md +++ /dev/null @@ -1,26 +0,0 @@ -.. _caveats-for-working-with-markdown: - -# Caveats for working with Markdown - -.. _cross-references: - -## Cross-references -You can [link to section headings](#cross-references) with the `[link to section headings](#cross-references)` format, but the Sphinx compiler will complain. To fix that, you will need to also add a reStructuredText label like `.. _cross-references:` above the heading, with blank lines before and after it. This rST label will appear if you render the .md file as pure Markdown (e.g., in VSCode's Markdown preview pane), but it will be invisible on the final generated webpage. - -If you forget to surround the label with blank lines, you will get errors like "Explicit markup ends without a blank line; unexpected unindent [docutils]" that often point to lines far away from the actual problem. - -## Inline math -(Note that parts of this section will be rendered incorrectly by Markdown parsers!) - -Inline math can't be achieved with the typical Markdown syntax of just surrounding your expression with dollar signs. Instead, you need to surround THAT with backticks. So to render `$y = mx + b$`, we can't do -``` -So to render $y = mx + b$, ... -``` -because we'd just see $y = mx + b$ on the generated webpage. Instead, we do -``` -So to render `$y = mx + b$`, ... -``` -We could also use rST's syntax like so: -``` -So to render :math:`y = mx + b`, ... -``` \ No newline at end of file diff --git a/doc/source/users_guide/working-with-documentation/docs-intro-and-recommended.md b/doc/source/users_guide/working-with-documentation/docs-intro-and-recommended.md index 04fac2b164..bfc537f223 100644 --- a/doc/source/users_guide/working-with-documentation/docs-intro-and-recommended.md +++ b/doc/source/users_guide/working-with-documentation/docs-intro-and-recommended.md @@ -1,42 +1,66 @@ .. _docs-intro-and-recommended: -# Introduction to working with the CTSM documentation - -## Documentation source files -The CTSM documentation is built from files in the `doc/source/tech_note/` and `doc/source/users_guide/` directories. These files are written in a mixture of what are called "markup languages." You may already be familiar—for better or for worse—with the LaTeX markup language. Fortunately, our documentation is simpler than that. It was originally written entirely in [reStructuredText](http://www.sphinx-doc.org/en/stable/rest.html), and it still mostly is, as you can tell by the predominance of .rst files. However, it's also possible to write Markdown documents (.md), which is nice because it's a much simpler and more widespread format (although see :ref:`caveats-for-working-with-markdown`). If you've formatted text on GitHub, for instance, you've used Markdown. - +# Working with the CTSM documentation .. _editing-the-documentation: +## One-time setup +You will need to have some software installed on your computer in order to build and the documentation and view the results: +- :ref:`building-docs-prereqs-mac` +- :ref:`building-docs-prereqs-windows` + ## Editing the documentation -Editing the documentation is as simple as opening the source file for the page you want to edit, then changing text. Make sure to use either reStructuredText or Markdown syntax, depending on the file's extension (.rst or .md, respectively). +First, you will need a clone of CTSM to get all the documentation files and infrastructure. (If you're on Windows, you will make this clone in your :ref:`Ubuntu VM `.) Note that you will clone this to your own computer, not Derecho or any cluster or anything. -If you're confident in your changes, or you're _not_ confident in your ability to preview and test the documentation (see [Building the documentation (recommended method)](#building-the-documentation-recommended-method) below), all you need to do is commit your changes and submit a pull request to the [CTSM GitHub repo](https://github.com/ESCOMP/CTSM). Automated testing will check the updated documentation for any errors, and a CTSM software engineer will review your PR. If everything looks good, they will merge it into the codebase and update the website. +The CTSM documentation is built from files in the `doc/source/tech_note/` and `doc/source/users_guide/` directories. These files are written in a mixture of what are called "markup languages." You may already be familiar—for better or for worse—with the LaTeX markup language. Fortunately, our documentation is simpler than that. It was originally written entirely in [reStructuredText](http://www.sphinx-doc.org/en/stable/rest.html), and it still mostly is, as you can tell by the predominance of .rst files. However, it's also possible to write Markdown documents (.md), which is nice because it's a much simpler and more widespread format (although see :ref:`tips-for-working-with-markdown`). If you've formatted text on GitHub, for instance, you've used Markdown. -.. _building-the-documentation-recommended-method: +Editing the documentation is as simple as opening the source file for the page you want to edit, then changing text. Make sure to use either reStructuredText or Markdown syntax, depending on the file's extension (.rst or .md, respectively). Note that "opening the source file" isn't completely straightforward on Windows; see :ref:`editing-text-files-wsl`. -## Building the documentation (recommended method) -We strongly suggest building the documentation on your personal computer before submitting a pull request, so that you can preview what your changes will look like. The recommended way to do this is using the `doc-builder` tool in conjunction with a "containerized" version of some required software. +If you're confident in your changes, or you're _not_ confident in your ability to preview and test the documentation (see [Building the documentation (recommended method)](#building-the-documentation) below), all you need to do is commit your changes and submit a pull request to the [CTSM GitHub repo](https://github.com/ESCOMP/CTSM). Automated testing will check the updated documentation for any errors, and a CTSM software engineer will review your PR. If everything looks good, they will merge it into the codebase and update the website. -### Required software -You will need [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git), [Git LFS](https://git-lfs.com/), and [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed on your machine. In addition, you will need a decently modern installation of Python available from the command line as `python3`. We test the most with Python 3.13.2 (available in the `ctsm_pylib` conda environment; see :ref:`using-ctsm-pylib`), but any version 3.7 or later should work (and maybe earlier). You can do `python3 --version` on the command line to check your version. +.. _building-the-documentation: -### Directories -You will need a place to build the documentation. It's fine if that doesn't exist; the build tool will make it for you. Alternatively, you can clone the [ctsm-docs](https://github.com/ESCOMP/ctsm-docs) repository and build there. The only restriction is that, at least for the recommended method described here, **your build directory must be somewhere in your user home directory**, which we represent as `$HOME`. The instructions here assume you want to do your build in `$HOME/path/to/build-dir/`. +## Building the documentation +We strongly suggest building the documentation on your personal computer before submitting a pull request, so that you can preview what your changes will look like. The recommended way to do this is using the `doc-builder` tool in conjunction with a "containerized" version of some required software. -Your CTSM clone, from which you're building the documentation, also needs to be somewhere in your user home directory. +### Directories +You will need a place to build the documentation. It's fine if that doesn't exist; the build tool will make it for you. The only restriction is that, at least for the recommended method described here, **your build directory must be somewhere in your CTSM clone**. (We recommend starting the name of your build directory with `_build` because CTSM knows to ignore such directories when it comes to `git`.) The instructions here assume you want to do your build in `doc/_build/`. -### Building a preview -Ensure that Docker Desktop is running. Then all you need to do is +### Building the docs +All you need to do to build the docs with our recommended method is ```shell cd doc -./build_docs -b $HOME/path/to/build-dir -d +./build_docs -b _build -c -d ``` +This runs a complicated series of scripts and software that culminate in something called Sphinx converting the .rst and .md files into HTML webpages. + +The `-c` means "do a clean build." If you leave it off, Sphinx will only rebuild files it thinks have changed since the last time you built the docs. It's not always right, which can lead to problems. If you get unexpected errors without `-c`, rerunning with `-c` is the first troubleshooting step. + +The `-d` means "run using the container." If you're not using the container and are instead using the `ctsm_pylib` Conda environment **(not recommended)**, leave off `-d`, and make sure you've activated `ctsm_pylib` before running the above command. See the "Container software or Conda environment" sections for :ref:`Mac ` or :ref:`Windows ` for more information on these two methods. + (Do `./build_docs --help` for more information and options.) -You can then open the documentation in a web browser by browsing to `$HOME/path/to/build-dir/html/` and opening `index.html`. +## Viewing your built docs -Note that there is a menu in the lower left of the webpage that lets readers switch between different versions of the documentation. The links to versions in this menu will not work when using the build command given above. If you wish to preview this version switching functionality, or you're building the docs in the process of actually updating the website, see :ref:`building-docs-multiple-versions`. +Note that there is a menu in the lower left of the webpage that lets readers switch between different versions of the documentation. The links to versions in this menu will not work when using the build command given above. If you wish to preview this version switching functionality, see :ref:`building-docs-multiple-versions`. -## Further reading -More complicated instructions and alternative methods for building the documentation can be found in :ref:`overview-of-the-recommended-build-method-and-alternative-methods`. +The process for viewing your build in a web browser differs depending on what kind of computer you have. + +### Mac + +You can open your build of the documentation in your default browser with +```shell +open _build/html/index.html +``` + +### Windows (Ubuntu VM) + +Assuming you installed the WSL Utilities in the :ref:`windows-docs-ubuntu-utilities` setup step, you can open your build of the documentation like so: +```shell +wslview _build/html/index.html +``` +If you didn't, you can do +```shell +explorer.exe $(wslpath -w _build/html/index.html) +``` +These both do the same thing, but the `wslview` method is simpler. Either way, at least the first time you do this, it will open a window asking which app you'd like to view the HTML file in. Choose a browser like Microsoft Edge or Chrome. At the bottom of the window, you can then choose whether you always want to open HTML files using the selected app or just this once. diff --git a/doc/source/users_guide/working-with-documentation/index.rst b/doc/source/users_guide/working-with-documentation/index.rst index 383fa52e80..198da612d4 100644 --- a/doc/source/users_guide/working-with-documentation/index.rst +++ b/doc/source/users_guide/working-with-documentation/index.rst @@ -7,10 +7,13 @@ Working with CTSM Documentation ####################################### .. toctree:: - :maxdepth: 2 + :maxdepth: 1 docs-intro-and-recommended.md - building-docs-multiple-versions.md - caveats-for-working-with-markdown.md + building-docs-prereqs-mac.md + building-docs-prereqs-windows.md + building-docs-multiple-versions.rst + tips-for-working-with-markdown.md + tips-for-working-with-rst.md building-docs-original-wiki.md diff --git a/doc/source/users_guide/working-with-documentation/tips-for-working-with-markdown.md b/doc/source/users_guide/working-with-documentation/tips-for-working-with-markdown.md new file mode 100644 index 0000000000..9575092459 --- /dev/null +++ b/doc/source/users_guide/working-with-documentation/tips-for-working-with-markdown.md @@ -0,0 +1,45 @@ +.. _tips-for-working-with-markdown: + +# Tips for working with Markdown + +Markdown is great for very simple documentation files—it's much easier to write and read Markdown source than reStructuredText source. However, there are some compromises that you should be aware of, and you may find yourself needing to mix in some reStructuredText. + +.. _md-cross-references: + +## Markdown: Cross-references +You can [link to section headings](#md-cross-references) with the `[link to section headings](#md-cross-references)` format, but the Sphinx compiler will complain. Instead, use the :ref:`reStructuredText cross-reference and label` syntax. + +## Markdown: Math +(Note that parts of this section will be rendered incorrectly by Markdown parsers!) + +Inline math can't be achieved with the typical Markdown syntax of just surrounding your expression with dollar signs. Instead, you need to surround THAT with backticks. So to render `$y = mx + b$`, we can't do +``` +So to render $y = mx + b$, ... +``` +because we'd just see $y = mx + b$ on the generated webpage. Instead, we do +``` +So to render `$y = mx + b$`, ... +``` +We could also use :ref:`rST's syntax` like so: +``` +So to render :math:`y = mx + b`, ... +``` + +You can also use Markdown's math block syntax for big equations on their own lines: +``` +$$ +y = mx + b +$$ +``` +$$ +y = mx + b +$$ + +However, you won't get the equation numbering or labeling that you would with the :ref:`reStructuredText math format`. + +## Markdown: Comments +If you want to add some text that's only visible in the documentation source file, there's not really a way to do that in Markdown. However, you can use the :ref:`reStructuredText comment syntax` in a Markdown document. + +## Markdown: Tables + +Markdown tables are supported. See [GitHub's "Organizing information with tables"](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables) for more info. \ No newline at end of file diff --git a/doc/source/users_guide/working-with-documentation/tips-for-working-with-rst.md b/doc/source/users_guide/working-with-documentation/tips-for-working-with-rst.md new file mode 100644 index 0000000000..164f24115b --- /dev/null +++ b/doc/source/users_guide/working-with-documentation/tips-for-working-with-rst.md @@ -0,0 +1,165 @@ +.. _tips-for-working-with-rst: + +# Tips for working with reStructuredText + +If you've never used reStructuredText before, you should be aware that its syntax is pretty different from anything you've ever used before. We recommend the following resources as references for the syntax: +- [Sphinx's reStructuredText Primer](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) +- The [Quick reStructuredText](https://docutils.sourceforge.io/docs/user/rst/quickref.html) cheat sheet + +Some especially useful bits: +- [Section headers](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections) +- [Hyperlinks](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#hyperlinks) +- [Callout blocks (e.g., warning, tip)](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#admonitions-messages-and-warnings) + +On this page, we've compiled some supplemental information that might be helpful, including a list of common errors and their causes. + +.. contents:: + :depth: 1 + :backlinks: top + :local: + +.. _rst-math: + +## reStructuredText: Math +You can write inline math like ``:math:`y = mx + b``` → :math:`y = mx + b`. You can also write bigger equations on their own line that will automatically be numbered: + +```reStructuredText +.. math:: + :label: equation for a line + + y = mx + b +``` +.. math:: + :label: equation for a line + + y = mx + b + +Note (a) the leading spaces for each line after `.. math::` and (b) the empty line after the label. If you don't include the `:label:` line, the equation will not be numbered. + +reStructuredText math largely follows LaTeX syntax. + +.. _rst-cross-references: + +## reStructuredText: Cross-references +reStructuredText lets you define labels that can be cross-referenced as links elsewhere in the documentation. A label looks like ``.. _this-is-my-label:`` or ``.. _This is my label with CAPS and spaces:``, on its own line surrounded by blank lines. The leading ``.. _`` and trailing ``:`` are what tell rST "this line is a label." E.g.: + +``` +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +.. _this-is-my-label: + +My cool information +^^^^^^^^^^^^^^^^^^^ +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +``` + +You could then refer to that section with ``:XYZ:`this-is-my-label``` (leaving off the leading `.. _`), where `XYZ` can be `ref`, `numref`, or `eq` (see examples below). This will create a link that, when clicked, takes the reader to the "My cool information" section. + +Here are some examples. Note that the displayed link text will update automatically as needed (e.g., a section number or figure caption gets changed). +- Section headings, text: ``:ref:`rst-cross-references``` → :ref:`rst-cross-references` +- Section headings, number: ``:numref:`rst-cross-references``` → :numref:`rst-cross-references`. Note that, unlike `numref` for other things mentioned here, "Section" is not automatically prepended to the section number in the link text. +- Table, text: ``:ref:`Table Crop plant functional types``` → :ref:`Table Crop plant functional types` +- Table, number: ``:numref:`Table Crop plant functional types``` → :numref:`Table Crop plant functional types` +- Figure, text (uses entire caption): ``:ref:`Figure CLM subgrid hierarchy``` → :ref:`Figure CLM subgrid hierarchy` +- Figure, number: ``:numref:`Figure CLM subgrid hierarchy``` → :numref:`Figure CLM subgrid hierarchy` +- Equation, number: ``:eq:`equation for a line``` → :eq:`equation for a line`. The parentheses in the link text seem unavoidable, and there seems to be no way to refer to have the link show the label text or anything else aside from the number. + +You can have any link (except for equations) show custom text by putting the referenced label at the end in ``. E.g., ``:ref:`Diagram of CLM subgrid hierarchy
``` → :ref:`Diagram of CLM subgrid hierarchy
`. + +Note that this is necessary for labels that aren't immediately followed by a section heading, a table with a caption, or a figure with a caption. For instance, to refer to labels in our bibliography, you could do ``:ref:`(Bonan, 1996)``` → :ref:`(Bonan, 1996)`. + +.. _rst-comments: + +## reStructuredText: Comments +If you want to add some text that's only visible in the documentation source file, you can use the reStructuredText comment syntax: + +``` +.. + This will not appear on the webpage or even anywhere in the generated HTML. + +``` + +Make sure to include at least one empty line after the comment text. + + +## reStructuredText: Tables +Tables defined with the [:table: directive](https://docutils.sourceforge.io/docs/ref/rst/directives.html#table) can be annoying because they're very sensitive to the cells inside them being precisely the right widths, as defined by the first `====` strings. If you don't get the widths right, you'll see "Text in column margin" errors. Instead, define your tables using the [list-table](https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table) directive. + +If you already have a table in some other format, like comma-separated values (CSV), you may want to check out the R package [knitr](https://cran.r-project.org/web/packages/knitr/index.html). Its [kable](https://bookdown.org/yihui/rmarkdown-cookbook/kable.html) command allows automatic conversion of R dataframes to tables in reStructuredText and other formats. + + +## reStructuredText: Common error messages and how to handle them + +.. _error-unexpected-unindent: + +### "ERROR: Unexpected indentation" + +Like Python, reStructuredText is very particular about how lines are indented. Indentation is used, for example, to denote [code ("literal") blocks](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks) and [quote blocks](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#lists-and-quote-like-blocks). An error like +``` +/path/to/file.rst:102: ERROR: Unexpected indentation. [docutils] +``` +indicates that line 102 is indented but not in a way that reStructuredText expects. + +### "WARNING: Block quote ends without a blank line; unexpected unindent" + +This is essentially the inverse of :ref:`error-unexpected-unindent`: The above line was indented but this one isn't. reStructuredText tried to interpret the indented line as a [block quote](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#lists-and-quote-like-blocks), but block quotes require a blank line after them. + +.. _inline-literal-start-without-end: + +### "WARNING: Inline literal start-string without end-string" + +An "inline literal" is when you want to mix code into a normal line of text (as opposed to in its own code block) ``like this``. This is accomplished with double-backticks: +```reStructuredText +An "inline literal" is when you want to mix code into a normal line of +text (as opposed to in its own code block) ``like this``. +``` +(A backtick is what you get if you press the key to the left of 1 on a standard US English keyboard.) + +If you have a double-backtick on a line, reStructuredText will think, "They want to start an inline literal here," then look for another double-backtick to end the literal. The "WARNING: Inline literal start-string without end-string" means it can't find one on that line. + +This might happen, for example, if you try to put a [Markdown code block](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) in a .rst file. In that case, use the [reStructuredText code block syntax](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#literal-blocks) instead (optionally with [syntax highlighting](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-highlight)). + +### "WARNING: Inline interpreted text or phrase reference start-string without end-string" + +Like :ref:`inline-literal-start-without-end`, this is probably related to having one double-backtick without another on the same line. As with that other error, it could be the result of a Markdown code block in a .rst file. + +### "ERROR: Error in "code" directive: maximum 1 argument(s) allowed, 19 supplied" + +This error might show something other than "code," like "highlight" or "sourcecode". It also will probably show a second number that's not 19. The problem is that you tried to write a [reStructuredText code block with syntax highlighting](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-highlight) but didn't include a blank line after the first one: + +```reStructuredText +.. code:: shell + # How to list all the available grids + cd cime/scripts + ./query_config --grids +``` + +Fix this by adding a blank line: +```reStructuredText +.. code:: shell + + # How to list all the available grids + cd cime/scripts + ./query_config --grids +``` + +### 'ERROR: Error in "math" directive: invalid option block' + +You might have forgotten the empty line after an equation label. + +### "WARNING: Explicit markup ends without a blank line; unexpected unindent" + +You might have forgotten the leading spaces for every line after `.. math::`. As a reminder, you need at least one leading space on each line. + +You can also get this error if you forget to surround a :ref:`cross-reference label` with blank lines. In this case, the error message might point to lines far away from the actual problem. + +### "WARNING: Failed to create a cross reference: A title or caption not found" +This probably means you tried to `:ref:` a label that's not immediately followed by (a) a table/figure with a caption or (b) a section. + +### "WARNING: undefined label" + +If you're sure the label you referenced actually exists, this probably means you tried to ``:numref:`` a label that's not immediately followed by a table, figure, or section (see above). Alternatively, you might have tried to ``:ref:`` an :ref:`equation`; in that case, use ``:eq:`` instead. + +### "WARNING: malformed hyperlink target" + +You may have forgotten the trailing `:` on a label line. \ No newline at end of file diff --git a/doc/substitutions.py b/doc/substitutions.py new file mode 100644 index 0000000000..29b6fb54de --- /dev/null +++ b/doc/substitutions.py @@ -0,0 +1,63 @@ +""" +Substitutions for Sphinx +""" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# pylint: disable=invalid-name + +################################# +### Standard Sphinx variables ### +################################# + +# General information about the project. +project = "ctsm" +copyright = "2020, UCAR" # pylint: disable=redefined-builtin +author = "" + +# The short X.Y version. +version = "CTSM1" + +# The full version, including alpha/beta/rc tags. +release = "CTSM master" + +##################################################### +### Custom variables needed for doc-builder setup ### +##################################################### + +# Version label used at the top of some pages. +version_label = "the latest development code" + +####################################################### +### Custom variables optional for doc-builder setup ### +####################################################### + +tex_category = "Miscellaneous" + +# Used by HTML help builder +htmlhelp = { + "basename": "clmdocdoc", # Output file base name +} + +# Used for LaTeX output +latex = { + "target_name": "clmdoc.tex", + "title": "CLM Documentation", + "documentclass": "manual", # howto, manual, or own class + "category": tex_category, +} + +# Used for man_pages and texinfo_documents +mantex = { + "name": "clmdoc", + "title": "clmdoc Documentation", +} + +# Used for texinfo_documents +tex = { + "dirmenu_entry": "clmdoc", + "description": "One line description of project.", + "category": tex_category, +} \ No newline at end of file diff --git a/doc/test/compose_test_cmd.sh b/doc/test/compose_test_cmd.sh new file mode 100755 index 0000000000..2b2fd3cf67 --- /dev/null +++ b/doc/test/compose_test_cmd.sh @@ -0,0 +1,13 @@ +# This should only be run locally within another shell + +if [[ "${cli_tool}" == "" ]]; then + echo "${msg} (no container)" +else + cmd="${cmd} -d" + if [[ "${cli_tool}" != "default" ]]; then + cmd="${cmd} --container-cli-tool ${cli_tool}" + fi + echo "${msg} (container: ${cli_tool})" +fi + +echo cmd diff --git a/doc/test/test_build_docs_-b.sh b/doc/test/test_build_docs_-b.sh new file mode 100755 index 0000000000..8b49e2f7aa --- /dev/null +++ b/doc/test/test_build_docs_-b.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +cli_tool="$1" + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}/.." + +msg="~~~~~ Check that -b works" +cmd="./build_docs -b _build -c" + +. test/compose_test_cmd.sh +set -x +$cmd + +exit 0 diff --git a/doc/test/test_build_docs_-r-v.sh b/doc/test/test_build_docs_-r-v.sh new file mode 100755 index 0000000000..6f9415b563 --- /dev/null +++ b/doc/test/test_build_docs_-r-v.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +cli_tool="$1" + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}/.." + +msg="~~~~~ Check that -r -v works" +cmd="./build_docs -r _build -v latest -c --conf-py-path doc-builder/test/conf.py --static-path ../_static --templates-path ../_templates" + +. test/compose_test_cmd.sh +set -x +$cmd + +exit 0 diff --git a/doc/test/test_container_eq_ctsm_pylib.sh b/doc/test/test_container_eq_ctsm_pylib.sh new file mode 100755 index 0000000000..729f1b723e --- /dev/null +++ b/doc/test/test_container_eq_ctsm_pylib.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +# Compare docs built with container vs. ctsm_pylib + +cli_tool="$1" + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}/.." + +rm -rf _publish* + +# Build all docs using container +echo "~~~~~ Build all docs using container" +# Also do a custom --conf-py-path +rm -rf _build _publish +d1="$PWD/_publish_container" +./build_docs_to_publish -r _build -d --site-root "$PWD/_publish" +# VERSION LINKS WILL NOT RESOLVE IN _publish_container +cp -a _publish "${d1}" + +# Build all docs using ctsm_pylib +echo "~~~~~ Build all docs using ctsm_pylib" +rm -rf _build _publish +d2="$PWD/_publish_nocontainer" +conda run -n ctsm_pylib --no-capture-output ./build_docs_to_publish -r _build --site-root "$PWD/_publish" --conf-py-path doc-builder/test/conf.py --static-path ../_static --templates-path ../_templates +# VERSION LINKS WILL NOT RESOLVE IN _publish_nocontainer +cp -a _publish "${d2}" + +# Make sure container version is identical to no-container version +echo "~~~~~ Make sure container version is identical to no-container version" +diff -qr "${d1}" "${d2}" +echo "Successful: Docs built with container are identical to those built without" + +exit 0 diff --git a/doc/test/test_doc-builder_tests.sh b/doc/test/test_doc-builder_tests.sh new file mode 100755 index 0000000000..07cfa73ea1 --- /dev/null +++ b/doc/test/test_doc-builder_tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}" + +echo "~~~~~ Check that doc-builder tests pass" +cd ../doc-builder/test +set -x +conda run --no-capture-output -n ctsm_pylib make test + +exit 0 diff --git a/doc/test/test_makefile_method.sh b/doc/test/test_makefile_method.sh new file mode 100755 index 0000000000..b0fd80984e --- /dev/null +++ b/doc/test/test_makefile_method.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +cli_tool="$1" + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}/.." + +echo "~~~~~ Check that Makefile method works" +set -x +make SPHINXOPTS="-W --keep-going" BUILDDIR=${PWD}/_build html + +exit 0 diff --git a/doc/test/testing.sh b/doc/test/testing.sh new file mode 100755 index 0000000000..2e91025e6c --- /dev/null +++ b/doc/test/testing.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# Fail on any non-zero exit code +set -e + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_DIR}/" + +# Compare docs built with container vs. ctsm_pylib +./test_container_eq_ctsm_pylib.sh + +# Check that -r -v works (Docker) +# Also do a custom --conf-py-path and other stuff +cd "${SCRIPT_DIR}/" +rm -rf _build +./test_build_docs_-r-v.sh docker + +# Check that Makefile method works +cd "${SCRIPT_DIR}/" +rm -rf _build +conda run --no-capture-output -n ctsm_pylib ./test_makefile_method.sh + +# Check that -b works +cd "${SCRIPT_DIR}/" +rm -rf _build +./test_build_docs_-b.sh docker + +# Check that doc-builder tests pass +# Don't run if on a GitHub runner; failing 🤷. Trust that doc-builder does this test. +if [[ "${GITHUB_ACTIONS}" == "" ]]; then + cd "${SCRIPT_DIR}/" + ./test_doc-builder_tests.sh +fi + +exit 0 diff --git a/doc/version_list.py b/doc/version_list.py new file mode 100644 index 0000000000..56610c3018 --- /dev/null +++ b/doc/version_list.py @@ -0,0 +1,31 @@ +""" +Define the versions we want to build +""" +import sys +import os +dir2add = os.path.join(os.path.dirname(__file__), "doc-builder") +if not os.path.exists(dir2add): + raise FileNotFoundError(dir2add) +sys.path.insert(0, dir2add) +# pylint: disable=wrong-import-position +from doc_builder.docs_version import DocsVersion # pylint: disable=import-error,no-name-in-module +from doc_builder.sys_utils import get_git_head_or_branch # pylint: disable=import-error,no-name-in-module + +# Branch name, tag, or commit SHA whose version of certain files we want to preserve +LATEST_REF = get_git_head_or_branch() + +# List of version definitions +VERSION_LIST = [ + DocsVersion( + short_name="latest", + display_name="Latest development code", + landing_version=True, + ref=LATEST_REF, + ), + DocsVersion( + short_name="release-clm5.0", + display_name="CLM5.0", + ref="release-clm5.0", + ), +] +# End version definitions (keep this comment; Sphinx is looking for it) diff --git a/libraries/mpi-serial b/libraries/mpi-serial index 39416b7546..3092629c7d 160000 --- a/libraries/mpi-serial +++ b/libraries/mpi-serial @@ -1 +1 @@ -Subproject commit 39416b754652bd281a89e86b37734aa5f3ffafd6 +Subproject commit 3092629c7d9ee6b10e58e1aa0aa1d263bde168df diff --git a/libraries/parallelio b/libraries/parallelio index 6539ef05ae..b38e34eeb9 160000 --- a/libraries/parallelio +++ b/libraries/parallelio @@ -1 +1 @@ -Subproject commit 6539ef05ae7584ec570a56fdab9f7dfb336c2b80 +Subproject commit b38e34eeb9b75ce81ac94daf7c5245931de00b9d diff --git a/parse_cime.cs.status b/parse_cime.cs.status index daaaef2293..eb5c140df9 100755 --- a/parse_cime.cs.status +++ b/parse_cime.cs.status @@ -328,13 +328,17 @@ sub print_categories { my $scrdir = shift(@_); my %csstatus = @_; - my $expectedfailfile = "$scrdir/components/clm/cime_config/testdefs/ExpectedTestFails.xml"; - if ( ! -f $expectedfailfile ) { - $expectedfailfile = "$scrdir/cime_config/testdefs/ExpectedTestFails.xml"; + my $srcroot = "$scrdir"; + my $expectedfailfile = "$srcroot/cime_config/testdefs/ExpectedTestFails.xml"; + if ( $srcroot =~ m|/components/clm$|) { + $srcroot = absolute_path( "$scrdir/../.." ); + if ( ! -f $expectedfailfile ) { + die "ERROR: CTSM ExpectedTestFails.xml file NOT found in $scrdir\n"; + } } - my @failfiles = ( $expectedfailfile, "$scrdir/components/mizuRoute/cime_config/testdefs/ExpectedTestFails.xml", - #"$scrdir/components/mosart/cime_config/testdefs/ExpectedTestFails.xml", - "$scrdir/components/cmeps/cime_config/ExpectedTestFails.xml" ); + my @failfiles = ( $expectedfailfile, "$srcroot/components/mizuroute/cime_config/testdefs/ExpectedTestFails.xml", + "$srcroot/components/mosart/cime_config/testdefs/ExpectedTestFails.xml", + "$srcroot/components/cmeps/cime_config/ExpectedTestFails.xml" ); my @passes; my @fails; my @pendings; diff --git a/py_env_create b/py_env_create index 5b4515bc75..cf34131e41 100755 --- a/py_env_create +++ b/py_env_create @@ -143,11 +143,26 @@ if [ ! -f $condafile ]; then exit -1 fi +# Given two version number strings (unsigned integers separated by periods), echo "true" if the +# second one is "greater than or equal to" the first. Otherwise, echo "false". +# Based on https://unix.stackexchange.com/a/285928/208738 +function is_version2_ge_version1 { + requiredver=$1 + currentver=$2 + if [ "$(printf '%s\n' "$requiredver" "$currentver" | sort -V | head -n1)" = "$requiredver" ]; then + echo "true" + else + echo "false" + fi +} + # # Handle an environment that already exists # function conda_env_exists { - ${condamamba} env list | grep -oE "/$1$" | wc -l + # Not using ${condamamba} because, at least as of mamba 2.3.1, it doesn't always show environment + # names. See https://github.com/mamba-org/mamba/issues/4045 + conda env list | grep -oE "/$1$" | wc -l } function rename_existing_env { if [[ "$CONDA_DEFAULT_ENV" == *"$1" ]]; then @@ -155,7 +170,14 @@ function rename_existing_env { exit 1 elif [[ $(conda_env_exists $2) -eq 0 ]]; then echo "Renaming $1 to $2 (this will take a few minutes)..." - yes_tmp=${yes/yes/force} + conda_version=$(conda --version | cut -d" " -f2) + if [[ "$(is_version2_ge_version1 25.3.0 ${conda_version})" == "true" ]]; then + yes_tmp=$yes + else + # Before conda 25.3.0, conda rename needed --force instead of --yes + # https://github.com/conda/conda/blob/408abc3a3826c80750883accb8896005e3ac97b2/CHANGELOG.md?plain=1#L291 + yes_tmp=${yes/yes/force} + fi # Use conda instead of $condamamba because, at least as of mamba 1.5.9 / conda 24.7.1, # rename is not supported through mamba. if [[ "${quiet}" == "--quiet" ]]; then diff --git a/python/Makefile b/python/Makefile index 9645242111..3d607cab43 100644 --- a/python/Makefile +++ b/python/Makefile @@ -19,7 +19,7 @@ ifneq ($(verbose), not-set) endif PYLINT=pylint -PYLINT_ARGS=-j 4 --rcfile=ctsm/.pylintrc --fail-under=0 +PYLINT_ARGS=-j 4 --rcfile=ctsm/.pylintrc PYLINT_SRC = \ ctsm # NOTE: These don't pass pylint checking and should be added when we put into effort to get them to pass diff --git a/python/README.python_pkgs.rst b/python/README.python_pkgs.rst index e5a9af742e..d1cf803509 100644 --- a/python/README.python_pkgs.rst +++ b/python/README.python_pkgs.rst @@ -24,7 +24,6 @@ the tools on -- that needs to be fixed. - We need to tell the user how long to expect the conda environment to load, and give them options if the conda load is taking too long - Conda environments need to build robustly even for users who don't have ctsm_pylib loaded in their conda environment -- Currently dask will NOT be something we require for any of the main CTSM tools - Currently we won't use conda-lock - We specify the black version exactly so that black will function identically for all users - We specify the pylint version exactly because pylint is finicky with version and we need it to work identically for all developers diff --git a/python/conda_env_ctsm_py.yml b/python/conda_env_ctsm_py.yml index 7c13b34eba..96656696db 100644 --- a/python/conda_env_ctsm_py.yml +++ b/python/conda_env_ctsm_py.yml @@ -5,6 +5,7 @@ channels: - defaults dependencies: - python=3.13.2 + - dask=2025.7.0 - xarray=2025.1.2 - tqdm=4.67.1 - scipy=1.15.2 @@ -18,6 +19,10 @@ dependencies: - matplotlib=3.10.1 - pip=25.0.1 + # For VS code + # fortls used in FORTRAN-IntelliSense extension, add path to fortls below to the VS setting fort-ls.executablePath + - fortls=3.2.2 + # For building docs - pip: - -r ../doc/ctsm-docs_container/requirements.txt diff --git a/python/ctsm/args_utils.py b/python/ctsm/args_utils.py index 612e3f09a1..ed0e934d32 100644 --- a/python/ctsm/args_utils.py +++ b/python/ctsm/args_utils.py @@ -46,3 +46,12 @@ def plon_type(plon): "ERROR: Longitude should be between 0 and 360 or -180 and 180." ) return plon_float + + +def comma_separated_list(value): + """ + Helper function for argparse to split comma-separated strings into a list. + """ + if value is None: + return None + return [v.strip() for v in value.split(",")] diff --git a/python/ctsm/crop_calendars/check_rxboth_run.py b/python/ctsm/crop_calendars/check_rxboth_run.py index 2bb0872d45..568cb63822 100644 --- a/python/ctsm/crop_calendars/check_rxboth_run.py +++ b/python/ctsm/crop_calendars/check_rxboth_run.py @@ -78,7 +78,7 @@ def main(argv): any_bad = False - annual_outfiles = glob.glob(os.path.join(args.directory, "*.clm2.h1.*.nc")) + annual_outfiles = glob.glob(os.path.join(args.directory, "*.clm2.h1i.*.nc")) # These should be constant in a Prescribed Calendars (rxboth) run, as long as the inputs were # static. diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index c7ee9c581a..393e3a9cd5 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -13,6 +13,7 @@ from ctsm.crop_calendars.cropcal_constants import DEFAULT_GDD_MIN from ctsm.crop_calendars.import_ds import import_ds from ctsm.utils import is_instantaneous +from ctsm.ctsm_logging import log MISSING_RX_GDD_VAL = -1 @@ -49,11 +50,12 @@ def check_and_trim_years(year_1, year_n, ds_in): return ds_in -def open_lu_ds(filename, year_1, year_n, existing_ds, ungrid=True): +def open_lu_ds(filename, year_1, year_n, existing_ds, *, logger, ungrid=True): """ Open land-use dataset """ # Open and trim to years of interest + log(logger, f"Opening this_ds_gridded: {filename}") this_ds_gridded = xr.open_dataset(filename).sel(time=slice(year_1, year_n)) # Assign actual lon/lat coordinates @@ -347,6 +349,7 @@ def import_output( gdds_rx_ds=None, verbose=False, throw_errors=True, + logger=None, ): """ Import CLM output @@ -354,7 +357,7 @@ def import_output( any_bad = False # Import - this_ds = import_ds(filename, my_vars=my_vars, my_vegtypes=my_vegtypes) + this_ds = import_ds(filename, my_vars=my_vars, my_vegtypes=my_vegtypes, logger=logger) # Trim to years of interest (do not include extra year needed for finishing last growing season) if year_1 and year_n: diff --git a/python/ctsm/crop_calendars/generate_gdds.py b/python/ctsm/crop_calendars/generate_gdds.py index bde28ca80d..308431d003 100644 --- a/python/ctsm/crop_calendars/generate_gdds.py +++ b/python/ctsm/crop_calendars/generate_gdds.py @@ -18,6 +18,7 @@ os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, os.pardir, "python" ) sys.path.insert(1, _CTSM_PYTHON) +from ctsm.ctsm_logging import log, error # pylint: disable=wrong-import-position import ctsm.crop_calendars.cropcal_module as cc # pylint: disable=wrong-import-position import ctsm.crop_calendars.generate_gdds_functions as gddfn # pylint: disable=wrong-import-position @@ -85,11 +86,11 @@ def main( raise RuntimeError( "only_make_figs True but not all plotting modules are available" ) from exc - gddfn.log(logger, "Not all plotting modules are available; disabling save_figs") + log(logger, "Not all plotting modules are available; disabling save_figs") save_figs = False # Print some info - gddfn.log(logger, f"Saving to {output_dir}") + log(logger, f"Saving to {output_dir}") # Parse list of crops to skip if "," in skip_crops: @@ -107,7 +108,7 @@ def main( yr_1_import_str = f"{first_season+1}-01-01" yr_n_import_str = f"{last_season+2}-01-01" - gddfn.log( + log( logger, f"Importing netCDF time steps {yr_1_import_str} through {yr_n_import_str} " + "(years are +1 because of CTSM output naming)", @@ -191,7 +192,7 @@ def main( h1_instantaneous, ) - gddfn.log(logger, f" Saving pickle file ({pickle_file})...") + log(logger, f" Saving pickle file ({pickle_file})...") with open(pickle_file, "wb") as file: pickle.dump( [ @@ -219,9 +220,10 @@ def main( [i for i, c in enumerate(gddaccum_yp_list) if not isinstance(c, type(None))] ] - gddfn.log(logger, "Done") + log(logger, "Done") if not h2_ds: + log(logger, f"Opening h2_ds: {h2_ds_file}") h2_ds = xr.open_dataset(h2_ds_file) ###################################################### @@ -236,7 +238,7 @@ def main( "s", sdates_rx, incl_patches1d_itype_veg, mxsowings, logger ) - gddfn.log(logger, "Getting and gridding mean GDDs...") + log(logger, "Getting and gridding mean GDDs...") gdd_maps_ds = gddfn.yp_list_to_ds( gddaccum_yp_list, h2_ds, incl_vegtypes_str, sdates_rx, longname_prefix, logger ) @@ -247,10 +249,10 @@ def main( # Fill NAs with dummy values dummy_fill = -1 gdd_maps_ds = gdd_maps_ds.fillna(dummy_fill) - gddfn.log(logger, "Done getting and gridding means.") + log(logger, "Done getting and gridding means.") # Add dummy variables for crops not actually simulated - gddfn.log(logger, "Adding dummy variables...") + log(logger, "Adding dummy variables...") # Unnecessary? template_ds = xr.open_dataset(sdates_file, decode_times=True) all_vars = [v.replace("sdate", "gdd") for v in template_ds if "sdate" in v] @@ -278,9 +280,7 @@ def make_dummy(this_crop_gridded, addend): for var_index, this_var in enumerate(dummy_vars): if this_var in gdd_maps_ds: - gddfn.error( - logger, f"{this_var} is already in gdd_maps_ds. Why overwrite it with dummy?" - ) + error(logger, f"{this_var} is already in gdd_maps_ds. Why overwrite it with dummy?") dummy_gridded.name = this_var dummy_gridded.attrs["long_name"] = dummy_longnames[var_index] gdd_maps_ds[this_var] = dummy_gridded @@ -294,14 +294,14 @@ def add_lonlat_attrs(this_ds): gdd_maps_ds = add_lonlat_attrs(gdd_maps_ds) gddharv_maps_ds = add_lonlat_attrs(gddharv_maps_ds) - gddfn.log(logger, "Done.") + log(logger, "Done.") ###################### ### Save to netCDF ### ###################### if not only_make_figs: - gddfn.log(logger, "Saving...") + log(logger, "Saving...") # Get output file path datestr = dt.datetime.now().strftime("%Y%m%d_%H%M%S") @@ -336,7 +336,7 @@ def save_gdds(sdates_file, hdates_file, outfile, gdd_maps_ds, sdates_rx): save_gdds(sdates_file, hdates_file, outfile, gdd_maps_ds, sdates_rx) - gddfn.log(logger, "Done saving.") + log(logger, "Done saving.") ######################################## ### Save things needed for mapmaking ### diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index f80f1e55f7..be724659dd 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -6,12 +6,12 @@ import warnings import os import glob -import datetime as dt from importlib import util as importlib_util import numpy as np import xarray as xr from ctsm.utils import is_instantaneous +from ctsm.ctsm_logging import log, error import ctsm.crop_calendars.cropcal_utils as utils import ctsm.crop_calendars.cropcal_module as cc from ctsm.crop_calendars.xr_flexsel import xr_flexsel @@ -54,23 +54,6 @@ CAN_PLOT = False -def log(logger, string): - """ - Simultaneously print INFO messages to console and to log file - """ - print(string) - logger.info(string) - - -def error(logger, string): - """ - Simultaneously print ERROR messages to console and to log file - """ - print(string) - logger.error(string) - raise RuntimeError(string) - - def check_sdates(dates_ds, sdates_rx, outdir_figs, logger, verbose=False): """ Checking that input and output sdates match @@ -271,7 +254,6 @@ def import_and_process_1yr( """ save_figs = True log(logger, f"netCDF year {this_year}...") - log(logger, dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) # Without dask, this can take a LONG time at resolutions finer than 2-deg if importlib_util.find_spec("dask"): @@ -280,13 +262,13 @@ def import_and_process_1yr( chunks = None # Get h1 file (list) - h1_pattern = os.path.join(indir, "*h1.*.nc") + h1_pattern = os.path.join(indir, "*h1i.*.nc") h1_filelist = glob.glob(h1_pattern) if not h1_filelist: - h1_pattern = os.path.join(indir, "*h1.*.nc.base") + h1_pattern = os.path.join(indir, "*h1i.*.nc.base") h1_filelist = glob.glob(h1_pattern) if not h1_filelist: - error(logger, "No files found matching pattern '*h1.*.nc(.base)'") + error(logger, "No files found matching pattern '*h1i.*.nc(.base)'") # Get list of crops to include if skip_crops is not None: @@ -308,6 +290,7 @@ def import_and_process_1yr( my_vegtypes=crops_to_read, time_slice=slice(f"{slice_year}-01-01", f"{slice_year}-12-31"), chunks=chunks, + logger=logger, ) for timestep in dates_ds["time"].values: print(timestep) @@ -566,7 +549,7 @@ def import_and_process_1yr( log(logger, " Importing accumulated GDDs...") clm_gdd_var = "GDDACCUM" my_vars = [clm_gdd_var, "GDDHARV"] - patterns = [f"*h2.{this_year-1}-01*.nc", f"*h2.{this_year-1}-01*.nc.base"] + patterns = [f"*h2i.{this_year-1}-01*.nc", f"*h2i.{this_year-1}-01*.nc.base"] for pat in patterns: pattern = os.path.join(indir, pat) h2_files = glob.glob(pattern) @@ -579,6 +562,7 @@ def import_and_process_1yr( my_vars=my_vars, my_vegtypes=crops_to_read, chunks=chunks, + logger=logger, ) # Restrict to patches we're including @@ -604,7 +588,7 @@ def import_and_process_1yr( incl_vegtype_indices = [] for var, vegtype_str in enumerate(incl_vegtypes_str): if vegtype_str in skip_crops: - log(logger, f" SKIPPING {vegtype_str}") + log(logger, f"SKIPPING {vegtype_str}") continue vegtype_int = utils.vegtype_str2int(vegtype_str)[0] @@ -619,7 +603,7 @@ def import_and_process_1yr( check_gddharv = True if not this_crop_gddaccum_da.size: continue - log(logger, f" {vegtype_str}...") + log(logger, f"{vegtype_str}...") incl_vegtype_indices = incl_vegtype_indices + [var] # Get prescribed harvest dates for these patches @@ -1116,7 +1100,9 @@ def make_figures( if land_use_file: year_1_lu = year_1 if first_land_use_year is None else first_land_use_year year_n_lu = year_n if last_land_use_year is None else last_land_use_year - lu_ds = cc.open_lu_ds(land_use_file, year_1_lu, year_n_lu, gdd_maps_ds, ungrid=False) + lu_ds = cc.open_lu_ds( + land_use_file, year_1_lu, year_n_lu, gdd_maps_ds, logger=logger, ungrid=False + ) lu_years_text = f" (masked by {year_1_lu}-{year_n_lu} area)" lu_years_file = f"_mask{year_1_lu}-{year_n_lu}" else: diff --git a/python/ctsm/crop_calendars/import_ds.py b/python/ctsm/crop_calendars/import_ds.py index 71ce28bcce..66a0ec9746 100644 --- a/python/ctsm/crop_calendars/import_ds.py +++ b/python/ctsm/crop_calendars/import_ds.py @@ -12,14 +12,16 @@ import numpy as np import xarray as xr from ctsm.utils import is_instantaneous +from ctsm.ctsm_logging import log import ctsm.crop_calendars.cropcal_utils as utils from ctsm.crop_calendars.xr_flexsel import xr_flexsel -def compute_derived_vars(ds_in, var): +def compute_derived_vars(ds_in, var, logger=None): """ Compute derived variables """ + log(logger, f"Getting {var}...") if ( var == "HYEARS" and "HDATES" in ds_in @@ -44,13 +46,14 @@ def compute_derived_vars(ds_in, var): return ds_in -def manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice): +def manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice, logger=None): """ Opening a list of files with Xarray's open_mfdataset requires dask. This function is a workaround for Python environments that don't have dask. """ ds_out = None for filename in filelist: + log(logger, f"Opening ds_in: {ds_in}") ds_in = xr.open_dataset(filename) ds_in = mfdataset_preproc(ds_in, my_vars, my_vegtypes, time_slice) if ds_out is None: @@ -66,7 +69,7 @@ def manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice): return ds_out -def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice): +def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice, logger=None): """ Function to drop unwanted variables in preprocessing of open_mfdataset(). @@ -76,17 +79,16 @@ def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice): named like "patch". This can later be reversed, for compatibility with other code, using patch2pft(). """ + log(logger, "Start") + # Rename "pft" dimension and variables to "patch", if needed if "pft" in ds_in.dims: - pattern = re.compile("pft.*1d") - matches = [x for x in list(ds_in.keys()) if pattern.search(x) is not None] - pft2patch_dict = {"pft": "patch"} - for match in matches: - pft2patch_dict[match] = match.replace("pft", "patch").replace("patchs", "patches") - ds_in = ds_in.rename(pft2patch_dict) + ds_in = rename_pft_to_patch(ds_in, logger) derived_vars = [] if vars_to_import is not None: + log(logger, "Getting vars to drop...") + # Split vars_to_import into variables that are vs. aren't already in ds derived_vars = [v for v in vars_to_import if v not in ds_in] present_vars = [v for v in vars_to_import if v in ds_in] @@ -123,10 +125,12 @@ def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice): vars_to_drop = list(np.setdiff1d(varlist, vars_to_import)) # Drop them + log(logger, f"Dropping variables: {vars_to_drop}") ds_in = ds_in.drop_vars(vars_to_drop) # Add vegetation type info if "patches1d_itype_veg" in list(ds_in): + log(logger, "Adding vegetation type info") this_pftlist = utils.define_pftlist() utils.get_patch_ivts( ds_in, this_pftlist @@ -146,22 +150,43 @@ def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice): # Restrict to veg. types of interest, if any if vegtypes_to_import is not None: + log(logger, f"Restricting veg types to: {vegtypes_to_import}") ds_in = xr_flexsel(ds_in, vegtype=vegtypes_to_import) # Restrict to time slice, if any if time_slice: + log(logger, f"Restricting time slice to: {time_slice}") ds_in = utils.safer_timeslice(ds_in, time_slice) # Finish import + log(logger, "decode_cf()...") ds_in = xr.decode_cf(ds_in, decode_times=True) # Compute derived variables + if derived_vars: + log(logger, "decode_cf()...") for var in derived_vars: - ds_in = compute_derived_vars(ds_in, var) + ds_in = compute_derived_vars(ds_in, var, logger) + + log(logger, "End") return ds_in +def rename_pft_to_patch(ds_in, logger): + """ + Rename "pft" dimension and variables to "patch", if needed + """ + log(logger, 'mfdataset_preproc(): Rename "pft" dimension and variables to "patch"') + pattern = re.compile("pft.*1d") + matches = [x for x in list(ds_in.keys()) if pattern.search(x) is not None] + pft2patch_dict = {"pft": "patch"} + for match in matches: + pft2patch_dict[match] = match.replace("pft", "patch").replace("patchs", "patches") + ds_in = ds_in.rename(pft2patch_dict) + return ds_in + + def process_inputs(filelist, my_vars, my_vegtypes, my_vars_missing_ok): """ Process inputs to import_ds() @@ -201,6 +226,7 @@ def import_ds( my_vars_missing_ok=None, rename_lsmlatlon=False, chunks=None, + logger=None, ): """ Import a dataset that can be spread over multiple files, only including specified variables @@ -209,6 +235,8 @@ def import_ds( - DOES actually read the dataset into memory, but only AFTER dropping unwanted variables and/or vegetation types. """ + log(logger, "Start") + filelist, my_vars, my_vegtypes, my_vars_missing_ok = process_inputs( filelist, my_vars, my_vegtypes, my_vars_missing_ok ) @@ -221,10 +249,12 @@ def import_ds( if time_slice: new_filelist = [] for file in sorted(filelist): + log(logger, f"Getting filetime from file: {file}") filetime = xr.open_dataset(file).time filetime_sel = utils.safer_timeslice(filetime, time_slice) include_this_file = filetime_sel.size if include_this_file: + log(logger, f"Including filetime : {filetime_sel['time'].values}") new_filelist.append(file) # If you found some matching files, but then you find one that doesn't, stop going @@ -250,7 +280,7 @@ def import_ds( warnings.filterwarnings(action="ignore", category=DeprecationWarning) dask_unavailable = find_spec("dask") is None if dask_unavailable: - this_ds = manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice) + this_ds = manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice, logger=logger) else: this_ds = xr.open_mfdataset( sorted(filelist), @@ -263,8 +293,11 @@ def import_ds( chunks=chunks, ) elif isinstance(filelist, str): + log(logger, f"Opening this_ds from filelist: {filelist}") this_ds = xr.open_dataset(filelist, chunks=chunks) + log(logger, "Calling mfdataset_preproc()...") this_ds = mfdataset_preproc(this_ds, my_vars, my_vegtypes, time_slice) + log(logger, "Calling compute()...") this_ds = this_ds.compute() # Warn and/or error about variables that couldn't be imported or derived @@ -289,4 +322,5 @@ def import_ds( if "lsmlon" in this_ds.dims: this_ds = this_ds.rename({"lsmlon": "lon"}) + log(logger, "End") return this_ds diff --git a/python/ctsm/ctsm_logging.py b/python/ctsm/ctsm_logging.py index e14ec2754c..e6aa01a254 100644 --- a/python/ctsm/ctsm_logging.py +++ b/python/ctsm/ctsm_logging.py @@ -27,10 +27,17 @@ setup_logging_for_tests (this is typically done via unit_testing.setup_for_tests) """ +import inspect import logging +from ctsm.utils import datetime_string + logger = logging.getLogger(__name__) +# In logfile lines, what should be used as spacing between the leading datetime string and the +# message text? +LOG_SPACING = " " * 4 + def setup_logging_pre_config(): """Setup logging for a script / application @@ -99,3 +106,34 @@ def output_to_file(file_path, message, log_to_logger=False): log_file.write(message) if log_to_logger: logger.info(message) + + +def _compose_log_msg(string, frame_record=2): + """ + Prepend the log/error string with reference information + """ + # Get name of the function that called log() or error() + caller_name = inspect.stack()[frame_record][3] + + return datetime_string() + LOG_SPACING + caller_name + LOG_SPACING + string + + +def log(logger_in, string): + """ + Simultaneously print INFO messages to console and to log file + """ + msg = _compose_log_msg(string) + print(msg) + if logger_in: + logger_in.info(msg) + + +def error(logger_in, string, *, error_type=RuntimeError): + """ + Simultaneously print ERROR messages to console and to log file + """ + msg = _compose_log_msg(string) + print(msg) + if logger_in: + logger_in.error(msg) + raise error_type(string) diff --git a/python/ctsm/longitude.py b/python/ctsm/longitude.py index bb04e2b98f..96fd134082 100644 --- a/python/ctsm/longitude.py +++ b/python/ctsm/longitude.py @@ -3,6 +3,8 @@ """ import logging +from argparse import ArgumentTypeError +import numpy as np logger = logging.getLogger(__name__) @@ -11,16 +13,22 @@ def _check_lon_type_180(lon_in): """ Checks value range of longitude with type 180 """ - if not -180 <= lon_in <= 180: - raise ValueError(f"lon_in needs to be in the range [-180, 180]: {lon_in}") + lon_min = np.min(lon_in) + lon_max = np.max(lon_in) + for lon in [lon_min, lon_max]: + if not -180 <= lon <= 180: + raise ValueError("(All values of) lon_in must be in the range [-180, 180]") def _check_lon_type_360(lon_in): """ Checks value range of longitude with type 360 """ - if not 0 <= lon_in <= 360: - raise ValueError(f"lon_in needs to be in the range [0, 360]: {lon_in}") + lon_min = np.min(lon_in) + lon_max = np.max(lon_in) + for lon in [lon_min, lon_max]: + if not 0 <= lon <= 360: + raise ValueError("(All values of) lon_in must be in the range [0, 360]") def _check_lon_value_given_type(lon_in, lon_type_in): @@ -50,6 +58,31 @@ def _convert_lon_type_180_to_360(lon_in): return lon_out +def detect_lon_type(lon_in): + """ + Detect longitude type of a given numeric. If lon_in contains more than one number (as in a list + or Numpy array), this function will assume all members are of the same type if (a) there is at + least one unambiguous member and (b) all unambiguous members are of the same type. + """ + lon_min = np.min(lon_in) + lon_max = np.max(lon_in) + if lon_min < -180: + raise ValueError(f"(Minimum) longitude < -180: {lon_min}") + if lon_max > 360: + raise ValueError(f"(Maximum) longitude > 360: {lon_max}") + min_type_180 = lon_min < 0 + max_type_360 = lon_max > 180 + if min_type_180 and max_type_360: + raise RuntimeError("Longitude array contains values of both types 180 and 360") + if not min_type_180 and not max_type_360: + raise ArgumentTypeError("Longitude(s) ambiguous; could be type 180 or 360") + if min_type_180: + lon_type = 180 + else: + lon_type = 360 + return lon_type + + def _convert_lon_type_360_to_180(lon_in): """ Convert a longitude from type 360 to type 180 @@ -70,6 +103,22 @@ def _convert_lon_type_360_to_180(lon_in): return lon_out +def check_other_is_lontype(other): + """ + Used in comparison operators to throw an error if the "other" object being compared isn't also + a Longitude object. This makes it so that comparing longitudes requires that both sides of the + comparison must be Longitude objects and thus must have a specified longitude type (180 or 360). + + We could try to coerce non-Longitude `other` to Longitude, but that might result in + situations where tests think everything works but code will fail if `other` is + ambiguous. + """ + if not isinstance(other, Longitude): + raise TypeError( + f"Comparison not supported between instances of 'Longitude' and '{type(other)}'" + ) + + class Longitude: """ A class to keep track of a longitude and its type @@ -93,6 +142,48 @@ def __init__(self, lon, lon_type): self._lon = lon self._lon_type = lon_type + def _check_lons_same_type(self, other): + """ + If you're comparing two Longitudes of different types in different hemispheres, then + `lon1 > lon2` and `lon2 < lon1` will incorrectly give different answers! We could make it so + that this doesn't fail as long as symmetricality isn't violated, but that might lead to + unexpected failures in practice. + """ + if self.lon_type() != other.lon_type(): + raise TypeError("Comparison not supported between Longitudes of different types") + + # __eq__ makes it so that == and != both work. + def __eq__(self, other): + check_other_is_lontype(other) + return self._lon == other.get(self._lon_type) + + def __lt__(self, other): + check_other_is_lontype(other) + self._check_lons_same_type(other) + return self._lon < other._lon + + def __gt__(self, other): + check_other_is_lontype(other) + self._check_lons_same_type(other) + return self._lon > other._lon + + def __le__(self, other): + check_other_is_lontype(other) + self._check_lons_same_type(other) + return self._lon <= other._lon + + def __ge__(self, other): + check_other_is_lontype(other) + self._check_lons_same_type(other) + return self._lon >= other._lon + + def __str__(self): + """ + We don't allow implicit string conversion because the user should always specify the + Longitude type they want + """ + raise NotImplementedError("Use Longitude.get_str() instead of implicit string conversion") + def get(self, lon_type_out): """ Get the longitude value, converting longitude type if needed @@ -104,3 +195,17 @@ def get(self, lon_type_out): if lon_type_out == 360: return _convert_lon_type_180_to_360(self._lon) raise RuntimeError(f"Add handling for lon_type_out {lon_type_out}") + + def get_str(self, lon_type_out): + """ + Get the longitude value as a string, converting longitude type if needed + """ + lon_out = self.get(lon_type_out) + # Use float() because the standard in CTSM filenames is to put .0 after whole-number values + return str(float(lon_out)) + + def lon_type(self): + """ + Getter method for self._lon_type + """ + return self._lon_type diff --git a/python/ctsm/netcdf_utils.py b/python/ctsm/netcdf_utils.py new file mode 100644 index 0000000000..025c7f73b6 --- /dev/null +++ b/python/ctsm/netcdf_utils.py @@ -0,0 +1,141 @@ +""" +Helper functions for working with netCDF files +""" + +import numpy as np +import xarray as xr +from netCDF4 import Dataset # pylint: disable=no-name-in-module + + +def _is_dtype_nan_capable(ndarray: np.ndarray): + """ + Given a numpy array, return True if it's capable of taking a NaN + """ + try: + np.isnan(ndarray) + return True + except TypeError: + return False + + +def _are_dicts_identical_nansequal(dict0: dict, dict1: dict, keys_to_ignore=None): + """ + Compare two dictionaries, considering NaNs to be equal. Don't be strict here about types; if + they can be coerced to comparable types and then they match, return True. + """ + # pylint: disable=too-many-return-statements + + if keys_to_ignore is None: + keys_to_ignore = [] + keys_to_ignore = np.array(keys_to_ignore) + + if len(dict0) != len(dict1): + return False + for key, value0 in dict0.items(): + if key in keys_to_ignore: + continue + if key not in dict1: + return False + value1 = dict1[key] + + # Coerce to numpy arrays to simplify comparison code + value0 = np.array(value0) + value1 = np.array(value1) + + # Compare, only asking to check equal NaNs if both are capable of taking NaN values + both_are_nan_capable = _is_dtype_nan_capable(value0) and _is_dtype_nan_capable(value1) + if not np.array_equal(value0, value1, equal_nan=both_are_nan_capable): + return False + + return True + + +def get_netcdf_format(file_path): + """ + Get format of netCDF file + """ + with Dataset(file_path, "r") as netcdf_file: + netcdf_format = netcdf_file.data_model + return netcdf_format + + +def _is_dataarray_metadata_identical(da0: xr.DataArray, da1: xr.DataArray, keys_to_ignore=None): + """ + Check whether two DataArrays have identical-enough metadata + """ + + # Check data type + if da0.dtype != da1.dtype: + return False + + # Check encoding + if not _are_dicts_identical_nansequal( + da0.encoding, da1.encoding, keys_to_ignore=keys_to_ignore + ): + return False + + # Check attributes + if not _are_dicts_identical_nansequal(da0.attrs, da1.attrs): + return False + + # Check name + if da0.name != da1.name: + return False + + # Check dims + if da0.dims != da1.dims: + return False + + return True + + +def _is_dataarray_data_identical(da0: xr.DataArray, da1: xr.DataArray): + """ + Check whether two DataArrays have identical data + """ + # pylint: disable=too-many-return-statements + + # Check sizes + if da0.sizes != da1.sizes: + return False + + # Check coordinates + if bool(da0.coords) or bool(da1.coords): + if not bool(da0.coords) or not bool(da1.coords): + return False + if not da0.coords.equals(da1.coords): + return False + + # Check values ("The array's data converted to numpy.ndarray") + if not np.array_equal(da0.values, da1.values): + # Try-except to avoid TypeError from putting NaN-incapable dtypes through + # np.array_equal(..., equal_nan=True) + try: + if not np.array_equal(da0.values, da1.values, equal_nan=True): + return False + except TypeError: + return False + + # Check data ("The DataArray's data as an array. The underlying array type (e.g. dask, sparse, + # pint) is preserved.") + da0_data_type = type(da0.data) + if not isinstance(da1.data, da0_data_type): + return False + if not isinstance(da0.data, np.ndarray): + raise NotImplementedError(f"Add support for comparing two objects of type {da0_data_type}") + + return True + + +def are_xr_dataarrays_identical(da0: xr.DataArray, da1: xr.DataArray, keys_to_ignore=None): + """ + Comprehensively check whether two DataArrays are identical + """ + if not _is_dataarray_metadata_identical(da0, da1, keys_to_ignore=keys_to_ignore): + return False + + if not _is_dataarray_data_identical(da0, da1): + return False + + # Fallback to however xarray defines equality, in case we missed something above + return da0.equals(da1) diff --git a/python/ctsm/param_utils/__init__.py b/python/ctsm/param_utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python/ctsm/param_utils/paramfile_shared.py b/python/ctsm/param_utils/paramfile_shared.py new file mode 100644 index 0000000000..d12773dbdb --- /dev/null +++ b/python/ctsm/param_utils/paramfile_shared.py @@ -0,0 +1,136 @@ +""" +Functions etc. shared among parameter file utilities +""" + +import argparse +import xarray as xr + +from ctsm.netcdf_utils import are_xr_dataarrays_identical + +PFTNAME_VAR = "pftname" + + +def are_paramfile_dataarrays_identical(da0: xr.DataArray, da1: xr.DataArray): + """ + Check whether parameter DataArrays are identical enough, ignoring some metadata + """ + return are_xr_dataarrays_identical(da0, da1, keys_to_ignore=["source", "original_shape"]) + + +def check_pfts_in_paramfile(selected_pfts, ds): + """ + Check that the given PFTs are present in the parameter file. + + Parameters + ---------- + selected_pfts : list of str + List of PFT names to check. + ds : xarray.Dataset + The parameter file dataset. + + Returns + ------- + list of str + List of all PFT names in the file. + + Raises + ------ + KeyError + If any selected PFT is not found in the file, or if PFTNAME_VAR is missing. + """ + if PFTNAME_VAR not in ds: + raise KeyError(f"paramfile missing variable: {PFTNAME_VAR}") + pft_names = get_pft_names(ds) + pfts_not_in_file = [] + for pft in selected_pfts: + if pft not in pft_names: + pfts_not_in_file += [pft] + if pfts_not_in_file: + raise KeyError(f"PFT(s) not found in parameter file: {', '.join(pfts_not_in_file)}") + + return pft_names + + +def get_pft_names(ds): + """ + Get the list of PFT names from the parameter file dataset. + + Parameters + ---------- + ds : xarray.Dataset + The parameter file dataset. + + Returns + ------- + list of str + List of PFT names. + """ + pft_names = [pft.decode().strip() for pft in ds[PFTNAME_VAR].values] + return pft_names + + +def get_selected_pft_indices(selected_pfts, pft_names): + """ + Get indices of selected PFTs in the list of all PFT names. + + Parameters + ---------- + selected_pfts : list of str + List of PFT names to select. + pft_names : list of str + List of all PFT names. + + Returns + ------- + list of int + Indices of selected PFTs. + """ + if isinstance(selected_pfts, str): + selected_pfts = [selected_pfts] + indices = [pft_names.index(pft) for pft in selected_pfts] + return indices + + +def open_paramfile(file_in, mask_and_scale=False): + """ + Open a parameter file as an xarray.Dataset. + + Parameters + ---------- + file_in : str + Path to the input netCDF file. + mask_and_scale : bool, optional + Whether to apply mask and scale (default: False). + + Returns + ------- + xarray.Dataset + The opened dataset. + """ + return xr.open_dataset(file_in, decode_timedelta=False, mask_and_scale=mask_and_scale) + + +def paramfile_parser_setup(description): + """ + Set up an argument parser for parameter file utilities. + + Parameters + ---------- + description : str + Description for the argument parser. + + Returns + ------- + tuple + (parser, pft_flags) where parser is an ArgumentParser and pft_flags is a list of flags for + PFT argument. + """ + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument("-i", "--input", required=True, help="Input netCDF file") + + # Flags that can be used for the PFT argument + pft_flags = ["-p", "--pft"] + + return parser, pft_flags diff --git a/python/ctsm/param_utils/query_paramfile.py b/python/ctsm/param_utils/query_paramfile.py new file mode 100644 index 0000000000..6103010827 --- /dev/null +++ b/python/ctsm/param_utils/query_paramfile.py @@ -0,0 +1,110 @@ +""" +Query parameters in a CTSM paramfile. + +This script allows users to print the values of one or more parameters from a CTSM parameter file +(netCDF format). Users can specify variables and optionally filter output by PFTs. The script +handles multi-dimensional variables and provides formatted output for PFT-specific parameters. +""" + +from ctsm.args_utils import comma_separated_list +from ctsm.param_utils.paramfile_shared import paramfile_parser_setup +from ctsm.param_utils.paramfile_shared import open_paramfile +from ctsm.param_utils.paramfile_shared import PFTNAME_VAR, check_pfts_in_paramfile +from ctsm.param_utils.paramfile_shared import get_selected_pft_indices, get_pft_names + + +def get_arguments(): + """ + Parse command-line arguments for querying variables from a netCDF file. + + Returns + ------- + argparse.Namespace + Parsed arguments with attributes: + - input: Path to the netCDF file + - variables: List of variable names to query + - pft: Optional list of PFT names to print + """ + parser, pft_flags = paramfile_parser_setup( + "Print values of one or more parameters from a CTSM paramfile." + ) + parser.add_argument( + "variables", + help="Names of variables to query (space-separated)", + nargs="*", + ) + parser.add_argument( + *pft_flags, + help="Comma-separated list of PFT names to print (only applies to PFT-specific variables)", + type=comma_separated_list, + ) + args = parser.parse_args() + return args + + +def print_values(ds, var, selected_pfts, pft_names): + """ + Print the values of a variable from the dataset, optionally filtered by PFTs. + + Parameters + ---------- + ds : xarray.Dataset + The opened netCDF dataset. + var : str + Variable name to print. + selected_pfts : list or None + List of selected PFT names to print, or None to print all. + pft_names : list or None + List of all PFT names in the file. + """ + data = ds[var].values + if list(ds[var].dims) == ["pft"]: + print(var + ":") + indices = range(len(pft_names)) + if selected_pfts is not None: + indices = get_selected_pft_indices(selected_pfts, pft_names) + max_name_len = max(len(pft_names[i]) for i in indices) if indices else 0 + else: + max_name_len = max(len(name) for name in pft_names) + for p in indices: + print(f" {pft_names[p]:<{max_name_len}}: {data[p]}") + elif ds[var].ndim > 1: + print(f"{var}:") + print(data) + else: + print(f"{var}: {data}") + + +def main(): + """ + Main entry point for query_paramfile. + + Parses arguments, opens the netCDF file, and prints requested variable values, + optionally filtered by PFTs. + """ + args = get_arguments() + + ds = open_paramfile(args.input, mask_and_scale=True) + + # If user didn't specify variables, print all + if not args.variables: + args.variables = ds.variables + + selected_pfts = args.pft + pft_names = None + if selected_pfts: + pft_names = check_pfts_in_paramfile(selected_pfts, ds) + elif PFTNAME_VAR in ds.coords: + pft_names = get_pft_names(ds) + + for var in args.variables: + if var in ds.variables: + print_values(ds, var, selected_pfts, pft_names) + else: + print(f"Variable '{var}' not found in {args.input}") + + ds.close() + + +if __name__ == "__main__": + main() diff --git a/python/ctsm/param_utils/set_paramfile.py b/python/ctsm/param_utils/set_paramfile.py new file mode 100644 index 0000000000..6efa491846 --- /dev/null +++ b/python/ctsm/param_utils/set_paramfile.py @@ -0,0 +1,382 @@ +""" +Tool for changing parameters on CTSM paramfile. + +This script allows users to modify one or more parameters in a CTSM parameter file (netCDF format). +It supports selecting specific PFTs, dropping other PFTs, and changing parameter values, including +setting values to fill (missing) values. The script ensures safe file handling and provides +detailed error checking for argument validity and parameter changes. +""" + +import os +import sys +from datetime import datetime +import numpy as np +import xarray as xr + +from ctsm.args_utils import comma_separated_list +from ctsm.netcdf_utils import get_netcdf_format +from ctsm.param_utils.paramfile_shared import paramfile_parser_setup, open_paramfile +from ctsm.param_utils.paramfile_shared import check_pfts_in_paramfile, get_selected_pft_indices +from ctsm.param_utils.paramfile_shared import PFTNAME_VAR + + +def check_arguments(args): + """ + Validate command-line arguments for set_paramfile. + + Checks for existence of input file, prevents overwriting output files, + and ensures logical consistency of PFT-related options. + """ + if not os.path.exists(args.input): + raise FileNotFoundError(args.input) + + # Avoid potentially overwriting canonical files + if os.path.exists(args.output): + raise FileExistsError(args.output) + + # --drop-other-pfts makes no sense without --pfts + if args.drop_other_pfts and not args.pft: + raise RuntimeError("--drop-other-pfts makes no sense without -p/--pft") + + +def get_arguments(): + """ + Parse command-line arguments for setting variables on a netCDF file. + + Returns + ------- + argparse.Namespace + Parsed arguments with attributes: + - input: Path to the input netCDF file + - output: Path to the output netCDF file + - pft: Optional list of PFT names whose values you want to change + - drop_other_pfts: Boolean flag to drop PFTs not specified + - param_changes: List of parameter changes to apply + """ + parser, pft_flags = paramfile_parser_setup( + "Change values of one or more parameters in a CTSM paramfile." + ) + + parser.add_argument( + "-o", "--output", required=True, help="Output netCDF file. Must not already exist." + ) + + # TODO: Add mutually-exclusive --exclude-pfts argument for PFTs you DON'T want to include + parser.add_argument( + *pft_flags, + help="Comma-separated list of PFTs to include (only applies to PFT-specific variables)", + type=comma_separated_list, + ) + + parser.add_argument( + "--drop-other-pfts", + help=f"Do not include PFTs other than the ones given in {'/'.join(pft_flags)}", + action="store_true", + ) + + parser.add_argument( + "param_changes", + help=( + "Parameter changes to apply. Use nan to set to the fill value. E.g.:\n" + " param1=new_value1 pftparam=pft1_val,nan,... param3=nan" + ), + nargs="*", + ) + + args = parser.parse_args() + check_arguments(args) + + return args + + +def is_integer(obj): + """ + Determine if an object is an integer or a numpy array of integer dtype. + + Parameters + ---------- + obj : object + Object to check. + + Returns + ------- + bool + True if obj is an integer or numpy array of integer dtype, False otherwise. + """ + if isinstance(obj, np.ndarray): + obj_type = obj.dtype + else: + obj_type = type(obj) + return np.issubdtype(obj_type, np.integer) + + +def check_correct_ndims(da, new_value, throw_error=False): + """ + Check that the new value for a parameter has the correct number of dimensions. + + Parameters + ---------- + da : xarray.DataArray + The parameter DataArray to check against. + new_value : array-like + The new value to assign. + throw_error : bool, optional + If True, raise an error on mismatch. + + Returns + ------- + bool + True if dimensions match, False otherwise. + """ + expected = da.ndim + actual = np.array(new_value).ndim + is_ndim_correct = actual in (0, expected) # If actual 0, apply it to all + if throw_error and not is_ndim_correct: + raise RuntimeError(f"Incorrect N dims: Expected {expected}, got {actual}") + return is_ndim_correct + + +def drop_other_pfts(selected_pfts, ds): + """ + Drop PFTs from the dataset that are not in the selected list. + + Parameters + ---------- + selected_pfts : list of str + List of PFT names to retain. + ds : xarray.Dataset + The parameter file dataset. + + Returns + ------- + xarray.Dataset + Dataset containing only the selected PFTs. + """ + pft_names = check_pfts_in_paramfile(selected_pfts, ds) + indices = get_selected_pft_indices(selected_pfts, pft_names) + ds = ds.isel({"pft": indices}) + return ds + + +def _add_cmd_to_history(ds): + """ + Prepend the calling command and timestamp to the netCDF history attribute. + + Parameters + ---------- + ds : xarray.Dataset + Dataset to update. + + Returns + ------- + xarray.Dataset + Dataset with updated history attribute. + """ + if "history" not in ds.attrs: + ds.attrs["history"] = "" + cmd_items = [f"'{x}'" if " " in x else x for x in sys.argv] + datetime_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ds.attrs["history"] = f"{datetime_str}: {' '.join(cmd_items)}\n{ds.attrs['history']}" + return ds + + +def save_paramfile(ds_out: xr.Dataset, output_path, *, nc_format="NETCDF3_CLASSIC"): + """ + Save an xarray Dataset to a netCDF parameter file. + + Parameters + ---------- + ds_out : xarray.Dataset + Dataset to save. + output_path : str + Path to output netCDF file. + nc_format : str, optional + NetCDF format to use (default: "NETCDF3_CLASSIC"). + """ + + # We don't want to add _FillValue to parameters that didn't already have one. This dict will + # track such parameters and be passed to .to_netcdf(..., encoding=encoding) + encoding = {} + for var in ds_out: + if "_FillValue" not in ds_out[var].encoding: + encoding[var] = {"_FillValue": None} + + ds_out = _add_cmd_to_history(ds_out) + + ds_out.to_netcdf(output_path, format=nc_format, encoding=encoding) + + +def _replace_nans_with_fill(var_encoding, new_value, *, chg=None): + """ + Replace NaNs in the new parameter value array with the fill value. + + Parameters + ---------- + var_encoding : dict + Encoding of an xarray DataArray. + new_value : numpy.ndarray + Array of new values. + chg : str, optional (keyword-only) + Change string from command line, used for error messages. + + Returns + ------- + numpy.ndarray + Array with NaNs replaced by fill value. + """ + if any(np.isnan(np.atleast_1d(new_value))): + # TODO: Add code to add fill value to parameters without it + if "_FillValue" not in var_encoding: + msg = "Can't set parameter to fill value if it doesn't already have one" + if chg is not None: + msg += f": {chg}" + raise NotImplementedError(msg) + fill_value = var_encoding["_FillValue"] + new_value[np.isnan(new_value)] = fill_value + + return new_value + + +def _convert_to_output_dtype(ds_out, var, new_value, *, chg=None): + """ + Convert new_value array to the output variable's data type. + + Parameters + ---------- + ds_out : xarray.Dataset + Output dataset. + var : str + Variable name. + new_value : numpy.ndarray + Array of new values. + chg : str, optional (keyword-only) + Change string from command line, used for error messages. + + Returns + ------- + numpy.ndarray + Array converted to output dtype. + """ + try: + new_value = new_value.astype(type(ds_out[var].dtype)) + except ValueError as e: + msg = str(e) + if "invalid literal for int() with base 10" in msg: + # Throw a nicer error message including the entire requested change + err_msg = "Invalid assignment to an integer parameter" + if chg is not None: + err_msg += f": {chg}" + raise ValueError(err_msg) from e + raise e + + return new_value + + +def apply_new_value_to_parameter(args, ds_out, var, new_value, var_encoding, *, chg=None): + """ + Apply a new value to a parameter in the output dataset, handling PFT selection, dimension + checks, fill value replacement, and assignment. + + Parameters + ---------- + args : argparse.Namespace + Parsed command-line arguments. + ds_out : xarray.Dataset + Output dataset to modify. + var : str + Name of the variable to change. + new_value : numpy.ndarray + Array of new values to assign. + var_encoding : dict + Encoding dictionary for the variable, used for fill value replacement. + chg : str, optional (keyword-only) + Change string from command line, used for error messages. + + Returns + ------- + xarray.Dataset + Modified output dataset with the new parameter value applied. + """ + # Are we acting on just some PFTs? If so, we'll need some stuff. + just_some_pfts = PFTNAME_VAR in ds_out[var].coords and args.pft + # pylint is probably wrong with the possibly-used-before-assignment warning, but do this + # here just to placate it. Make it an invalid index so we get an error if we try to use + # it. + indices = -1 + if just_some_pfts: + pft_names = check_pfts_in_paramfile(args.pft, ds_out) + indices = get_selected_pft_indices(args.pft, pft_names) + + # Check that correct number of dimensions were given for new values. Special handling needed + # if we're just acting on one PFT. + da_to_check = ds_out[var] + if just_some_pfts and len(args.pft) == 1: + da_to_check = da_to_check.isel(pft=indices).squeeze() + check_correct_ndims(da_to_check, new_value, throw_error=True) + + # Handle the situation where we're only changing values for some PFTs but keeping the others + if just_some_pfts and not args.drop_other_pfts: + tmp = ds_out[var].values.copy() + tmp[indices] = new_value + new_value = tmp + + # Ensure that any NaNs are replaced with the fill value + new_value = _replace_nans_with_fill(var_encoding, new_value, chg=chg) + + # This can be needed if, (a) you're selecting and changing just one PFT or (b) you're changing + # all values in a dimensioned parameter to match one value. + if ds_out[var].values.ndim > 0 and new_value.ndim == 0: + ds_out[var].values[:] = new_value + else: + ds_out[var].values = new_value + return ds_out + + +def main(): + """ + Main entry point for set_paramfile. + + Parses arguments, opens the input netCDF file, applies requested changes, + and saves the modified dataset to a new netCDF file. + """ + args = get_arguments() + + ds_in_masked_scaled = open_paramfile(args.input, mask_and_scale=True) + + ds_in = open_paramfile(args.input) + ds_out = ds_in.copy() + + # If --drop-other-pfts was given, drop PFTs not in args.pft + if args.drop_other_pfts: + ds_out = drop_other_pfts(args.pft, ds_out) + + # Apply parameter changes, if any + for chg in args.param_changes: + var, new_value = chg.split("=") + + # TODO: Add handling of multi-dimensional parameters + if ds_out[var].ndim > 1: + raise NotImplementedError("Can't yet change multi-dimensional parameters") + + # Split at commas, if any, and convert to numpy array + new_value = np.array(new_value.split(",")).squeeze() + + # TODO: Add code to set integer variables to their missing value. This is harder than it + # sounds. + if np.any(np.char.lower(new_value) == "nan") and is_integer(ds_out[var].values): + raise NotImplementedError(f"Can't set integer parameter to fill value: {chg}") + + # Convert to the output data type + new_value = _convert_to_output_dtype(ds_out, var, new_value, chg=chg) + + # Extract some information + var_encoding = ds_in_masked_scaled[var].encoding + + # Apply new value to parameter + ds_out = apply_new_value_to_parameter(args, ds_out, var, new_value, var_encoding, chg=chg) + + save_paramfile(ds_out, args.output, nc_format=get_netcdf_format(args.input)) + + +if __name__ == "__main__": + main() diff --git a/python/ctsm/pft_utils.py b/python/ctsm/pft_utils.py new file mode 100644 index 0000000000..40ab8b9f23 --- /dev/null +++ b/python/ctsm/pft_utils.py @@ -0,0 +1,21 @@ +""" +Constants and functions relating to PFTs +""" + +MIN_PFT = 0 # bare ground +MIN_NAT_PFT = 1 # minimum natural pft (not including bare ground) +MAX_NAT_PFT = 14 # maximum natural pft +MAX_PFT_GENERICCROPS = 16 # for runs with generic crops +MAX_PFT_MANAGEDCROPS = 78 # for runs with explicit crops + + +def is_valid_pft(pft_num, managed_crops): + """ + Given a number, check whether it represents a valid PFT (bare ground OK) + """ + if managed_crops: + max_allowed_pft = MAX_PFT_MANAGEDCROPS + else: + max_allowed_pft = MAX_PFT_GENERICCROPS + + return MIN_PFT <= pft_num <= max_allowed_pft diff --git a/python/ctsm/run_sys_tests.py b/python/ctsm/run_sys_tests.py index 8108668246..1733b73841 100644 --- a/python/ctsm/run_sys_tests.py +++ b/python/ctsm/run_sys_tests.py @@ -749,6 +749,22 @@ def _check_py_env(test_attributes): except ModuleNotFoundError as err: raise ModuleNotFoundError("modify_fsurdat" + err_msg) from err + # Check requirements for using set_paramfile Python module, if needed + set_paramfile_users = ["SETPARAMFILE"] + if any(any(u in t for u in set_paramfile_users) for t in test_attributes): + try: + import ctsm.param_utils.set_paramfile + except ModuleNotFoundError as err: + raise ModuleNotFoundError("set_paramfile" + err_msg) from err + + # Check requirements for using subset_data Python module, if needed + subset_data_users = ["SUBSETDATAPOINT", "SUBSETDATAREGION"] + if any(any(u in t for u in subset_data_users) for t in test_attributes): + try: + import ctsm.subset_data + except ModuleNotFoundError as err: + raise ModuleNotFoundError("subset_data" + err_msg) from err + # Check requirements for RXCROPMATURITY, if needed if any("RXCROPMATURITY" in t for t in test_attributes): try: diff --git a/python/ctsm/site_and_regional/plumber2_shared.py b/python/ctsm/site_and_regional/plumber2_shared.py new file mode 100644 index 0000000000..d4ab9d00b3 --- /dev/null +++ b/python/ctsm/site_and_regional/plumber2_shared.py @@ -0,0 +1,21 @@ +""" +Things shared between plumber2 scripts +""" + +import os +import pandas as pd +from ctsm.path_utils import path_to_ctsm_root + +PLUMBER2_SITES_CSV = os.path.join( + path_to_ctsm_root(), + "tools", + "site_and_regional", + "PLUMBER2_sites.csv", +) + + +def read_plumber2_sites_csv(file=PLUMBER2_SITES_CSV): + """ + Read PLUMBER2_sites.csv using pandas + """ + return pd.read_csv(file, skiprows=5) diff --git a/python/ctsm/site_and_regional/plumber2_surf_wrapper.py b/python/ctsm/site_and_regional/plumber2_surf_wrapper.py index 022914d17e..cedc6b25e0 100755 --- a/python/ctsm/site_and_regional/plumber2_surf_wrapper.py +++ b/python/ctsm/site_and_regional/plumber2_surf_wrapper.py @@ -22,16 +22,18 @@ import argparse import logging -import os -import subprocess +import sys import tqdm -import pandas as pd +# pylint:disable=wrong-import-position +from ctsm.site_and_regional.plumber2_shared import PLUMBER2_SITES_CSV, read_plumber2_sites_csv +from ctsm import subset_data +from ctsm.pft_utils import MAX_PFT_MANAGEDCROPS, is_valid_pft -def get_parser(): +def get_args(): """ - Get parser object for this script. + Get arguments for this script. """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter @@ -45,39 +47,44 @@ def get_parser(): help="Verbose mode will print more information. ", action="store_true", dest="verbose", - default=False, ) parser.add_argument( - "--16pft", - help="Create and/or modify 16-PFT surface datasets (e.g. for a FATES run) ", + "--crop", + help=f"Create and/or modify {MAX_PFT_MANAGEDCROPS}-PFT " + "surface datasets (e.g. for a non-FATES run)", action="store_true", - dest="pft_16", - default=True, + dest="use_managed_crops", ) - return parser + parser.add_argument( + "--overwrite", + help="Overwrite any existing files", + action="store_true", + ) + + parser.add_argument( + "--plumber2-sites-csv", + help=f"Comma-separated value (CSV) file with Plumber2 sites. Default: {PLUMBER2_SITES_CSV}", + default=PLUMBER2_SITES_CSV, + ) + + return parser.parse_args() def execute(command): """ - Function for running a command on shell. + Runs subset_data with given arguments. Args: - command (str): - command that we want to run. + command (list): + list of args for command that we want to run. Raises: - Error with the return code from shell. + Whatever error subset_data gives, if any. """ print("\n", " >> ", *command, "\n") - try: - subprocess.check_call(command, stdout=open(os.devnull, "w"), stderr=subprocess.STDOUT) - - except subprocess.CalledProcessError as err: - # raise RuntimeError("command '{}' return with error - # (code {}): {}".format(e.cmd, e.returncode, e.output)) - # print (e.ouput) - print(err) + sys.argv = command + subset_data.main() def main(): @@ -85,97 +92,103 @@ def main(): Read plumber2_sites from csv, iterate through sites, and add dominant PFT """ - args = get_parser().parse_args() + args = get_args() if args.verbose: logging.basicConfig(level=logging.DEBUG) - plumber2_sites = pd.read_csv("PLUMBER2_sites.csv", skiprows=4) + plumber2_sites = read_plumber2_sites_csv(args.plumber2_sites_csv) for _, row in tqdm.tqdm(plumber2_sites.iterrows()): lat = row["Lat"] lon = row["Lon"] site = row["Site"] + + clmsite = "1x1_PLUMBER2_" + site + print("Now processing site :", site) + + # Set up part of subset_data command that is shared among all options + subset_command = [ + "./subset_data", + "point", + "--lat", + str(lat), + "--lon", + str(lon), + "--site", + clmsite, + "--create-surface", + "--uniform-snowpack", + "--cap-saturation", + "--lon-type", + "180", + ] + + # Read info for first PFT pft1 = row["pft1"] + if not is_valid_pft(pft1, args.use_managed_crops): + raise RuntimeError(f"pft1 must be a valid PFT; got {pft1}") pctpft1 = row["pft1-%"] cth1 = row["pft1-cth"] cbh1 = row["pft1-cbh"] - pft2 = row["pft2"] - pctpft2 = row["pft2-%"] - cth2 = row["pft2-cth"] - cbh2 = row["pft2-cbh"] - # overwrite missing values from .csv file - if pft1 == -999: - pft1 = 0 - pctpft1 = 0 - cth1 = 0 - cbh1 = 0 - if pft2 == -999: - pft2 = 0 - pctpft2 = 0 - cth2 = 0 - cbh2 = 0 - clmsite = "1x1_PLUMBER2_" + site - print("Now processing site :", site) - if args.pft_16: - # use surface dataset with 16 pfts, but overwrite to 100% 1 dominant PFT - # don't set crop flag - # set dominant pft - subset_command = [ - "./subset_data", - "point", - "--lat", - str(lat), - "--lon", - str(lon), - "--site", - clmsite, + # Read info for second PFT, if a valid one is given in the .csv file + pft2 = row["pft2"] + if is_valid_pft(pft2, args.use_managed_crops): + pctpft2 = row["pft2-%"] + cth2 = row["pft2-cth"] + cbh2 = row["pft2-cbh"] + + # Set dominant PFT(s) + if is_valid_pft(pft2, args.use_managed_crops): + subset_command += [ "--dompft", str(pft1), str(pft2), "--pctpft", str(pctpft1), str(pctpft2), - "--cth", - str(cth1), - str(cth2), - "--cbh", - str(cbh1), - str(cbh2), - "--create-surface", - "--uniform-snowpack", - "--cap-saturation", - "--verbose", - "--overwrite", ] else: - # use surface dataset with 78 pfts, and overwrite to 100% 1 dominant PFT - # NOTE: FATES will currently not run with a 78-PFT surface dataset - # set crop flag - # set dominant pft - subset_command = [ - "./subset_data", - "point", - "--lat", - str(lat), - "--lon", - str(lon), - "--site", - clmsite, - "--crop", + subset_command += [ "--dompft", str(pft1), - str(pft2), "--pctpft", str(pctpft1), - str(pctpft2), - "--create-surface", - "--uniform-snowpack", - "--cap-saturation", - "--verbose", - "--overwrite", ] + + if not args.use_managed_crops: + # use surface dataset with 78 pfts, but overwrite to 100% 1 dominant PFT + # don't set crop flag + # set canopy top and bottom heights + if is_valid_pft(pft2, args.use_managed_crops): + subset_command += [ + "--cth", + str(cth1), + str(cth2), + "--cbh", + str(cbh1), + str(cbh2), + ] + else: + subset_command += [ + "--cth", + str(cth1), + "--cbh", + str(cbh1), + ] + else: + # use surface dataset with 78 pfts, and overwrite to 100% 1 dominant PFT + # NOTE: FATES will currently not run with a 78-PFT surface dataset + # set crop flag + subset_command += ["--crop"] + # don't set canopy top and bottom heights + + if args.verbose: + subset_command += ["--verbose"] + if args.overwrite: + subset_command += ["--overwrite"] + execute(subset_command) diff --git a/python/ctsm/site_and_regional/plumber2_usermods.py b/python/ctsm/site_and_regional/plumber2_usermods.py index 7b7f294a24..7c8f37b1b5 100644 --- a/python/ctsm/site_and_regional/plumber2_usermods.py +++ b/python/ctsm/site_and_regional/plumber2_usermods.py @@ -13,7 +13,8 @@ import os import tqdm -import pandas as pd +# pylint:disable=wrong-import-position +from ctsm.site_and_regional.plumber2_shared import read_plumber2_sites_csv # Big ugly function to create usermod_dirs for each site @@ -155,7 +156,7 @@ def main(): """ # For now we can just run the 'main' program as a loop - plumber2_sites = pd.read_csv("PLUMBER2_sites.csv", skiprows=4) + plumber2_sites = read_plumber2_sites_csv() for _, row in tqdm.tqdm(plumber2_sites.iterrows()): lat = row["Lat"] diff --git a/python/ctsm/site_and_regional/regional_case.py b/python/ctsm/site_and_regional/regional_case.py index a0b746d47d..1b52e72ab4 100644 --- a/python/ctsm/site_and_regional/regional_case.py +++ b/python/ctsm/site_and_regional/regional_case.py @@ -19,6 +19,7 @@ from ctsm.utils import add_tag_to_filename from ctsm.utils import abort from ctsm.config_utils import check_lon1_lt_lon2 +from ctsm.longitude import Longitude, detect_lon_type logger = logging.getLogger(__name__) @@ -132,6 +133,47 @@ def __init__( self.ni = None self.nj = None + def _subset_lon_lat(self, x_dim, y_dim, f_in): + """ + subset longitude and latitude arrays + """ + lat = f_in["lat"] + lon = f_in["lon"] + + # Detect longitude type (180 or 360) of input file, throwing a helpful error if it can't be + # determined. + f_lon_type = detect_lon_type(lon) + lon1_type = self.lon1.lon_type() + lon2_type = self.lon2.lon_type() + if lon1_type != lon2_type: + raise RuntimeError(f"lon1 type ({lon1_type}) doesn't match lon2 type ({lon2_type})") + if f_lon_type != lon1_type: + # This may be overly strict; we might want to allow conversion to lon1 type. + raise RuntimeError( + f"File lon type ({f_lon_type}) doesn't match boundary lon type ({lon1_type})" + ) + + # Convert input file longitudes to Longitude class, then trim where it's in region bounds + lon = Longitude(lon, lon1_type) + xind = np.where((lon >= self.lon1) & (lon <= self.lon2))[0] + yind = np.where((lat >= self.lat1) & (lat <= self.lat2))[0] + f_out = f_in.isel({y_dim: yind, x_dim: xind}) + return f_out + + def _get_lon_strings(self): + """ + Get the string versions of the region's longitudes + """ + if isinstance(self.lon1, Longitude): + lon1_str = self.lon1.get_str(self.lon1.lon_type()) + else: + lon1_str = str(self.lon1) + if isinstance(self.lon2, Longitude): + lon2_str = self.lon2.get_str(self.lon2.lon_type()) + else: + lon2_str = str(self.lon2) + return lon1_str, lon2_str + def create_tag(self): """ Create a tag for a region which is either the region name @@ -141,9 +183,8 @@ def create_tag(self): if self.reg_name: self.tag = self.reg_name else: - self.tag = "{}-{}_{}-{}".format( - str(self.lon1), str(self.lon2), str(self.lat1), str(self.lat2) - ) + lon1_str, lon2_str = self._get_lon_strings() + self.tag = "{}-{}_{}-{}".format(lon1_str, lon2_str, str(self.lat1), str(self.lat2)) def check_region_bounds(self): """ @@ -151,10 +192,11 @@ def check_region_bounds(self): """ # If you're calling this, lat/lon bounds need to have been provided if any(x is None for x in [self.lon1, self.lon2, self.lat1, self.lat2]): + lon1_str, lon2_str = self._get_lon_strings() raise argparse.ArgumentTypeError( "Latitude and longitude bounds must be provided and not None.\n" - + f" lon1: {self.lon1}\n" - + f" lon2: {self.lon2}\n" + + f" lon1: {lon1_str}\n" + + f" lon2: {lon2_str}\n" + f" lat1: {self.lat1}\n" + f" lat2: {self.lat2}" ) @@ -192,14 +234,12 @@ def create_domain_at_reg(self, indir, file): logger.info("Creating domain file at region: %s", self.tag) # create 1d coordinate variables to enable sel() method - f_in = self.create_1d_coord(fdomain_in, "xc", "yc", "ni", "nj") - lat = f_in["lat"] - lon = f_in["lon"] + x_dim = "ni" + y_dim = "nj" + f_in = self.create_1d_coord(fdomain_in, "xc", "yc", x_dim, y_dim) # subset longitude and latitude arrays - xind = np.where((lon >= self.lon1) & (lon <= self.lon2))[0] - yind = np.where((lat >= self.lat1) & (lat <= self.lat2))[0] - f_out = f_in.isel(nj=yind, ni=xind) + f_out = self._subset_lon_lat(x_dim, y_dim, f_in) # update attributes self.update_metadata(f_out) @@ -240,14 +280,12 @@ def create_surfdata_at_reg(self, indir, file, user_mods_dir, specify_fsurf_out): logger.info("fsurf_out: %s", os.path.join(self.out_dir, fsurf_out)) # create 1d coordinate variables to enable sel() method - f_in = self.create_1d_coord(fsurf_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat") - lat = f_in["lat"] - lon = f_in["lon"] + x_dim = "lsmlon" + y_dim = "lsmlat" + f_in = self.create_1d_coord(fsurf_in, "LONGXY", "LATIXY", x_dim, y_dim) # subset longitude and latitude arrays - xind = np.where((lon >= self.lon1) & (lon <= self.lon2))[0] - yind = np.where((lat >= self.lat1) & (lat <= self.lat2))[0] - f_out = f_in.isel(lsmlat=yind, lsmlon=xind) + f_out = self._subset_lon_lat(x_dim, y_dim, f_in) # update attributes self.update_metadata(f_out) @@ -304,14 +342,12 @@ def create_landuse_at_reg(self, indir, file, user_mods_dir): logger.info("fluse_out: %s", os.path.join(self.out_dir, fluse_out)) # create 1d coordinate variables to enable sel() method - f_in = self.create_1d_coord(fluse_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat") - lat = f_in["lat"] - lon = f_in["lon"] + x_dim = "lsmlon" + y_dim = "lsmlat" + f_in = self.create_1d_coord(fluse_in, "LONGXY", "LATIXY", x_dim, y_dim) # subset longitude and latitude arrays - xind = np.where((lon >= self.lon1) & (lon <= self.lon2))[0] - yind = np.where((lat >= self.lat1) & (lat <= self.lat2))[0] - f_out = f_in.isel(lsmlat=yind, lsmlon=xind) + f_out = self._subset_lon_lat(x_dim, y_dim, f_in) # update attributes self.update_metadata(f_out) diff --git a/python/ctsm/site_and_regional/single_point_case.py b/python/ctsm/site_and_regional/single_point_case.py index bd16bae226..c99a240513 100644 --- a/python/ctsm/site_and_regional/single_point_case.py +++ b/python/ctsm/site_and_regional/single_point_case.py @@ -15,17 +15,11 @@ # -- import local classes for this script from ctsm.site_and_regional.base_case import BaseCase, USRDAT_DIR, DatmFiles from ctsm.utils import add_tag_to_filename, ensure_iterable +from ctsm.longitude import detect_lon_type +from ctsm.pft_utils import MAX_NAT_PFT, MAX_PFT_GENERICCROPS, MAX_PFT_MANAGEDCROPS logger = logging.getLogger(__name__) -NAT_PFT = 15 # natural pfts -NUM_PFT = 17 # for runs with generic crops -MAX_PFT = 78 # for runs with explicit crops - -# -- constants to represent months of year -FIRST_MONTH = 1 -LAST_MONTH = 12 - class SinglePointCase(BaseCase): """ @@ -151,6 +145,29 @@ def __init__( # self.check_nonveg() self.check_pct_pft() + def convert_plon_to_filetype_if_needed(self, lon_da): + """ + Check that point and input file longitude types are equal. If not, convert point to match + file. + """ + plon_in = self.plon + f_lon_type = detect_lon_type(lon_da) + plon_type = plon_in.lon_type() + if f_lon_type == plon_type: + plon_out = plon_in.get(plon_type) + else: + plon_orig = plon_in.get(plon_type) + plon_out = plon_in.get(f_lon_type) + if plon_orig != plon_out: + logger.info( + "Converted plon from type %s (value %f) to type %s (value %f)", + plon_type, + plon_orig, + f_lon_type, + plon_out, + ) + return plon_out + def create_tag(self): """ Create a tag for single point which is the site name @@ -159,7 +176,7 @@ def create_tag(self): if self.site_name: self.tag = self.site_name else: - self.tag = "{}_{}".format(str(self.plon), str(self.plat)) + self.tag = "{}_{}".format(self.plon.get_str(self.plon.lon_type()), str(self.plat)) def check_dom_pft(self): """ @@ -173,20 +190,21 @@ def check_dom_pft(self): same range. e.g. If users specified multiple dom_pft, they should be either in : - - 0 - NAT_PFT-1 range + - 0 - MAX_NAT_PFT range or - - NAT_PFT - MAX_PFT range + - MAX_NAT_PFT+1 - MAX_PFT_MANAGEDCROPS range - give an error: mixed land units not possible ------------- Raises: Error (ArgumentTypeError): - If any dom_pft is bigger than MAX_PFT. + If any dom_pft is bigger than MAX_PFT_MANAGEDCROPS. Error (ArgumentTypeError): If any dom_pft is less than 1. Error (ArgumentTypeError): If mixed land units are chosen. - dom_pft values are both in range of (0 - NAT_PFT-1) and (NAT_PFT - MAX_PFT). + dom_pft values are both in range of + (0 - MAX_NAT_PFT) and (MAX_NAT_PFT+1 - MAX_PFT_MANAGEDCROPS). """ @@ -200,27 +218,29 @@ def check_dom_pft(self): min_dom_pft = min(self.dom_pft) max_dom_pft = max(self.dom_pft) - # -- check dom_pft values should be between 0-MAX_PFT - if min_dom_pft < 0 or max_dom_pft > MAX_PFT: - err_msg = "values for --dompft should be between 1 and 78." + # -- check dom_pft values should be between 0-MAX_PFT_MANAGEDCROPS + if min_dom_pft < 0 or max_dom_pft > MAX_PFT_MANAGEDCROPS: + err_msg = f"values for --dompft should be between 1 and {MAX_PFT_MANAGEDCROPS}." raise argparse.ArgumentTypeError(err_msg) # -- check dom_pft vs num_pft if max_dom_pft > self.num_pft: - err_msg = "Please use --crop flag when --dompft is above 16." + err_msg = f"Please use --crop flag when --dompft is above {MAX_PFT_GENERICCROPS}." raise argparse.ArgumentTypeError(err_msg) # -- check dom_pft vs MAX_pft - if self.num_pft - 1 < max_dom_pft < NUM_PFT: + if self.num_pft - 1 < max_dom_pft <= MAX_PFT_GENERICCROPS: logger.info( - "WARNING, you trying to run with generic crops (16 PFT surface dataset)" + "WARNING, you are trying to run with generic crops (%s PFT surface dataset)", + MAX_PFT_GENERICCROPS, ) # -- check if all dom_pft are in the same range: - if min_dom_pft < NAT_PFT <= max_dom_pft: + if min_dom_pft <= MAX_NAT_PFT < max_dom_pft: err_msg = ( "You are subsetting using mixed land units that have both " - "natural pfts and crop cfts. Check your surface dataset. " + "natural pfts and crop cfts. Check your surface dataset.\n" + f"{min_dom_pft} <= {MAX_NAT_PFT} < {max_dom_pft}\n" ) raise argparse.ArgumentTypeError(err_msg) @@ -316,7 +336,11 @@ def create_domain_at_point(self, indir, file): Create domain file for this SinglePointCase class. """ logger.info("----------------------------------------------------------------------") - logger.info("Creating domain file at %s, %s.", str(self.plon), str(self.plat)) + logger.info( + "Creating domain file at %s, %s.", + self.plon.get_str(self.plon.lon_type()), + str(self.plat), + ) # specify files fdomain_in = os.path.join(indir, file) @@ -350,7 +374,7 @@ def create_landuse_at_point(self, indir, file, user_mods_dir): logger.info("----------------------------------------------------------------------") logger.info( "Creating land use file at %s, %s.", - str(self.plon), + self.plon.get_str(self.plon.lon_type()), str(self.plat), ) @@ -363,8 +387,11 @@ def create_landuse_at_point(self, indir, file, user_mods_dir): # create 1d coordinate variables to enable sel() method f_in = self.create_1d_coord(fluse_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat") + # get point longitude, converting to match file type if needed + plon_float = self.convert_plon_to_filetype_if_needed(f_in["lsmlon"]) + # extract gridcell closest to plon/plat - f_out = f_in.sel(lsmlon=self.plon, lsmlat=self.plat, method="nearest") + f_out = f_in.sel(lsmlon=plon_float, lsmlat=self.plat, method="nearest") # expand dimensions f_out = f_out.expand_dims(["lsmlat", "lsmlon"]) @@ -405,7 +432,7 @@ def modify_surfdata_atpoint(self, f_orig): if self.dom_pft is not None: max_dom_pft = max(self.dom_pft) # -- First initialize everything: - if max_dom_pft < NAT_PFT: + if max_dom_pft <= MAX_NAT_PFT: f_mod["PCT_NAT_PFT"][:, :, :] = 0 else: f_mod["PCT_CFT"][:, :, :] = 0 @@ -424,10 +451,10 @@ def modify_surfdata_atpoint(self, f_orig): if cth is not None: f_mod["MONTHLY_HEIGHT_TOP"][:, :, :, dom_pft] = cth f_mod["MONTHLY_HEIGHT_BOT"][:, :, :, dom_pft] = cbh - if dom_pft < NAT_PFT: + if dom_pft <= MAX_NAT_PFT: f_mod["PCT_NAT_PFT"][:, :, dom_pft] = pct_pft else: - dom_pft = dom_pft - NAT_PFT + dom_pft = dom_pft - (MAX_NAT_PFT + 1) f_mod["PCT_CFT"][:, :, dom_pft] = pct_pft # ------------------------------- @@ -445,7 +472,7 @@ def modify_surfdata_atpoint(self, f_orig): if self.dom_pft is not None: max_dom_pft = max(self.dom_pft) - if max_dom_pft < NAT_PFT: + if max_dom_pft <= MAX_NAT_PFT: f_mod["PCT_NATVEG"][:, :] = 100 f_mod["PCT_CROP"][:, :] = 0 else: @@ -482,7 +509,7 @@ def create_surfdata_at_point(self, indir, file, user_mods_dir, specify_fsurf_out logger.info("----------------------------------------------------------------------") logger.info( "Creating surface dataset file at %s, %s", - str(self.plon), + self.plon.get_str(self.plon.lon_type()), str(self.plat), ) @@ -498,8 +525,11 @@ def create_surfdata_at_point(self, indir, file, user_mods_dir, specify_fsurf_out # create 1d coordinate variables to enable sel() method f_in = self.create_1d_coord(fsurf_in, "LONGXY", "LATIXY", "lsmlon", "lsmlat") + # get point longitude, converting to match file type if needed + plon_float = self.convert_plon_to_filetype_if_needed(f_in["lsmlon"]) + # extract gridcell closest to plon/plat - f_tmp = f_in.sel(lsmlon=self.plon, lsmlat=self.plat, method="nearest") + f_tmp = f_in.sel(lsmlon=plon_float, lsmlat=self.plat, method="nearest") # expand dimensions f_tmp = f_tmp.expand_dims(["lsmlat", "lsmlon"]).copy(deep=True) @@ -525,10 +555,10 @@ def create_surfdata_at_point(self, indir, file, user_mods_dir, specify_fsurf_out # update lsmlat and lsmlon to match site specific instead of the nearest point # we do this so that if we create user_mods the PTS_LON and PTS_LAT in CIME match # the surface data coordinates - which is required - f_out["lsmlon"] = np.atleast_1d(self.plon) + f_out["lsmlon"] = np.atleast_1d(plon_float) f_out["lsmlat"] = np.atleast_1d(self.plat) f_out["LATIXY"][:, :] = self.plat - f_out["LONGXY"][:, :] = self.plon + f_out["LONGXY"][:, :] = plon_float # update attributes self.update_metadata(f_out) @@ -554,7 +584,7 @@ def create_datmdomain_at_point(self, datm_tuple: DatmFiles): logger.info("----------------------------------------------------------------------") logger.info( "Creating DATM domain file at %s, %s", - str(self.plon), + self.plon.get_str(self.plon.lon_type()), str(self.plat), ) @@ -568,8 +598,11 @@ def create_datmdomain_at_point(self, datm_tuple: DatmFiles): # create 1d coordinate variables to enable sel() method f_in = self.create_1d_coord(fdatmdomain_in, "xc", "yc", "ni", "nj") + # get point longitude, converting to match file type if needed + plon_float = self.convert_plon_to_filetype_if_needed(f_in["lon"]) + # extract gridcell closest to plon/plat - f_out = f_in.sel(ni=self.plon, nj=self.plat, method="nearest") + f_out = f_in.sel(ni=plon_float, nj=self.plat, method="nearest") # expand dimensions f_out = f_out.expand_dims(["nj", "ni"]) @@ -591,14 +624,17 @@ def extract_datm_at(self, file_in, file_out): # create 1d coordinate variables to enable sel() method f_in = self.create_1d_coord(file_in, "LONGXY", "LATIXY", "lon", "lat") + # get point longitude, converting to match file type if needed + plon_float = self.convert_plon_to_filetype_if_needed(f_in["lon"]) + # extract gridcell closest to plon/plat - f_out = f_in.sel(lon=self.plon, lat=self.plat, method="nearest") + f_out = f_in.sel(lon=plon_float, lat=self.plat, method="nearest") # expand dimensions f_out = f_out.expand_dims(["lat", "lon"]) # specify dimension order - f_out = f_out.transpose("scalar", "time", "lat", "lon") + f_out = f_out.transpose("time", "lat", "lon") # update attributes self.update_metadata(f_out) @@ -617,7 +653,9 @@ def write_shell_commands(self, file, datm_syr, datm_eyr): with open(file, "w") as nl_file: self.write_to_file("# Change below line if you move the subset data directory", nl_file) self.write_to_file("./xmlchange {}={}".format(USRDAT_DIR, self.out_dir), nl_file) - self.write_to_file("./xmlchange PTS_LON={}".format(str(self.plon)), nl_file) + self.write_to_file( + "./xmlchange PTS_LON={}".format(self.plon.get_str(self.plon.lon_type())), nl_file + ) self.write_to_file("./xmlchange PTS_LAT={}".format(str(self.plat)), nl_file) self.write_to_file("./xmlchange MPILIB=mpi-serial", nl_file) if self.create_datm: @@ -643,7 +681,9 @@ def create_datm_at_point(self, datm_tuple: DatmFiles, datm_syr, datm_eyr, datm_s Create all of a DATM dataset at a point. """ logger.info("----------------------------------------------------------------------") - logger.info("Creating DATM files at %s, %s", str(self.plon), str(self.plat)) + logger.info( + "Creating DATM files at %s, %s", self.plon.get_str(self.plon.lon_type()), str(self.plat) + ) # -- create data files infile = [] @@ -653,46 +693,36 @@ def create_datm_at_point(self, datm_tuple: DatmFiles, datm_syr, datm_eyr, datm_s tpqwfiles = [] for year in range(datm_syr, datm_eyr + 1): ystr = str(year) - for month in range(FIRST_MONTH, LAST_MONTH + 1): - mstr = str(month) - if month < 10: - mstr = "0" + mstr - - dtag = ystr + "-" + mstr - fsolar = os.path.join( - datm_tuple.indir, - datm_tuple.dir_solar, - "{}{}.nc".format(datm_tuple.tag_solar, dtag), - ) - fsolar2 = "{}{}.{}.nc".format(datm_tuple.tag_solar, self.tag, dtag) - fprecip = os.path.join( - datm_tuple.indir, - datm_tuple.dir_prec, - "{}{}.nc".format(datm_tuple.tag_prec, dtag), - ) - fprecip2 = "{}{}.{}.nc".format(datm_tuple.tag_prec, self.tag, dtag) - ftpqw = os.path.join( - datm_tuple.indir, - datm_tuple.dir_tpqw, - "{}{}.nc".format(datm_tuple.tag_tpqw, dtag), - ) - ftpqw2 = "{}{}.{}.nc".format(datm_tuple.tag_tpqw, self.tag, dtag) - - outdir = os.path.join(self.out_dir, datm_tuple.outdir) - infile += [fsolar, fprecip, ftpqw] - outfile += [ - os.path.join(outdir, fsolar2), - os.path.join(outdir, fprecip2), - os.path.join(outdir, ftpqw2), - ] - solarfiles.append( - os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fsolar2) - ) - precfiles.append( - os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fprecip2) - ) - tpqwfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, ftpqw2)) + fsolar = os.path.join( + datm_tuple.indir, + datm_tuple.dir_solar, + "{}{}.nc".format(datm_tuple.tag_solar, ystr), + ) + fsolar2 = "{}{}.{}.nc".format(datm_tuple.tag_solar, self.tag, ystr) + fprecip = os.path.join( + datm_tuple.indir, + datm_tuple.dir_prec, + "{}{}.nc".format(datm_tuple.tag_prec, ystr), + ) + fprecip2 = "{}{}.{}.nc".format(datm_tuple.tag_prec, self.tag, ystr) + ftpqw = os.path.join( + datm_tuple.indir, + datm_tuple.dir_tpqw, + "{}{}.nc".format(datm_tuple.tag_tpqw, ystr), + ) + ftpqw2 = "{}{}.{}.nc".format(datm_tuple.tag_tpqw, self.tag, ystr) + + outdir = os.path.join(self.out_dir, datm_tuple.outdir) + infile += [fsolar, fprecip, ftpqw] + outfile += [ + os.path.join(outdir, fsolar2), + os.path.join(outdir, fprecip2), + os.path.join(outdir, ftpqw2), + ] + solarfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fsolar2)) + precfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, fprecip2)) + tpqwfiles.append(os.path.join("${}".format(USRDAT_DIR), datm_tuple.outdir, ftpqw2)) for idx, out_f in enumerate(outfile): logger.debug(out_f) diff --git a/python/ctsm/subset_data.py b/python/ctsm/subset_data.py index e4a323be53..ed9282ef46 100644 --- a/python/ctsm/subset_data.py +++ b/python/ctsm/subset_data.py @@ -69,7 +69,8 @@ from ctsm.path_utils import path_to_ctsm_root from ctsm.utils import abort from ctsm.config_utils import check_lon1_lt_lon2 -from ctsm.longitude import Longitude +from ctsm.longitude import Longitude, detect_lon_type +from ctsm.pft_utils import MAX_PFT_GENERICCROPS, MAX_PFT_MANAGEDCROPS # -- import ctsm logging flags from ctsm.ctsm_logging import ( @@ -597,14 +598,14 @@ def determine_num_pft(crop): num_pft (int) : number of pfts for surface dataset """ if crop: - num_pft = "78" + num_pft = str(MAX_PFT_MANAGEDCROPS) else: - num_pft = "16" + num_pft = str(MAX_PFT_GENERICCROPS) logger.debug("crop_flag = %s => num_pft = %s", str(crop), num_pft) return num_pft -def setup_files(args, defaults, cesmroot): +def setup_files(args, defaults, cesmroot, testing=False): """ Sets up the files and folders needed for this program """ @@ -622,64 +623,72 @@ def setup_files(args, defaults, cesmroot): else: clmforcingindir = args.inputdatadir - if not os.path.isdir(clmforcingindir): + if not testing and not os.path.isdir(clmforcingindir): logger.info("clmforcingindir does not exist: %s", clmforcingindir) - abort("inputdata directory does not exist") + abort(f"inputdata directory does not exist: {clmforcingindir}") + + file_dict = {"main_dir": clmforcingindir} # DATM data - # TODO Issue #2960: Make datm_type a user option at the command - # line. For reference, this option affects three .cfg files: - # tools/site_and_regional/default_data_1850.cfg - # tools/site_and_regional/default_data_2000.cfg - # python/ctsm/test/testinputs/default_data.cfg - datm_type = "datm_crujra" # also available: datm_type = "datm_gswp3" - dir_output_datm = "datmdata" - dir_input_datm = os.path.join(clmforcingindir, defaults.get(datm_type, "dir")) + # To find the affected files, from the top level of ctsm, do: + # grep "\[datm\]" $(find . -type f -name "*cfg") if args.create_datm: + datm_cfg_section = "datm" + + # Issue #3269: Changes in PR #3259 mean that --create-datm won't work with GSWP3 + settings_to_check_for_gswp3 = ["solartag", "prectag", "tpqwtag"] + for setting in settings_to_check_for_gswp3: + value = defaults.get(datm_cfg_section, setting) + if "gswp3" in value.lower(): + msg = ( + "--create-datm is no longer supported for GSWP3 data; " + "see https://github.com/ESCOMP/CTSM/issues/3269" + ) + raise NotImplementedError(msg) + + dir_output_datm = "datmdata" + dir_input_datm = os.path.join(clmforcingindir, defaults.get(datm_cfg_section, "dir")) if not os.path.isdir(os.path.join(args.out_dir, dir_output_datm)): os.mkdir(os.path.join(args.out_dir, dir_output_datm)) logger.info("dir_input_datm : %s", dir_input_datm) logger.info("dir_output_datm: %s", os.path.join(args.out_dir, dir_output_datm)) + file_dict["datm_tuple"] = DatmFiles( + dir_input_datm, + dir_output_datm, + defaults.get(datm_cfg_section, "domain"), + defaults.get(datm_cfg_section, "solardir"), + defaults.get(datm_cfg_section, "precdir"), + defaults.get(datm_cfg_section, "tpqwdir"), + defaults.get(datm_cfg_section, "solartag"), + defaults.get(datm_cfg_section, "prectag"), + defaults.get(datm_cfg_section, "tpqwtag"), + defaults.get(datm_cfg_section, "solarname"), + defaults.get(datm_cfg_section, "precname"), + defaults.get(datm_cfg_section, "tpqwname"), + ) # if the crop flag is on - we need to use a different land use and surface data file num_pft = determine_num_pft(args.crop_flag) - fsurf_in = defaults.get("surfdat", "surfdat_" + num_pft + "pft") - fluse_in = defaults.get("landuse", "landuse_" + num_pft + "pft") - if args.out_surface: - fsurf_out = args.out_surface - else: - fsurf_out = None - - file_dict = { - "main_dir": clmforcingindir, - "fdomain_in": defaults.get("domain", "file"), - "fsurf_dir": os.path.join( + if args.create_domain: + file_dict["fdomain_in"] = defaults.get("domain", "file") + if args.create_surfdata: + file_dict["fsurf_dir"] = os.path.join( clmforcingindir, os.path.join(defaults.get("surfdat", "dir")), - ), - "fluse_dir": os.path.join( + ) + file_dict["fsurf_in"] = defaults.get("surfdat", "surfdat_" + num_pft + "pft") + if args.out_surface: + fsurf_out = args.out_surface + else: + fsurf_out = None + file_dict["fsurf_out"] = fsurf_out + if args.create_landuse: + file_dict["fluse_in"] = defaults.get("landuse", "landuse_" + num_pft + "pft") + file_dict["fluse_dir"] = os.path.join( clmforcingindir, os.path.join(defaults.get("landuse", "dir")), - ), - "fsurf_in": fsurf_in, - "fsurf_out": fsurf_out, - "fluse_in": fluse_in, - "datm_tuple": DatmFiles( - dir_input_datm, - dir_output_datm, - defaults.get(datm_type, "domain"), - defaults.get(datm_type, "solardir"), - defaults.get(datm_type, "precdir"), - defaults.get(datm_type, "tpqwdir"), - defaults.get(datm_type, "solartag"), - defaults.get(datm_type, "prectag"), - defaults.get(datm_type, "tpqwtag"), - defaults.get(datm_type, "solarname"), - defaults.get(datm_type, "precname"), - defaults.get(datm_type, "tpqwname"), - ), - } + ) return file_dict @@ -813,7 +822,7 @@ def subset_region(args, file_dict: dict): print("\nFor running this regional case with the created user_mods : ") print( "./create_newcase --case case --res CLM_USRDAT --compset I2000Clm60BgcCrop", - "--run-unsupported --user-mods-dirs ", + "--run-unsupported --user-mods-dir ", args.user_mods_dir, "\n\n", ) @@ -821,17 +830,6 @@ def subset_region(args, file_dict: dict): logger.info("Successfully ran script for a regional case.") -def _detect_lon_type(lon_in): - if lon_in < 0: - lon_type = 180 - elif lon_in > 180: - lon_type = 360 - else: - msg = "When providing an ambiguous longitude, you must specify --lon-type 180 or 360" - raise argparse.ArgumentTypeError(msg) - return lon_type - - def process_args(args): """ Process arguments after parsing @@ -845,10 +843,10 @@ def process_args(args): if any(lon_arg_values): if args.lon_type is None: if hasattr(args, "plon"): - args.lon_type = _detect_lon_type(args.plon) + args.lon_type = detect_lon_type(args.plon) else: - lon1_type = _detect_lon_type(args.lon1) - lon2_type = _detect_lon_type(args.lon2) + lon1_type = detect_lon_type(args.lon1) + lon2_type = detect_lon_type(args.lon2) if lon1_type != lon2_type: raise argparse.ArgumentTypeError( "--lon1 and --lon2 seem to be of different types" diff --git a/python/ctsm/test/test_advanced_sys_mesh_plotter.py b/python/ctsm/test/test_advanced_sys_mesh_plotter.py index 4a7c63ecf6..aadd4d6626 100755 --- a/python/ctsm/test/test_advanced_sys_mesh_plotter.py +++ b/python/ctsm/test/test_advanced_sys_mesh_plotter.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Advanced System tests for mesh_plotter (requires the ctsm_pylib_wdask conda environment) +Advanced System tests for mesh_plotter """ diff --git a/python/ctsm/test/test_sys_gen_mksurfdata_jobscript_single_derecho.py b/python/ctsm/test/test_sys_gen_mksurfdata_jobscript_single_derecho.py new file mode 100755 index 0000000000..627fb1d32b --- /dev/null +++ b/python/ctsm/test/test_sys_gen_mksurfdata_jobscript_single_derecho.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +""" +System tests for gen_mksurfdata_jobscript_single.py subroutines on Derecho +""" + +import unittest +import os + +from ctsm import unit_testing +from ctsm.test_gen_mksurfdata_jobscript_single_parent import TestFGenMkSurfJobscriptSingleParent +from ctsm.path_utils import path_to_cime +from ctsm.os_utils import run_cmd_output_on_error +from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_parser +from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_mpirun +from ctsm.toolchain.gen_mksurfdata_jobscript_single import check_parser_args +from ctsm.toolchain.gen_mksurfdata_jobscript_single import write_runscript_part1 + + +# Allow test names that pylint doesn't like; otherwise hard to make them +# readable +# pylint: disable=invalid-name + + +# pylint: disable=protected-access +# pylint: disable=too-many-instance-attributes +class TestFGenMkSurfJobscriptSingleDerecho(TestFGenMkSurfJobscriptSingleParent): + """Tests the gen_mksurfdata_jobscript_single subroutines on Derecho""" + + def test_derecho_mpirun(self): + """ + test derecho mpirun. This would've helped caught a problem we ran into + It will also be helpful when sumodules are updated to guide to solutions + to problems + """ + machine = "derecho" + nodes = 4 + tasks = 128 + unit_testing.add_machine_node_args(machine, nodes, tasks) + args = get_parser().parse_args() + check_parser_args(args) + self.assertEqual(machine, args.machine) + self.assertEqual(tasks, args.tasks_per_node) + self.assertEqual(nodes, args.number_of_nodes) + self.assertEqual(self._account, args.account) + # Create the env_mach_specific.xml file needed for get_mpirun + # This will catch problems with our usage of CIME objects + # Doing this here will also catch potential issues in the gen_mksurfdata_build script + configure_path = os.path.join(path_to_cime(), "CIME", "scripts", "configure") + self.assertTrue(os.path.exists(configure_path)) + options = " --macros-format CMake --silent --compiler intel --machine " + machine + cmd = configure_path + options + cmd_list = cmd.split() + run_cmd_output_on_error( + cmd=cmd_list, errmsg="Trouble running configure", cwd=self._bld_path + ) + self.assertTrue(os.path.exists(self._env_mach)) + expected_attribs = {"mpilib": "default"} + with open(self._jobscript_file, "w", encoding="utf-8") as runfile: + attribs = write_runscript_part1( + number_of_nodes=nodes, + tasks_per_node=tasks, + machine=machine, + account=self._account, + walltime=args.walltime, + runfile=runfile, + ) + self.assertEqual(attribs, expected_attribs) + (executable, mksurfdata_path, env_mach_path) = get_mpirun(args, attribs) + expected_exe = "time mpibind " + self.assertEqual(executable, expected_exe) + self.assertEqual(mksurfdata_path, self._mksurf_exe) + self.assertEqual(env_mach_path, self._env_mach) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_sys_plumber2_surf_wrapper.py b/python/ctsm/test/test_sys_plumber2_surf_wrapper.py new file mode 100755 index 0000000000..12ca561150 --- /dev/null +++ b/python/ctsm/test/test_sys_plumber2_surf_wrapper.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +"""System tests for plumber2_surf_wrapper""" + +import os +import unittest +import tempfile +import shutil +import sys + +from ctsm import unit_testing +from ctsm.site_and_regional.plumber2_surf_wrapper import main +from ctsm.site_and_regional.plumber2_shared import read_plumber2_sites_csv +from ctsm.path_utils import path_to_ctsm_root + +# Allow test names that pylint doesn't like; otherwise hard to make them +# readable +# pylint: disable=invalid-name + + +class TestSysPlumber2SurfWrapper(unittest.TestCase): + """ + System tests for plumber2_surf_wrapper + """ + + def setUp(self): + """ + Make tempdir for use by these tests. + """ + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + os.chdir(self._tempdir) # cd to tempdir + + # Path to script + self.tool_path = os.path.join( + path_to_ctsm_root(), + "tools", + "site_and_regional", + "plumber2_surf_wrapper", + ) + + # Path to test inputs directory + self.test_inputs = os.path.join( + os.path.dirname(__file__), "testinputs", "plumber2_surf_wrapper" + ) + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) + + def test_plumber2_surf_wrapper(self): + """ + Run the entire tool with default settings. + CAN ONLY RUN ON SYSTEMS WITH INPUTDATA + """ + + sys.argv = [self.tool_path] + main() + + # How many files do we expect? + plumber2_csv = read_plumber2_sites_csv() + n_files_expected = len(plumber2_csv) + + # How many files did we get? + file_list = os.listdir("subset_data_single_point") + n_files = len(file_list) + + # Check + self.assertEqual(n_files_expected, n_files) + + def test_plumber2_surf_wrapper_78pft(self): + """ + Run the entire tool with --crop. + CAN ONLY RUN ON SYSTEMS WITH INPUTDATA + """ + + sys.argv = [self.tool_path, "--crop"] + main() + + # How many files do we expect? + plumber2_csv = read_plumber2_sites_csv() + n_files_expected = len(plumber2_csv) + + # How many files did we get? + file_list = os.listdir("subset_data_single_point") + n_files = len(file_list) + + # Check + self.assertEqual(n_files_expected, n_files) + + def test_plumber2_surf_wrapper_invalid_pft(self): + """ + plumber2_surf_wrapper should error if invalid PFT is given + """ + + sys.argv = [ + self.tool_path, + "--plumber2-sites-csv", + os.path.join(self.test_inputs, "PLUMBER2_sites_invalid_pft.csv"), + ] + with self.assertRaisesRegex(RuntimeError, "must be a valid PFT"): + main() + + def test_plumber2_surf_wrapper_existing_no_overwrite_fails(self): + """ + plumber2_surf_wrapper should fail if file exists but --overwrite isn't given + """ + + sys_argv_shared = [ + self.tool_path, + "--plumber2-sites-csv", + os.path.join(self.test_inputs, "PLUMBER2_site_valid.csv"), + ] + + # Run twice, expecting second to fail + sys.argv = sys_argv_shared + main() + sys.argv = sys_argv_shared + with self.assertRaisesRegex(SystemExit, "exists"): + main() + + def test_plumber2_surf_wrapper_existing_overwrite_passes(self): + """ + plumber2_surf_wrapper should pass if file exists and --overwrite is given + """ + + sys_argv_shared = [ + self.tool_path, + "--plumber2-sites-csv", + os.path.join(self.test_inputs, "PLUMBER2_site_valid.csv"), + ] + + # Run once to generate the files + sys.argv = sys_argv_shared + main() + + # Run again with --overwrite, expecting pass + sys.argv = sys_argv_shared + ["--overwrite"] + main() + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_sys_py_env_create.py b/python/ctsm/test/test_sys_py_env_create.py index 43f9d76a56..dc15a2f738 100644 --- a/python/ctsm/test/test_sys_py_env_create.py +++ b/python/ctsm/test/test_sys_py_env_create.py @@ -60,9 +60,11 @@ def setUp(self): self.py_env_create = os.path.join(path_to_ctsm_root(), "py_env_create") assert os.path.exists(self.py_env_create) - # Get path to testing condafile + # Get path to testing conda/mambafile: + # conda needs a completely empty file (# comments okay) as of 25.5.1, but mamba as of 2.3.1 + # needs at least some YML structure. self.empty_condafile = os.path.join(path_to_ctsm_root(), "python", "empty.txt") - assert os.path.exists(self.empty_condafile) + self.empty_mambafile = os.path.join(path_to_ctsm_root(), "python", "empty.yml") # Set up other variables self.env_names = [] @@ -89,7 +91,12 @@ def _create_empty_env(self, check=None, extra_args=None, expect_error=False, new self.env_names.append(get_unique_env_name(5)) # Form and run command - cmd = [self.py_env_create, "-n", self.env_names[-1], "-f", self.empty_condafile, "--yes"] + if extra_args is not None and ("-m" in extra_args or "--mamba" in extra_args): + empty_file = self.empty_mambafile + else: + empty_file = self.empty_condafile + assert os.path.exists(empty_file) + cmd = [self.py_env_create, "-n", self.env_names[-1], "-f", empty_file, "--yes"] if extra_args: cmd += extra_args out = subprocess.run(cmd, capture_output=True, text=True, check=False) @@ -327,7 +334,8 @@ def test_complete_py_env_create(self): raise e env_list = get_conda_envs() for env_name in self.env_names: - assert does_env_exist(env_name, env_list) + if not does_env_exist(env_name, env_list): + raise AssertionError(f"environment not found: {env_name}") def test_complete_py_env_create_mamba(self): """ @@ -358,7 +366,8 @@ def test_complete_py_env_create_mamba(self): raise e env_list = get_conda_envs() for env_name in self.env_names: - assert does_env_exist(env_name, env_list) + if not does_env_exist(env_name, env_list): + raise AssertionError(f"environment not found: {env_name}") if __name__ == "__main__": diff --git a/python/ctsm/test/test_sys_query_paramfile.py b/python/ctsm/test/test_sys_query_paramfile.py new file mode 100755 index 0000000000..c01e7bc5e8 --- /dev/null +++ b/python/ctsm/test/test_sys_query_paramfile.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +"""System tests for query_paramfile""" + +import unittest +import os +import sys +import io +from contextlib import redirect_stdout +import tempfile +import shutil +import xarray as xr + +from ctsm import unit_testing + +from ctsm.param_utils import query_paramfile as qp + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + +PARAMFILE = os.path.join( + os.path.dirname(__file__), "testinputs", "ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc" +) + + +class TestSysQueryParamfile(unittest.TestCase): + """System tests of query_paramfile""" + + def setUp(self): + self.orig_argv = sys.argv + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + sys.argv = self.orig_argv + shutil.rmtree(self.tempdir, ignore_errors=True) + + def test_query_paramfile_scalar_nopfts(self): + """Test that print_values works with scalar parameter and no PFTs specified""" + + sys.argv = ["get_arguments", "-i", PARAMFILE, "phenology_soil_depth"] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + out = f.getvalue() + self.assertEqual("phenology_soil_depth: 0.08\n", out) + + def test_query_paramfile_scalar_ignorepfts(self): + """Test that print_values works with scalar parameter and PFTs specified (ignored)""" + + sys.argv = ["get_arguments", "-i", PARAMFILE, "phenology_soil_depth", "--pft", "c3_crop"] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + out = f.getvalue() + self.assertEqual("phenology_soil_depth: 0.08\n", out) + + def test_query_paramfile_pft_selectnone(self): + """Test that print_values works with PFT-dim parameter and no PFTs specified""" + + sys.argv = ["get_arguments", "-i", PARAMFILE, "rswf_min"] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + out = f.getvalue() + self.assertRegex( + out, + ( + r"rswf_min:\n" + r"\s+not_vegetated\s*: 0\.25\n" + r"\s+needleleaf_evergreen_temperate_tree\s*: 0\.25\n.*" + ), + ) + + def test_query_paramfile_pft_select2(self): + """Test that print_values works with PFT-dim parameter and two PFTs specified""" + + sys.argv = [ + "get_arguments", + "-i", + PARAMFILE, + "--pft", + "not_vegetated,needleleaf_evergreen_temperate_tree", + "rswf_min", + ] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + out = f.getvalue() + self.assertRegex( + out, + ( + r"rswf_min:\n" + r"\s+not_vegetated\s*: 0\.25\n" + r"\s+needleleaf_evergreen_temperate_tree\s*: 0\.25\n" + ), + ) + + def test_query_paramfile_no_variables_fake(self): + """ + Test that print_values prints every variable when no variables are given. Use a small fake + paramfile so we can check that what gets printed is what we expect. + """ + + fake_da1 = xr.DataArray(data=[1, 2, 3]) + fake_da2 = xr.DataArray(data=[4, 5, 6]) + fake_ds = xr.Dataset(data_vars={"fake1": fake_da1, "fake2": fake_da2}) + fake_nc_path = os.path.join(self.tempdir, "fake_da.nc") + fake_ds.to_netcdf(fake_nc_path) + + sys.argv = [ + "query_paramfile", + "-i", + fake_nc_path, + ] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + out = f.getvalue() + self.assertRegex(out, (r"fake1: \[1 2 3\]\nfake2: \[4 5 6\]\n")) + + def test_query_paramfile_no_variables_real(self): + """ + Test that query_paramfile doesn't error when trying to print every variable from a real + paramfile. Don't actually check that it matches what we expect; that's done in + test_query_paramfile_no_variables_fake. + """ + + sys.argv = [ + "query_paramfile", + "-i", + PARAMFILE, + ] + + f = io.StringIO() + with redirect_stdout(f): + qp.main() + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_sys_set_paramfile.py b/python/ctsm/test/test_sys_set_paramfile.py new file mode 100755 index 0000000000..f65c8e0d50 --- /dev/null +++ b/python/ctsm/test/test_sys_set_paramfile.py @@ -0,0 +1,824 @@ +#!/usr/bin/env python3 + +"""System tests for set_paramfile""" + +import unittest +import os +import sys +import shutil +import tempfile +import numpy as np +import xarray as xr + +from ctsm import unit_testing + +from ctsm.netcdf_utils import get_netcdf_format +from ctsm.param_utils import set_paramfile as sp +from ctsm.param_utils.paramfile_shared import open_paramfile, are_paramfile_dataarrays_identical +from ctsm.param_utils.paramfile_shared import check_pfts_in_paramfile, get_selected_pft_indices + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +PARAMFILE = os.path.join( + os.path.dirname(__file__), "testinputs", "ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc" +) + + +class TestSysSetParamfile(unittest.TestCase): + """System tests of set_paramfile""" + + # pylint: disable=too-many-public-methods + + def setUp(self): + self.orig_argv = sys.argv + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + sys.argv = self.orig_argv + shutil.rmtree(self.tempdir, ignore_errors=True) + + def test_set_paramfile_copyfile(self): + """Test that set_paramfile can straight-up copy to a new file""" + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = ["set_paramfile", "-i", PARAMFILE, "-o", output_path] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that contents are functionally identical + self.assertEqual(ds_in, ds_out) + + # Check that both are the same kind of netCDF + self.assertEqual(get_netcdf_format(PARAMFILE), get_netcdf_format(output_path)) + + def test_set_paramfile_copy_without_adding_fillvalue(self): + """Test that set_paramfile can copy to a new file without adding _FillValue""" + input_path = os.path.join(self.tempdir, "input.nc") + output_path = os.path.join(self.tempdir, "output.nc") + param_name = "param0" + + # Save a test paramfile without _FillValue + da = xr.DataArray(data=np.float64([1, 2, 3])) + da.encoding["_FillValue"] = None + ds = xr.Dataset(data_vars={param_name: da}) + ds.to_netcdf(input_path, encoding={param_name: {"_FillValue": None}}) + ds_in = open_paramfile(input_path) + self.assertFalse("_FillValue" in ds_in[param_name].encoding) + self.assertFalse("_FillValue" in ds_in[param_name].attrs) + + # Use set_paramfile to copy to new file + sys.argv = ["set_paramfile", "-i", input_path, "-o", output_path] + sp.main() + + # Check that _FillValue wasn't added + ds_out = open_paramfile(output_path) + self.assertFalse("_FillValue" in ds_out[param_name].encoding) + self.assertFalse("_FillValue" in ds_out[param_name].attrs) + + def test_set_paramfile_extractpfts(self): + """Test that set_paramfile can copy to a new file with only some requested PFTs""" + output_path = os.path.join(self.tempdir, "output.nc") + pfts_to_include = ["not_vegetated", "needleleaf_evergreen_temperate_tree"] + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + ",".join(pfts_to_include), + "--drop-other-pfts", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that included variables/coords match + for var in ds_in.variables: + actual = ds_out[var] + if sp.PFTNAME_VAR in ds_in[var].coords: + expected = ds_in[var].isel(pft=[0, 1]) + else: + expected = ds_in[var] + self.assertTrue(are_paramfile_dataarrays_identical(expected, actual)) + + def test_set_paramfile_changeparams_scalar_errors_given_list(self): + """Test that set_paramfile errors if given a list for a scalar parameter""" + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "a_coef=0.87,0.91", + ] + with self.assertRaisesRegex(RuntimeError, "Incorrect N dims"): + sp.main() + + def test_set_paramfile_changeparam_1d_given_scalar(self): + """ + Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it + to set all members of the 1d array to the given scalar. + """ + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "mxmat=1987", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == "mxmat": + self.assertTrue(np.all(ds_out[var].values == 1987)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_set_paramfile_changeparam_1d_given_scalar_and_pftlist(self): + """ + Test that set_paramfile works correctly if given a scalar for a 1-d parameter. We want it + to set all members of the 1d array to the given scalar. As + test_set_paramfile_changeparam_1d_given_scalar, but here we give a pft list. + """ + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + "temperate_corn,irrigated_temperate_corn", + "mxmat=1987", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == "mxmat": + # First, check that they weren't 1987 before + self.assertFalse(np.any(ds_in[var].values[17:18] == 1987)) + # Now check that they are 1987 + self.assertTrue(np.all(ds_out[var].values[17:18] == 1987)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_set_paramfile_changeparams_scalar_double(self): + """Test that set_paramfile can copy to a new file with some scalar double params changed""" + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "a_coef=0.87", + "bgc_cn_s2=87", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == "a_coef": + self.assertTrue(ds_in[var].values == 0.13) + self.assertTrue(ds_out[var].values == 0.87) + elif var == "bgc_cn_s2": + self.assertTrue(ds_in[var].values == 11) + self.assertTrue(ds_out[var].values == 87) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + # Check that data type hasn't changed + self.assertTrue(ds_in[var].dtype == ds_out[var].dtype) + + # Check that fill value hasn't changed + if "_FillValue" in ds_in[var].encoding: + fv_in = ds_in[var].encoding["_FillValue"] + fv_out = ds_out[var].encoding["_FillValue"] + if isinstance(fv_in, bytes): + self.assertTrue(isinstance(fv_out, bytes)) + self.assertEqual(fv_in, fv_out) + else: + self.assertEqual(np.isnan(fv_in), np.isnan(fv_out)) + if not np.isnan(fv_in): + self.assertEqual(fv_in, fv_out) + + def test_set_paramfile_changeparams_1d_double(self): + """ + Test that set_paramfile can copy to a new file with a 1-d double param changed (not PFT- + dimensioned) + """ + output_path = os.path.join(self.tempdir, "output.nc") + this_var = "mimics_fmet" + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + f"{this_var}=0.1,0.2,0.3,0.4", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the ones we changed, which should be + # set to what we asked + if var == this_var: + self.assertTrue( + np.array_equal(ds_in[var].values, np.array([0.75, 0.85, 0.013, 40])) + ) + self.assertTrue(np.array_equal(ds_out[var].values, np.array([0.1, 0.2, 0.3, 0.4]))) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + # Check that data type hasn't changed + self.assertTrue(ds_in[var].dtype == ds_out[var].dtype) + + # Check that fill value hasn't changed + if "_FillValue" in ds_in[var].encoding: + fv_in = ds_in[var].encoding["_FillValue"] + fv_out = ds_out[var].encoding["_FillValue"] + if isinstance(fv_in, bytes): + self.assertTrue(isinstance(fv_out, bytes)) + self.assertEqual(fv_in, fv_out) + else: + self.assertEqual(np.isnan(fv_in), np.isnan(fv_out)) + if not np.isnan(fv_in): + self.assertEqual(fv_in, fv_out) + + def test_set_paramfile_changeparams_scalar_int(self): + """Test that set_paramfile can copy to a new file with a scalar integer param changed""" + output_path = os.path.join(self.tempdir, "output.nc") + this_var = "upplim_destruct_metamorph" + new_value = 1987 + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + f"upplim_destruct_metamorph={new_value}", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that the variable in question is actually an integer to begin with + self.assertTrue(sp.is_integer(ds_in[this_var].values)) + # Also check that it actually differs from our new value + self.assertTrue(ds_in[this_var].values != new_value) + + for var in ds_in.variables: + # Check that all variables/coords are equal except the one we changed, which should be + # set to what we asked + if var == this_var: + self.assertTrue(ds_out[var].values == new_value) + else: + self.assertTrue(ds_in[var].equals(ds_out[var])) + + # Check that data type hasn't changed + self.assertTrue(ds_in[var].dtype == ds_out[var].dtype) + + def test_set_paramfile_extractpfts_changeparam_dbl(self): + """ + Test that set_paramfile can (1) copy to a new file with only some requested PFTs and (2) + change the values of double parameters of those PFTs + """ + output_path = os.path.join(self.tempdir, "output.nc") + pfts_to_include = ["not_vegetated", "needleleaf_evergreen_temperate_tree"] + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + ",".join(pfts_to_include), + "--drop-other-pfts", + "xl=0.724,0.87", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that included variables/coords match as expected + for var in ds_in.variables: + if var == "xl": + self.assertTrue(np.array_equal(np.array([0.724, 0.87]), ds_out[var].values)) + elif sp.PFTNAME_VAR in ds_in[var].coords: + self.assertTrue(ds_in[var].isel(pft=[0, 1]).equals(ds_out[var])) + else: + self.assertTrue(ds_in[var].equals(ds_out[var])) + + def test_set_paramfile_changeparam_dbl_onlysomepfts(self): + """ + Test that set_paramfile can (1) copy to a new file with only some requested PFTs and (2) + change the values of double parameters of those PFTs + """ + output_path = os.path.join(self.tempdir, "output.nc") + pfts_to_include = ["not_vegetated", "needleleaf_evergreen_temperate_tree"] + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + ",".join(pfts_to_include), + "xl=0.724,0.87", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that included variables/coords match as expected + for var in ds_in.variables: + if var == "xl": + + # Changed values (first 2) + this_slice = slice(0, 2) + expected = np.array([0.724, 0.87]) + result = ds_out[var].isel(pft=this_slice).values + self.assertTrue(np.array_equal(expected, result)) + + # Preserved values (everything but the first 2) + this_slice = slice(2, None) + expected = ds_in["xl"].isel(pft=this_slice) + result = ds_out["xl"].isel(pft=this_slice) + self.assertTrue(are_paramfile_dataarrays_identical(expected, result)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_set_paramfile_extractpfts_changeparam_int(self): + """ + Test that set_paramfile can (1) copy to a new file with only some requested PFTs and (2) + change the values of integer parameters of those PFTs + """ + output_path = os.path.join(self.tempdir, "output.nc") + pfts_to_include = ["not_vegetated", "needleleaf_evergreen_temperate_tree"] + this_var = "max_NH_planting_date" + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + ",".join(pfts_to_include), + "--drop-other-pfts", + f"{this_var}=1986,1987", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_in = open_paramfile(PARAMFILE) + ds_out = open_paramfile(output_path) + + # Check that the variable in question is actually an integer to begin with + self.assertTrue(sp.is_integer(ds_in[this_var].values)) + # Also check that it actually differs from our new values + self.assertTrue(ds_in[this_var].values[0] != 1986) + self.assertTrue(ds_in[this_var].values[1] != 1987) + + # Check that included variables/coords match as expected + for var in ds_in.variables: + if var == this_var: + self.assertTrue(np.array_equal(np.array([1986, 1987]), ds_out[var].values)) + elif sp.PFTNAME_VAR in ds_in[var].coords: + self.assertTrue( + are_paramfile_dataarrays_identical(ds_in[var].isel(pft=[0, 1]), ds_out[var]) + ) + else: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_set_paramfile_fill_value_scalar_double_nan(self): + """ + Test that setting scalar double to fill value writes a literal NaN if that's the _FillValue + """ + # Create paramfile with a double variable with fill value NaN + input_path = os.path.join(self.tempdir, "input.nc") + ds = open_paramfile(PARAMFILE, mask_and_scale=True) + new_param_name = "new_param_abc123" + ds[new_param_name] = xr.DataArray(data=np.array(3.14)) + ds[new_param_name].encoding["_FillValue"] = np.nan + self.assertTrue(new_param_name in ds) + ds.to_netcdf(input_path) + + # Check that its fill value is NaN + ds_in = open_paramfile(input_path, mask_and_scale=True) + self.assertTrue("_FillValue" in ds_in[new_param_name].encoding) + self.assertTrue(np.isnan(ds_in[new_param_name].encoding["_FillValue"])) + + # Ask to set it to the FillValue + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + input_path, + "-o", + output_path, + f"{new_param_name}=nan", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + + # Ensure it wrote a literal NaN + ds_out = open_paramfile(output_path, mask_and_scale=False) + self.assertTrue(np.isnan(ds_out[new_param_name])) + + # Ensure it preserved NaN FillValue + ds_out = open_paramfile(output_path, mask_and_scale=True) + self.assertTrue(np.isnan(ds_out[new_param_name].encoding["_FillValue"])) + + def test_set_paramfile_fill_value_scalar_double_real(self): + """ + Test that setting scalar double to fill value does NOT write NaN if that's not the + _FillValue + """ + # Create paramfile with a double variable with fill value -999.9 + input_path = os.path.join(self.tempdir, "input.nc") + ds = open_paramfile(PARAMFILE, mask_and_scale=True) + new_param_name = "new_param_abc123" + ds[new_param_name] = xr.DataArray(data=np.array(3.14)) + fill_value = -999.9 + ds[new_param_name].encoding["_FillValue"] = fill_value + self.assertTrue(new_param_name in ds) + ds.to_netcdf(input_path) + + # Check that its fill value is what we asked for + ds_in = open_paramfile(input_path, mask_and_scale=True) + self.assertTrue("_FillValue" in ds_in[new_param_name].encoding) + self.assertEqual(fill_value, ds_in[new_param_name].encoding["_FillValue"]) + + # Ask to set it to the FillValue + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + input_path, + "-o", + output_path, + f"{new_param_name}=nan", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + + # Ensure it preserved FillValue + ds_out = open_paramfile(output_path, mask_and_scale=True) + self.assertEqual(fill_value, ds_out[new_param_name].encoding["_FillValue"]) + self.assertTrue(np.isnan(ds_out[new_param_name].values)) + + # Ensure it wrote the FillValue and not a literal NaN + ds_out = open_paramfile(output_path, mask_and_scale=False) + self.assertEqual(fill_value, ds_out[new_param_name].values) + + def test_set_paramfile_setparams_scalar_double_tonan_with_nancaps(self): + """Test setting scalar double to NaN using 'NaN'""" + output_path = os.path.join(self.tempdir, "output.nc") + this_var = "a_coef" + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + f"{this_var}=NaN", + ] + + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_out = open_paramfile(output_path, mask_and_scale=True) + self.assertTrue(np.isnan(ds_out[this_var])) + + def test_set_paramfile_setparams_pft_double_tonan_with_nan(self): + """Test setting PFT-dimensioned double to NaN using 'nan'""" + output_path = os.path.join(self.tempdir, "output.nc") + pfts_to_include = ["not_vegetated", "needleleaf_evergreen_temperate_tree"] + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + ",".join(pfts_to_include), + "--drop-other-pfts", + "xl=nan,nan", + "planting_temp=nan,nan", + ] + + # Check that planting_temp is already nan and xl isn't + ds_in = open_paramfile(PARAMFILE, mask_and_scale=True) + self.assertTrue(all(np.isnan(ds_in["planting_temp"].isel(pft=[0, 1])))) + self.assertTrue(not any(np.isnan(ds_in["xl"].isel(pft=[0, 1])))) + + sp.main() + self.assertTrue(os.path.exists(output_path)) + ds_out = open_paramfile(output_path, mask_and_scale=True) + self.assertTrue(all(np.isnan(ds_out["xl"]))) + self.assertTrue(all(np.isnan(ds_out["planting_temp"]))) + + def test_set_paramfile_setparams_nan_but_no_fillvalue(self): + """Test that NotImplementedError is given if trying to set NaN but param has no FillValue""" + + # Create paramfile with a double variable without fill value + input_path = os.path.join(self.tempdir, "input.nc") + ds = open_paramfile(PARAMFILE, mask_and_scale=True) + new_param_name = "new_param_abc123" + ds[new_param_name] = xr.DataArray(data=np.array(3.14)) + ds[new_param_name].encoding["_FillValue"] = None + self.assertTrue(new_param_name in ds) + ds.to_netcdf(input_path) + + # Check that it doesn't have fill value + ds_in = open_paramfile(input_path, mask_and_scale=True) + self.assertFalse("_FillValue" in ds_in[new_param_name].encoding) + + # Call set_paramfile, trying to set it to NaN + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + input_path, + "-o", + output_path, + f"{new_param_name}=nan", + ] + with self.assertRaisesRegex( + NotImplementedError, "Can't set parameter to fill value if it doesn't already have one:" + ): + sp.main() + + def test_set_paramfile_setparams_scalar_int_tonan_with_nan(self): + """Test that NotImplementedError is given if trying to set NaN for an integer""" + output_path = os.path.join(self.tempdir, "output.nc") + this_var = "upplim_destruct_metamorph" + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + f"{this_var}=nan", + ] + + with self.assertRaisesRegex( + NotImplementedError, "Can't set integer parameter to fill value:" + ): + sp.main() + + # TODO: Test changing param when extracting just one PFT + + # TODO: Test changing PFT name + + def test_set_paramfile_changeparam_multidim_errors(self): + """ + Test that set_paramfile errors if requesting change of a multi-dimensional parameter. This + test will obviously need to be replaced once that functionality is added. + """ + output_path = os.path.join(self.tempdir, "output.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "mimics_till_decompk_multipliers=dummy", + ] + + with self.assertRaises(NotImplementedError): + sp.main() + + def test_set_paramfile_setparams_just_one_pft(self): + """Test changing just one PFT's value of something without dropping others""" + output_path = os.path.join(self.tempdir, "output.nc") + pft_to_include = "needleleaf_deciduous_boreal_tree" + this_var = "rswf_max" + new_value = 0.7 + + # Ensure it wasn't new_value before + ds_in = open_paramfile(PARAMFILE) + pft_names = check_pfts_in_paramfile([pft_to_include], ds_in) + pft_index = get_selected_pft_indices([pft_to_include], pft_names)[0] + self.assertFalse(ds_in[this_var].values[pft_index] == new_value) + + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + pft_to_include, + f"{this_var}={new_value}", + ] + sp.main() + + ds_out = open_paramfile(output_path) + for i, value in enumerate(ds_out[this_var]): + if i == pft_index: + self.assertTrue(value == new_value) + else: + self.assertTrue(value == ds_in[this_var].values[i]) + + def test_set_paramfile_setparams_just_one_pft_dropothers_noset(self): + """Test dropping all but one PFT without changing any parameters""" + output_path = os.path.join(self.tempdir, "output.nc") + pft_to_include = "needleleaf_deciduous_boreal_tree" + + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + pft_to_include, + "--drop-other-pfts", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + + # Check that the file is just what you get if you drop all but the one PFT + ds_in = open_paramfile(PARAMFILE) + ds_in_1pft = sp.drop_other_pfts([pft_to_include], ds_in) + ds_out = open_paramfile(output_path) + self.assertTrue(set(ds_in_1pft.variables) == set(ds_out.variables)) + for var in ds_in_1pft: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in_1pft[var], ds_out[var])) + self.assertTrue(ds_in_1pft.equals(ds_out)) + self.assertEqual(ds_in_1pft.sizes["pft"], 1) + self.assertEqual(ds_out.sizes["pft"], 1) + + def test_set_paramfile_setparams_just_one_pft_dropothers_doset(self): + """Test dropping all but one PFT, changing one parameter""" + output_path = os.path.join(self.tempdir, "output.nc") + pft_to_include = "needleleaf_deciduous_boreal_tree" + this_var = "rswf_max" + new_value = 0.7 + + # Ensure it wasn't new_value before + ds_in = open_paramfile(PARAMFILE) + pft_names = check_pfts_in_paramfile([pft_to_include], ds_in) + pft_index = get_selected_pft_indices([pft_to_include], pft_names)[0] + self.assertFalse(ds_in[this_var].values[pft_index] == new_value) + + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output_path, + "-p", + pft_to_include, + "--drop-other-pfts", + f"{this_var}={new_value}", + ] + sp.main() + self.assertTrue(os.path.exists(output_path)) + + # Check that all variables match except for the one we changed + ds_out = open_paramfile(output_path) + ds_in_1pft = sp.drop_other_pfts([pft_to_include], ds_in) + for var in ds_in.variables: + da_in = ds_in_1pft[var] + da_out = ds_out[var] + if var == this_var: + self.assertFalse(are_paramfile_dataarrays_identical(da_in, da_out)) + else: + self.assertTrue(are_paramfile_dataarrays_identical(da_in, da_out)) + + def test_set_paramfile_int_errors_given_float_point0(self): + """ + Test that set_paramfile errors if given float value ending in .0 for an int field + """ + input_path = os.path.join(self.tempdir, "input.nc") + output_path = os.path.join(self.tempdir, "output.nc") + param_name = "param0" + + # Save a test paramfile with an int field + da = xr.DataArray(data=np.int32(3)) + ds = xr.Dataset(data_vars={param_name: da}) + ds.to_netcdf(input_path, encoding={param_name: {"_FillValue": None}}) + ds_in = open_paramfile(input_path) + self.assertTrue(sp.is_integer(ds_in[param_name].values)) + + # Try giving it a value ending in .0 + sys.argv = ["set_paramfile", "-i", input_path, "-o", output_path, f"{param_name}=4.0"] + with self.assertRaisesRegex(ValueError, "Invalid assignment to an integer parameter:"): + sp.main() + + def test_set_paramfile_int_errors_given_float_point1(self): + """ + Test that set_paramfile errors if given float value NOT ending in .0 for an int field + """ + input_path = os.path.join(self.tempdir, "input.nc") + output_path = os.path.join(self.tempdir, "output.nc") + param_name = "param0" + + # Save a test paramfile with an int field + da = xr.DataArray(data=np.int32(3)) + ds = xr.Dataset(data_vars={param_name: da}) + ds.to_netcdf(input_path, encoding={param_name: {"_FillValue": None}}) + ds_in = open_paramfile(input_path) + self.assertTrue(sp.is_integer(ds_in[param_name].values)) + + # Try giving it a value ending in .1 + sys.argv = ["set_paramfile", "-i", input_path, "-o", output_path, f"{param_name}=4.1"] + with self.assertRaisesRegex(ValueError, "Invalid assignment to an integer parameter:"): + sp.main() + + def test_set_paramfile_double_ok_given_int(self): + """ + Test that set_paramfile works if given int value for a double field + """ + input_path = os.path.join(self.tempdir, "input.nc") + output_path = os.path.join(self.tempdir, "output.nc") + param_name = "param0" + + # Save a test paramfile with a double field + da = xr.DataArray(data=np.float32(3.14)) + ds = xr.Dataset(data_vars={param_name: da}) + ds.to_netcdf(input_path, encoding={param_name: {"_FillValue": None}}) + ds_in = open_paramfile(input_path) + self.assertFalse(sp.is_integer(ds_in[param_name].values)) + + # Give it an integer + sys.argv = ["set_paramfile", "-i", input_path, "-o", output_path, f"{param_name}=4"] + sp.main() + + # Check that it's still a double after saving + ds_out = open_paramfile(output_path) + self.assertFalse(sp.is_integer(ds_out[param_name].values)) + + def test_set_paramfile_pft_order(self): + """ + Test that set_paramfile gives the same result regardless of the order you specify the PFTs + """ + + # First order + pfts_to_include = ["rice", "irrigated_rice"] + output0_path = os.path.join(self.tempdir, "output0.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output0_path, + "-p", + ",".join(pfts_to_include), + "mxmat=100,200", + ] + sp.main() + + # Reverse order + pfts_to_include.reverse() + output1_path = os.path.join(self.tempdir, "output1.nc") + sys.argv = [ + "set_paramfile", + "-i", + PARAMFILE, + "-o", + output1_path, + "-p", + ",".join(pfts_to_include), + "mxmat=200,100", + ] + sp.main() + + # These files should be identical + ds0 = open_paramfile(output0_path) + ds1 = open_paramfile(output1_path) + self.assertTrue(ds0.equals(ds1)) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_sys_subset_data.py b/python/ctsm/test/test_sys_subset_data.py new file mode 100644 index 0000000000..453df7c18e --- /dev/null +++ b/python/ctsm/test/test_sys_subset_data.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python3 +""" +System tests for subset_data + +You can run this by: + python -m unittest test_sys_subset_data.py +""" + +import unittest +import os +import sys +import tempfile +import inspect +import xarray as xr +from CIME.scripts.create_newcase import _main_func as create_newcase # pylint: disable=import-error + +# -- add python/ctsm to path (needed if we want to run the test stand-alone) +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) +sys.path.insert(1, _CTSM_PYTHON) + +# pylint: disable=wrong-import-position +from ctsm import unit_testing +from ctsm import subset_data +from ctsm.utils import find_one_file_matching_pattern + + +def _get_sitename_str_point(include_sitename, sitename, lon, lat): + """ + Given a site, return the string to use in output filenames + """ + if include_sitename: + sitename_str = sitename + else: + sitename_str = f"{float(lon)}_{float(lat)}" + return sitename_str + + +class TestSubsetDataSys(unittest.TestCase): + """ + Basic class for testing subset_data.py. + """ + + def setUp(self): + self.previous_dir = os.getcwd() + self.temp_dir_out = tempfile.TemporaryDirectory() + self.temp_dir_umd = tempfile.TemporaryDirectory() + self.temp_dir_caseparent = tempfile.TemporaryDirectory() + self.inputdata_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) + + def tearDown(self): + self.temp_dir_out.cleanup() + self.temp_dir_umd.cleanup() + os.chdir(self.previous_dir) + + def _check_create_newcase(self): + """ + Check that you can call create_newcase using the usermods from subset_data + """ + case_dir = os.path.join(self.temp_dir_caseparent.name, "case") + sys.argv = [ + "create_newcase", + "--case", + case_dir, + "--res", + "CLM_USRDAT", + "--compset", + "I2000Clm60Bgc", + "--run-unsupported", + "--user-mods-dir", + self.temp_dir_umd.name, + ] + create_newcase() + + def _check_result_file_matches_expected(self, expected_output_files, caller_n): + """ + Loop through a list of output files, making sure they match what we expect. + + caller_n should be an integer giving the number of levels above this function you need to + traverse before you hit the actual test name. If the test is calling this function directly, + caller_n = 1. If the test is calling a function that calls this function, caller_n = 2. Etc. + """ + all_files_present_and_match = True + result_file_found = True + expected_file_found = True + for basename in expected_output_files: + + # Check whether result (output) file exists. If not, note it but continue. + result_file = os.path.join(self.temp_dir_out.name, basename) + try: + result_file = find_one_file_matching_pattern(result_file) + except FileNotFoundError: + result_file_found = False + + # Check whether expected file exists. If not, note it but continue. + expected_file = os.path.join( + os.path.dirname(__file__), + "testinputs", + "expected_result_files", + inspect.stack()[caller_n][3], # Name of calling function (i.e., test name) + basename, + ) + try: + expected_file = find_one_file_matching_pattern(expected_file) + except FileNotFoundError: + expected_file_found = False + + # Raise an AssertionError if either file was not found + if not (result_file_found and expected_file_found): + msg = "" + if not result_file_found: + this_dir = os.path.dirname(result_file) + msg += f"\nResult file '{result_file}' not found. " + msg += f"Contents of directory '{this_dir}':\n\t" + msg += "\n\t".join(os.listdir(this_dir)) + if not expected_file_found: + this_dir = os.path.dirname(expected_file) + msg += f"\nExpected file '{expected_file}' not found. " + msg += f"Contents of directory '{this_dir}':\n\t" + msg += "\n\t".join(os.listdir(this_dir)) + raise AssertionError(msg) + + # Compare the two files + ds_result = xr.open_dataset(result_file) + ds_expected = xr.open_dataset(expected_file) + if not ds_result.equals(ds_expected): + print("Result differs from expected: " + basename) + print(ds_result) + print(ds_expected) + all_files_present_and_match = False + return all_files_present_and_match + + def _do_test_subset_data_reg_amazon(self, include_regname=True): + """ + Convenience function for multiple tests of subset_data region for the Amazon + """ + regname = "TMP" + lat1 = -12 + lat2 = -7 + lon1 = 291 + lon2 = 299 + cfg_file = os.path.join( + self.inputdata_dir, + "ctsm", + "test", + "testinputs", + "subset_data_amazon.cfg", + ) + print(cfg_file) + sys.argv = [ + "subset_data", + "region", + "--lat1", + str(lat1), + "--lat2", + str(lat2), + "--lon1", + str(lon1), + "--lon2", + str(lon2), + "--create-mesh", + "--create-domain", + "--create-surface", + "--surf-year", + "2000", + "--create-user-mods", + "--outdir", + self.temp_dir_out.name, + "--user-mods-dir", + self.temp_dir_umd.name, + "--inputdata-dir", + self.inputdata_dir, + "--cfg-file", + cfg_file, + "--overwrite", + ] + if include_regname: + sys.argv += ["--reg", regname] + subset_data.main() + + # Loop through all the output files, making sure they match what we expect. + daystr = "[0-9][0-9][0-9][0-9][0-9][0-9]" # 6-digit day code, yymmdd + if include_regname: + regname_str = regname + else: + regname_str = f"{float(lon1)}-{float(lon2)}_{float(lat1)}-{float(lat2)}" + expected_output_files = [ + f"domain.lnd.5x5pt-amazon_navy_{regname_str}_c{daystr}_ESMF_UNSTRUCTURED_MESH.nc", + f"domain.lnd.5x5pt-amazon_navy_{regname_str}_c{daystr}.nc", + f"surfdata_{regname_str}_amazon_hist_16pfts_CMIP6_2000_c{daystr}.nc", + ] + self.assertTrue(self._check_result_file_matches_expected(expected_output_files, 2)) + + # Check that create_newcase works + # SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + self._check_create_newcase() + + def test_subset_data_reg_amazon(self): + """ + Test subset_data for Amazon region + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_reg_amazon() + + def test_subset_data_reg_amazon_noregname(self): + """ + Test subset_data for Amazon region + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_reg_amazon(include_regname=False) + + def test_subset_data_reg_infile_detect360(self): + """ + Test subset_data for region with ambiguous longitudes. We specify the longitude type for + lon1 and lon2 but not for the input data files. This should still work as long as the input + data file longitude type is detectable and matches --lon-type. + """ + sys.argv = [ + "subset_data", + "region", + "--lat1", + "-12", + "--lat2", + "-7", + "--lon1", + "15", + "--lon2", + "23", + "--lon-type", + "360", + "--reg", + "TMP", + "--create-mesh", + "--create-domain", + "--create-surface", + "--surf-year", + "2000", + "--create-user-mods", + "--outdir", + self.temp_dir_out.name, + "--user-mods-dir", + self.temp_dir_umd.name, + "--overwrite", + ] + subset_data.main() + + def test_subset_data_reg_infile_detect180_error(self): + """ + Specifying --lon-type 180 but an input file of type 360 should error + """ + sys.argv = [ + "subset_data", + "region", + "--lat1", + "-12", + "--lat2", + "-7", + "--lon1", + "15", + "--lon2", + "23", + "--lon-type", + "180", + "--reg", + "TMP", + "--create-mesh", + "--create-domain", + "--create-surface", + "--surf-year", + "2000", + "--create-user-mods", + "--outdir", + self.temp_dir_out.name, + "--user-mods-dir", + self.temp_dir_umd.name, + "--overwrite", + ] + with self.assertRaisesRegex( + RuntimeError, r"File lon type \(360\) doesn't match boundary lon type \(180\)" + ): + subset_data.main() + + def _do_test_subset_data_pt_surface(self, lon, include_sitename=True): + """ + Given a longitude, test subset_data point --create-surface + """ + lat = -12 + cfg_file = os.path.join( + self.inputdata_dir, + "ctsm", + "test", + "testinputs", + "subset_data_amazon.cfg", + ) + print(cfg_file) + sys.argv = [ + "subset_data", + "point", + "--lat", + str(lat), + "--lon", + str(lon), + "--create-domain", + "--create-surface", + "--surf-year", + "2000", + "--create-user-mods", + "--outdir", + self.temp_dir_out.name, + "--user-mods-dir", + self.temp_dir_umd.name, + "--inputdata-dir", + self.inputdata_dir, + "--cfg-file", + cfg_file, + "--overwrite", + ] + sitename = "TMP" + if include_sitename: + sys.argv += ["--site", sitename] + subset_data.main() + + # Loop through all the output files, making sure they match what we expect. + daystr = "[0-9][0-9][0-9][0-9][0-9][0-9]" # 6-digit day code, yymmdd + sitename_str = _get_sitename_str_point(include_sitename, sitename, lon, lat) + expected_output_files = [ + f"surfdata_{sitename_str}_amazon_hist_16pfts_CMIP6_2000_c{daystr}.nc", + ] + self.assertTrue(self._check_result_file_matches_expected(expected_output_files, 2)) + + # Check that create_newcase works + # SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + self._check_create_newcase() + + def test_subset_data_pt_surface_amazon_type360(self): + """ + Test subset_data --create-surface for Amazon point with longitude type 360 + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_surface(291) + + def test_subset_data_pt_surface_amazon_type180(self): + """ + Test subset_data --create-surface for Amazon point with longitude type 180 + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_surface(-69) + + def test_subset_data_pt_surface_amazon_type180_nositename(self): + """ + Test subset_data --create-surface for Amazon point with longitude type 180 + without specifying a site name + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_surface(-69, include_sitename=False) + + def _do_test_subset_data_pt_landuse(self, lon, include_sitename=True): + """ + Given a longitude, test subset_data point --create-landuse + """ + lat = -12 + sitename = "TMP" + cfg_file = os.path.join( + self.inputdata_dir, + "ctsm", + "test", + "testinputs", + "subset_data_amazon_1850.cfg", + ) + print(cfg_file) + sys.argv = [ + "subset_data", + "point", + "--lat", + str(lat), + "--lon", + str(lon), + "--create-domain", + "--create-surface", + "--surf-year", + "1850", + "--create-landuse", + "--create-user-mods", + "--outdir", + self.temp_dir_out.name, + "--user-mods-dir", + self.temp_dir_umd.name, + "--inputdata-dir", + self.inputdata_dir, + "--cfg-file", + cfg_file, + "--overwrite", + ] + if include_sitename: + sys.argv += ["--site", sitename] + subset_data.main() + + # Loop through all the output files, making sure they match what we expect. + daystr = "[0-9][0-9][0-9][0-9][0-9][0-9]" # 6-digit day code, yymmdd + sitename_str = _get_sitename_str_point(include_sitename, sitename, lon, lat) + expected_output_files = [ + f"surfdata_{sitename_str}_amazon_hist_1850_78pfts_c{daystr}.nc", + f"landuse.timeseries_{sitename_str}_amazon_hist_1850-1853_78pfts_c{daystr}.nc", + ] + self.assertTrue(self._check_result_file_matches_expected(expected_output_files, 2)) + + # Check that create_newcase works + # SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + self._check_create_newcase() + + def test_subset_data_pt_landuse_amazon_type360(self): + """ + Test subset_data --create-landuse for Amazon point with longitude type 360 + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_landuse(291) + + def test_subset_data_pt_landuse_amazon_type360_nositename(self): + """ + Test subset_data --create-landuse for Amazon point with longitude type 360 and no site name + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_landuse(291, include_sitename=False) + + def test_subset_data_pt_landuse_amazon_type180(self): + """ + Test subset_data --create-landuse for Amazon point with longitude type 180 + SHOULD WORK ONLY ON CESM-SUPPORTED MACHINES + """ + self._do_test_subset_data_pt_landuse(-69) + + def _do_test_subset_data_pt_datm(self, lon, include_sitename=True): + """ + Given a longitude, test subset_data point --create-datm + """ + start_year = 1986 + end_year = 1988 + sitename = "TMP" + lat = -12 + outdir = self.temp_dir_out.name + sys.argv = [ + "subset_data", + "point", + "--lat", + str(lat), + "--lon", + str(lon), + "--create-datm", + "--datm-syr", + str(start_year), + "--datm-eyr", + str(end_year), + "--create-user-mods", + "--outdir", + outdir, + "--user-mods-dir", + self.temp_dir_umd.name, + "--overwrite", + ] + if include_sitename: + sys.argv += ["--site", sitename] + subset_data.main() + + # Loop through all the output files, making sure they match what we expect. + daystr = "[0-9][0-9][0-9][0-9][0-9][0-9]" # 6-digit day code, yymmdd + sitename_str = _get_sitename_str_point(include_sitename, sitename, lon, lat) + expected_output_files = [ + f"domain.crujra_v2.3_0.5x0.5_{sitename_str}_c{daystr}.nc", + ] + for year in list(range(start_year, end_year + 1)): + for forcing in ["Solr", "Prec", "TPQWL"]: + expected_output_files.append( + f"clmforc.CRUJRAv2.5_0.5x0.5.{forcing}.{sitename_str}.{year}.nc" + ) + expected_output_files = [os.path.join("datmdata", x) for x in expected_output_files] + self.assertTrue(self._check_result_file_matches_expected(expected_output_files, 2)) + + # Check that create_newcase works + self._check_create_newcase() + + def test_subset_data_pt_datm_amazon_type360(self): + """ + Test subset_data --create-datm for Amazon point with longitude type 360 + FOR NOW CAN ONLY BE RUN ON DERECHO/CASPER + """ + self._do_test_subset_data_pt_datm(291) + + def test_subset_data_pt_datm_amazon_type180(self): + """ + Test subset_data --create-datm for Amazon point with longitude type 180 + FOR NOW CAN ONLY BE RUN ON DERECHO/CASPER + """ + self._do_test_subset_data_pt_datm(-69) + + def test_subset_data_pt_datm_amazon_type180_nositename(self): + """ + Test subset_data --create-datm for Amazon point with longitude type 180 without providing + site name. + FOR NOW CAN ONLY BE RUN ON DERECHO/CASPER + """ + self._do_test_subset_data_pt_datm(-69, include_sitename=False) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_args_utils.py b/python/ctsm/test/test_unit_args_utils.py index 548e7d9389..b4b101dda6 100755 --- a/python/ctsm/test/test_unit_args_utils.py +++ b/python/ctsm/test/test_unit_args_utils.py @@ -17,6 +17,7 @@ # pylint: disable=wrong-import-position from ctsm.args_utils import plon_type, plat_type +from ctsm.args_utils import comma_separated_list from ctsm import unit_testing # pylint: disable=invalid-name @@ -118,6 +119,48 @@ def test_platType_outOfBounds_negative(self): _ = plat_type(-91) +class TestArgsCommaSeparatedList(unittest.TestCase): + """ + Test comma_separated_list argparse helper + """ + + def setUp(self): + self.orig_argv = sys.argv + + def tearDown(self): + sys.argv = self.orig_argv + + def test_comma_separated_list_1(self): + """ + Test comma_separated_list with one item in list + """ + parser = argparse.ArgumentParser() + parser.add_argument("--list-arg", type=comma_separated_list) + sys.argv = ["scriptname", "--list-arg", "abc"] + args = parser.parse_args() + self.assertEqual(["abc"], args.list_arg) + + def test_comma_separated_list_1plus(self): + """ + Test comma_separated_list with one item in list but a comma too + """ + parser = argparse.ArgumentParser() + parser.add_argument("--list-arg", type=comma_separated_list) + sys.argv = ["scriptname", "--list-arg", "abc,"] + args = parser.parse_args() + self.assertEqual(["abc", ""], args.list_arg) + + def test_comma_separated_list_2(self): + """ + Test comma_separated_list with two items in list + """ + parser = argparse.ArgumentParser() + parser.add_argument("--list-arg", type=comma_separated_list) + sys.argv = ["scriptname", "--list-arg", "abc,def"] + args = parser.parse_args() + self.assertEqual(["abc", "def"], args.list_arg) + + if __name__ == "__main__": unit_testing.setup_for_tests() unittest.main() diff --git a/python/ctsm/test/test_unit_ctsm_logging.py b/python/ctsm/test/test_unit_ctsm_logging.py new file mode 100755 index 0000000000..d1b4891b17 --- /dev/null +++ b/python/ctsm/test/test_unit_ctsm_logging.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +"""Unit tests for functions in ctsm_logging""" + +import unittest +import io +from contextlib import redirect_stdout + +from ctsm import unit_testing +from ctsm.ctsm_logging import log, error +from ctsm.utils import datetime_string + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +DATETIME_STR_PATTERN = r"\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}" + + +class TestLog(unittest.TestCase): + """ + Tests of log() function + + Currently not testing ability to write to file, because unittest makes that difficult. So just + testing write to stdout. + """ + + def test_datetime_str_pattern(self): + """Test that our regex matches output of datetime_str""" + self.assertRegex(datetime_string(), expected_regex=DATETIME_STR_PATTERN) + + def test_log_without_logger(self): + """ + Tests the log() function without providing a logger for writing to file + """ + msg = "abc123" + f = io.StringIO() + with redirect_stdout(f): + log(None, msg) + + # Check that stdout matches what we expect + stdout = f.getvalue() + expected_regex = DATETIME_STR_PATTERN + r"\s+test_log_without_logger\s+" + msg + self.assertRegex(stdout, expected_regex=expected_regex) + + +class TestError(unittest.TestCase): + """ + Tests of error() function + + Currently not testing ability to write to file, because unittest makes that difficult. So just + testing write to stdout and error raising. + """ + + def test_error_without_logger(self): + """ + Tests the error() function without providing a logger for writing to file and without + specifying a custom error type + """ + msg = "abc123" + f = io.StringIO() + error_raised = None + try: + with redirect_stdout(f): + error(None, msg) + except Exception as e: # pylint: disable=broad-exception-caught + error_raised = e + + # Check that stdout matches what we expect + stdout = f.getvalue() + expected_regex = DATETIME_STR_PATTERN + r"\s+test_error_without_logger\s+" + msg + self.assertRegex(stdout, expected_regex=expected_regex) + + # Check that error is correct + self.assertFalse(error_raised is None) + self.assertIsInstance(error_raised, RuntimeError) + self.assertEqual(msg, str(error_raised)) + + def test_error_without_logger_custom_err(self): + """ + Tests the error() function without providing a logger for writing to file and + specifying a custom error type + """ + msg = "abc123" + f = io.StringIO() + error_raised = None + error_type = ValueError + try: + with redirect_stdout(f): + error(None, msg, error_type=error_type) + except Exception as e: # pylint: disable=broad-exception-caught + error_raised = e + + # Check that stdout matches what we expect + stdout = f.getvalue() + expected_regex = DATETIME_STR_PATTERN + r"\s+test_error_without_logger_custom_err\s+" + msg + self.assertRegex(stdout, expected_regex=expected_regex) + + # Check that error is correct + self.assertFalse(error_raised is None) + self.assertIsInstance(error_raised, error_type) + self.assertEqual(msg, str(error_raised)) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_gen_mksurfdata_jobscript_single.py b/python/ctsm/test/test_unit_gen_mksurfdata_jobscript_single.py index bee1aac715..3980b3bd49 100755 --- a/python/ctsm/test/test_unit_gen_mksurfdata_jobscript_single.py +++ b/python/ctsm/test/test_unit_gen_mksurfdata_jobscript_single.py @@ -6,40 +6,15 @@ import unittest import os -import sys import shutil -import tempfile - from ctsm import unit_testing -from ctsm.path_utils import path_to_ctsm_root -from ctsm.path_utils import path_to_cime -from ctsm.os_utils import run_cmd_output_on_error +from ctsm.test_gen_mksurfdata_jobscript_single_parent import TestFGenMkSurfJobscriptSingleParent from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_parser -from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_mpirun from ctsm.toolchain.gen_mksurfdata_jobscript_single import check_parser_args from ctsm.toolchain.gen_mksurfdata_jobscript_single import write_runscript_part1 -def add_args(machine, nodes, tasks): - """add arguments to sys.argv""" - args_to_add = [ - "--machine", - machine, - "--number-of-nodes", - str(nodes), - "--tasks-per-node", - str(tasks), - ] - for item in args_to_add: - sys.argv.append(item) - - -def create_empty_file(filename): - """create an empty file""" - os.system("touch " + filename) - - # Allow test names that pylint doesn't like; otherwise hard to make them # readable # pylint: disable=invalid-name @@ -47,65 +22,9 @@ def create_empty_file(filename): # pylint: disable=protected-access # pylint: disable=too-many-instance-attributes -class TestFGenMkSurfJobscriptSingle(unittest.TestCase): +class TestFGenMkSurfJobscriptSingle(TestFGenMkSurfJobscriptSingleParent): """Tests the gen_mksurfdata_jobscript_single subroutines""" - def setUp(self): - """Setup for trying out the methods""" - testinputs_path = os.path.join(path_to_ctsm_root(), "python/ctsm/test/testinputs") - self._testinputs_path = testinputs_path - self._previous_dir = os.getcwd() - self._tempdir = tempfile.mkdtemp() - os.chdir(self._tempdir) - self._account = "ACCOUNT_NUMBER" - self._jobscript_file = "output_jobscript" - self._output_compare = """#!/bin/bash -# Edit the batch directives for your batch system -# Below are default batch directives for derecho -#PBS -N mksurfdata -#PBS -j oe -#PBS -k eod -#PBS -S /bin/bash -#PBS -l walltime=12:00:00 -#PBS -A ACCOUNT_NUMBER -#PBS -q main -#PBS -l select=1:ncpus=128:mpiprocs=64:mem=218GB - -# This is a batch script to run a set of resolutions for mksurfdata_esmf input namelist -# NOTE: THIS SCRIPT IS AUTOMATICALLY GENERATED SO IN GENERAL YOU SHOULD NOT EDIT it!! - -""" - self._bld_path = os.path.join(self._tempdir, "tools_bld") - os.makedirs(self._bld_path) - self.assertTrue(os.path.isdir(self._bld_path)) - self._nlfile = os.path.join(self._tempdir, "namelist_file") - create_empty_file(self._nlfile) - self.assertTrue(os.path.exists(self._nlfile)) - self._mksurf_exe = os.path.join(self._bld_path, "mksurfdata") - create_empty_file(self._mksurf_exe) - self.assertTrue(os.path.exists(self._mksurf_exe)) - self._env_mach = os.path.join(self._bld_path, ".env_mach_specific.sh") - create_empty_file(self._env_mach) - self.assertTrue(os.path.exists(self._env_mach)) - sys.argv = [ - "gen_mksurfdata_jobscript_single", - "--bld-path", - self._bld_path, - "--namelist-file", - self._nlfile, - "--jobscript-file", - self._jobscript_file, - "--account", - self._account, - ] - - def tearDown(self): - """ - Remove temporary directory - """ - os.chdir(self._previous_dir) - shutil.rmtree(self._tempdir, ignore_errors=True) - def assertFileContentsEqual(self, expected, filepath, msg=None): """Asserts that the contents of the file given by 'filepath' are equal to the string given by 'expected'. 'msg' gives an optional message to be @@ -123,7 +42,7 @@ def test_simple_derecho_args(self): machine = "derecho" nodes = 1 tasks = 64 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) args = get_parser().parse_args() check_parser_args(args) with open(self._jobscript_file, "w", encoding="utf-8") as runfile: @@ -139,57 +58,12 @@ def test_simple_derecho_args(self): self.assertFileContentsEqual(self._output_compare, self._jobscript_file) - def test_derecho_mpirun(self): - """ - test derecho mpirun. This would've helped caught a problem we ran into - It will also be helpful when sumodules are updated to guide to solutions - to problems - """ - machine = "derecho" - nodes = 4 - tasks = 128 - add_args(machine, nodes, tasks) - args = get_parser().parse_args() - check_parser_args(args) - self.assertEqual(machine, args.machine) - self.assertEqual(tasks, args.tasks_per_node) - self.assertEqual(nodes, args.number_of_nodes) - self.assertEqual(self._account, args.account) - # Create the env_mach_specific.xml file needed for get_mpirun - # This will catch problems with our usage of CIME objects - # Doing this here will also catch potential issues in the gen_mksurfdata_build script - configure_path = os.path.join(path_to_cime(), "CIME", "scripts", "configure") - self.assertTrue(os.path.exists(configure_path)) - options = " --macros-format CMake --silent --compiler intel --machine " + machine - cmd = configure_path + options - cmd_list = cmd.split() - run_cmd_output_on_error( - cmd=cmd_list, errmsg="Trouble running configure", cwd=self._bld_path - ) - self.assertTrue(os.path.exists(self._env_mach)) - expected_attribs = {"mpilib": "default"} - with open(self._jobscript_file, "w", encoding="utf-8") as runfile: - attribs = write_runscript_part1( - number_of_nodes=nodes, - tasks_per_node=tasks, - machine=machine, - account=self._account, - walltime=args.walltime, - runfile=runfile, - ) - self.assertEqual(attribs, expected_attribs) - (executable, mksurfdata_path, env_mach_path) = get_mpirun(args, attribs) - expected_exe = "time mpibind " - self.assertEqual(executable, expected_exe) - self.assertEqual(mksurfdata_path, self._mksurf_exe) - self.assertEqual(env_mach_path, self._env_mach) - def test_too_many_tasks(self): """test trying to use too many tasks""" machine = "derecho" nodes = 1 tasks = 129 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) args = get_parser().parse_args() check_parser_args(args) with open(self._jobscript_file, "w", encoding="utf-8") as runfile: @@ -212,7 +86,7 @@ def test_zero_tasks(self): machine = "derecho" nodes = 5 tasks = 0 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) args = get_parser().parse_args() with self.assertRaisesRegex( SystemExit, @@ -225,7 +99,7 @@ def test_bld_build_path(self): machine = "derecho" nodes = 10 tasks = 64 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) # Remove the build path directory shutil.rmtree(self._bld_path, ignore_errors=True) args = get_parser().parse_args() @@ -237,7 +111,7 @@ def test_mksurfdata_exist(self): machine = "derecho" nodes = 10 tasks = 64 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) args = get_parser().parse_args() os.remove(self._mksurf_exe) with self.assertRaisesRegex(SystemExit, "mksurfdata_esmf executable "): @@ -248,7 +122,7 @@ def test_env_mach_specific_exist(self): machine = "derecho" nodes = 10 tasks = 64 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) args = get_parser().parse_args() os.remove(self._env_mach) with self.assertRaisesRegex(SystemExit, "Environment machine specific file"): @@ -259,7 +133,7 @@ def test_bad_machine(self): machine = "zztop" nodes = 1 tasks = 64 - add_args(machine, nodes, tasks) + unit_testing.add_machine_node_args(machine, nodes, tasks) with self.assertRaises(SystemExit): get_parser().parse_args() diff --git a/python/ctsm/test/test_unit_longitude.py b/python/ctsm/test/test_unit_longitude.py index 1810cfb10b..2382b2c303 100644 --- a/python/ctsm/test/test_unit_longitude.py +++ b/python/ctsm/test/test_unit_longitude.py @@ -3,21 +3,92 @@ """Unit tests for config_utils""" import unittest +from argparse import ArgumentTypeError +import numpy as np from ctsm import unit_testing from ctsm.longitude import Longitude from ctsm.longitude import _convert_lon_type_180_to_360, _convert_lon_type_360_to_180 +from ctsm.longitude import _check_lon_type_180, _check_lon_type_360 +from ctsm.longitude import detect_lon_type # Allow test names that pylint doesn't like; otherwise hard to make them # readable # pylint: disable=invalid-name -# pylint: disable=protected-access +# pylint: disable=protected-access,too-many-public-methods class TestLongitude(unittest.TestCase): """Tests of Longitude class and helper functions""" + # Checking longitude type + + def test_check_lon_type_180(self): + """ + Check that a single value in [-180, 180] passes _check_lon_type_180() + """ + _check_lon_type_180(-180) + _check_lon_type_180(-55) + _check_lon_type_180(55) + _check_lon_type_180(155) + _check_lon_type_180(180) + + def test_check_lon_type_180_list(self): + """ + Check that a list with all values in [-180, 180] passes _check_lon_type_180() + """ + _check_lon_type_180([-180, -55, 55, 155, 180]) + + def test_check_lon_type_180_errors(self): + """ + Check that a single value outside [-180, 180] fails _check_lon_type_180() + """ + msg = r"lon_in must be in the range \[-180, 180\]" + with self.assertRaisesRegex(ValueError, msg): + _check_lon_type_180(-181) + with self.assertRaisesRegex(ValueError, msg): + _check_lon_type_180(181) + + def test_check_lon_type_180_list_errors(self): + """ + Check that a list with one value outside [-180, 180] fails _check_lon_type_180() + """ + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[-180, 180\]"): + _check_lon_type_180([-191, -180, -55, 55, 155, 180]) + + def test_check_lon_type_360(self): + """ + Check that a single value in [0, 360] passes _check_lon_type_360() + """ + _check_lon_type_360(0) + _check_lon_type_360(55) + _check_lon_type_360(180) + _check_lon_type_360(360) + + def test_check_lon_type_360_list(self): + """ + Check that a list with all values in [0, 360] passes _check_lon_type_360() + """ + _check_lon_type_360([0, 55, 180, 360]) + + def test_check_lon_type_360_errors(self): + """ + Check that a single value outside [0, 360] fails _check_lon_type_360() + """ + msg = r"lon_in must be in the range \[0, 360\]" + with self.assertRaisesRegex(ValueError, msg): + _check_lon_type_360(-1) + with self.assertRaisesRegex(ValueError, msg): + _check_lon_type_360(361) + + def test_check_lon_type_360_list_errors(self): + """ + Check that a list with one value outside [0, 360] fails _check_lon_type_360() + """ + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[0, 360\]"): + _check_lon_type_360([-155, 0, 55, 180, 360]) + # Converting between types 180 and 360 def test_convert_lon_type_180_to_360_positive(self): @@ -47,14 +118,14 @@ def test_convert_lon_type_180_to_360_upperbound(self): def test_convert_lon_type_180_to_360_toohigh(self): """Test conversion 180→360 for a value > 180: Should error""" lon = 555 - with self.assertRaisesRegex(ValueError, r"lon_in needs to be in the range \[-180, 180\]"): + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[-180, 180\]"): _convert_lon_type_180_to_360(lon) def test_convert_lon_type_180_to_360_toolow(self): """Test conversion 180→360 for a value < -180: Should error""" lon = -555 - with self.assertRaisesRegex(ValueError, r"lon_in needs to be in the range \[-180, 180\]"): + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[-180, 180\]"): _convert_lon_type_180_to_360(lon) def test_convert_lon_type_360_to_180_positive_low(self): @@ -84,14 +155,14 @@ def test_convert_lon_type_360_to_180_upperbound(self): def test_convert_lon_type_360_to_180_toohigh(self): """Test conversion 360→180 for a value > 180: Should error""" lon = 555 - with self.assertRaisesRegex(ValueError, r"lon_in needs to be in the range \[0, 360\]"): + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[0, 360\]"): _convert_lon_type_360_to_180(lon) def test_convert_lon_type_360_to_180_toolow(self): """Test conversion 360→180 for a value < -180: Should error""" lon = -555 - with self.assertRaisesRegex(ValueError, r"lon_in needs to be in the range \[0, 360\]"): + with self.assertRaisesRegex(ValueError, r"lon_in must be in the range \[0, 360\]"): _convert_lon_type_360_to_180(lon) # Initializing new Longitude objects @@ -152,6 +223,250 @@ def test_lon_obj_type360_max(self): lon_obj = Longitude(this_lon, lon_type) self.assertEqual(lon_obj.get(lon_type), this_lon) + def test_lon_eq_both360(self): + """Test that == works for two equal Longitudes both of type 360""" + lon1 = Longitude(275, 360) + lon2 = Longitude(275, 360) + self.assertTrue(lon1 == lon2) + + def test_lon_eq_360num_error(self): + """Test that == fails if RHS isn't Longitude""" + lon1 = Longitude(275, 360) + lon2 = 275 + with self.assertRaisesRegex( + TypeError, "Comparison not supported between instances of 'Longitude' and " + ): + _ = lon1 == lon2 + + def test_lon_eq_num360_error(self): + """Test that == fails if LHS isn't Longitude""" + lon1 = 275 + lon2 = Longitude(275, 360) + with self.assertRaisesRegex( + TypeError, "Comparison not supported between instances of 'Longitude' and " + ): + _ = lon1 == lon2 + + def test_lon_eq_both180(self): + """Test that == works for two equal Longitudes both of type 180""" + lon1 = Longitude(-5, 180) + lon2 = Longitude(-5, 180) + self.assertTrue(lon1 == lon2) + + def test_lon_eq_180360(self): + """Test that == works for two equal Longitudes of different types""" + lon1 = Longitude(-5, 180) + lon2 = Longitude(355, 360) + self.assertTrue(lon1 == lon2) + self.assertTrue(lon2 == lon1) + + def test_lon_eqfalse_180360(self): + """Test that == works for two unequal Longitudes of different types""" + lon1 = Longitude(-1, 180) + lon2 = Longitude(355, 360) + self.assertFalse(lon1 == lon2) + self.assertFalse(lon2 == lon1) + + def test_lon_noteqtrue_180360(self): + """Test that != works for two unequal Longitudes of different types""" + lon1 = Longitude(-1, 180) + lon2 = Longitude(355, 360) + self.assertTrue(lon1 != lon2) + self.assertTrue(lon2 != lon1) + + def test_lon_compare_both360(self): + """ + Ensure that comparison operators work if both are type 360 + """ + lon1 = Longitude(155, 360) + lon2 = Longitude(150, 360) + self.assertTrue(lon1 > lon2) + self.assertTrue(lon1 >= lon2) + self.assertFalse(lon1 <= lon2) + self.assertFalse(lon1 < lon2) + + def test_lon_compare_both180(self): + """ + Ensure that comparison operators work if both are type 180 + """ + lon1 = Longitude(155, 180) + lon2 = Longitude(150, 180) + self.assertTrue(lon1 > lon2) + self.assertTrue(lon1 >= lon2) + self.assertFalse(lon1 <= lon2) + self.assertFalse(lon1 < lon2) + + def test_lon_compare_arrays(self): + """ + Ensure that comparison operators work elementwise for arrays + """ + lon1 = Longitude(np.array([155, 156, 157]), 180) + lon2 = Longitude(np.array([155, 157, 156]), 180) + result = lon1 == lon2 + expected = [True, False, False] + for i in np.arange(3): + self.assertTrue(result[i] == expected[i]) + result = lon1 < lon2 + expected = [False, True, False] + for i in np.arange(3): + self.assertTrue(result[i] == expected[i]) + result = lon1 <= lon2 + expected = [True, True, False] + for i in np.arange(3): + self.assertTrue(result[i] == expected[i]) + result = lon1 > lon2 + expected = [False, False, True] + for i in np.arange(3): + self.assertTrue(result[i] == expected[i]) + result = lon1 >= lon2 + expected = [True, False, True] + for i in np.arange(3): + self.assertTrue(result[i] == expected[i]) + + def test_lon_compare_lists(self): + """ + Ensure that comparison operators work on the entire object for lists + """ + lon1 = Longitude([155, 156, 157], 360) + lon2 = Longitude([155, 157, 156], 360) + self.assertFalse(lon1 == lon2) + self.assertTrue(lon1 != lon2) + lon2 = lon1 + self.assertTrue(lon1 == lon2) + self.assertFalse(lon1 != lon2) + + def test_lon_compare_difftypes_error(self): + """ + Ensure that comparison operators fail if Longitudes are different types + """ + lon1 = Longitude(155, 360) + lon2 = Longitude(150, 180) + msg = "Comparison not supported between Longitudes of different types" + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 < lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 > lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 <= lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 >= lon2 + + def test_lon_compare_notlon_error(self): + """ + Ensure that comparison operators fail if one isn't a Longitude + """ + lon1 = Longitude(155, 360) + lon2 = 255 + msg = "Comparison not supported between instances of 'Longitude' and" + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 < lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 > lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 <= lon2 + with self.assertRaisesRegex(TypeError, msg): + _ = lon1 >= lon2 + + def test_detect_lon_type_mid_180(self): + """test that detect_lon_type works for an unambiguously 180 value""" + self.assertEqual(detect_lon_type(-150), 180) + + def test_detect_lon_type_min_180(self): + """test that detect_lon_type works at -180""" + self.assertEqual(detect_lon_type(-180), 180) + + def test_detect_lon_type_mid_360(self): + """test that detect_lon_type works for an unambiguously 360 value""" + self.assertEqual(detect_lon_type(355), 360) + + def test_detect_lon_type_max_360(self): + """test that detect_lon_type works at 360""" + self.assertEqual(detect_lon_type(360), 360) + + def test_detect_lon_type_list_180(self): + """test that detect_lon_type works for a list with just one unambiguously 180 value""" + self.assertEqual(detect_lon_type([-150, 150]), 180) + + def test_detect_lon_type_list_360(self): + """test that detect_lon_type works for a list with just one unambiguously 360 value""" + self.assertEqual(detect_lon_type([256, 150]), 360) + + def test_detect_lon_type_ambig(self): + """test that detect_lon_type fails if ambiguous""" + with self.assertRaisesRegex(ArgumentTypeError, r"Longitude\(s\) ambiguous"): + detect_lon_type(150) + + def test_detect_lon_type_list_ambig(self): + """test that detect_lon_type fails for an ambiguous list""" + with self.assertRaisesRegex(ArgumentTypeError, r"Longitude\(s\) ambiguous"): + detect_lon_type([150, 170]) + + def test_detect_lon_type_list_both(self): + """test that detect_lon_type fails for a list with unambiguous members of both types""" + with self.assertRaisesRegex(RuntimeError, r"Longitude array contains values of both types"): + detect_lon_type([-150, 270]) + + def test_detect_lon_type_ambig0(self): + """test that detect_lon_type fails at 0""" + with self.assertRaisesRegex(ArgumentTypeError, r"Longitude\(s\) ambiguous"): + detect_lon_type(0) + + def test_detect_lon_type_oob_low(self): + """test that detect_lon_type fails if out of bounds below min""" + with self.assertRaisesRegex(ValueError, r"\(Minimum\) longitude < -180"): + detect_lon_type(-300) + + def test_detect_lon_type_oob_high(self): + """test that detect_lon_type fails if out of bounds above max""" + with self.assertRaisesRegex(ValueError, r"\(Maximum\) longitude > 360"): + detect_lon_type(500) + + def test_list_as_lon(self): + """ + Test that converting a List to Longitude works + """ + expected_lon = [1, 5] + expected_type = 360 + result = Longitude(expected_lon, expected_type) + self.assertEqual(expected_lon, result._lon) + self.assertEqual(expected_type, result.lon_type()) + + def test_array_as_lon(self): + """ + Test that converting a Numpy array to Longitude works + """ + expected_lon = np.array([1, 5]) + expected_type = 180 + result = Longitude(expected_lon, expected_type) + self.assertTrue(np.array_equal(expected_lon, result._lon)) + self.assertEqual(expected_type, result.lon_type()) + + def test_lon_type_getter(self): + """Test lon_type() getter method""" + lon = Longitude(55, 180) + self.assertEqual(lon.lon_type(), 180) + + def test_no_implicit_string_conversion(self): + """Ensure that implicit string conversion is disallowed""" + lon = Longitude(55, 180) + with self.assertRaisesRegex( + NotImplementedError, r"Use Longitude\.get_str\(\) instead of implicit string conversion" + ): + _ = f"{lon}" + with self.assertRaisesRegex( + NotImplementedError, r"Use Longitude\.get_str\(\) instead of implicit string conversion" + ): + _ = str(lon) + + def test_get_str(self): + """Ensure that explicit string conversion works as expected""" + lon = Longitude(55, 180) + self.assertEqual(lon.get_str(180), "55.0") + self.assertEqual(lon.get_str(360), "55.0") + lon = Longitude(-55, 180) + self.assertEqual(lon.get_str(180), "-55.0") + self.assertEqual(lon.get_str(360), "305.0") + if __name__ == "__main__": unit_testing.setup_for_tests() diff --git a/python/ctsm/test/test_unit_netcdf_utils.py b/python/ctsm/test/test_unit_netcdf_utils.py new file mode 100755 index 0000000000..2aa96262ba --- /dev/null +++ b/python/ctsm/test/test_unit_netcdf_utils.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +""" +Unit tests for netcdf_utils.py functions +""" + +import os +import sys +import unittest +import shutil +import tempfile +import numpy as np +import pandas as pd +import xarray as xr + +# -- add python/ctsm to path (needed if we want to run the test stand-alone) +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) +sys.path.insert(1, _CTSM_PYTHON) + +# pylint: disable=wrong-import-position +import ctsm.netcdf_utils as nu +from ctsm import unit_testing + +# pylint: disable=invalid-name +# pylint: disable=protected-access + + +class TestUnitGetNetcdfFormat(unittest.TestCase): + """ + Unit tests for get_netcdf_format + """ + + def setUp(self): + self.tempdir = tempfile.mkdtemp() + self.outfile = os.path.join(self.tempdir, "file.nc") + da = xr.DataArray(data=[1, 2, 3]) + self.ds = xr.Dataset(data_vars={"var": da}) + + def tearDown(self): + shutil.rmtree(self.tempdir, ignore_errors=True) + + def test_get_netcdf_format_classic(self): + """ + Test that get_netcdf_format() gets "classic" format right + """ + nc_format = "NETCDF3_CLASSIC" + self.ds.to_netcdf(self.outfile, format=nc_format) + self.assertEqual(nu.get_netcdf_format(self.outfile), nc_format) + + def test_get_netcdf_format_netcdf4(self): + """ + Test that get_netcdf_format() gets "netCDF4" format right + """ + nc_format = "NETCDF4" + self.ds.to_netcdf(self.outfile, format=nc_format) + self.assertEqual(nu.get_netcdf_format(self.outfile), nc_format) + + +class TestUnitAreXrDataArraysIdentical(unittest.TestCase): + """ + Unit tests for are_xr_dataarrays_identical + """ + + # pylint: disable=too-many-public-methods + + def test_are_xr_dataarrays_identical_data_types_match(self): + """Should be true if dtypes match""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_data_types_differ(self): + """Should be false if dtypes differ""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(np.float64(1)) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_data_types_differ_precision(self): + """Should be false if dtypes differ only in their precision""" + da0 = xr.DataArray(np.float64(1)) + da1 = xr.DataArray(np.float32(1)) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_encodings_match(self): + """Should be true if encodings are the exact same""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.encoding["dummy0"] = -999 + da1.encoding["dummy0"] = -999 + da0.encoding["dummy1"] = -999 + da1.encoding["dummy1"] = -999 + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_encodings_match_except_order(self): + """Should be true if encodings are the same as long as you don't care about order""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.encoding["dummy0"] = -999 + da0.encoding["dummy1"] = -999 + da1.encoding["dummy1"] = -999 + da1.encoding["dummy0"] = -999 + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_encodings_differ_number(self): + """Should be false if encodings have a different number of keys""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.encoding["dummy0"] = -999 + da1.encoding["dummy0"] = -999 + da1.encoding["dummy1"] = -999 + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_encodings_differ(self): + """Should be false if encodings have the same number and order of keys but not values""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.encoding["dummy0"] = -999 + da1.encoding["dummy0"] = -999 + da0.encoding["dummy1"] = -998 + da1.encoding["dummy1"] = -999 + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_attributes_match(self): + """Should be true if attributes are the exact same""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.attrs["dummy0"] = -999 + da1.attrs["dummy0"] = -999 + da0.attrs["dummy1"] = -999 + da1.attrs["dummy1"] = -999 + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_attributes_match_nan(self): + """Should be true if attributes are the exact same and NaN""" + da0 = xr.DataArray(1.0) + da1 = xr.DataArray(1.0) + da0.attrs["_FillValue"] = np.float64(np.nan) + da1.attrs["_FillValue"] = np.float64(np.nan) + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_attributes_match_except_order(self): + """Should be true if attributes are the same as long as you don't care about order""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.attrs["dummy0"] = -999 + da0.attrs["dummy1"] = -999 + da1.attrs["dummy1"] = -999 + da1.attrs["dummy0"] = -999 + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_attributes_differ_number(self): + """Should be false if attributes have a different number of keys""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.attrs["dummy0"] = -999 + da1.attrs["dummy0"] = -999 + da1.attrs["dummy1"] = -999 + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_attributes_differ(self): + """Should be false if attributes have the same number and order of keys but not values""" + da0 = xr.DataArray(int(1)) + da1 = xr.DataArray(int(1)) + da0.attrs["dummy0"] = -999 + da1.attrs["dummy0"] = -999 + da0.attrs["dummy1"] = -998 + da1.attrs["dummy1"] = -999 + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_values_ndarrays_match(self): + """Should be true if values match and they're both numpy arrays under the hood""" + da0 = xr.DataArray(np.array(int(1))) + da1 = xr.DataArray(np.array(int(1))) + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_values_ndarrays_differ(self): + """Should be false if values differ and they're both numpy arrays under the hood""" + da0 = xr.DataArray(np.array(int(1))) + da1 = xr.DataArray(np.array(int(2))) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_values_ndarrays_differ_nantypeerror(self): + """ + Should be false if values differ, they're both numpy arrays under the hood, and they can't + be coerced to a type capable of NaN + """ + da0 = xr.DataArray(np.array(["a", "b", "c"])) + da1 = xr.DataArray(np.array(["d", "e", "f"])) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_coords_match(self): + """Should be true if coordinates match""" + time = pd.date_range("2000-01-01", periods=3) + da0 = xr.DataArray( + dims=["time"], + coords={"time": time}, + ) + da1 = xr.DataArray( + dims=["time"], + coords={"time": time}, + ) + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + # To make sure we don't need a separate "test_are_xr_dataarrays_identical_indexes_match" + self.assertEqual(len(da0.indexes), len(da1.indexes)) + for key in da0.indexes: + self.assertTrue(da0.indexes[key].equals(da1.indexes.get(key))) + + def test_are_xr_dataarrays_identical_coords_onemissing(self): + """Should be false if only one has coords""" + data = [1, 2, 3] + time = pd.date_range("2000-01-01", periods=len(data)) + da0 = xr.DataArray( + data=data, + dims=["time"], + coords={"time": time}, + ) + da1 = xr.DataArray( + data=data, + dims=["time"], + ) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_coords_differ(self): + """Should be false if coordinates differ""" + time0 = pd.date_range("2000-01-01", periods=3) + da0 = xr.DataArray( + dims=["time"], + coords={"time": time0}, + ) + time1 = pd.date_range("1987-01-01", periods=3) + da1 = xr.DataArray( + dims=["time"], + coords={"time": time1}, + ) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_dims_match(self): + """Should be true if dimensions match""" + da0 = xr.DataArray(data=[1], dims=["dim"]) + da1 = xr.DataArray(data=[1], dims=["dim"]) + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_dims_differ(self): + """Should be false if dimensions differ""" + da0 = xr.DataArray(data=[1], dims=["dim0"]) + da1 = xr.DataArray(data=[1], dims=["dim1"]) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_names_match(self): + """Should be true if names match""" + da0 = xr.DataArray(data=[1], name="da_name") + da1 = xr.DataArray(data=[1], name="da_name") + self.assertTrue(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_names_differ(self): + """Should be false if names differ""" + da0 = xr.DataArray(data=[1], name="da0_name") + da1 = xr.DataArray(data=[1], name="da1_name") + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + def test_are_xr_dataarrays_identical_sizes_differ(self): + """Should be false if sizes differ""" + da0 = xr.DataArray(data=[1]) + da1 = xr.DataArray(data=[1, 1]) + self.assertFalse(nu.are_xr_dataarrays_identical(da0, da1)) + + # Enabled now that dask is in ctsm_pylib: + # TODO: False if data types don't match + # TODO: NotImplementedError if data types match but aren't np.array + # TODO: True if the only difference is chunked or not + # TODO: True if the only difference is chunk sizes + + +class TestUnitAreDictsIdenticalNansEqual(unittest.TestCase): + """ + Unit tests for _are_dicts_identical_nansequal + """ + + def test_are_dicts_identical_nansequal_yes_noignore_nonan(self): + """ + Test two identical dicts with no keys being ignored and no nans + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "b": 2} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_yes_ignorestr_nonan(self): + """ + Test two identical dicts with a key being ignored (as str) and no nans + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "b": 3} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1, keys_to_ignore="b")) + + def test_are_dicts_identical_nansequal_yes_ignorelist_nonan(self): + """ + Test two identical dicts with a key being ignored (as list) and no nans + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "b": 3} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1, keys_to_ignore=["b"])) + + def test_are_dicts_identical_nansequal_yes_ignorestr_nan(self): + """ + Test two identical dicts with a key being ignored (as str) and one key with matching nan + values + """ + dict0 = {"a": np.nan, "b": 2} + dict1 = {"a": np.nan, "b": 3} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1, keys_to_ignore="b")) + + def test_are_dicts_identical_nansequal_no_nonan(self): + """ + Test two different dicts with no nans + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "b": 4} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_nansmatch(self): + """ + Test two different dicts with matching nans + """ + dict0 = {"a": np.nan, "b": 2} + dict1 = {"a": np.nan, "b": 4} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_nansdiffer(self): + """ + Test two different dicts that only differ in that one has a nan where another doesn't + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "b": np.nan} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_keysdiffer(self): + """ + Test two different dicts that have different keys + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1, "c": 2} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_lengthsdiffer(self): + """ + Test two different dicts that have different lengths + """ + dict0 = {"a": 1, "b": 2} + dict1 = {"a": 1} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_both_nparrays(self): + """ + Test two different dicts that have differing numpy arrays for one value + """ + dict0 = {"a": 1, "b": np.array([1, 2])} + dict1 = {"a": 1, "b": np.array([1, 3])} + self.assertFalse(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_no_differ_nparrays(self): + """ + Test two dicts where one has a value that's a numpy array and the other doesn't, but they're + identical if you coerce them both to numpy arrays + """ + dict0 = {"a": 1, "b": np.array([1, 2])} + dict1 = {"a": 1, "b": [1, 2]} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_yes_both_nparrays(self): + """ + Test two different dicts that have identical numpy arrays for one value + """ + dict0 = {"a": 1, "b": np.array([1, 2])} + dict1 = {"a": 1, "b": np.array([1, 2])} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1)) + + def test_are_dicts_identical_nansequal_yes_both_nparrays_str(self): + """ + Test two different dicts that have identical numpy arrays of strings for one value + """ + dict0 = {"a": 1, "b": np.array(["1", "2"])} + dict1 = {"a": 1, "b": np.array(["1", "2"])} + self.assertTrue(nu._are_dicts_identical_nansequal(dict0, dict1)) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_paramfile_shared.py b/python/ctsm/test/test_unit_paramfile_shared.py new file mode 100755 index 0000000000..e0adebc7c2 --- /dev/null +++ b/python/ctsm/test/test_unit_paramfile_shared.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +"""Unit tests for paramfile_shared""" + +import unittest + +from ctsm import unit_testing + +from ctsm.param_utils import paramfile_shared as ps + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +class TestUnitGetSelectedPftIndices(unittest.TestCase): + """Unit tests of get_selected_pft_indices""" + + def test_get_selected_pft_indices_1strselected_onlyinlist(self): + """Check get_selected_pft_indices() given the only one in the list, as a string""" + selected_pfts = "rice" + pft_names = ["rice"] + result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + self.assertListEqual(result, [0]) + + def test_get_selected_pft_indices_1selected_onlyinlist(self): + """Check get_selected_pft_indices() given the only one in the list, as a list""" + selected_pfts = ["rice"] + pft_names = ["rice"] + result = ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + self.assertListEqual(result, [0]) + + def test_get_selected_pft_indices_2selected_sameorder(self): + """Check get_selected_pft_indices() given 2 selected in the same order as the list""" + pft_names = ["rice", "irrigated_rice"] + result = ps.get_selected_pft_indices(selected_pfts=pft_names, pft_names=pft_names) + self.assertListEqual(result, [0, 1]) + + def test_get_selected_pft_indices_2selected_difforder(self): + """Check get_selected_pft_indices() given 2 selected NOT in the same order as the list""" + pft_names = ["rice", "irrigated_rice"] + result = ps.get_selected_pft_indices( + selected_pfts=list(reversed(pft_names)), pft_names=pft_names + ) + self.assertListEqual(result, [1, 0]) + + def test_get_selected_pft_indices_missing_valueerror(self): + """Check get_selected_pft_indices() given selected pft NOT in the list""" + selected_pfts = ["wheat"] + pft_names = ["rice", "irrigated_rice"] + with self.assertRaises(ValueError): + ps.get_selected_pft_indices(selected_pfts=selected_pfts, pft_names=pft_names) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_plumber2_surf_wrapper.py b/python/ctsm/test/test_unit_plumber2_surf_wrapper.py index 66f5578caa..4b84752edb 100755 --- a/python/ctsm/test/test_unit_plumber2_surf_wrapper.py +++ b/python/ctsm/test/test_unit_plumber2_surf_wrapper.py @@ -16,7 +16,7 @@ # pylint: disable=wrong-import-position from ctsm import unit_testing -from ctsm.site_and_regional.plumber2_surf_wrapper import get_parser +from ctsm.site_and_regional.plumber2_surf_wrapper import get_args # pylint: disable=invalid-name @@ -26,12 +26,60 @@ class TestPlumber2SurfWrapper(unittest.TestCase): Basic class for testing plumber2_surf_wrapper.py. """ - def test_parser(self): + def setUp(self): + sys.argv = ["subset_data"] # Could actually be anything + + def test_parser_default_csv_exists(self): + """ + Test that default PLUMBER2 sites CSV file exists + """ + + args = get_args() + self.assertTrue(os.path.exists(args.plumber2_sites_csv)) + + def test_parser_custom_csv(self): + """ + Test that script accepts custom CSV file path + """ + + custom_path = "path/to/custom.csv" + sys.argv += ["--plumber2-sites-csv", custom_path] + args = get_args() + self.assertEqual(args.plumber2_sites_csv, custom_path) + + def test_parser_verbose_false_default(self): + """ + Test that script is not verbose by default + """ + + args = get_args() + self.assertFalse(args.verbose) + + def test_parser_verbose_true(self): + """ + Test that --verbose sets verbose to True + """ + + sys.argv += ["--verbose"] + args = get_args() + self.assertTrue(args.verbose) + + def test_parser_78pft_false_default(self): + """ + Test that script does not use 78pft mode by default + """ + + args = get_args() + self.assertFalse(args.use_managed_crops) + + def test_parser_78pft_true(self): """ - Test that parser has same defaults as expected + Test that --crop sets use_managed_crops to True """ - self.assertEqual(get_parser().argument_default, None, "Parser not working as expected") + sys.argv += ["--crop"] + args = get_args() + self.assertTrue(args.use_managed_crops) if __name__ == "__main__": diff --git a/python/ctsm/test/test_unit_query_paramfile.py b/python/ctsm/test/test_unit_query_paramfile.py new file mode 100755 index 0000000000..b796253ace --- /dev/null +++ b/python/ctsm/test/test_unit_query_paramfile.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +"""Unit tests for query_paramfile""" + +import unittest +import sys +import io +from contextlib import redirect_stdout +import xarray as xr + +from ctsm import unit_testing + +from ctsm.param_utils import query_paramfile as qp +from ctsm.param_utils.paramfile_shared import PFTNAME_VAR + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +def _setup_pft_parameter_ds(): + """ + Set up a parameter Dataset with a PFT-dimensioned parameter + """ + pft_dimname = "pft" + pft_names_list = ["pft0", "pft1"] + pft_names_da = xr.DataArray( + name=PFTNAME_VAR, + data=pft_names_list, + dims=[pft_dimname], + coords={pft_dimname: pft_names_list}, + ) + var_name = "pft_param" + pft_param_da = xr.DataArray( + data=[1986.0325, 1987.0724], dims=[pft_dimname], coords={PFTNAME_VAR: pft_names_da} + ) + ds = xr.Dataset(data_vars={var_name: pft_param_da, pft_dimname: pft_names_da}) + return ds, var_name, pft_names_list + + +class TestUnitQueryParamfile(unittest.TestCase): + """Unit tests of query_paramfile""" + + def setUp(self): + self.orig_argv = sys.argv + + def tearDown(self): + sys.argv = self.orig_argv + + def test_query_paramfile_args_short(self): + """Test that all arguments can be set correctly with shortnames""" + input_path = "/path/to/input.nc" + sys.argv = ["get_arguments", "-i", input_path, "-p", "pft1,pft2", "var1", "var2"] + args = qp.get_arguments() + self.assertEqual(input_path, args.input) + self.assertEqual(["pft1", "pft2"], args.pft) + self.assertEqual(["var1", "var2"], args.variables) + + def test_query_paramfile_args_long(self): + """Test that all arguments can be set correctly with longnames""" + input_path = "/path/to/input.nc" + sys.argv = ["get_arguments", "--input", input_path, "--pft", "pft1,pft2", "var1", "var2"] + args = qp.get_arguments() + self.assertEqual(input_path, args.input) + self.assertEqual(["pft1", "pft2"], args.pft) + self.assertEqual(["var1", "var2"], args.variables) + + def test_query_paramfile_print_scalar(self): + """Test that print_values works with a scalar parameter""" + scalar_da = xr.DataArray(data=1987.0724) + var_name = "scalar_param" + ds = xr.Dataset(data_vars={var_name: scalar_da}) + + f = io.StringIO() + with redirect_stdout(f): + qp.print_values(ds, var_name, selected_pfts=None, pft_names=None) + out = f.getvalue() + self.assertEqual("scalar_param: 1987.0724\n", out) + + def test_query_paramfile_print_pfts_selectnone(self): + """Test that print_values works with PFT-dimensioned parameter, selecting no PFTs""" + ds, var_name, pft_names_list = _setup_pft_parameter_ds() + + f = io.StringIO() + with redirect_stdout(f): + qp.print_values(ds, var_name, selected_pfts=None, pft_names=pft_names_list) + out = f.getvalue() + self.assertEqual("pft_param:\n pft0: 1986.0325\n pft1: 1987.0724\n", out) + + def test_query_paramfile_print_pfts_selectall(self): + """Test that print_values works with PFT-dimensioned parameter, selecting all PFTs""" + ds, var_name, pft_names_list = _setup_pft_parameter_ds() + + f = io.StringIO() + with redirect_stdout(f): + qp.print_values(ds, var_name, selected_pfts=pft_names_list, pft_names=pft_names_list) + out = f.getvalue() + self.assertEqual("pft_param:\n pft0: 1986.0325\n pft1: 1987.0724\n", out) + + def test_query_paramfile_print_pfts_selectone(self): + """Test that print_values works with PFT-dimensioned parameter, selecting one PFT""" + ds, var_name, pft_names_list = _setup_pft_parameter_ds() + + f = io.StringIO() + with redirect_stdout(f): + qp.print_values(ds, var_name, selected_pfts=["pft0"], pft_names=pft_names_list) + out = f.getvalue() + self.assertEqual("pft_param:\n pft0: 1986.0325\n", out) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_set_paramfile.py b/python/ctsm/test/test_unit_set_paramfile.py new file mode 100755 index 0000000000..13be015c67 --- /dev/null +++ b/python/ctsm/test/test_unit_set_paramfile.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 + +"""Unit tests for set_paramfile""" + +import unittest +import os +import sys +import tempfile +import shutil +import numpy as np +import xarray as xr + +from ctsm import unit_testing + +from ctsm.netcdf_utils import get_netcdf_format +from ctsm.param_utils import set_paramfile as sp +from ctsm.param_utils.paramfile_shared import open_paramfile, are_paramfile_dataarrays_identical + +# Allow names that pylint doesn't like, because otherwise I find it hard +# to make readable unit test names +# pylint: disable=invalid-name + + +PARAMFILE = os.path.join( + os.path.dirname(__file__), "testinputs", "ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc" +) + + +def save_paramfile_with_integer_that_has_fillvalue(tempdir): + """ + Convenience function for creating a parameter file that has an integer parameter with a fill + value + """ + input_path = os.path.join(tempdir, "input.nc") + new_param_name = "new_param_abc123" + fill_value = -999 + + # Construct the Dataset + data = np.array([1, 2, fill_value, 4, 5], dtype=np.int32) + da = xr.DataArray(data) + da.encoding["_FillValue"] = fill_value + ds = xr.Dataset(data_vars={new_param_name: da}) + + # Check it + assert new_param_name in ds + assert sp.is_integer(ds[new_param_name].values) + + # Save it + sp.save_paramfile(ds, input_path) + + return input_path, new_param_name, fill_value + + +class TestUnitCheckCorrectNdims(unittest.TestCase): + """Unit tests of check_correct_ndims""" + + def test_checkcorrectndims_0d_int(self): + """Check True when given a standard int for a 0d parameter""" + da = xr.DataArray(data=1) + self.assertTrue(sp.check_correct_ndims(da, 1)) + + def test_checkcorrectndims_0d_int_np(self): + """Check True when given a numpy int for a 0d parameter""" + da = xr.DataArray(data=1) + self.assertTrue(sp.check_correct_ndims(da, np.int32(1))) + + def test_checkcorrectndims_1d_int(self): + """Check True when given a standard int for a 0d parameter""" + da = xr.DataArray(data=[1, 2]) + self.assertTrue(sp.check_correct_ndims(da, 1)) + + def test_checkcorrectndims_1d_int_np(self): + """Check True when given a numpy int for a 0d parameter""" + da = xr.DataArray(data=[1, 2]) + self.assertTrue(sp.check_correct_ndims(da, np.int32(1))) + + def test_checkcorrectndims_0d_list(self): + """Check False when given a list for a 0d parameter""" + da = xr.DataArray(data=1) + self.assertFalse(sp.check_correct_ndims(da, [1, 2])) + + def test_checkcorrectndims_0d_nparray(self): + """Check False when given a numpy array for a 0d parameter""" + da = xr.DataArray(data=1) + self.assertFalse(sp.check_correct_ndims(da, np.array([1, 2]))) + + def test_checkcorrectndims_0d_nparray_error(self): + """Check for error when given a numpy array for a 0d parameter and requesting throw_error""" + da = xr.DataArray(data=1) + with self.assertRaisesRegex(RuntimeError, "Incorrect N dims: Expected 0, got 1"): + sp.check_correct_ndims(da, np.array([1, 2]), throw_error=True) + + def test_checkcorrectndims_1d_list(self): + """Check True when given a list for a 1d parameter""" + da = xr.DataArray(data=[1, 2]) + self.assertTrue(sp.check_correct_ndims(da, [1, 2])) + + def test_checkcorrectndims_1d_nparray(self): + """Check True when given a numpy array for a 1d parameter""" + da = xr.DataArray(data=[1, 2]) + self.assertTrue(sp.check_correct_ndims(da, np.array([1, 2]))) + + +class TestUnitIsInteger(unittest.TestCase): + """Unit tests of is_integer""" + + def test_isinteger_obj_int(self): + """Check True if given an object of type int""" + self.assertTrue(sp.is_integer(int(1))) + + def test_isinteger_obj_int_np(self): + """Check True if given an object of a numpy integer type""" + self.assertTrue(sp.is_integer(np.int32(1))) + + def test_isinteger_obj_int_array0d_np(self): + """Check True if given an object of a numpy array with integer dtype""" + self.assertTrue(sp.is_integer(np.array(1, dtype=np.int32))) + + def test_isinteger_obj_int_0d_xr(self): + """Check True if given a numpy scalar integer object via xarray""" + da = xr.DataArray(data=1) + self.assertTrue(sp.is_integer(da.values)) + + def test_isinteger_obj_int_1d_xr(self): + """Check True if given a numpy 1-d integer object via xarray""" + da = xr.DataArray(data=[1]) + self.assertTrue(sp.is_integer(da.values)) + da = xr.DataArray(data=[1, 2]) + self.assertTrue(sp.is_integer(da.values)) + + def test_isinteger_obj_float(self): + """Check False if given an object of type float""" + self.assertFalse(sp.is_integer(float(3.14))) + + def test_isinteger_obj_float_np(self): + """Check False if given an object of a numpy float type""" + self.assertFalse(sp.is_integer(np.float32(3.14))) + + def test_isinteger_type_int(self): + """Check False if given a type""" + self.assertFalse(sp.is_integer(int)) + + +class TestUnitSetParamfile(unittest.TestCase): + """Unit tests of set_paramfile""" + + def setUp(self): + self.orig_argv = sys.argv + + def tearDown(self): + sys.argv = self.orig_argv + + def test_set_paramfile_args_short(self): + """Test that all arguments can be set correctly with shortnames""" + output_path = "/path/to/output.nc" + sys.argv = [ + "get_arguments", + "-i", + PARAMFILE, + "-p", + "pft1,pft2", + "-o", + output_path, + "param1=new_value1", + "param2=new_value2", + ] + args = sp.get_arguments() + self.assertEqual(PARAMFILE, args.input) + self.assertEqual(["pft1", "pft2"], args.pft) + self.assertEqual(output_path, args.output) + self.assertEqual(["param1=new_value1", "param2=new_value2"], args.param_changes) + + def test_set_paramfile_args_long(self): + """Test that all arguments can be set correctly with longnames""" + output_path = "/path/to/output.nc" + sys.argv = [ + "get_arguments", + "--input", + PARAMFILE, + "--pft", + "pft1,pft2", + "--drop-other-pfts", + "--output", + output_path, + "param1=new_value1", + "param2=new_value2", + ] + args = sp.get_arguments() + self.assertEqual(PARAMFILE, args.input) + self.assertEqual(["pft1", "pft2"], args.pft) + self.assertEqual(output_path, args.output) + self.assertEqual(["param1=new_value1", "param2=new_value2"], args.param_changes) + + def test_set_paramfile_error_missing_input(self): + """Test that it errors if input file doesn't exist""" + output_path = "/path/to/output.nc" + sys.argv = [ + "get_arguments", + "--input", + "nwuefweirbdfdiurbe", + "--pft", + "pft1,pft2", + "--output", + output_path, + ] + with self.assertRaises(FileNotFoundError): + sp.get_arguments() + + def test_set_paramfile_error_existing_output(self): + """Test that it errors if output file already exists""" + sys.argv = [ + "get_arguments", + "--input", + PARAMFILE, + "--pft", + "pft1,pft2", + "--output", + PARAMFILE, + ] + with self.assertRaises(FileExistsError): + sp.get_arguments() + + def test_set_paramfile_error_dropotherpfts_without_pft(self): + """Test that it errors if given --drop-other-pfts without --pft""" + output_path = "/path/to/output.nc" + sys.argv = [ + "get_arguments", + "--input", + PARAMFILE, + "--drop-other-pfts", + "--output", + output_path, + ] + with self.assertRaises(RuntimeError): + sp.get_arguments() + + +class TestUnitSaveParamfile(unittest.TestCase): + """Unit tests of save_paramfile""" + + def setUp(self): + self.tempdir = tempfile.mkdtemp() + self.output_path = os.path.join(self.tempdir, "output.nc") + + def tearDown(self): + shutil.rmtree(self.tempdir, ignore_errors=True) + + def test_save_paramfile(self): + """Test that save_paramfile can save our usual test file to a new file without changes""" + input_path = PARAMFILE + ds_in = open_paramfile(input_path) + sp.save_paramfile(ds_in, self.output_path, nc_format=get_netcdf_format(input_path)) + ds_out = open_paramfile(self.output_path) + self.assertTrue(ds_out.equals(ds_in)) + self.assertTrue(set(ds_in.variables) == set(ds_out.variables)) + for var in ds_in: + self.assertTrue(are_paramfile_dataarrays_identical(ds_in[var], ds_out[var])) + + def test_save_paramfile_integer_with_fillvalue(self): + """Test that save_paramfile can successfully save an integer parameter with a fill value""" + + # Create paramfile with a integer variable with fill value -999 + input_path, new_param_name, fill_value = save_paramfile_with_integer_that_has_fillvalue( + self.tempdir + ) + + # Read it, checking that its fill value is what we asked for. Note: We need to mask, because + # otherwise the fill value won't be read. + ds = xr.open_dataset(input_path, mask_and_scale=True) + self.assertTrue("_FillValue" in ds[new_param_name].encoding) + self.assertEqual(fill_value, ds[new_param_name].encoding["_FillValue"]) + + # Check that the saved variable is an integer type. Note: We need to NOT mask, because + # masking converts fill values to NaN, which forces conversion to float. + ds = xr.open_dataset(input_path, mask_and_scale=False) + self.assertTrue(sp.is_integer(ds[new_param_name].values)) + + +if __name__ == "__main__": + unit_testing.setup_for_tests() + unittest.main() diff --git a/python/ctsm/test/test_unit_singlept_data.py b/python/ctsm/test/test_unit_singlept_data.py index 644af82588..489763282d 100755 --- a/python/ctsm/test/test_unit_singlept_data.py +++ b/python/ctsm/test/test_unit_singlept_data.py @@ -18,6 +18,8 @@ # pylint: disable=wrong-import-position from ctsm import unit_testing from ctsm.site_and_regional.single_point_case import SinglePointCase +from ctsm.pft_utils import MAX_PFT_GENERICCROPS, MAX_PFT_MANAGEDCROPS +from ctsm.longitude import Longitude # pylint: disable=invalid-name @@ -28,7 +30,7 @@ class TestSinglePointCase(unittest.TestCase): """ plat = 20.1 - plon = 50.5 + plon = Longitude(50.5, lon_type=180) site_name = None create_domain = True create_surfdata = True @@ -38,7 +40,7 @@ class TestSinglePointCase(unittest.TestCase): dom_pft = [8] evenly_split_cropland = False pct_pft = None - num_pft = 16 + num_pft = MAX_PFT_GENERICCROPS cth = [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9] cbh = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] include_nonveg = False @@ -131,7 +133,7 @@ def test_check_dom_pft_too_big(self): out_dir=self.out_dir, overwrite=self.overwrite, ) - single_point.dom_pft = [16, 36, 79] + single_point.dom_pft = [MAX_PFT_GENERICCROPS, 36, 79] with self.assertRaisesRegex(argparse.ArgumentTypeError, "values for --dompft should*"): single_point.check_dom_pft() @@ -161,7 +163,7 @@ def test_check_dom_pft_too_small(self): out_dir=self.out_dir, overwrite=self.overwrite, ) - single_point.dom_pft = [16, 36, -1] + single_point.dom_pft = [MAX_PFT_GENERICCROPS, 36, -1] with self.assertRaisesRegex(argparse.ArgumentTypeError, "values for --dompft should*"): single_point.check_dom_pft() @@ -192,7 +194,7 @@ def test_check_dom_pft_numpft(self): overwrite=self.overwrite, ) single_point.dom_pft = [15, 53] - single_point.num_pft = 16 + single_point.num_pft = MAX_PFT_GENERICCROPS with self.assertRaisesRegex(argparse.ArgumentTypeError, "Please use --crop*"): single_point.check_dom_pft() @@ -223,7 +225,7 @@ def test_check_dom_pft_mixed_range(self): overwrite=self.overwrite, ) single_point.dom_pft = [1, 5, 15] - single_point.num_pft = 78 + single_point.num_pft = MAX_PFT_MANAGEDCROPS with self.assertRaisesRegex( argparse.ArgumentTypeError, "You are subsetting using mixed land*" ): diff --git a/python/ctsm/test/test_unit_singlept_data_surfdata.py b/python/ctsm/test/test_unit_singlept_data_surfdata.py index 2106799a4b..11ee416d4a 100755 --- a/python/ctsm/test/test_unit_singlept_data_surfdata.py +++ b/python/ctsm/test/test_unit_singlept_data_surfdata.py @@ -23,6 +23,8 @@ # pylint: disable=wrong-import-position from ctsm import unit_testing from ctsm.site_and_regional.single_point_case import SinglePointCase +from ctsm.pft_utils import MAX_PFT_GENERICCROPS, MAX_PFT_MANAGEDCROPS +from ctsm.longitude import Longitude # pylint: disable=invalid-name # pylint: disable=too-many-lines @@ -36,7 +38,7 @@ class TestSinglePointCaseSurfaceNoCrop(unittest.TestCase): """ plat = 20.1 - plon = 50.5 + plon = Longitude(50.5, lon_type=180) site_name = None create_domain = True create_surfdata = True @@ -46,7 +48,7 @@ class TestSinglePointCaseSurfaceNoCrop(unittest.TestCase): dom_pft = [8] evenly_split_cropland = False pct_pft = None - num_pft = 16 + num_pft = MAX_PFT_GENERICCROPS cth = 0.9 cbh = 0.1 include_nonveg = False @@ -657,7 +659,7 @@ class TestSinglePointCaseSurfaceCrop(unittest.TestCase): """ plat = 20.1 - plon = 50.5 + plon = Longitude(50.5, lon_type=180) site_name = None create_domain = True create_surfdata = True @@ -667,7 +669,7 @@ class TestSinglePointCaseSurfaceCrop(unittest.TestCase): dom_pft = [17] evenly_split_cropland = False pct_pft = None - num_pft = 78 + num_pft = MAX_PFT_MANAGEDCROPS cth = 0.9 cbh = 0.1 include_nonveg = False diff --git a/python/ctsm/test/test_unit_sspmatrix.py b/python/ctsm/test/test_unit_sspmatrix.py index 1b1bc60185..dd81a7df4f 100755 --- a/python/ctsm/test/test_unit_sspmatrix.py +++ b/python/ctsm/test/test_unit_sspmatrix.py @@ -53,7 +53,7 @@ def create_clone( Extend to handle creation of user_nl_clm file """ clone = super().create_clone(newcase, keepexe=keepexe) - os.mknod(os.path.join(newcase, "user_nl_clm")) + Path.touch(os.path.join(newcase, "user_nl_clm")) # Also make the needed case directories clone.make_case_dirs(self._tempdir) return clone @@ -165,7 +165,7 @@ def test_append_user_nl_step2(self): if os.path.exists(ufile): os.remove(ufile) - os.mknod(ufile) + Path.touch(ufile) expect = "\nhist_nhtfrq = -8760, hist_mfilt = 2\n" self.ssp.append_user_nl(caseroot=".", n=2) diff --git a/python/ctsm/test/test_unit_subset_data.py b/python/ctsm/test/test_unit_subset_data.py index d1b27d12cc..a127a282e0 100755 --- a/python/ctsm/test/test_unit_subset_data.py +++ b/python/ctsm/test/test_unit_subset_data.py @@ -7,10 +7,14 @@ """ import unittest +import tempfile +import shutil import configparser import argparse import os import sys +import numpy as np +import xarray as xr # -- add python/ctsm to path (needed if we want to run the test stand-alone) _CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) @@ -21,7 +25,50 @@ from ctsm.subset_data import get_parser, setup_files, check_args, _set_up_regional_case from ctsm.path_utils import path_to_ctsm_root -# pylint: disable=invalid-name,too-many-public-methods +# pylint: disable=invalid-name,too-many-public-methods,protected-access + + +def setup_fake_dataset(fake_values, lon_values, lat_values): + """ + Set up a Dataset with some fake data for use in testing subset_data + """ + + # Define longitude dimension/coordinates + x_dimname = "lon_dim" + x_varname = "lon_var" + lon_da = xr.DataArray( + data=lon_values, + name=x_varname, + dims=x_dimname, + coords={x_dimname: lon_values}, + ) + + # Define latitude dimension/coordinates + y_dimname = "lat_dim" + y_varname = "lat_var" + lat_da = xr.DataArray( + data=lat_values, + name=y_varname, + dims=y_dimname, + coords={y_dimname: lat_values}, + ) + + # Make DataArray (lat x lon) with fake data + fake_da = xr.DataArray( + data=fake_values, + dims=[y_dimname, x_dimname], + ) + + # Make Dataset + fake_ds = xr.Dataset( + data_vars={ + "lon": lon_da, + "lat": lat_da, + "fake": fake_da, + } + ) + + return x_dimname, y_dimname, fake_ds class TestSubsetData(unittest.TestCase): @@ -40,12 +87,24 @@ def setUp(self): self.defaults = configparser.ConfigParser() self.defaults.read(os.path.join(self.cesmroot, "tools/site_and_regional", DEFAULTS_FILE)) + # Work in temporary directory + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + os.chdir(self._tempdir) # cd to tempdir + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) + def test_inputdata_setup_files_basic(self): """ Test """ self.args = check_args(self.args) - files = setup_files(self.args, self.defaults, self.cesmroot) + files = setup_files(self.args, self.defaults, self.cesmroot, testing=True) self.assertEqual( files["fsurf_in"], "surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc", @@ -71,6 +130,23 @@ def test_inputdata_setup_files_inputdata_dne(self): with self.assertRaisesRegex(SystemExit, "inputdata directory does not exist"): setup_files(self.args, self.defaults, self.cesmroot) + def test_inputdata_setup_files_gswp3_error(self): + """ + Test that error is thrown if user tries to --create-datm GSWP3 + """ + cfg_file = os.path.join( + _CTSM_PYTHON, "ctsm", "test", "testinputs", "default_data_gswp3.cfg" + ) + sys.argv = ["subset_data", "point", "--create-datm", "--cfg-file", cfg_file] + self.args = self.parser.parse_args() + self.defaults = configparser.ConfigParser() + self.defaults.read(self.args.config_file) + + with self.assertRaisesRegex( + NotImplementedError, "https://github.com/ESCOMP/CTSM/issues/3269" + ): + setup_files(self.args, self.defaults, self.cesmroot) + def test_check_args_nooutput(self): """ Test that check args aborts when no-output is asked for @@ -108,7 +184,7 @@ def test_check_args_outsurfdat_provided(self): sys.argv = ["subset_data", "point", "--create-surface", "--out-surface", "outputsurface.nc"] self.args = self.parser.parse_args() self.args = check_args(self.args) - files = setup_files(self.args, self.defaults, self.cesmroot) + files = setup_files(self.args, self.defaults, self.cesmroot, testing=True) self.assertEqual( files["fsurf_out"], "outputsurface.nc", @@ -184,7 +260,7 @@ def test_check_args_outsurfdat_fails_without_overwrite(self): for an existing dataset without the overwrite option """ outfile = os.path.join( - os.getcwd(), + _CTSM_PYTHON, "ctsm/test/testinputs/", "surfdata_1x1_mexicocityMEX_hist_16pfts_CMIP6_2000_c231103.nc", ) @@ -328,7 +404,7 @@ def test_region_lon_type_360_toolow(self): args = self.parser.parse_args() with self.assertRaisesRegex( ValueError, - r"lon_in needs to be in the range \[0, 360\]", + r"\(All values of\) lon_in must be in the range \[0, 360\]", ): check_args(args) @@ -499,7 +575,7 @@ def test_point_ambiguous_lon_errors(self): args = self.parser.parse_args() with self.assertRaisesRegex( argparse.ArgumentTypeError, - "When providing an ambiguous longitude, you must specify --lon-type 180 or 360", + r"Longitude\(s\) ambiguous; could be type 180 or 360", ): check_args(args) @@ -564,7 +640,7 @@ def test_region_ambiguous_lon_errors(self): args = self.parser.parse_args() with self.assertRaisesRegex( argparse.ArgumentTypeError, - "When providing an ambiguous longitude, you must specify --lon-type 180 or 360", + r"Longitude\(s\) ambiguous; could be type 180 or 360", ): check_args(args) @@ -591,7 +667,7 @@ def test_region_ambiguous_lons_errors(self): args = self.parser.parse_args() with self.assertRaisesRegex( argparse.ArgumentTypeError, - "When providing an ambiguous longitude, you must specify --lon-type 180 or 360", + r"Longitude\(s\) ambiguous; could be type 180 or 360", ): check_args(args) @@ -673,6 +749,127 @@ def test_region_lon_type_180_ok_at_180(self): self.assertEqual(args.lon2.get(lon_type), lon2) _set_up_regional_case(args) + def test_check_region_bounds_none_error(self): + """ + In region mode, test that error is thrown if any region bound is None + """ + # Define a good region to pass initial setup + sys.argv = [ + "subset_data", + "region", + "--create-domain", + "--verbose", + "--lat1", + "0", + "--lat2", + "40", + "--lon1", + "194", + "--lon2", + "287", + ] + self.parser = get_parser() + args = self.parser.parse_args() + args = check_args(args) + region = _set_up_regional_case(args) + + # Mess up the region + region.lon1 = None + err_msg = "Latitude and longitude bounds must be provided and not None" + with self.assertRaisesRegex(argparse.ArgumentTypeError, err_msg): + region.check_region_bounds() + + def test_check_region_bounds_lat_eq_error(self): + """ + In region mode, test that error is thrown if lat1 == lat2 + """ + # Define a good region to pass initial setup + sys.argv = [ + "subset_data", + "region", + "--create-domain", + "--verbose", + "--lat1", + "0", + "--lat2", + "40", + "--lon1", + "194", + "--lon2", + "287", + ] + self.parser = get_parser() + args = self.parser.parse_args() + args = check_args(args) + region = _set_up_regional_case(args) + + # Mess up the region + region.lat2 = region.lat1 + err_msg = "ERROR: lat1 is bigger than lat2" + with self.assertRaisesRegex(argparse.ArgumentTypeError, err_msg): + region.check_region_bounds() + + def test_subset_lon_lat(self): + """ + Test that RegionalCase._subset_lon_lat() works as expected + """ + + # Define lon/lat boundaries of the fake Dataset we'll be making + fakefile_bounds_lon = [-21, -18] + fakefile_bounds_lat = [3, 7] + # Get lon/lat values within those bounds, with 1-deg increments + lon_values = np.arange(fakefile_bounds_lon[0], fakefile_bounds_lon[1] + 1) + lat_values = np.arange(fakefile_bounds_lat[0], fakefile_bounds_lat[1] + 1) + # Define array of data to be in the "fake" variable of our Dataset + fake_values = np.array( + [ + [0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11], + [12, 13, 14, 15], + [16, 17, 18, 19], + ] + ) + # Set up fake input Dataset + x_dimname, y_dimname, fake_ds = setup_fake_dataset(fake_values, lon_values, lat_values) + + # Define lon/lat boundaries of the region from that file we're subsetting + region_bounds_lon = [-21, -19] + region_bounds_lat = [4, 6] + # Set up command-line arguments for subset region boundaries + region_bound_args = [ + "--lat1", + str(region_bounds_lat[0]), + "--lat2", + str(region_bounds_lat[1]), + "--lon1", + str(region_bounds_lon[0]), + "--lon2", + str(region_bounds_lon[1]), + ] + + sys.argv = [ + "subset_data", + "region", + "--create-domain", + "--verbose", + ] + region_bound_args + self.parser = get_parser() + args = self.parser.parse_args() + args = check_args(args) + region = _set_up_regional_case(args) + + # Test subsetting + result = region._subset_lon_lat(x_dimname, y_dimname, fake_ds) + expected_fake_values = np.array( + [ + [4, 5, 6], + [8, 9, 10], + [12, 13, 14], + ] + ) + self.assertTrue(np.array_equal(result["fake"].values, expected_fake_values)) + if __name__ == "__main__": unit_testing.setup_for_tests() diff --git a/python/ctsm/test/test_unit_utils.py b/python/ctsm/test/test_unit_utils.py index 75b240b365..77bbbd7e34 100755 --- a/python/ctsm/test/test_unit_utils.py +++ b/python/ctsm/test/test_unit_utils.py @@ -9,6 +9,7 @@ from ctsm import unit_testing from ctsm.utils import fill_template_file, ensure_iterable +from ctsm.utils import find_one_file_matching_pattern from ctsm.config_utils import _handle_config_value # Allow names that pylint doesn't like, because otherwise I find it hard @@ -316,6 +317,54 @@ def test_ensure_iterable_error_wrong_length(self): ensure_iterable([11, 12], 3) +class TestUtilsFindOneFileMatchingPattern(unittest.TestCase): + """Tests of utils: find_one_file_matching_pattern""" + + def setUp(self): + self._testdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self._testdir) + + def test_find_one_file_matching_pattern(self): + """ + Tests that find_one_file_matching_pattern passes if one file matches + """ + # Create empty file + # pylint: disable=consider-using-with,unspecified-encoding + test_file_path = os.path.join(self._testdir, "abc123.txt") + open(test_file_path, "x").close() + + # Look for empty file given a pattern with wildcard + pattern = os.path.join(self._testdir, "abc*") + result = find_one_file_matching_pattern(pattern) + self.assertEqual(result, test_file_path) + + def test_find_one_file_matching_pattern_0found(self): + """ + Tests that find_one_file_matching_pattern errors if no files match + """ + # Look for non-existent empty file given a pattern with wildcard + pattern = os.path.join(self._testdir, "abc*") + with self.assertRaisesRegex(FileNotFoundError, "No file found matching pattern"): + find_one_file_matching_pattern(pattern) + + def test_find_one_file_matching_pattern_2found(self): + """ + Tests that find_one_file_matching_pattern errors if multiple files match + """ + # Create empty files + # pylint: disable=consider-using-with,unspecified-encoding + open(os.path.join(self._testdir, "abc123.txt"), "x").close() + open(os.path.join(self._testdir, "abc456.txt"), "x").close() + + # Look for empty file given a pattern with wildcard + pattern = os.path.join(self._testdir, "abc*") + err_msg = "Expected 1 but found 2 files found matching pattern" + with self.assertRaisesRegex(RuntimeError, err_msg): + find_one_file_matching_pattern(pattern) + + if __name__ == "__main__": unit_testing.setup_for_tests() unittest.main() diff --git a/python/ctsm/test/testinputs/ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc b/python/ctsm/test/testinputs/ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc new file mode 100644 index 0000000000..ab47c530f2 --- /dev/null +++ b/python/ctsm/test/testinputs/ctsm5.3.041.Nfix_params.v13.c250221_upplim250.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:539679e78631b252a2a9e00fe73024cbc9fdf455e496921e94483750c6cf610c +size 200764 diff --git a/python/ctsm/test/testinputs/default_data.cfg b/python/ctsm/test/testinputs/default_data.cfg index a832d810cc..60c012561c 100644 --- a/python/ctsm/test/testinputs/default_data.cfg +++ b/python/ctsm/test/testinputs/default_data.cfg @@ -1,7 +1,7 @@ [main] clmforcingindir = /glade/campaign/cesm/cesmdata/cseg/inputdata -[datm_crujra] +[datm] dir = atm/datm7/atm_forcing.datm7.CRUJRA.0.5d.c20241231/three_stream domain = domain.crujra_v2.3_0.5x0.5.c220801.nc solardir = . @@ -14,19 +14,6 @@ solarname = CLMCRUJRA2024.Solar precname = CLMCRUJRA2024.Precip tpqwname = CLMCRUJRA2024.TPQW -[datm_gswp3] -dir = atm/datm7/atm_forcing.datm7.GSWP3.0.5d.v1.c170516 -domain = domain.lnd.360x720_gswp3.0v1.c170606.nc -solardir = Solar -precdir = Precip -tpqwdir = TPHWL -solartag = clmforc.GSWP3.c2011.0.5x0.5.Solr. -prectag = clmforc.GSWP3.c2011.0.5x0.5.Prec. -tpqwtag = clmforc.GSWP3.c2011.0.5x0.5.TPQWL. -solarname = CLMGSWP3v1.Solar -precname = CLMGSWP3v1.Precip -tpqwname = CLMGSWP3v1.TPQW - [surfdat] dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc diff --git a/python/ctsm/test/testinputs/default_data_gswp3.cfg b/python/ctsm/test/testinputs/default_data_gswp3.cfg new file mode 100644 index 0000000000..09e1463eb2 --- /dev/null +++ b/python/ctsm/test/testinputs/default_data_gswp3.cfg @@ -0,0 +1,30 @@ +[main] +clmforcingindir = /glade/campaign/cesm/cesmdata/cseg/inputdata + +[datm] +dir = atm/datm7/atm_forcing.datm7.GSWP3.0.5d.v1.c170516 +domain = domain.lnd.360x720_gswp3.0v1.c170606.nc +solardir = Solar +precdir = Precip +tpqwdir = TPHWL +solartag = clmforc.GSWP3.c2011.0.5x0.5.Solr. +prectag = clmforc.GSWP3.c2011.0.5x0.5.Prec. +tpqwtag = clmforc.GSWP3.c2011.0.5x0.5.TPQWL. +solarname = CLMGSWP3v1.Solar +precname = CLMGSWP3v1.Precip +tpqwname = CLMGSWP3v1.TPQW + +[surfdat] +dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 +surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc +surfdat_78pft = surfdata_0.9x1.25_hist_2000_78pfts_c240908.nc +mesh_dir = share/meshes/ +mesh_surf = fv0.9x1.25_141008_ESMFmesh.nc + +[landuse] +dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 +landuse_16pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc +landuse_78pft = landuse.timeseries_0.9x1.25_SSP2-4.5_1850-2100_78pfts_c240908.nc + +[domain] +file = share/domains/domain.lnd.fv0.9x1.25_gx1v7.151020.nc diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1986.nc new file mode 100644 index 0000000000..84da04d260 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1986.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e1075a199de0d85b974bd9dbd09216e460eda035b3a6652cbfc59b75829e3ee +size 13136 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1987.nc new file mode 100644 index 0000000000..f05b8eb442 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1987.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21fb1ae2b2e75336e409770988dabd80e9ee69d990e5aa63dc7008c9145a455f +size 13136 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1988.nc new file mode 100644 index 0000000000..3d521c66f4 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1988.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e2624c686c86d5d1071ed618b564e4589731555836083cad0a1e8259b7962e +size 13136 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1986.nc new file mode 100644 index 0000000000..1d551867f0 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1986.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b4164da71cf6bdf351143b936d2ad84da0c943378cb54534ec46f03513e2d17 +size 13144 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1987.nc new file mode 100644 index 0000000000..b752309969 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1987.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93e9ab5686acc5fb7ddaf775e7f561d572a4fbecab28088b643868432e3d1ed3 +size 13144 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1988.nc new file mode 100644 index 0000000000..c3c47b61be --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1988.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbb6d1679040959e540928b7df056a848e9a385441d725f5f84271a07c64889c +size 13144 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1986.nc new file mode 100644 index 0000000000..9be8249601 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1986.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7145768c96bdf8b3cbab234b2a09c4506916dbbc8db9fbc73282d643251ed318 +size 37324 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1987.nc new file mode 100644 index 0000000000..068a7ff28e --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1987.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1e38846646d2514671bd340daa0954bf1981aa328d4923cb42044097bb77f38 +size 37324 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1988.nc new file mode 100644 index 0000000000..1b7094dbee --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1988.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:395aa495fd3b926521cd355fd2a012cdcd07d19b7a00467fdc49dafbf80751a1 +size 37324 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/domain.crujra_v2.3_0.5x0.5_TMP_c250620.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/domain.crujra_v2.3_0.5x0.5_TMP_c250620.nc new file mode 100644 index 0000000000..c9b19f474b --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180/datmdata/domain.crujra_v2.3_0.5x0.5_TMP_c250620.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:206ba64ca50dbd3b34e93f498eb1f526689e3a6900762f12e30c3af9b75ccb5c +size 2000 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1986.nc new file mode 120000 index 0000000000..0e14bd986a --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1986.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1986.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1987.nc new file mode 120000 index 0000000000..28b7abf80d --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1987.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1987.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1988.nc new file mode 120000 index 0000000000..a238ab07c2 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Prec.-69.0_-12.0.1988.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Prec.TMP.1988.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1986.nc new file mode 120000 index 0000000000..a2045e914c --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1986.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1986.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1987.nc new file mode 120000 index 0000000000..24cc171353 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1987.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1987.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1988.nc new file mode 120000 index 0000000000..00eacece43 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.Solr.-69.0_-12.0.1988.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.Solr.TMP.1988.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1986.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1986.nc new file mode 120000 index 0000000000..3806e36151 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1986.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1986.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1987.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1987.nc new file mode 120000 index 0000000000..44ce035a2a --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1987.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1987.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1988.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1988.nc new file mode 120000 index 0000000000..cd8cdfb7c9 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.-69.0_-12.0.1988.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//clmforc.CRUJRAv2.5_0.5x0.5.TPQWL.TMP.1988.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/domain.crujra_v2.3_0.5x0.5_-69.0_-12.0_c250620.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/domain.crujra_v2.3_0.5x0.5_-69.0_-12.0_c250620.nc new file mode 120000 index 0000000000..1dc0d0d5f2 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type180_nositename/datmdata/domain.crujra_v2.3_0.5x0.5_-69.0_-12.0_c250620.nc @@ -0,0 +1 @@ +../../test_subset_data_pt_datm_amazon_type180/datmdata//domain.crujra_v2.3_0.5x0.5_TMP_c250620.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type360 b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type360 new file mode 120000 index 0000000000..88385bbff2 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_datm_amazon_type360 @@ -0,0 +1 @@ +test_subset_data_pt_datm_amazon_type180 \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/landuse.timeseries_TMP_amazon_hist_1850-1853_78pfts_c250618.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/landuse.timeseries_TMP_amazon_hist_1850-1853_78pfts_c250618.nc new file mode 100644 index 0000000000..d34fdf3acf --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/landuse.timeseries_TMP_amazon_hist_1850-1853_78pfts_c250618.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b063aeb04ed3a0a613608ecf88ac47efb39de7ba74bf6e33a490925540bf47fb +size 18176 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/surfdata_TMP_amazon_hist_1850_78pfts_c250618.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/surfdata_TMP_amazon_hist_1850_78pfts_c250618.nc new file mode 100644 index 0000000000..02999b6b00 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type180/surfdata_TMP_amazon_hist_1850_78pfts_c250618.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efbf02729f8741bfdfbd51d748cce31c2d90b0c9ef2f00d841d2940dea5bc144 +size 53256 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360 b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360 new file mode 120000 index 0000000000..ad4f251586 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360 @@ -0,0 +1 @@ +test_subset_data_pt_landuse_amazon_type180 \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/landuse.timeseries_291.0_-12.0_amazon_hist_1850-1853_78pfts_c250618.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/landuse.timeseries_291.0_-12.0_amazon_hist_1850-1853_78pfts_c250618.nc new file mode 120000 index 0000000000..8678639d98 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/landuse.timeseries_291.0_-12.0_amazon_hist_1850-1853_78pfts_c250618.nc @@ -0,0 +1 @@ +../test_subset_data_pt_landuse_amazon_type360/landuse.timeseries_TMP_amazon_hist_1850-1853_78pfts_c250618.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/surfdata_291.0_-12.0_amazon_hist_1850_78pfts_c250618.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/surfdata_291.0_-12.0_amazon_hist_1850_78pfts_c250618.nc new file mode 120000 index 0000000000..2efbb745f4 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_landuse_amazon_type360_nositename/surfdata_291.0_-12.0_amazon_hist_1850_78pfts_c250618.nc @@ -0,0 +1 @@ +../test_subset_data_pt_landuse_amazon_type360/surfdata_TMP_amazon_hist_1850_78pfts_c250618.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250617.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250617.nc new file mode 100644 index 0000000000..6e742560d0 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250617.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e694ca46925fbe07270b5468fe3899ead98dcc7d41353a6551dcc1ec92a9f9e0 +size 27740 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180_nositename/surfdata_-69.0_-12.0_amazon_hist_16pfts_CMIP6_2000_c250617.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180_nositename/surfdata_-69.0_-12.0_amazon_hist_16pfts_CMIP6_2000_c250617.nc new file mode 120000 index 0000000000..9e811ca9c3 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type180_nositename/surfdata_-69.0_-12.0_amazon_hist_16pfts_CMIP6_2000_c250617.nc @@ -0,0 +1 @@ +../test_subset_data_pt_surface_amazon_type180/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250617.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type360 b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type360 new file mode 120000 index 0000000000..3a7bc5efe3 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_pt_surface_amazon_type360 @@ -0,0 +1 @@ +test_subset_data_pt_surface_amazon_type180 \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508.nc new file mode 100644 index 0000000000..ed5b318782 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bcaf4ac786ae6b37fb2000fbc79d288072ff41f7e39ca08e55de5bfca58518c +size 3804 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508_ESMF_UNSTRUCTURED_MESH.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508_ESMF_UNSTRUCTURED_MESH.nc new file mode 100644 index 0000000000..55f1e3ae36 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508_ESMF_UNSTRUCTURED_MESH.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86ed3c23c471689f97803bc65737f19d2255d8cdbbf4050c388fc5c4aef6a154 +size 14606 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250508.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250508.nc new file mode 100644 index 0000000000..1c0ef655a6 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250508.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30c6a1f3d1e32ec75b2ef406362cc1da2cd8200fccb88ab1ea0579dd14500b42 +size 105164 diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508.nc new file mode 120000 index 0000000000..99d8401b46 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508.nc @@ -0,0 +1 @@ +../test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508_ESMF_UNSTRUCTURED_MESH.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508_ESMF_UNSTRUCTURED_MESH.nc new file mode 120000 index 0000000000..6f8ca4e665 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/domain.lnd.5x5pt-amazon_navy_291.0-299.0_-12.0--7.0_c250508_ESMF_UNSTRUCTURED_MESH.nc @@ -0,0 +1 @@ +../test_subset_data_reg_amazon/domain.lnd.5x5pt-amazon_navy_TMP_c250508_ESMF_UNSTRUCTURED_MESH.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/surfdata_291.0-299.0_-12.0--7.0_amazon_hist_16pfts_CMIP6_2000_c250508.nc b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/surfdata_291.0-299.0_-12.0--7.0_amazon_hist_16pfts_CMIP6_2000_c250508.nc new file mode 120000 index 0000000000..73bde404b0 --- /dev/null +++ b/python/ctsm/test/testinputs/expected_result_files/test_subset_data_reg_amazon_noregname/surfdata_291.0-299.0_-12.0--7.0_amazon_hist_16pfts_CMIP6_2000_c250508.nc @@ -0,0 +1 @@ +../test_subset_data_reg_amazon/surfdata_TMP_amazon_hist_16pfts_CMIP6_2000_c250508.nc \ No newline at end of file diff --git a/python/ctsm/test/testinputs/landuse.timeseries_5x5_amazon_hist_1850-1853_78pfts_c250617.nc b/python/ctsm/test/testinputs/landuse.timeseries_5x5_amazon_hist_1850-1853_78pfts_c250617.nc new file mode 100644 index 0000000000..9e81ad351c --- /dev/null +++ b/python/ctsm/test/testinputs/landuse.timeseries_5x5_amazon_hist_1850-1853_78pfts_c250617.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83b34be6da2047bb9a099346f7f5472b932ead7033fe8ab817540b99ff3117b8 +size 215248 diff --git a/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_site_valid.csv b/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_site_valid.csv new file mode 100644 index 0000000000..2c1580bc03 --- /dev/null +++ b/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_site_valid.csv @@ -0,0 +1,7 @@ +#pftX-cth and pftX-cbh are the site=specific canopy top and bottom heights +#start_year and end_year will be used to define DATM_YR_ALIGH, DATM_YR_START and DATM_YR_END, and STOP_N in units of nyears. +#RUN_STARTDATE and START_TOD are specified because we are starting at GMT corresponding to local midnight. +#ATM_NCPL is specified so that the time step of the model matches the time interval specified by the atm forcing data. +#longitudes must be in the range [-180,180] +,Site,Lat,Lon,pft1,pft1-%,pft1-cth,pft1-cbh,pft2,pft2-%,pft2-cth,pft2-cbh,start_year,end_year,RUN_STARTDATE,START_TOD,ATM_NCPL +27,BE-Lon,50.551590, 4.746130,15,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2005,2014,2004-12-31,82800,48 diff --git a/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_sites_invalid_pft.csv b/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_sites_invalid_pft.csv new file mode 100644 index 0000000000..e8f0eb8fbb --- /dev/null +++ b/python/ctsm/test/testinputs/plumber2_surf_wrapper/PLUMBER2_sites_invalid_pft.csv @@ -0,0 +1,8 @@ +#pftX-cth and pftX-cbh are the site=specific canopy top and bottom heights +#start_year and end_year will be used to define DATM_YR_ALIGH, DATM_YR_START and DATM_YR_END, and STOP_N in units of nyears. +#RUN_STARTDATE and START_TOD are specified because we are starting at GMT corresponding to local midnight. +#ATM_NCPL is specified so that the time step of the model matches the time interval specified by the atm forcing data. +#longitudes must be in the range [-180,180] +,Site,Lat,Lon,pft1,pft1-%,pft1-cth,pft1-cbh,pft2,pft2-%,pft2-cth,pft2-cbh,start_year,end_year,RUN_STARTDATE,START_TOD,ATM_NCPL +26,Invalid-Pft,51.309166, 4.520560,-1,19.22,21.00,10.50,7,80.78,21.00,12.08,2004,2014,2003-12-31,82800,48 +27,BE-Lon,50.551590, 4.746130,15,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2005,2014,2004-12-31,82800,48 diff --git a/python/ctsm/test/testinputs/subset_data_amazon.cfg b/python/ctsm/test/testinputs/subset_data_amazon.cfg new file mode 100644 index 0000000000..f99486c82a --- /dev/null +++ b/python/ctsm/test/testinputs/subset_data_amazon.cfg @@ -0,0 +1,9 @@ +[surfdat] +dir = ctsm/test/testinputs +surfdat_16pft = surfdata_5x5_amazon_hist_16pfts_CMIP6_2000_c231031.nc +surfdat_78pft = surfdata_5x5_amazon_hist_78pfts_CMIP6_2000_c230517.nc +mesh_dir = ctsm/test/testinputs +mesh_surf = ESMF_mesh_5x5pt_amazon_from_domain_c230308.nc + +[domain] +file = ctsm/test/testinputs/domain.lnd.5x5pt-amazon_navy.090715.nc diff --git a/python/ctsm/test/testinputs/subset_data_amazon_1850.cfg b/python/ctsm/test/testinputs/subset_data_amazon_1850.cfg new file mode 100644 index 0000000000..6b16160f48 --- /dev/null +++ b/python/ctsm/test/testinputs/subset_data_amazon_1850.cfg @@ -0,0 +1,14 @@ +[surfdat] +dir = ctsm/test/testinputs +surfdat_16pft = surfdata_5x5_amazon_hist_1850_78pfts_c250617.nc +surfdat_78pft = surfdata_5x5_amazon_hist_1850_78pfts_c250617.nc +mesh_dir = ctsm/test/testinputs +mesh_surf = ESMF_mesh_5x5pt_amazon_from_domain_c230308.nc + +[landuse] +dir = ctsm/test/testinputs +landuse_16pft = landuse.timeseries_5x5_amazon_hist_1850-1853_78pfts_c250617.nc +landuse_78pft = landuse.timeseries_5x5_amazon_hist_1850-1853_78pfts_c250617.nc + +[domain] +file = ctsm/test/testinputs/domain.lnd.5x5pt-amazon_navy.090715.nc diff --git a/python/ctsm/test/testinputs/surfdata_5x5_amazon_hist_1850_78pfts_c250617.nc b/python/ctsm/test/testinputs/surfdata_5x5_amazon_hist_1850_78pfts_c250617.nc new file mode 100644 index 0000000000..747c33a2b0 --- /dev/null +++ b/python/ctsm/test/testinputs/surfdata_5x5_amazon_hist_1850_78pfts_c250617.nc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0795d84b3e07a9437c7e9869810b74002210f7c55349f57983c36db9990db4a +size 893512 diff --git a/python/ctsm/test_gen_mksurfdata_jobscript_single_parent.py b/python/ctsm/test_gen_mksurfdata_jobscript_single_parent.py new file mode 100755 index 0000000000..b6a3741444 --- /dev/null +++ b/python/ctsm/test_gen_mksurfdata_jobscript_single_parent.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +""" +Parent class for some unittest modules relating to gen_mksurfdata_jobscript_single.py +""" + +import unittest +import os +import sys +import shutil +from pathlib import Path + +import tempfile + +from ctsm.path_utils import path_to_ctsm_root + + +# pylint: disable=too-many-instance-attributes +class TestFGenMkSurfJobscriptSingleParent(unittest.TestCase): + """Parent class for some unittest modules relating to gen_mksurfdata_jobscript_single.py""" + + def setUp(self): + """Setup for trying out the methods""" + testinputs_path = os.path.join(path_to_ctsm_root(), "python/ctsm/test/testinputs") + self._testinputs_path = testinputs_path + self._previous_dir = os.getcwd() + self._tempdir = tempfile.mkdtemp() + os.chdir(self._tempdir) + self._account = "ACCOUNT_NUMBER" + self._jobscript_file = "output_jobscript" + self._output_compare = """#!/bin/bash +# Edit the batch directives for your batch system +# Below are default batch directives for derecho +#PBS -N mksurfdata +#PBS -j oe +#PBS -k eod +#PBS -S /bin/bash +#PBS -l walltime=12:00:00 +#PBS -A ACCOUNT_NUMBER +#PBS -q main +#PBS -l select=1:ncpus=128:mpiprocs=64:mem=218GB + +# This is a batch script to run a set of resolutions for mksurfdata_esmf input namelist +# NOTE: THIS SCRIPT IS AUTOMATICALLY GENERATED SO IN GENERAL YOU SHOULD NOT EDIT it!! + +""" + self._bld_path = os.path.join(self._tempdir, "tools_bld") + os.makedirs(self._bld_path) + self.assertTrue(os.path.isdir(self._bld_path)) + self._nlfile = os.path.join(self._tempdir, "namelist_file") + Path.touch(self._nlfile) + self.assertTrue(os.path.exists(self._nlfile)) + self._mksurf_exe = os.path.join(self._bld_path, "mksurfdata") + Path.touch(self._mksurf_exe) + self.assertTrue(os.path.exists(self._mksurf_exe)) + self._env_mach = os.path.join(self._bld_path, ".env_mach_specific.sh") + Path.touch(self._env_mach) + self.assertTrue(os.path.exists(self._env_mach)) + sys.argv = [ + "gen_mksurfdata_jobscript_single", + "--bld-path", + self._bld_path, + "--namelist-file", + self._nlfile, + "--jobscript-file", + self._jobscript_file, + "--account", + self._account, + ] + + def tearDown(self): + """ + Remove temporary directory + """ + os.chdir(self._previous_dir) + shutil.rmtree(self._tempdir, ignore_errors=True) diff --git a/python/ctsm/toolchain/gen_mksurfdata_jobscript_multi.py b/python/ctsm/toolchain/gen_mksurfdata_jobscript_multi.py index 14957a0177..5a5425dc60 100755 --- a/python/ctsm/toolchain/gen_mksurfdata_jobscript_multi.py +++ b/python/ctsm/toolchain/gen_mksurfdata_jobscript_multi.py @@ -203,11 +203,11 @@ def main(): "ultra_hi_res_no_crop": ["mpasa15", "mpasa3p75"], "standard_res": ["360x720cru", "0.9x1.25", "1.9x2.5", "C96", "mpasa120"], "standard_res_no_f09": ["360x720cru", "1.9x2.5", "C96", "mpasa120"], - "low_res": ["4x5", "10x15", "ne3np4.pg3"], + "low_res": ["4x5", "10x15", "ne3np4.pg3", "ne3np4"], "mpasa480": ["mpasa480"], "nldas_res": ["0.125nldas2"], "5x5_amazon": ["5x5_amazon"], - "ne3": ["ne3np4.pg3"], + "ne3": ["ne3np4", "ne3np4.pg3"], "ne16": ["ne16np4.pg3"], "ne30": ["ne30np4.pg3", "ne30np4.pg2", "ne30np4"], "ne0np4": [ diff --git a/python/ctsm/toolchain/gen_mksurfdata_jobscript_single.py b/python/ctsm/toolchain/gen_mksurfdata_jobscript_single.py index d517ecf244..c3f762380e 100755 --- a/python/ctsm/toolchain/gen_mksurfdata_jobscript_single.py +++ b/python/ctsm/toolchain/gen_mksurfdata_jobscript_single.py @@ -37,7 +37,7 @@ def base_get_parser(default_js_name="mksurfdata_jobscript_single.sh"): default_account = os.environ.get("ACCOUNT") if default_account is None: - default_account = "P93300641" + default_account = "P93300041" parser.add_argument( "--account", help="""account number (default: %(default)s)""", diff --git a/python/ctsm/toolchain/gen_mksurfdata_namelist.py b/python/ctsm/toolchain/gen_mksurfdata_namelist.py index 31fcbfe8ff..3a405bf5fa 100755 --- a/python/ctsm/toolchain/gen_mksurfdata_namelist.py +++ b/python/ctsm/toolchain/gen_mksurfdata_namelist.py @@ -15,6 +15,7 @@ from ctsm.path_utils import path_to_ctsm_root, path_to_cime from ctsm.ctsm_logging import setup_logging_pre_config, add_logging_args, process_logging_args +from ctsm.pft_utils import MAX_PFT_GENERICCROPS, MAX_PFT_MANAGEDCROPS logger = logging.getLogger(__name__) @@ -306,9 +307,9 @@ def main(): # Determine num_pft if nocrop_flag: - num_pft = "16" + num_pft = str(MAX_PFT_GENERICCROPS) else: - num_pft = "78" + num_pft = str(MAX_PFT_MANAGEDCROPS) logger.info("num_pft is %s", num_pft) # Write out if surface dataset will be created diff --git a/python/ctsm/unit_testing.py b/python/ctsm/unit_testing.py index d3a308c796..8370830b4d 100644 --- a/python/ctsm/unit_testing.py +++ b/python/ctsm/unit_testing.py @@ -1,8 +1,22 @@ """Functions to aid unit tests""" +import sys from ctsm.ctsm_logging import setup_logging_for_tests +def add_machine_node_args(machine, nodes, tasks): + """add arguments to sys.argv""" + args_to_add = [ + "--machine", + machine, + "--number-of-nodes", + str(nodes), + "--tasks-per-node", + str(tasks), + ] + sys.argv += args_to_add + + def setup_for_tests(enable_critical_logs=False): """Call this at the beginning of unit testing diff --git a/python/ctsm/utils.py b/python/ctsm/utils.py index 8ae6d9435e..df2b78ab7c 100644 --- a/python/ctsm/utils.py +++ b/python/ctsm/utils.py @@ -3,11 +3,12 @@ import logging import os import sys +import glob import string import re import pdb -from datetime import date, timedelta +from datetime import date, timedelta, datetime from getpass import getuser from ctsm.git_utils import get_ctsm_git_short_hash @@ -250,3 +251,26 @@ def is_instantaneous(time_var): if "time at exact middle" in long_name: return False raise RuntimeError(f"Does this long_name mean instantaneous or not? {long_name}") + + +def find_one_file_matching_pattern(pattern): + """ + Given a file path with wildcards, find all the matching files. Throw an error if there is not + exactly one matching file. If there's just one, return its path. + """ + file_list = glob.glob(pattern) + if not file_list: + raise FileNotFoundError("No file found matching pattern: " + pattern) + n_found = len(file_list) + if n_found > 1: + raise RuntimeError( + f"Expected 1 but found {n_found} files found matching pattern: " + pattern + ) + return file_list[0] + + +def datetime_string(): + """ + Return a datetime string like "YYYY-mm-dd HH:MM:SS" + """ + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/python/empty.yml b/python/empty.yml new file mode 100644 index 0000000000..befe96c419 --- /dev/null +++ b/python/empty.yml @@ -0,0 +1,7 @@ +# An empty file for use in testing py_env_create with mamba, which needs some YML structure +# (as opposed to conda, which can use a truly empty file) +# +channels: + - conda-forge + - defaults +dependencies: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9707af4f0b..bc925b7dd1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,17 +1,18 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.10) list(APPEND CMAKE_MODULE_PATH ${CIME_CMAKE_MODULE_DIRECTORY}) include(CIME_initial_setup) -#list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../tools/mksurfdata_esmf/cmake") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../share/cmake") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../components/cmeps/cmake") +set(SRCROOT "${CIMEROOT}/..") +set(CLM_ROOT "..") + +list(APPEND CMAKE_MODULE_PATH "${SRCROOT}/share/cmake") +list(APPEND CMAKE_MODULE_PATH "${SRCROOT}/components/cmeps/cmake") project(clm_tests Fortran C) include(CIME_utils) -set(CLM_ROOT "..") # find needed external packages # NetCDF is required -- because PIO and NetCDF are required by the standard default ESMF libraries @@ -38,13 +39,15 @@ link_libraries(${ESMF_INTERFACE_LINK_LIBRARIES}) # done first, so that in case of name collisions, the CLM versions take # precedence (when there are two files with the same name, the one added later # wins). -add_subdirectory(${CLM_ROOT}/share/src csm_share) -add_subdirectory(${CLM_ROOT}/share/unit_test_stubs/util csm_share_stubs) +add_subdirectory(${SRCROOT}/share/src csm_share) +add_subdirectory(${SRCROOT}/share/unit_test_stubs/util csm_share_stubs) # Add files needed from CMEPS list ( APPEND drv_sources_needed - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90 - ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_dust_emis_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_dust_emis_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_expr_parser_mod.F90 + ${SRCROOT}/components/cmeps/cesm/nuopc_cap_share/shr_fire_emis_mod.F90 ) # Add CLM source directories @@ -91,10 +94,10 @@ add_library(csm_share ${share_sources} ${drv_sources_needed}) declare_generated_dependencies(csm_share "${share_genf90_sources}") add_library(clm ${clm_sources}) declare_generated_dependencies(clm "${clm_genf90_sources}") -add_dependencies(clm csm_share esmf) +add_dependencies(clm csm_share ESMF) # We need to look for header files here, in order to pick up shr_assert.h -include_directories(${CLM_ROOT}/share/include) +include_directories(${SRCROOT}/share/include) # Tell cmake to look for libraries & mod files here, because this is where we built libraries include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/biogeochem/AtmCarbonIsotopeStreamType.F90 b/src/biogeochem/AtmCarbonIsotopeStreamType.F90 new file mode 100644 index 0000000000..0a49ac8061 --- /dev/null +++ b/src/biogeochem/AtmCarbonIsotopeStreamType.F90 @@ -0,0 +1,238 @@ +module AtmCarbonIsotopeStreamType + ! + ! Description: + ! + ! This extends the stream base type to implement streams for atmospheric + ! Carbon isotope ratios that are read in from streams datasets (delta C13 and delta C14). + ! + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl , only : iulog + use abortutils , only : endrun + use decompMod , only : bounds_type + use CTSMForce2DStreamBaseType, only : ctsm_force_2DStream_base_type + + implicit none + private + + !----------------------------------------------------------------------- + ! Atmospheric Delta C13 Stream Type + !----------------------------------------------------------------------- + character(len=*), parameter :: varname_c13 = 'delta13co2_in_air' + type, public, extends(ctsm_force_2DStream_base_type) :: atm_delta_c13_stream_type + private + real(r8), public, allocatable :: atm_delta_c13(:) ! delta C13 data array + contains + + ! Public Methods + procedure, public :: C13Init ! C13 initialization + procedure, public :: Init => C13Init ! Generic name for the initialization + procedure, public :: C13Interp ! C13 Interp method to fill the local data array + procedure, public :: Interp => C13Interp ! Generic name for the Interp method + procedure, public :: C13ClassClean ! C13 clean method as a class method + procedure, public :: Clean => C13ClassClean ! Generic name for the clean method + final :: C13TypeClean ! This clean method may be called by the compiler when the type goes out of scope + ! Private methods + procedure, private :: C13InitAllocate ! Allocate the local C13 data + + end type atm_delta_c13_stream_type + + !----------------------------------------------------------------------- + ! Atmospheric Delta C14 Stream Type + !----------------------------------------------------------------------- + character(len=*), parameter :: varname_c14 = 'Delta14co2_in_air' + type, public, extends(ctsm_force_2DStream_base_type) :: atm_delta_c14_stream_type + private + real(r8), public, allocatable :: atm_delta_c14(:) ! delta c14 data array + contains + + ! Public Methods + procedure, public :: C14Init ! C14 initialization + procedure, public :: Init => C14Init ! Generic name for the initialization + procedure, public :: C14Interp ! C14 Interp method to fill the local data array + procedure, public :: Interp => C14Interp ! Generic name for the Interp method + procedure, public :: C14ClassClean ! C14 clean method as a class method + procedure, public :: Clean => C14ClassClean ! Generic name for the clean method + final :: C14TypeClean ! This clean method may be called by the compiler when the type goes out of scope + ! Private methods + procedure, private :: C14InitAllocate ! Allocate the local C14 data + + end type atm_delta_c14_stream_type + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + !----------------------------------------------------------------------- + contains + !----------------------------------------------------------------------- + + !------------------------------------------------------------------------------------- + + subroutine C13Init( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! + ! Initialize the atmospheric delta C13 stream type + ! + ! Arguments: + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + call this%InitBase( bounds, varnames = (/ varname_c13 /), fldfilename=fldfilename, meshfile=meshfile, & + mapalgo=mapalgo, tintalgo=tintalgo, taxmode=taxmode, name=varname_c13, & + year_first=year_first, year_last=year_last, model_year_align=model_year_align ) + call this%C13InitAllocate( bounds ) + + end subroutine C13Init + + !------------------------------------------------------------------------------------- + + subroutine C13InitAllocate( this, bounds ) + ! Allocate memory for the delta C13 data array + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + integer :: begg, endg + + begg = bounds%begg; endg = bounds%endg + allocate( this%atm_delta_c13( bounds%begg : bounds%endg ) ); this%atm_delta_c13 = nan + end subroutine C13InitAllocate + + !------------------------------------------------------------------------------------- + + subroutine C13ClassClean( this ) + ! Clean up memory for the C13 stream type as a class method + class(atm_delta_c13_stream_type), intent(inout) :: this + + call C13TypeClean( this ) + + end subroutine C13ClassClean + + !------------------------------------------------------------------------------------- + + subroutine C13TypeClean( this ) + ! Clean up memory for the C13 stream type for this specific type + type(atm_delta_c13_stream_type), intent(inout) :: this + + deallocate( this%atm_delta_c13 ) + call this%CleanBase() + + end subroutine C13TypeClean + + !------------------------------------------------------------------------------------- + + subroutine C13Interp( this, bounds ) + ! + ! Fill the local CTSM grid delta C13 array with data from the stream + ! + ! Arguments + class(atm_delta_c13_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + ! Local Variables + integer :: g + real(r8), pointer :: dataptr1d(:) + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call this%GetPtr1D( varname_c13, dataptr1d ) + + do g = bounds%begg, bounds%endg + this%atm_delta_c13(g) = dataptr1d(g) + end do + end subroutine C13Interp + + !------------------------------------------------------------------------------------- + + subroutine C14Init( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! + ! Initialize the atmospheric delta C14 stream type + ! + ! Arguments: + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + call this%InitBase( bounds, varnames = (/ varname_c14 /), fldfilename=fldfilename, meshfile=meshfile, & + mapalgo=mapalgo, tintalgo=tintalgo, taxmode=taxmode, name=varname_c14, & + year_first=year_first, year_last=year_last, model_year_align=model_year_align ) + call this%C14InitAllocate( bounds ) + + end subroutine C14Init + + !------------------------------------------------------------------------------------- + + subroutine C14InitAllocate( this, bounds ) + ! Allocate memory for the delta C14 data array + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + ! Arguments + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + integer :: begg, endg + + begg = bounds%begg; endg = bounds%endg + allocate( this%atm_delta_c14( bounds%begg : bounds%endg ) ); this%atm_delta_c14 = nan + end subroutine C14InitAllocate + + !------------------------------------------------------------------------------------- + + subroutine C14Interp( this, bounds ) + ! Fill the local CTSM grid delta C13 array with data from the stream + ! + ! Arguments: + class(atm_delta_c14_stream_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + + ! Local Variables + integer :: g + real(r8), pointer :: dataptr1d(:) + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call this%GetPtr1D( varname_c14, dataptr1d ) + + do g = bounds%begg, bounds%endg + this%atm_delta_c14(g) = dataptr1d(g) + end do + end subroutine C14Interp + + !------------------------------------------------------------------------------------- + + subroutine C14ClassClean( this ) + ! Clean up memory for the C14 stream type as a class method + class(atm_delta_c14_stream_type), intent(inout) :: this + + call C14TypeClean( this ) + + end subroutine C14ClassClean + + !------------------------------------------------------------------------------------- + + subroutine C14TypeClean( this ) + ! Clean up memory for the C14 stream type for this specific type + type(atm_delta_c14_stream_type), intent(inout) :: this + + deallocate( this%atm_delta_c14 ) + call this%CleanBase() + + end subroutine C14TypeClean + + !------------------------------------------------------------------------------------- + +end module AtmCarbonIsotopeStreamType \ No newline at end of file diff --git a/src/biogeochem/CMakeLists.txt b/src/biogeochem/CMakeLists.txt index 270e85838b..393e4e4db2 100644 --- a/src/biogeochem/CMakeLists.txt +++ b/src/biogeochem/CMakeLists.txt @@ -2,6 +2,7 @@ # source files that are currently used in unit tests list(APPEND clm_sources + ch4varcon.F90 CNSharedParamsMod.F90 CNPhenologyMod.F90 CNSpeciesMod.F90 @@ -12,6 +13,14 @@ list(APPEND clm_sources DustEmisFactory.F90 CropReprPoolsMod.F90 CropType.F90 + CNFireBaseMod.F90 + CNFireNoFireMod.F90 + CNFireFactoryMod.F90 + CNFireLi2014Mod.F90 + CNFireLi2016Mod.F90 + CNFireLi2021Mod.F90 + CNFireLi2024Mod.F90 + CNVegMatrixMod.F90 CNVegStateType.F90 CNVegCarbonStateType.F90 CNVegCarbonFluxType.F90 @@ -19,7 +28,13 @@ list(APPEND clm_sources CNVegNitrogenStateType.F90 CNVegNitrogenFluxType.F90 CNCIsoAtmTimeSeriesReadMod.F90 + AtmCarbonIsotopeStreamType.F90 CNVegComputeSeedMod.F90 + FATESFireBase.F90 + FATESFireDataMod.F90 + FATESFireFactoryMod.F90 + FATESFireNoDataMod.F90 + SatellitePhenologyMod.F90 SpeciesBaseType.F90 SpeciesIsotopeType.F90 SpeciesNonIsotopeType.F90 diff --git a/src/biogeochem/CNAllocationMod.F90 b/src/biogeochem/CNAllocationMod.F90 index 254e951cfe..78dfadfaee 100644 --- a/src/biogeochem/CNAllocationMod.F90 +++ b/src/biogeochem/CNAllocationMod.F90 @@ -15,7 +15,7 @@ module CNAllocationMod use clm_varcon , only : secspday use clm_varctl , only : use_c13, use_c14, iulog use PatchType , only : patch - use pftconMod , only : pftcon, npcropmin + use pftconMod , only : pftcon, is_prognostic_crop use CropType , only : crop_type use CropType , only : cphase_planted, cphase_leafemerge, cphase_grainfill use PhotosynthesisMod , only : photosyns_type @@ -191,7 +191,7 @@ subroutine calc_gpp_mr_availc(bounds, num_soilp, filter_soilp, & mr = leaf_mr(p) + froot_mr(p) if (woody(ivt(p)) == 1.0_r8) then mr = mr + livestem_mr(p) + livecroot_mr(p) - else if (ivt(p) >= npcropmin) then + else if (is_prognostic_crop(ivt(p))) then if (croplive(p)) then reproductive_mr_tot = 0._r8 do k = 1, nrepr @@ -500,7 +500,7 @@ subroutine calc_allometry(num_soilp, filter_soilp, & end if f4 = flivewd(ivt(p)) - if (ivt(p) >= npcropmin) then + if (is_prognostic_crop(ivt(p))) then g1 = 0.25_r8 else g1 = grperc(ivt(p)) @@ -521,7 +521,7 @@ subroutine calc_allometry(num_soilp, filter_soilp, & c_allometry(p) = (1._r8+g1a)*(1._r8+f1+f3*(1._r8+f2)) n_allometry(p) = 1._r8/cnl + f1/cnfr + (f3*f4*(1._r8+f2))/cnlw + & (f3*(1._r8-f4)*(1._r8+f2))/cndw - else if (ivt(p) >= npcropmin) then ! skip generic crops + else if (is_prognostic_crop(ivt(p))) then ! skip generic crops cng = graincn(ivt(p)) f1 = aroot(p) / aleaf(p) f3 = astem(p) / aleaf(p) diff --git a/src/biogeochem/CNC14DecayMod.F90 b/src/biogeochem/CNC14DecayMod.F90 index 1679c602e4..8caabfc41d 100644 --- a/src/biogeochem/CNC14DecayMod.F90 +++ b/src/biogeochem/CNC14DecayMod.F90 @@ -11,7 +11,7 @@ module CNC14DecayMod use clm_varctl , only : spinup_state use CNSharedParamsMod , only : use_matrixcn use decompMod , only : bounds_type - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use CNVegCarbonStateType , only : cnveg_carbonstate_type use CNVegCarbonFluxType , only : cnveg_carbonflux_type use SoilBiogeochemDecompCascadeConType , only : decomp_cascade_con, use_soil_matrixcn @@ -205,12 +205,7 @@ subroutine C14Decay( bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & gresp_xfer(p) = gresp_xfer(p) * (1._r8 - decay_const * dt) pft_ctrunc(p) = pft_ctrunc(p) * (1._r8 - decay_const * dt) - ! NOTE(wjs, 2017-02-02) This isn't a completely robust way to check if this is a - ! prognostic crop patch (at the very least it should also check if <= npcropmax; - ! ideally it should use a prognostic_crop flag that doesn't seem to exist - ! currently). But I'm just being consistent with what's done elsewhere (e.g., in - ! CStateUpdate1). - if (patch%itype(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(patch%itype(p))) then ! skip 2 generic crops cropseedc_deficit(p) = cropseedc_deficit(p) * (1._r8 - decay_const * dt) end if end do diff --git a/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 b/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 index 529a547e88..736924fd4f 100644 --- a/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 +++ b/src/biogeochem/CNCIsoAtmTimeSeriesReadMod.F90 @@ -1,22 +1,27 @@ module CIsoAtmTimeseriesMod +#include "shr_assert.h" + !----------------------------------------------------------------------- ! Module for transient atmospheric boundary to the c13 and c14 codes ! ! !USES: - use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL use clm_time_manager , only : get_curr_date, get_curr_yearfrac - use clm_varcon , only : c14ratio, secspday + use clm_varcon , only : c13ratio, c14ratio, secspday use shr_const_mod , only : SHR_CONST_PDB ! Ratio of C13/C12 - use clm_varctl , only : iulog + use clm_varctl , only : iulog, use_c13, use_c14 use abortutils , only : endrun use spmdMod , only : masterproc use shr_log_mod , only : errMsg => shr_log_errMsg + use AtmCarbonIsotopeStreamType, only : atm_delta_c13_stream_type, atm_delta_c14_stream_type + use decompMod , only : bounds_type ! implicit none private ! ! !PUBLIC MEMBER FUNCTIONS: + public:: CIsoAtmReadNML ! Read namelist for atmospheric C14/C13 isotope time series public:: C14BombSpike ! Time series for C14 data public:: C14_init_BombSpike ! Initialize C14 data series and read data in public:: C13Timeseries ! Time series for C13 data @@ -27,91 +32,439 @@ module CIsoAtmTimeseriesMod character(len=256) , public :: atm_c14_filename = ' ' ! file name of C14 input data logical , public :: use_c13_timeseries = .false. ! do we use time-varying atmospheric C13? character(len=256) , public :: atm_c13_filename = ' ' ! file name of C13 input data - integer, parameter , public :: nsectors_c14 = 3 ! Number of latitude sectors the C14 data has + real(r8), allocatable, public, protected :: rc14_atm_grc(:) ! Ratio of C14 C12 data on gridcell + real(r8), allocatable, public, protected :: rc13_atm_grc(:) ! Ratio of C13 C12 data on gridcell ! ! !PRIVATE MEMBER FUNCTIONS: private:: check_units ! Check the units of the data on the input file + ! Private subroutines only made public for unit testing + public:: CIsoCheckNMLInputs ! Check that the namelist inputs are valid + public:: CIsoSetNMLInputs ! Set the namelist inputs for unit testing + public:: CIsoSetControl ! Set the control variables for Carbon Isotopes + public:: CIsoLogControl ! Write out the control settings to the logfile + + type(atm_delta_c13_stream_type), private :: atm_c13_stream ! Atmospheric C13 stream object + type(atm_delta_c14_stream_type), private :: atm_c14_stream ! Atmospheric C14 stream object + ! !PRIVATE TYPES: + integer, parameter , private :: nsectors_c14 = 3 ! Number of latitude sectors the C14 data has real(r8), allocatable, private :: atm_c14file_time(:) ! time for C14 data - real(r8), allocatable, private :: atm_delta_c14(:,:) ! Delta C14 data + real(r8), allocatable, private :: atm_delta_c14(:,:) ! Delta C14 data (time,nsectors) + real(r8), allocatable, private :: atm_delta_c14_grc(:) ! Delta C14 data on gridcell real(r8), allocatable, private :: atm_c13file_time(:) ! time for C13 data - real(r8), allocatable, private :: atm_delta_c13(:) ! Delta C13 data + real(r8), allocatable, private :: atm_delta_c13(:) ! Delta C13 data (time) + real(r8), allocatable, private :: atm_delta_c13_grc(:) ! Delta C13 data on gridcell real(r8), parameter :: time_axis_offset = 1850.0_r8 ! Offset in years of time on file + logical, private :: use_c13_streams = .false. ! By default read in the CMIP6 file format for C13 + logical, private :: use_c14_streams = .false. ! By default read in the CMIP6 file format for C14 + + ! Private data for the control namelist: + character(len=CL), private :: stream_fldfilename_atm_c14 = ' ' + character(len=CL), private :: stream_fldfilename_atm_c13 = ' ' + integer, private :: stream_year_first_atm_c14 = 1850 + integer, private :: stream_year_last_atm_c14 = 2023 + integer, private :: stream_model_year_align_atm_c14 = 1850 + integer, private :: stream_year_first_atm_c13 = 1850 + integer, private :: stream_year_last_atm_c13 = 2023 + integer, private :: stream_model_year_align_atm_c13 = 1850 + character(len=CL), private :: stream_mapalgo_atm_c14 = 'nn' + character(len=CL), private :: stream_tintalgo_atm_c14 = 'linear' + character(len=CL), private :: stream_taxmode_atm_c14 = 'extend' + character(len=CL), private :: stream_mapalgo_atm_c13 = 'nn' + character(len=CL), private :: stream_tintalgo_atm_c13 = 'linear' + character(len=CL), private :: stream_taxmode_atm_c13 = 'extend' + character(len=*), parameter, private :: sourcefile = & - __FILE__ + __FILE__ !----------------------------------------------------------------------- contains !----------------------------------------------------------------------- - subroutine C14BombSpike( rc14_atm ) + subroutine CIsoAtmReadNML( NLFilename ) + ! + ! !DESCRIPTION: + ! Read in the namelist for atmospheric C14/C13 isotope time series + ! + ! Uses: + use shr_nl_mod , only : shr_nl_find_group_name + use spmdMod , only : masterproc, mpicom + use shr_mpi_mod, only : shr_mpi_bcast + + ! Arguments: + character(len=*), intent(in) :: NLFilename ! Namelist filename to read + + ! !LOCAL VARIABLES: + integer :: ierr ! error code + integer :: unitn ! unit for namelist file + character(len=*), parameter :: nml_name = 'carbon_isotope_streams' ! MUST agree with name in namelist and read + + namelist /carbon_isotope_streams/ stream_fldfilename_atm_c14, & + stream_fldfilename_atm_c13, stream_year_first_atm_c14, & + stream_year_last_atm_c14, stream_model_year_align_atm_c14, & + stream_year_first_atm_c13, stream_year_last_atm_c13, & + stream_model_year_align_atm_c13 + + ! Read in the namelist on the main task + if (masterproc) then + open( newunit=unitn, file=trim(NLFilename), status='old', iostat=ierr ) + write(iulog,*) 'Read in '//nml_name//' namelist' + call shr_nl_find_group_name(unitn, nml_name, status=ierr) + if (ierr == 0) then + read(unitn, nml=carbon_isotope_streams, iostat=ierr) + if (ierr /= 0) then + call endrun(msg="ERROR reading "//nml_name//"namelist", file=sourcefile, line=__LINE__) + return + end if + else + call endrun(msg="ERROR could NOT find "//nml_name//"namelist", file=sourcefile, line=__LINE__) + return + end if + close( unitn ) + end if + ! Broadcast namelist values to all tasks + call shr_mpi_bcast( stream_fldfilename_atm_c14, mpicom ) + call shr_mpi_bcast( stream_year_first_atm_c14, mpicom ) + call shr_mpi_bcast( stream_year_last_atm_c14, mpicom ) + call shr_mpi_bcast( stream_model_year_align_atm_c14, mpicom ) + call shr_mpi_bcast( stream_fldfilename_atm_c13, mpicom ) + call shr_mpi_bcast( stream_year_first_atm_c13, mpicom ) + call shr_mpi_bcast( stream_year_last_atm_c13, mpicom ) + call shr_mpi_bcast( stream_model_year_align_atm_c13, mpicom ) + + ! Do some error checking of input namelist items, set control flags, and write to the log + call CIsoCheckNMLInputs() + + call CIsoSetControl() + call CIsoLogControl() + + end subroutine CIsoAtmReadNML + + !----------------------------------------------------------------------- + subroutine CIsoSetControl() + ! Set control settings based on the namelist inputs + ! Also do some assert checks to make sure other settings are as expected + ! + if ( use_c13_timeseries )then + ! Decide if C14/C13 streams are going to be used or the old method + if ( len_trim(stream_fldfilename_atm_c13) /= 0 ) then + use_c13_streams = .true. + else + use_c13_streams = .false. + call shr_assert( len_trim(atm_c13_filename) /= 0 , & + msg="ERROR: use_c13_timeseries is true but atm_c13_filename is blank", file=sourcefile, line=__LINE__) + call shr_assert( .not. use_c13_streams , & + msg="ERROR: stream_fldfilename_atm_c13 is blank but use_c13_streams is not TRUE", file=sourcefile, line=__LINE__) + end if + else + use_c13_streams = .false. + call shr_assert( .not. use_c13_streams , & + msg="ERROR: use_c13_timeseries is false, but use_c13_streams is TRUE", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c13_filename) == 0 , & + msg="ERROR: use_c13_timeseries is false but atm_c13_filename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(stream_fldfilename_atm_c13) == 0 , & + msg="ERROR: use_c13_timeseries is false but stream_fldfilename_atm_c13 is NOT blank", file=sourcefile, line=__LINE__) + end if + if ( use_c14_bombspike )then + if ( len_trim(stream_fldfilename_atm_c14) /= 0 ) then + use_c14_streams = .true. + else + use_c14_streams = .false. + call shr_assert( len_trim(atm_c14_filename) /= 0 , & + msg="ERROR: use_c14_bombspike is true but atm_c14_filename is blank", file=sourcefile, line=__LINE__) + call shr_assert( .not. use_c14_streams , & + msg="ERROR: stream_fldfilename_atm_c14 is blank but use_c14_streams is not TRUE", & + file=sourcefile, line=__LINE__) + end if + else + use_c14_streams = .false. + call shr_assert( .not. use_c14_streams , & + msg="ERROR: use_c14_bombspike is false, but use_c14_streams is TRUE", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c14_filename) == 0, & + msg="ERROR: use_c14_bombspike is false but atm_c14_filename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(stream_fldfilename_atm_c14) == 0 , & + msg="ERROR: use_c14_bombspike is false but stream_fldfilename_atm_c14 is NOT blank", & + file=sourcefile, line=__LINE__) + end if + + end subroutine CIsoSetControl + + !----------------------------------------------------------------------- + subroutine CIsoLogControl() + ! Log namelist and control settings to output to display what behavior will be + ! + if ( use_c13_timeseries )then + if ( use_c13_streams ) then + call shr_assert( len_trim(stream_fldfilename_atm_c13) /= 0 , & + msg="use_c13_streams is TRUE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C13 atmospheric data will be read in using the streams method from file: '// & + trim(stream_fldfilename_atm_c13) + else if ( len_trim(atm_c13_filename) /= 0 ) then + write(iulog,*) 'C13 atmospheric data will be read in using the CMIP6 time series method from file: '// & + trim(atm_c13_filename) + else + call endrun(msg="use_c13_timeseries is true but use_c13_streams=FALSE and atm_c13_filename is blank", & + file=sourcefile, line=__LINE__) + end if + else + call shr_assert( len_trim(stream_fldfilename_atm_c13) == 0 , & + msg="use_c13_timeseries is FALSE but stream_fldfilename is NOT blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c13_filename) == 0 , & + msg="use_c13_timeseries is FALSE but stream_fldfilename is NOT blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C13 atmospheric data will be the global constant pre-industrial level' + end if + if ( use_c14_bombspike )then + if ( use_c14_streams ) then + write(iulog,*) 'C14 atmospheric data will be read in using the streams method from file: '// & + trim(stream_fldfilename_atm_c14) + else if ( len_trim(atm_c14_filename) /= 0 ) then + write(iulog,*) 'C14 atmospheric data will be read in using the CMIP6 time series method from file: '// & + trim(atm_c14_filename) + else + call endrun(msg="use_c14_bombspike is true but use_c14_streams=FALSE and atm_c14_filename is blank", & + file=sourcefile, line=__LINE__) + end if + else + call shr_assert( len_trim(stream_fldfilename_atm_c14) == 0 , & + msg="use_c14_bombspike is FALSE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + call shr_assert( len_trim(atm_c14_filename) == 0 , & + msg="use_c14_bombspike is FALSE but stream_fldfilename is blank", file=sourcefile, line=__LINE__) + write(iulog,*) 'C14 atmospheric data will be global constant pre-industrial level' + end if + + end subroutine CIsoLogControl + + !----------------------------------------------------------------------- + subroutine CIsoCheckNMLInputs() + ! + ! !DESCRIPTION: + ! Check that the namelist inputs are valid + ! + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + ! When carbon isotopes are off nothing should be set + if ( .not. use_c13 )then + if ( use_c13_timeseries ) then + call endrun( msg="use_c13 is false but use_c13_timeseries is TRUE " // & + "(use_c13_timeseries can only be TRUE if use_c13 is TRUE)", file=sourcefile, line=__LINE__) + return + end if + end if + if ( .not. use_c14 )then + if ( use_c14_bombspike ) then + call endrun( msg="use_c14 is false but use_c14_bombspike is TRUE " // & + "(use_c14_bombspike can only be TRUE if use_c14 is TRUE)", & + file=sourcefile, line=__LINE__) + return + end if + end if + + ! + ! Check C14 stream namelist inputs + ! + if ( use_c14_bombspike ) then + if ( len_trim(atm_c14_filename) /= 0 .and. len_trim(stream_fldfilename_atm_c14) /= 0 ) then + call endrun(msg="use_c14_bombspike TRUE but both atm_c14_filename AND stream_fldfilename_atm_c14 are set and only one should be", & + file=sourcefile, line=__LINE__) + return + end if + if ( len_trim(atm_c14_filename) == 0 .and. len_trim(stream_fldfilename_atm_c14) == 0 ) then + call endrun(msg="use_c14_bombspike TRUE but neither atm_c14_filename nor stream_fldfilename_atm_c14 are set and one or the other needs to be", & + file=sourcefile, line=__LINE__) + return + end if + else + if ( len_trim(atm_c14_filename) /= 0 .or. len_trim(stream_fldfilename_atm_c14) /= 0 ) then + call endrun(msg="use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be", & + file=sourcefile, line=__LINE__) + return + end if + end if + ! + ! Check C13 stream namelist inputs + ! + if ( use_c13_timeseries ) then + if ( len_trim(atm_c13_filename) /= 0 .and. len_trim(stream_fldfilename_atm_c13) /= 0 ) then + call endrun(msg="use_c13_timeseries TRUE but both atm_c13_filename AND stream_fldfilename_atm_c13 are set and only one should be", & + file=sourcefile, line=__LINE__) + return + end if + if ( len_trim(atm_c13_filename) == 0 .and. len_trim(stream_fldfilename_atm_c13) == 0 ) then + call endrun(msg="use_c13_timeseries TRUE but neither atm_c13_filename nor stream_fldfilename_atm_c13 are set and one or the other needs to be", & + file=sourcefile, line=__LINE__) + return + end if + else + if ( len_trim(atm_c13_filename) /= 0 .or. len_trim(stream_fldfilename_atm_c13) /= 0 ) then + call endrun(msg="use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be", & + file=sourcefile, line=__LINE__) + return + end if + end if + + end subroutine CIsoCheckNMLInputs + + !----------------------------------------------------------------------- + subroutine CIsoSetNMLInputs( stream_fldfilename_atm_c13_in, stream_fldfilename_atm_c14_in, & + use_c13_streams_in, use_c14_streams_in ) + ! + ! !DESCRIPTION: + ! Set the namelist inputs for unit testing + ! + ! Arguments: + character(len=*), intent(in), optional :: stream_fldfilename_atm_c13_in + character(len=*), intent(in), optional :: stream_fldfilename_atm_c14_in + logical, intent(in), optional :: use_c13_streams_in + logical, intent(in), optional :: use_c14_streams_in + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + if ( present(stream_fldfilename_atm_c13_in) ) then + stream_fldfilename_atm_c13 = stream_fldfilename_atm_c13_in + end if + if ( present(stream_fldfilename_atm_c14_in) ) then + stream_fldfilename_atm_c14 = stream_fldfilename_atm_c14_in + end if + if ( present(use_c13_streams_in) ) then + use_c13_streams = use_c13_streams_in + end if + if ( present(use_c14_streams_in) ) then + use_c14_streams = use_c14_streams_in + end if + + end subroutine CIsoSetNMLInputs + + + !----------------------------------------------------------------------- + subroutine C14BombSpike( bounds ) ! ! !DESCRIPTION: ! for transient simulation, read in an atmospheric timeseries file to impose bomb spike ! + use GridcellType , only : grc ! !ARGUMENTS: implicit none - real(r8), intent(out) :: rc14_atm(nsectors_c14) ! Ratio of C14 to C12 + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: yr, mon, day, tod ! year, month, day, time-of-day real(r8) :: dateyear ! Date converted to year real(r8) :: delc14o2_atm(nsectors_c14) ! C14 delta units - integer :: fp, p, nt ! Indices + real(r8) :: rc14_atm(nsectors_c14) ! C14 ratio C14 C12units + integer :: fp, p, nt, g ! Indices integer :: ind_below ! Time index below current time integer :: ntim_atm_ts ! Number of times on file real(r8) :: twt_1, twt_2 ! weighting fractions for interpolating integer :: l ! Loop index of sectors !----------------------------------------------------------------------- - ! get current date - call get_curr_date(yr, mon, day, tod) - dateyear = real(yr) + get_curr_yearfrac() - - ! find points in atm timeseries to interpolate between - ntim_atm_ts = size(atm_c14file_time) - ind_below = 0 - do nt = 1, ntim_atm_ts - if ((dateyear - time_axis_offset) >= atm_c14file_time(nt) ) then - ind_below = ind_below+1 - endif - end do + ! + ! If the bombspike timeseries file is being used, read the file in + ! + if ( use_c14_bombspike )then + + if ( use_c14_streams )then + call C14Streams( bounds ) + RETURN + end if + ! get current date + call get_curr_date(yr, mon, day, tod) + dateyear = real(yr) + get_curr_yearfrac() + + ! find points in atm timeseries to interpolate between + ntim_atm_ts = size(atm_c14file_time) + ind_below = 0 + do nt = 1, ntim_atm_ts + if ((dateyear - time_axis_offset) >= atm_c14file_time(nt) ) then + ind_below = ind_below+1 + endif + end do + + ! loop over lat bands to pass all three to photosynthesis + do l = 1,nsectors_c14 + ! interpolate between nearest two points in atm c14 timeseries + if (ind_below .eq. 0 ) then + delc14o2_atm(l) = atm_delta_c14(l,1) + elseif (ind_below .eq. ntim_atm_ts ) then + delc14o2_atm(l) = atm_delta_c14(l,ntim_atm_ts) + else + twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c14file_time(ind_below)) & + / (atm_c14file_time(ind_below+1)-atm_c14file_time(ind_below)))) + twt_1 = 1._r8 - twt_2 + delc14o2_atm(l) = atm_delta_c14(l,ind_below) * twt_1 + atm_delta_c14(l,ind_below+1) * twt_2 + endif + + ! change delta units to ratio + rc14_atm(l) = (delc14o2_atm(l) * 1.e-3_r8 + 1._r8) * c14ratio + end do + ! + ! When not using a time series file -- use the constant preindustrial value + ! + else + rc14_atm(:) = c14ratio + delc14o2_atm(:) = (rc14_atm(1)/c14ratio -1.0_r8)*1000.0_r8 + endif + ! + ! Now map to the gridcell from the sectors + ! - ! loop over lat bands to pass all three to photosynthesis - do l = 1,nsectors_c14 - ! interpolate between nearest two points in atm c14 timeseries - if (ind_below .eq. 0 ) then - delc14o2_atm(l) = atm_delta_c14(l,1) - elseif (ind_below .eq. ntim_atm_ts ) then - delc14o2_atm(l) = atm_delta_c14(l,ntim_atm_ts) + do g = bounds%begg, bounds%endg + ! determine latitute sector for radiocarbon bomb spike inputs + if ( grc%latdeg(g) >= 30._r8 ) then + l = 1 + else if ( grc%latdeg(g) >= -30._r8 ) then + l = 2 else - twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c14file_time(ind_below)) & - / (atm_c14file_time(ind_below+1)-atm_c14file_time(ind_below)))) - twt_1 = 1._r8 - twt_2 - delc14o2_atm(l) = atm_delta_c14(l,ind_below) * twt_1 + atm_delta_c14(l,ind_below+1) * twt_2 + l = 3 endif - - ! change delta units to ratio - rc14_atm(l) = (delc14o2_atm(l) * 1.e-3_r8 + 1._r8) * c14ratio + atm_delta_c14_grc(g) = delc14o2_atm(l) + rc14_atm_grc(g) = rc14_atm(l) end do - + end subroutine C14BombSpike !----------------------------------------------------------------------- - subroutine C14_init_BombSpike() + subroutine C14Streams( bounds ) + ! Description: + ! + ! Use the streams method to read in atmospheric C14 bomb spike data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + integer :: g ! Indices + + call atm_c14_stream%Advance( ) + call atm_c14_stream%Interp( bounds) + + do g = bounds%begg, bounds%endg + atm_delta_c14_grc(g) = atm_c14_stream%atm_delta_c14(g) + rc14_atm_grc(g) = (atm_delta_c14_grc(g) * 1.e-3_r8 + 1._r8) * c14ratio + end do + + end subroutine C14Streams + + !----------------------------------------------------------------------- + subroutine C14_init_BombSpike( bounds ) ! ! !DESCRIPTION: - ! read netcdf file containing a timeseries of atmospheric delta C14 values; save in module-level array + ! read netcdf file containing a timeseries of atmospheric delta C14 values; save in module-level array ! ! !USES: use ncdio_pio , only : ncd_pio_openfile, ncd_pio_closefile, file_desc_t, ncd_inqdlen, ncd_io use fileutils , only : getfil + use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + implicit none + ! Arguments: + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: - implicit none character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id integer :: dimid,varid ! input netCDF id's @@ -122,116 +475,239 @@ subroutine C14_init_BombSpike() character(len=*), parameter :: vname = 'Delta14co2_in_air' ! Variable name on file !----------------------------------------------------------------------- - call getfil(atm_c14_filename, locfn, 0) - - if ( masterproc ) then - write(iulog, *) 'C14_init_BombSpike: preparing to open file:' - write(iulog, *) trim(locfn) - endif - - call ncd_pio_openfile (ncid, trim(locfn), 0) + ! Allocate the gridcell arrays + ! TODO: This should be below within the use_c14_bombspike if block + allocate(atm_delta_c14_grc(bounds%begg:bounds%endg)) + allocate(rc14_atm_grc(bounds%begg:bounds%endg)) + atm_delta_c14_grc(:) = nan + rc14_atm_grc(:) = nan + ! + ! If the bombspike timeseries file is being used, read the file in + ! + if ( use_c14_bombspike )then + + if ( use_c14_streams )then + write(iulog,*) 'Read in atmospheric C14 data from streams' + call C14StreamsInit( bounds ) + RETURN + end if + if ( .not. use_c14_streams .and. len_trim(atm_c14_filename) == 0 )then + write(iulog,*) 'Use constant preindustrial atmospheric C14 data' + RETURN + end if + + if ( masterproc ) then + write(iulog, *) 'C14_init_BombSpike: preparing to open file:' + write(iulog, *) trim(locfn) + endif - call ncd_inqdlen(ncid,dimid,ntim,'time') - call ncd_inqdlen(ncid,dimid,nsec,'sector') - if ( nsec /= nsectors_c14 )then - call endrun(msg="ERROR: number of sectors on file not what's expected"//errMsg(sourcefile, __LINE__)) + call getfil(atm_c14_filename, locfn, 0) + + call ncd_pio_openfile (ncid, trim(locfn), 0) + + call ncd_inqdlen(ncid,dimid,ntim,'time') + call ncd_inqdlen(ncid,dimid,nsec,'sector') + if ( nsec /= nsectors_c14 )then + call endrun(msg="ERROR: number of sectors on file not what's expected"//errMsg(sourcefile, __LINE__)) + end if + + !! allocate arrays based on size of netcdf timeseries + allocate(atm_c14file_time(ntim)) + allocate(atm_delta_c14(nsectors_c14,ntim)) + atm_delta_c14(:,:) = 0.0_r8 + call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c14file_time, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) + end if + + call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c14, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) + end if + ! Check units + call check_units( ncid, vname, "Modern" ) + call ncd_pio_closefile(ncid) + + ! check to make sure that time dimension is well behaved + do t = 2, ntim + if ( atm_c14file_time(t) - atm_c14file_time(t-1) <= 0._r8 ) then + write(iulog, *) 'C14_init_BombSpike: error. time axis must be monotonically increasing' + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + end do end if - !! allocate arrays based on size of netcdf timeseries - allocate(atm_c14file_time(ntim)) - allocate(atm_delta_c14(nsectors_c14,ntim)) - atm_delta_c14(:,:) = 0.0_r8 - - call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c14file_time, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) - end if + end subroutine C14_init_BombSpike - call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c14, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) - end if - ! Check units - call check_units( ncid, vname, "Modern" ) - call ncd_pio_closefile(ncid) - - ! check to make sure that time dimension is well behaved - do t = 2, ntim - if ( atm_c14file_time(t) - atm_c14file_time(t-1) <= 0._r8 ) then - write(iulog, *) 'C14_init_BombSpike: error. time axis must be monotonically increasing' - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - end do - end subroutine C14_init_BombSpike + !----------------------------------------------------------------------- + subroutine C14StreamsInit( bounds ) + ! Description: + ! + ! Initialize the streams method to read in atmospheric C14 bomb spike data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + if ( masterproc ) then + write(iulog, *) 'C14StreamsInit: Initializing C14 streams with file:' + write(iulog, *) trim(stream_fldfilename_atm_c14) + end if + ! Streams method + call atm_c14_stream%Init( bounds, & + fldfilename=stream_fldfilename_atm_c14, & + meshfile= 'none', & + mapalgo=stream_mapalgo_atm_c14, & + tintalgo=stream_tintalgo_atm_c14, & + taxmode=stream_taxmode_atm_c14, & + year_first=stream_year_first_atm_c14, & + year_last=stream_year_last_atm_c14, & + model_year_align=stream_model_year_align_atm_c14 ) + call atm_c14_stream%Advance( ) + call atm_c14_stream%Interp( bounds ) + + end subroutine C14StreamsInit !----------------------------------------------------------------------- - subroutine C13TimeSeries( rc13_atm ) + subroutine C13TimeSeries( bounds, atm2lnd_inst ) ! ! !DESCRIPTION: ! for transient pulse simulation, impose a time-varying atm boundary condition ! + use GridcellType , only : grc + use clm_varcon , only : preind_atm_del13c + use atm2lndType, only : atm2lnd_Type ! !ARGUMENTS: implicit none - real(r8), intent(out) :: rc13_atm ! Ratio of C13 to C12 + type(bounds_type), intent(in) :: bounds + type(atm2lnd_Type), intent(in) :: atm2lnd_inst ! ! !LOCAL VARIABLES: + real(r8) :: rc13_atm ! Ratio of C13 to C12 integer :: yr, mon, day, tod ! year, month, day, time-of-day real(r8) :: dateyear ! date translated to year real(r8) :: delc13o2_atm ! Delta C13 - integer :: fp, p, nt ! Indices + integer :: fp, p, nt, g ! Indices integer :: ind_below ! Index of time in file before current time integer :: ntim_atm_ts ! Number of times on file real(r8) :: twt_1, twt_2 ! weighting fractions for interpolating !----------------------------------------------------------------------- - ! get current date - call get_curr_date(yr, mon, day, tod) - dateyear = real(yr) + get_curr_yearfrac() - - ! find points in atm timeseries to interpolate between - ntim_atm_ts = size(atm_c13file_time) - ind_below = 0 - do nt = 1, ntim_atm_ts - if ((dateyear - time_axis_offset) >= atm_c13file_time(nt) ) then - ind_below = ind_below+1 - endif - end do + ! + ! If the timeseries file is being used, read the file in + ! + if ( use_c13_timeseries )then + + if ( use_c13_streams )then + call C13Streams( bounds ) + RETURN + end if + if ( .not. use_c13_streams .and. len_trim(atm_c13_filename) == 0 )then + write(iulog,*) 'Use constant preindustrial atmospheric C13 data' + RETURN + end if + ! get current date + call get_curr_date(yr, mon, day, tod) + dateyear = real(yr) + get_curr_yearfrac() + + ! find points in atm timeseries to interpolate between + ntim_atm_ts = size(atm_c13file_time) + ind_below = 0 + do nt = 1, ntim_atm_ts + if ((dateyear - time_axis_offset) >= atm_c13file_time(nt) ) then + ind_below = ind_below+1 + endif + end do + + ! interpolate between nearest two points in atm c13 timeseries + ! cdknotes. for now and for simplicity, just use the northern hemisphere values (sector 1) + if (ind_below .eq. 0 ) then + delc13o2_atm = atm_delta_c13(1) + elseif (ind_below .eq. ntim_atm_ts ) then + delc13o2_atm = atm_delta_c13(ntim_atm_ts) + else + twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c13file_time(ind_below)) & + / (atm_c13file_time(ind_below+1)-atm_c13file_time(ind_below)))) + twt_1 = 1._r8 - twt_2 + delc13o2_atm = atm_delta_c13(ind_below) * twt_1 + atm_delta_c13(ind_below+1) * twt_2 + endif - ! interpolate between nearest two points in atm c13 timeseries - ! cdknotes. for now and for simplicity, just use the northern hemisphere values (sector 1) - if (ind_below .eq. 0 ) then - delc13o2_atm = atm_delta_c13(1) - elseif (ind_below .eq. ntim_atm_ts ) then - delc13o2_atm = atm_delta_c13(ntim_atm_ts) + ! + ! When not using a time series file -- use the constant value + ! else - twt_2 = min(1._r8, max(0._r8,((dateyear - time_axis_offset)-atm_c13file_time(ind_below)) & - / (atm_c13file_time(ind_below+1)-atm_c13file_time(ind_below)))) - twt_1 = 1._r8 - twt_2 - delc13o2_atm = atm_delta_c13(ind_below) * twt_1 + atm_delta_c13(ind_below+1) * twt_2 - endif + rc13_atm = c13ratio + delc13o2_atm = (rc13_atm/SHR_CONST_PDB - 1.0_r8)*1000.0_r8 + end if ! change delta units to ratio, put on patch loop rc13_atm = (delc13o2_atm * 1.e-3_r8 + 1._r8) * SHR_CONST_PDB + ! + ! Copy to the gridcell arrays + ! + do g = bounds%begg, bounds%endg + + associate( & + forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) + forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) + ) + rc13_atm_grc(g) = rc13_atm + atm_delta_c13_grc(g) = delc13o2_atm + + ! Currently when C13 is fixed, it's dependent on CO2 levels and changes with pressure + ! NOTE: This duplicates code in lnd_import_export.F90 + if ( .not. use_c13_timeseries )then + rc13_atm_grc(g) = forc_pc13o2(g)/(forc_pco2(g) - forc_pc13o2(g)) + atm_delta_c13_grc(g) = (rc13_atm_grc(g) / SHR_CONST_PDB - 1.0_r8)*1000.0_r8 + end if + end associate + end do + end subroutine C13TimeSeries !----------------------------------------------------------------------- - subroutine C13_init_TimeSeries() + subroutine C13Streams( bounds ) + ! Description: + ! + ! Use the streams method to read in atmospheric C13 data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + integer :: g ! Indices + + call atm_c13_stream%Interp( bounds) + + do g = bounds%begg, bounds%endg + atm_delta_c13_grc(g) = atm_c13_stream%atm_delta_c13(g) + rc13_atm_grc(g) = (atm_delta_c13_grc(g) * 1.e-3_r8 + 1._r8) * SHR_CONST_PDB + end do + + end subroutine C13Streams + + !----------------------------------------------------------------------- + subroutine C13_init_TimeSeries( bounds ) ! ! !DESCRIPTION: - ! read netcdf file containing a timeseries of atmospheric delta C13 values; save in module-level array + ! read netcdf file containing a timeseries of atmospheric delta C13 values; save in module-level array ! ! !USES: use ncdio_pio , only : ncd_pio_openfile, ncd_pio_closefile, file_desc_t, ncd_inqdlen, ncd_io use fileutils , only : getfil + use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + implicit none ! + ! Arguments: + type(bounds_type), intent(in) :: bounds ! !LOCAL VARIABLES: - implicit none character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id integer :: dimid,varid ! input netCDF id's @@ -241,47 +717,96 @@ subroutine C13_init_TimeSeries() character(len=*), parameter :: vname = 'delta13co2_in_air' ! Variable name on file !----------------------------------------------------------------------- - call getfil(atm_c13_filename, locfn, 0) + ! TODO: This should be below within the use_c13_timeseries if block + ! Allocate the gridcell arrays + allocate(atm_delta_c13_grc(bounds%begg:bounds%endg) ) + allocate(rc13_atm_grc(bounds%begg:bounds%endg) ) + atm_delta_c13_grc(:) = nan + rc13_atm_grc(:) = nan + ! + ! If the timeseries file is being used, read the file in + ! + if ( use_c13_timeseries )then - if ( masterproc ) then - write(iulog, *) 'C13_init_TimeSeries: preparing to open file:' - write(iulog, *) trim(locfn) - endif + if ( use_c13_streams )then + call C13StreamsInit( bounds ) + RETURN + end if - call ncd_pio_openfile (ncid, trim(locfn), 0) + call getfil(atm_c13_filename, locfn, 0) - call ncd_inqdlen(ncid,dimid,ntim,'time') + if ( masterproc ) then + write(iulog, *) 'C13_init_TimeSeries: preparing to open file:' + write(iulog, *) trim(locfn) + endif - !! allocate arrays based on size of netcdf timeseries - allocate(atm_c13file_time(ntim)) - allocate(atm_delta_c13(ntim)) + call ncd_pio_openfile (ncid, trim(locfn), 0) - call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c13file_time, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) - end if + call ncd_inqdlen(ncid,dimid,ntim,'time') - call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c13, & - readvar=readvar) - if ( .not. readvar ) then - call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) - end if + !! allocate arrays based on size of netcdf timeseries + allocate(atm_c13file_time(ntim)) + allocate(atm_delta_c13(ntim)) - ! Check units - call check_units( ncid, vname, "VPDB" ) - call ncd_pio_closefile(ncid) - ! check to make sure that time dimension is well behaved - do t = 2, ntim - if ( atm_c13file_time(t) - atm_c13file_time(t-1) <= 0._r8 ) then - write(iulog, *) 'C13_init_TimeSeries: error. time axis must be monotonically increasing' - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - end do + call ncd_io(ncid=ncid, varname='time', flag='read', data=atm_c13file_time, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: time not on file"//errMsg(sourcefile, __LINE__)) + end if + + call ncd_io(ncid=ncid, varname=vname, flag='read', data=atm_delta_c13, & + readvar=readvar) + if ( .not. readvar ) then + call endrun(msg="ERROR: '//vname//' not on file"//errMsg(sourcefile, __LINE__)) + end if + + ! Check units + call check_units( ncid, vname, "VPDB" ) + call ncd_pio_closefile(ncid) + + ! check to make sure that time dimension is well behaved + do t = 2, ntim + if ( atm_c13file_time(t) - atm_c13file_time(t-1) <= 0._r8 ) then + write(iulog, *) 'C13_init_TimeSeries: error. time axis must be monotonically increasing' + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + end do + end if end subroutine C13_init_TimeSeries + !----------------------------------------------------------------------- + subroutine C13StreamsInit( bounds ) + ! Description: + ! + ! Initialize the streams method to read in atmospheric C13 data + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + if ( masterproc ) then + write(iulog, *) 'C13StreamsInit: Initializing C13 streams with file:' + write(iulog, *) trim(stream_fldfilename_atm_c13) + end if + ! Streams method + call atm_c13_stream%Init( bounds, & + fldfilename=stream_fldfilename_atm_c13, & + meshfile= 'none', & + mapalgo=stream_mapalgo_atm_c13, & + tintalgo=stream_tintalgo_atm_c13, & + taxmode=stream_taxmode_atm_c13, & + year_first=stream_year_first_atm_c13, & + year_last=stream_year_last_atm_c13, & + model_year_align=stream_model_year_align_atm_c13 ) + call atm_c13_stream%Advance( ) + call atm_c13_stream%Interp( bounds ) + + end subroutine C13StreamsInit + !----------------------------------------------------------------------- subroutine check_units( ncid, vname, relativeto ) ! diff --git a/src/biogeochem/CNCIsoFluxMod.F90 b/src/biogeochem/CNCIsoFluxMod.F90 index fbe4cd927f..561e9a58ca 100644 --- a/src/biogeochem/CNCIsoFluxMod.F90 +++ b/src/biogeochem/CNCIsoFluxMod.F90 @@ -1357,7 +1357,7 @@ subroutine CNCIsoLitterToColumn (num_soilp, filter_soilp, & ! ! !USES: !DML - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use clm_varctl , only : use_grainproduct !DML @@ -1404,7 +1404,7 @@ subroutine CNCIsoLitterToColumn (num_soilp, filter_soilp, & end do !DML - if (ivt(p) >= npcropmin) then ! add livestemc to litter + if (is_prognostic_crop(ivt(p))) then ! add livestemc to litter ! stem litter carbon fluxes do i = i_litr_min, i_litr_max phenology_c_to_litr_c(c,j,i) = & diff --git a/src/biogeochem/CNCStateUpdate1Mod.F90 b/src/biogeochem/CNCStateUpdate1Mod.F90 index 70f0b86a53..4f5876bc60 100644 --- a/src/biogeochem/CNCStateUpdate1Mod.F90 +++ b/src/biogeochem/CNCStateUpdate1Mod.F90 @@ -10,7 +10,7 @@ module CNCStateUpdate1Mod use clm_time_manager , only : get_step_size_real use clm_varpar , only : i_litr_min, i_litr_max, i_cwd use clm_varpar , only : i_met_lit, i_str_lit, i_phys_som, i_chem_som - use pftconMod , only : npcropmin, nc3crop, pftcon + use pftconMod , only : is_prognostic_crop, nc3crop, pftcon use abortutils , only : endrun use decompMod , only : bounds_type use CNVegCarbonStateType , only : cnveg_carbonstate_type @@ -290,7 +290,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%deadcrootc_patch(p) = cs_veg%deadcrootc_patch(p) + cf_veg%deadcrootc_xfer_to_deadcrootc_patch(p)*dt cs_veg%deadcrootc_xfer_patch(p) = cs_veg%deadcrootc_xfer_patch(p) - cf_veg%deadcrootc_xfer_to_deadcrootc_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops ! lines here for consistency; the transfer terms are zero cs_veg%livestemc_patch(p) = cs_veg%livestemc_patch(p) + cf_veg%livestemc_xfer_to_livestemc_patch(p)*dt cs_veg%livestemc_xfer_patch(p) = cs_veg%livestemc_xfer_patch(p) - cf_veg%livestemc_xfer_to_livestemc_patch(p)*dt @@ -313,7 +313,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%livecrootc_patch(p) = cs_veg%livecrootc_patch(p) - cf_veg%livecrootc_to_deadcrootc_patch(p)*dt cs_veg%deadcrootc_patch(p) = cs_veg%deadcrootc_patch(p) + cf_veg%livecrootc_to_deadcrootc_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%livestemc_patch(p) = cs_veg%livestemc_patch(p) - cf_veg%livestemc_to_litter_patch(p)*dt cs_veg%livestemc_patch(p) = cs_veg%livestemc_patch(p) - & (cf_veg%livestemc_to_biofuelc_patch(p) + cf_veg%livestemc_to_removedresiduec_patch(p))*dt @@ -337,7 +337,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & ! This part below MUST match exactly the code for the non-matrix part ! above! - if (ivt(p) >= npcropmin) then + if (is_prognostic_crop(ivt(p))) then cs_veg%cropseedc_deficit_patch(p) = cs_veg%cropseedc_deficit_patch(p) & - cf_veg%crop_seedc_to_leaf_patch(p) * dt do k = repr_grain_min, repr_grain_max @@ -359,7 +359,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%livestem_curmr_patch(p)*dt cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%livecroot_curmr_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%livestem_curmr_patch(p)*dt do k = 1, nrepr cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%reproductive_curmr_patch(p,k)*dt @@ -432,7 +432,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & ! NOTE: The equivalent changes for matrix code are in CNPhenology EBK (11/26/2019) end if !not use_matrixcn end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops if (carbon_resp_opt == 1) then cf_veg%cpool_to_livestemc_patch(p) = cf_veg%cpool_to_livestemc_patch(p) - cf_veg%cpool_to_livestemc_resp_patch(p) cf_veg%cpool_to_livestemc_storage_patch(p) = cf_veg%cpool_to_livestemc_storage_patch(p) - & @@ -468,7 +468,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_livecroot_gr_patch(p)*dt cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_deadcroot_gr_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_livestem_gr_patch(p)*dt do k = 1, nrepr cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_reproductive_gr_patch(p,k)*dt @@ -484,7 +484,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%gresp_xfer_patch(p) = cs_veg%gresp_xfer_patch(p) - cf_veg%transfer_livecroot_gr_patch(p)*dt cs_veg%gresp_xfer_patch(p) = cs_veg%gresp_xfer_patch(p) - cf_veg%transfer_deadcroot_gr_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%gresp_xfer_patch(p) = cs_veg%gresp_xfer_patch(p) - cf_veg%transfer_livestem_gr_patch(p)*dt do k = 1, nrepr cs_veg%gresp_xfer_patch(p) = cs_veg%gresp_xfer_patch(p) - cf_veg%transfer_reproductive_gr_patch(p,k)*dt @@ -501,7 +501,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_livecroot_storage_gr_patch(p)*dt cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_deadcroot_storage_gr_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%cpool_patch(p) = cs_veg%cpool_patch(p) - cf_veg%cpool_livestem_storage_gr_patch(p)*dt do k = 1, nrepr @@ -539,7 +539,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & ! NOTE: The equivalent changes for matrix code are in CNPhenology EBK (11/26/2019) end if !not use_matrixcn end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops ! lines here for consistency; the transfer terms are zero if(.not. use_matrixcn)then ! lines here for consistency; the transfer terms are zero @@ -556,7 +556,7 @@ subroutine CStateUpdate1( num_soilc, filter_soilc, num_soilp, filter_soilp, & end if !not use_matrixcn end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cs_veg%xsmrpool_patch(p) = cs_veg%xsmrpool_patch(p) - cf_veg%livestem_xsmr_patch(p)*dt do k = 1, nrepr cs_veg%xsmrpool_patch(p) = cs_veg%xsmrpool_patch(p) - cf_veg%reproductive_xsmr_patch(p,k)*dt diff --git a/src/biogeochem/CNDriverMod.F90 b/src/biogeochem/CNDriverMod.F90 index 68c438ed0e..e9aa3489d0 100644 --- a/src/biogeochem/CNDriverMod.F90 +++ b/src/biogeochem/CNDriverMod.F90 @@ -44,6 +44,7 @@ module CNDriverMod use SoilWaterRetentionCurveMod , only : soil_water_retention_curve_type use CLMFatesInterfaceMod , only : hlm_fates_interface_type use CropReprPoolsMod , only : nrepr + use SoilHydrologyType , only: soilhydrology_type ! ! !PUBLIC TYPES: implicit none @@ -60,7 +61,7 @@ module CNDriverMod contains !----------------------------------------------------------------------- - subroutine CNDriverInit(bounds, NLFilename, cnfire_method) + subroutine CNDriverInit(bounds, NLFilename) ! ! !DESCRIPTION: ! Initialzation of the CN Ecosystem dynamics. @@ -68,18 +69,15 @@ subroutine CNDriverInit(bounds, NLFilename, cnfire_method) ! !USES: use CNSharedParamsMod , only : use_fun use CNPhenologyMod , only : CNPhenologyInit - use FireMethodType , only : fire_method_type use SoilBiogeochemCompetitionMod, only : SoilBiogeochemCompetitionInit ! ! !ARGUMENTS: type(bounds_type) , intent(in) :: bounds character(len=*) , intent(in) :: NLFilename ! Namelist filename - class(fire_method_type) , intent(inout) :: cnfire_method !----------------------------------------------------------------------- call SoilBiogeochemCompetitionInit(bounds) if(use_cn)then call CNPhenologyInit(bounds) - call cnfire_method%FireInit(bounds, NLFilename) end if end subroutine CNDriverInit @@ -908,9 +906,6 @@ subroutine CNDriverNoLeaching(bounds, if (use_c14) call c14_products_inst%ComputeSummaryVars(bounds) call n_products_inst%ComputeSummaryVars(bounds) - if(use_fates_bgc)then - soilbiogeochem_carbonflux_inst%fates_product_loss_grc(bounds%begg:bounds%endg)=c_products_inst%product_loss_grc(bounds%begg:bounds%endg) - endif call t_stopf('CNWoodProducts') @@ -1019,7 +1014,7 @@ subroutine CNDriverLeaching(bounds, & c13_cnveg_carbonstate_inst,c14_cnveg_carbonstate_inst, & c13_cnveg_carbonflux_inst,c14_cnveg_carbonflux_inst, & c13_soilbiogeochem_carbonstate_inst,c14_soilbiogeochem_carbonstate_inst,& - c13_soilbiogeochem_carbonflux_inst,c14_soilbiogeochem_carbonflux_inst) + c13_soilbiogeochem_carbonflux_inst,c14_soilbiogeochem_carbonflux_inst, soilhydrology_inst) ! ! !DESCRIPTION: ! Update the nitrogen leaching rate as a function of soluble mineral N and total soil water outflow. @@ -1034,6 +1029,8 @@ subroutine CNDriverLeaching(bounds, & use clm_time_manager , only: is_first_step_of_this_run_segment,is_beg_curr_year,is_end_curr_year,get_curr_date use CNSharedParamsMod , only: use_matrixcn use SoilBiogeochemDecompCascadeConType, only: use_soil_matrixcn + use SoilNitrogenMovementMod , only: SoilNitrogenMovement + use clm_varctl, only : use_nvmovement ! ! !ARGUMENTS: type(bounds_type) , intent(in) :: bounds @@ -1051,6 +1048,7 @@ subroutine CNDriverLeaching(bounds, & type(cnveg_carbonflux_type) , intent(inout) :: cnveg_carbonflux_inst type(cnveg_carbonstate_type) , intent(inout) :: cnveg_carbonstate_inst type(soilstate_type) , intent(inout) :: soilstate_inst + type(soilhydrology_type) , intent(in) :: soilhydrology_inst type(soilbiogeochem_state_type) , intent(inout) :: soilbiogeochem_state_inst type(soilbiogeochem_carbonflux_type) , intent(inout) :: soilbiogeochem_carbonflux_inst type(soilbiogeochem_carbonstate_type) , intent(inout) :: soilbiogeochem_carbonstate_inst @@ -1068,8 +1066,16 @@ subroutine CNDriverLeaching(bounds, & type(soilbiogeochem_carbonflux_type) , intent(inout) :: c14_soilbiogeochem_carbonflux_inst integer p,fp,yr,mon,day,sec !----------------------------------------------------------------------- - - ! Mineral nitrogen dynamics (deposition, fixation, leaching) + + ! soil nitrate fast aqueous movement, leaching will be evaluted here + if (use_nitrif_denitrif .and. use_nvmovement) then + call t_startf('SoilNitrogenMovementMod') + call SoilNitrogenMovement(bounds, num_bgc_soilc, filter_bgc_soilc, waterstatebulk_inst, & + soilstate_inst, soilhydrology_inst, soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst) + call t_stopf('SoilNitrogenMovementMod') + end if + + ! Mineral nitrogen dynamics: deposition, fixation. If use_nvmoment false, also leaching. call t_startf('SoilBiogeochemNLeaching') call SoilBiogeochemNLeaching(bounds, num_bgc_soilc, filter_bgc_soilc, & diff --git a/src/biogeochem/CNFUNMod.F90 b/src/biogeochem/CNFUNMod.F90 index cdbe7ba71b..b8214eac22 100644 --- a/src/biogeochem/CNFUNMod.F90 +++ b/src/biogeochem/CNFUNMod.F90 @@ -23,7 +23,7 @@ module CNFUNMod use clm_varctl , only : iulog use PatchType , only : patch use ColumnType , only : col - use pftconMod , only : pftcon, npcropmin + use pftconMod , only : pftcon use decompMod , only : bounds_type use clm_varctl , only : use_nitrif_denitrif,use_flexiblecn use CNSharedParamsMod , only : use_matrixcn @@ -284,7 +284,7 @@ subroutine CNFUN(bounds,num_soilc, filter_soilc,num_soilp& use clm_varctl , only : use_nitrif_denitrif use PatchType , only : patch use subgridAveMod , only : p2c - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use CNVegMatrixMod , only : matrix_update_phn ! ! !ARGUMENTS: @@ -1271,7 +1271,7 @@ subroutine CNFUN(bounds,num_soilc, filter_soilc,num_soilp& ! Calculate appropriate degree of retranslocation !------------------------------------------------------------------------------- - if(leafc(p).gt.0.0_r8.and.litterfall_n_step(p,istp)* fixerfrac>0.0_r8.and.ivt(p) 0.0_r8 .and. (litterfall_n_step(p,istp) * fixerfrac) > 0.0_r8 .and. (.not. is_prognostic_crop(ivt(p)))) then call fun_retranslocation(p,dt,npp_to_spend,& litterfall_c_step(p,istp)* fixerfrac,& litterfall_n_step(p,istp)* fixerfrac,& diff --git a/src/biogeochem/CNFireBaseMod.F90 b/src/biogeochem/CNFireBaseMod.F90 index 2f9e99ea44..42a054b44c 100644 --- a/src/biogeochem/CNFireBaseMod.F90 +++ b/src/biogeochem/CNFireBaseMod.F90 @@ -85,13 +85,17 @@ module CNFireBaseMod private ! !PRIVATE MEMBER DATA: ! !PUBLIC MEMBER DATA (used by extensions of the base class): - real(r8), public, pointer :: btran2_patch (:) ! patch root zone soil wetness factor (0 to 1) + real(r8), public, pointer :: btran2_patch (:) => NULL() ! patch root zone soil wetness factor (0 to 1) contains ! ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: CNFireInit ! Initialization of Fire procedure, public :: FireInit => CNFireInit ! Initialization of Fire - procedure, public :: FireReadNML ! Read in namelist for CNFire + procedure, public :: CNFireCleanBase ! Deallocate fire data + procedure, public :: FireClean => CNFireCleanBase ! Deallocate fire data + procedure, public :: CNFireReadNML ! Read in namelist for CNFire + procedure, public :: FireReadNML => CNFireReadNML ! Read in namelist for CNFire procedure, public :: CNFireReadParams ! Read in constant parameters from the paramsfile procedure, public :: CNFireFluxes ! Calculate fire fluxes procedure, public :: CNFire_calc_fire_root_wetness_Li2014 ! Calculate CN-fire specific root wetness: original version @@ -129,17 +133,16 @@ end function need_lightning_and_popdens_interface contains !----------------------------------------------------------------------- - subroutine CNFireInit( this, bounds, NLFilename ) + subroutine CNFireInit( this, bounds ) ! ! !DESCRIPTION: ! Initialize CN Fire module ! !ARGUMENTS: class(cnfire_base_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- ! Call the base-class Initialization method - call this%BaseFireInit( bounds, NLFilename ) + call this%BaseFireInit( bounds ) ! Allocate memory call this%InitAllocate( bounds ) @@ -185,6 +188,24 @@ subroutine InitHistory( this, bounds ) ptr_patch=this%btran2_patch, l2g_scale_type='veg') end subroutine InitHistory + !---------------------------------------------------------------------- + + subroutine CNFireCleanBase( this ) + ! + ! Deallocate data + ! + ! !ARGUMENTS: + class(cnfire_base_type) :: this + !----------------------------------------------------------------------- + ! Call the base class clean method + !call this%BaseFireClean() + + if ( associated(this%btran2_patch) )then + deallocate(this%btran2_patch) + end if + this%btran2_patch => NULL() + end subroutine CNFireCleanBase + !---------------------------------------------------------------------- subroutine CNFire_calc_fire_root_wetness_Li2014( this, bounds, & num_exposedvegp, filter_exposedvegp, num_noexposedvegp, filter_noexposedvegp, & @@ -321,7 +342,7 @@ end subroutine CNFire_calc_fire_root_wetness_Li2021 !---------------------------------------------------------------------- !---------------------------------------------------------------------- - subroutine FireReadNML( this, NLFilename ) + subroutine CNFireReadNML( this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read the namelist for CNFire @@ -331,17 +352,17 @@ subroutine FireReadNML( this, NLFilename ) use shr_nl_mod , only : shr_nl_find_group_name use spmdMod , only : masterproc, mpicom use shr_mpi_mod , only : shr_mpi_bcast - use clm_varctl , only : iulog ! ! !ARGUMENTS: class(cnfire_base_type) :: this + type(bounds_type), intent(in):: bounds !bounds character(len=*), intent(in) :: NLFilename ! Namelist filename ! ! !LOCAL VARIABLES: integer :: ierr ! error code integer :: unitn ! unit for namelist file - character(len=*), parameter :: subname = 'FireReadNML' + character(len=*), parameter :: subname = 'CNFireReadNML' character(len=*), parameter :: nmlname = 'lifire_inparm' !----------------------------------------------------------------------- real(r8) :: cli_scale, boreal_peatfire_c, pot_hmn_ign_counts_alpha @@ -361,6 +382,9 @@ subroutine FireReadNML( this, NLFilename ) borpeat_fire_soilmoist_denom, nonborpeat_fire_precip_denom if ( this%need_lightning_and_popdens() ) then + ! Read the base namelist + call this%BaseFireReadNML( bounds, NLFilename ) + cli_scale = cnfire_const%cli_scale boreal_peatfire_c = cnfire_const%boreal_peatfire_c non_boreal_peatfire_c = cnfire_const%non_boreal_peatfire_c @@ -392,9 +416,11 @@ subroutine FireReadNML( this, NLFilename ) read(unitn, nml=lifire_inparm, iostat=ierr) if (ierr /= 0) then call endrun(msg="ERROR reading "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if else call endrun(msg="ERROR could NOT find "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if call relavu( unitn ) end if @@ -447,7 +473,7 @@ subroutine FireReadNML( this, NLFilename ) end if end if - end subroutine FireReadNML + end subroutine CNFireReadNML !----------------------------------------------------------------------- subroutine CNFireFluxes (this, bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & diff --git a/src/biogeochem/CNFireEmissionsMod.F90 b/src/biogeochem/CNFireEmissionsMod.F90 index 5a15e138d5..1ee8facfd4 100644 --- a/src/biogeochem/CNFireEmissionsMod.F90 +++ b/src/biogeochem/CNFireEmissionsMod.F90 @@ -347,7 +347,7 @@ function vert_dist_top( veg_type ) result(ztop) use pftconMod , only : nbrdlf_evr_shrub, nbrdlf_dcd_brl_shrub use pftconMod , only : nc3_arctic_grass, nc3_nonarctic_grass use pftconMod , only : nc3crop, nc3irrig - use pftconMod , only : npcropmin, npcropmax + use pftconMod , only : is_prognostic_crop implicit none integer, intent(in) :: veg_type @@ -376,7 +376,7 @@ function vert_dist_top( veg_type ) result(ztop) else if ( veg_type == nc3crop .or. veg_type <= nc3irrig ) then ztop = 1.e3_r8 ! m ! Prognostic crops - else if ( veg_type >= npcropmin .and. veg_type <= npcropmax ) then + else if (is_prognostic_crop(veg_type)) then ztop = 1.e3_r8 ! m else call endrun('ERROR:: undefined veg_type' ) diff --git a/src/biogeochem/CNFireFactoryMod.F90 b/src/biogeochem/CNFireFactoryMod.F90 index 44407da927..9d1962d4ff 100644 --- a/src/biogeochem/CNFireFactoryMod.F90 +++ b/src/biogeochem/CNFireFactoryMod.F90 @@ -9,17 +9,20 @@ module CNFireFactoryMod use abortutils , only : endrun use shr_log_mod , only : errMsg => shr_log_errMsg use clm_varctl , only : iulog + use shr_kind_mod , only : CS => SHR_KIND_CS implicit none save private ! ! !PUBLIC ROUTINES: - public :: CNFireReadNML ! read the fire namelist + public :: CNFireReadNML ! read the fire factory namelist to get the CN fire_method to use public :: create_cnfire_method ! create an object of class fire_method_type + ! For Unit Testing: + public :: CNFireSetFireMethod ! Set the fire_method ! !PRIVATE DATA MEMBERS: - character(len=80), private :: fire_method = "li2014qianfrc" + character(len=CS), private :: fire_method = "UNSET" character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -63,9 +66,11 @@ subroutine CNFireReadNML( NLFilename ) read(unitn, nml=cnfire_inparm, iostat=ierr) if (ierr /= 0) then call endrun(msg="ERROR reading "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if else call endrun(msg="ERROR finding "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if call relavu( unitn ) end if @@ -82,7 +87,7 @@ end subroutine CNFireReadNML !----------------------------------------------------------------------- !----------------------------------------------------------------------- - subroutine create_cnfire_method( NLFilename, cnfire_method ) + subroutine create_cnfire_method( cnfire_method ) ! ! !DESCRIPTION: ! Create and return an object of fire_method_type. The particular type @@ -98,11 +103,9 @@ subroutine create_cnfire_method( NLFilename, cnfire_method ) use decompMod , only : bounds_type ! ! !ARGUMENTS: - character(len=*), intent(in) :: NLFilename ! Namelist filename class(fire_method_type), allocatable, intent(inout) :: cnfire_method ! ! !LOCAL VARIABLES: - character(len=*), parameter :: subname = 'create_cnfire_method' !----------------------------------------------------------------------- select case (trim(fire_method)) @@ -119,13 +122,29 @@ subroutine create_cnfire_method( NLFilename, cnfire_method ) allocate(cnfire_li2024_type :: cnfire_method) case default - write(iulog,*) subname//' ERROR: unknown method: ', fire_method - call endrun(msg=errMsg(sourcefile, __LINE__)) + write(iulog,*) 'Unrecognized fire_method ' // errMsg(sourcefile, __LINE__) + call endrun( msg='Unknown option for namelist item fire_method: ' // trim(fire_method) ) + ! For unit-testing, make sure a valid cnfire_method is set and return, otherwise it fails with a seg-fault + allocate(cnfire_nofire_type :: cnfire_method) + return end select - call cnfire_method%FireReadNML( NLFilename ) end subroutine create_cnfire_method !----------------------------------------------------------------------- + subroutine CNFireSetFireMethod( fire_method_in ) + ! + ! !DESCRIPTION: + ! Set the fire_method (to be used in unit testing) + ! + ! !USES: + ! !ARGUMENTS: + character(len=*), intent(IN) :: fire_method_in + + fire_method = trim(fire_method_in) + + end subroutine CNFireSetFireMethod + !----------------------------------------------------------------------- + end module CNFireFactoryMod diff --git a/src/biogeochem/CNFireNoFireMod.F90 b/src/biogeochem/CNFireNoFireMod.F90 index e0605585e9..da6f28cd0d 100644 --- a/src/biogeochem/CNFireNoFireMod.F90 +++ b/src/biogeochem/CNFireNoFireMod.F90 @@ -8,6 +8,8 @@ module CNFireNoFireMod ! ! !USES: use shr_kind_mod , only : r8 => shr_kind_r8 + use abortutils , only : endrun + use clm_varctl , only : iulog use decompMod , only : bounds_type use atm2lndType , only : atm2lnd_type use CNVegStateType , only : cnveg_state_type @@ -36,10 +38,15 @@ module CNFireNoFireMod contains ! ! !PUBLIC MEMBER FUNCTIONS: - procedure, public :: need_lightning_and_popdens - procedure, public :: CNFireArea ! Calculate fire area + procedure, public :: need_lightning_and_popdens ! If need lightning and/or population density (always .false. here) + procedure, public :: NoFireInit ! Initiialization + procedure, public :: FireInit => NoFireInit ! Initiialization + procedure, public :: CNFireArea ! Calculate fire area end type cnfire_nofire_type + character(len=*), parameter, private :: sourcefile = & + __FILE__ + contains !----------------------------------------------------------------------- @@ -56,6 +63,28 @@ function need_lightning_and_popdens(this) need_lightning_and_popdens = .false. end function need_lightning_and_popdens + !----------------------------------------------------------------------- + subroutine NoFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize No Fire module + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod , only : errMsg => shr_log_errMsg + ! !ARGUMENTS: + class(cnfire_nofire_type) :: this + type(bounds_type), intent(in) :: bounds + + if ( shr_fire_emis_mechcomps_n > 0) then + write(iulog,*) "Fire emissions can NOT be active for fire_method=nofire" // & + errMsg(sourcefile, __LINE__) + call endrun(msg="Having fire emissions on requires fire_method to be something besides nofire" ) + return + end if + call this%CNFireInit( bounds ) + + end subroutine NoFireInit + !----------------------------------------------------------------------- + !----------------------------------------------------------------------- subroutine CNFireArea (this, bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & num_exposedvegp, filter_exposedvegp, num_noexposedvegp, filter_noexposedvegp, & diff --git a/src/biogeochem/CNGRespMod.F90 b/src/biogeochem/CNGRespMod.F90 index de8b145615..29d25487e4 100644 --- a/src/biogeochem/CNGRespMod.F90 +++ b/src/biogeochem/CNGRespMod.F90 @@ -7,7 +7,7 @@ module CNGRespMod ! ! !USES: use shr_kind_mod , only : r8 => shr_kind_r8 - use pftconMod , only : npcropmin, pftcon + use pftconMod , only : is_prognostic_crop, pftcon use CNVegcarbonfluxType , only : cnveg_carbonflux_type use PatchType , only : patch use CanopyStateType , only : canopystate_type @@ -145,7 +145,7 @@ subroutine CNGResp(num_soilp, filter_soilp, cnveg_carbonflux_inst, canopystate_i respfact_livecroot_storage = 1.0_r8 respfact_livestem_storage = 1.0_r8 - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cpool_livestem_gr(p) = cpool_to_livestemc(p) * grperc(ivt(p)) * respfact_livestem cpool_livestem_storage_gr(p) = cpool_to_livestemc_storage(p) * grperc(ivt(p)) * grpnow(ivt(p)) * & diff --git a/src/biogeochem/CNGapMortalityMod.F90 b/src/biogeochem/CNGapMortalityMod.F90 index 24f0f6b145..6b9859265b 100644 --- a/src/biogeochem/CNGapMortalityMod.F90 +++ b/src/biogeochem/CNGapMortalityMod.F90 @@ -121,7 +121,7 @@ subroutine CNGapMortality (bounds, num_soilp, filter_soilp, & use clm_varpar , only: nlevdecomp_full use clm_varcon , only: secspday use clm_varctl , only: use_cndv, spinup_state - use pftconMod , only: npcropmin + use pftconMod , only: is_prognostic_crop ! ! !ARGUMENTS: type(bounds_type) , intent(in) :: bounds @@ -348,7 +348,7 @@ subroutine CNGapMortality (bounds, num_soilp, filter_soilp, & end if !use_matrixcn end if - if (ivt(p) < npcropmin) then + if (.not. is_prognostic_crop(ivt(p))) then if(.not. use_matrixcn)then cnveg_nitrogenflux_inst%m_retransn_to_litter_patch(p) = cnveg_nitrogenstate_inst%retransn_patch(p) * m else diff --git a/src/biogeochem/CNMRespMod.F90 b/src/biogeochem/CNMRespMod.F90 index 33eb0b0e9a..ae0040008b 100644 --- a/src/biogeochem/CNMRespMod.F90 +++ b/src/biogeochem/CNMRespMod.F90 @@ -13,7 +13,7 @@ module CNMRespMod use decompMod , only : bounds_type use abortutils , only : endrun use shr_log_mod , only : errMsg => shr_log_errMsg - use pftconMod , only : npcropmin, pftcon + use pftconMod , only : is_prognostic_crop, pftcon use SoilStateType , only : soilstate_type use CanopyStateType , only : canopystate_type use TemperatureType , only : temperature_type @@ -265,7 +265,7 @@ subroutine CNMResp(bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & if (woody(ivt(p)) == 1) then livestem_mr(p) = livestemn(p)*br*tc livecroot_mr(p) = livecrootn(p)*br_root*tc - else if (ivt(p) >= npcropmin) then + else if (is_prognostic_crop(ivt(p))) then livestem_mr(p) = livestemn(p)*br*tc do k = 1, nrepr reproductive_mr(p,k) = reproductiven(p,k)*br*tc diff --git a/src/biogeochem/CNNStateUpdate1Mod.F90 b/src/biogeochem/CNNStateUpdate1Mod.F90 index 742afa77dd..7bd346c82a 100644 --- a/src/biogeochem/CNNStateUpdate1Mod.F90 +++ b/src/biogeochem/CNNStateUpdate1Mod.F90 @@ -17,7 +17,7 @@ module CNNStateUpdate1Mod use SoilBiogeochemDecompCascadeConType, only : decomp_method, mimics_decomp, use_soil_matrixcn use CNSharedParamsMod , only : use_matrixcn use clm_varcon , only : nitrif_n2o_loss_frac - use pftconMod , only : npcropmin, pftcon + use pftconMod , only : is_prognostic_crop, pftcon use decompMod , only : bounds_type use CNVegNitrogenStateType , only : cnveg_nitrogenstate_type use CNVegNitrogenFluxType , only : cnveg_nitrogenflux_type @@ -228,7 +228,7 @@ subroutine NStateUpdate1(num_soilc, filter_soilc, num_soilp, filter_soilp, & ns_veg%deadcrootn_xfer_patch(p) = ns_veg%deadcrootn_xfer_patch(p) - nf_veg%deadcrootn_xfer_to_deadcrootn_patch(p)*dt end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops ! lines here for consistency; the transfer terms are zero ns_veg%livestemn_patch(p) = ns_veg%livestemn_patch(p) + nf_veg%livestemn_xfer_to_livestemn_patch(p)*dt ns_veg%livestemn_xfer_patch(p) = ns_veg%livestemn_xfer_patch(p) - nf_veg%livestemn_xfer_to_livestemn_patch(p)*dt @@ -287,7 +287,7 @@ subroutine NStateUpdate1(num_soilc, filter_soilc, num_soilp, filter_soilp, & ! NOTE: The equivalent changes for matrix code are in CNPhenology EBK (11/26/2019) end if !not use_matrixcn end if - if (ivt(p) >= npcropmin) then ! Beth adds retrans from froot + if (is_prognostic_crop(ivt(p))) then ! Beth adds retrans from froot ! ! State update without the matrix solution ! @@ -391,7 +391,7 @@ subroutine NStateUpdate1(num_soilc, filter_soilc, num_soilp, filter_soilp, & end if ! not use_matrixcn end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops ns_veg%npool_patch(p) = ns_veg%npool_patch(p) - nf_veg%npool_to_livestemn_patch(p)*dt ns_veg%npool_patch(p) = ns_veg%npool_patch(p) - nf_veg%npool_to_livestemn_storage_patch(p)*dt do k = 1, nrepr @@ -452,7 +452,7 @@ subroutine NStateUpdate1(num_soilc, filter_soilc, num_soilp, filter_soilp, & ! NOTE: The equivalent changes for matrix code are in CNPhenology EBK (11/26/2019) end if ! not use_matrixcn - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops ! lines here for consistency; the transfer terms are zero ! diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index ff1e5b1b74..fb43c37804 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2691,8 +2691,9 @@ subroutine CropPhenologyInit(bounds) ! initialized, and after pftcon file is read in. ! ! !USES: - use pftconMod , only: npcropmin, npcropmax use clm_time_manager, only: get_calday + use pftconMod, only: is_prognostic_crop + use clm_varpar, only: mxpft ! ! !ARGUMENTS: type(bounds_type), intent(in) :: bounds @@ -2713,8 +2714,8 @@ subroutine CropPhenologyInit(bounds) ! Convert planting dates into julian day minplantjday(:,:) = huge(1) maxplantjday(:,:) = huge(1) - do n = npcropmin, npcropmax - if (pftcon%is_pft_known_to_model(n)) then + do n = 1, mxpft + if (is_prognostic_crop(n) .and. pftcon%is_pft_known_to_model(n)) then minplantjday(n, inNH) = int( get_calday( pftcon%mnNHplantdate(n), 0 ) ) maxplantjday(n, inNH) = int( get_calday( pftcon%mxNHplantdate(n), 0 ) ) @@ -3120,7 +3121,9 @@ subroutine vernalization(p, & tkil = (tbase - 6._r8) - 6._r8 * hdidx(p) if (tkil >= tcrown) then if ((0.95_r8 - 0.02_r8 * (tcrown - tkil)**2) >= 0.02_r8) then - write (iulog,*) 'crop damaged by cold temperatures at p,c =', p,c + if (.not. generate_crop_gdds) then + write (iulog,*) 'crop damaged by cold temperatures at p,c =', p,c + end if else if (tlai(p) > 0._r8) then ! slevis: kill if past phase1 by forcing through harvest ! srabin: do this with force_harvest instead of setting @@ -3129,7 +3132,9 @@ subroutine vernalization(p, & ! on "maturity." This can occur when generate_crop_gdds ! is true. force_harvest = .true. - write (iulog,*) '95% of crop killed by cold temperatures at p,c =', p,c + if (.not. generate_crop_gdds) then + write (iulog,*) '95% of crop killed by cold temperatures at p,c =', p,c + end if end if end if end if @@ -3350,7 +3355,7 @@ subroutine CNOffsetLitterfall (num_soilp, filter_soilp, & ! pools during the phenological offset period. ! ! !USES: - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use pftconMod , only : nmiscanthus, nirrig_miscanthus, nswitchgrass, nirrig_switchgrass use CNSharedParamsMod, only : use_fun @@ -3526,7 +3531,7 @@ subroutine CNOffsetLitterfall (num_soilp, filter_soilp, & end if ! use_matrixcn ! this assumes that offset_counter == dt for crops ! if this were ever changed, we'd need to add code to the "else" - if (ivt(p) >= npcropmin) then + if (is_prognostic_crop(ivt(p))) then ! How many harvests have occurred? h = crop_inst%harvest_count(p) @@ -4371,7 +4376,7 @@ subroutine CNLitterToColumn (bounds, num_bgc_vegp, filter_bgc_vegp, & ! ! !USES: use clm_varpar , only : nlevdecomp - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use clm_varctl , only : use_grainproduct ! ! !ARGUMENTS: @@ -4449,7 +4454,7 @@ subroutine CNLitterToColumn (bounds, num_bgc_vegp, filter_bgc_vegp, & ! new ones for now (slevis) ! also for simplicity I've put "food" into the litter pools - if (ivt(p) >= npcropmin) then ! add livestemc to litter + if (is_prognostic_crop(ivt(p))) then ! add livestemc to litter do i = i_litr_min, i_litr_max ! stem litter carbon fluxes phenology_c_to_litr_c(c,j,i) = & diff --git a/src/biogeochem/CNVegCarbonFluxType.F90 b/src/biogeochem/CNVegCarbonFluxType.F90 index b4c581c081..12daf746af 100644 --- a/src/biogeochem/CNVegCarbonFluxType.F90 +++ b/src/biogeochem/CNVegCarbonFluxType.F90 @@ -27,7 +27,7 @@ module CNVegCarbonFluxType use clm_varctl , only : use_grainproduct use clm_varctl , only : iulog use landunit_varcon , only : istsoil, istcrop, istdlak - use pftconMod , only : npcropmin, pftcon + use pftconMod , only : is_prognostic_crop, pftcon use CropReprPoolsMod , only : nrepr, repr_grain_min, repr_grain_max, repr_structure_min, repr_structure_max use CropReprPoolsMod , only : get_repr_hist_fname, get_repr_rest_fname, get_repr_longname use LandunitType , only : lun @@ -4921,7 +4921,7 @@ subroutine Summary_carbonflux(this, & this%livestem_mr_patch(p) + & this%livecroot_mr_patch(p) end if - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%mr_patch(p) = & this%mr_patch(p) + & @@ -4939,7 +4939,7 @@ subroutine Summary_carbonflux(this, & this%cpool_deadstem_gr_patch(p) + & this%cpool_livecroot_gr_patch(p) + & this%cpool_deadcroot_gr_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%current_gr_patch(p) = this%current_gr_patch(p) + & this%cpool_reproductive_gr_patch(p,k) @@ -4955,7 +4955,7 @@ subroutine Summary_carbonflux(this, & this%transfer_deadstem_gr_patch(p) + & this%transfer_livecroot_gr_patch(p) + & this%transfer_deadcroot_gr_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%transfer_gr_patch(p) = this%transfer_gr_patch(p) + & this%transfer_reproductive_gr_patch(p,k) @@ -4971,7 +4971,7 @@ subroutine Summary_carbonflux(this, & this%cpool_livecroot_storage_gr_patch(p) + & this%cpool_deadcroot_storage_gr_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%storage_gr_patch(p) = this%storage_gr_patch(p) + & this%cpool_reproductive_storage_gr_patch(p,k) @@ -4985,7 +4985,7 @@ subroutine Summary_carbonflux(this, & this%storage_gr_patch(p) ! autotrophic respiration (AR) adn - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then this%ar_patch(p) = & this%mr_patch(p) + & this%gr_patch(p) @@ -5045,7 +5045,7 @@ subroutine Summary_carbonflux(this, & this%cpool_to_deadstemc_patch(p) + & this%deadstemc_xfer_to_deadstemc_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%agnpp_patch(p) = & this%agnpp_patch(p) + & @@ -5139,7 +5139,7 @@ subroutine Summary_carbonflux(this, & this%gru_livecrootc_to_litter_patch(p) + & this%gru_deadcrootc_to_litter_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then this%litfall_patch(p) = & this%litfall_patch(p) + & this%livestemc_to_litter_patch(p) diff --git a/src/biogeochem/CNVegCarbonStateType.F90 b/src/biogeochem/CNVegCarbonStateType.F90 index 142578e656..3604821057 100644 --- a/src/biogeochem/CNVegCarbonStateType.F90 +++ b/src/biogeochem/CNVegCarbonStateType.F90 @@ -9,7 +9,7 @@ module CNVegCarbonStateType use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) use shr_const_mod , only : SHR_CONST_PDB use shr_log_mod , only : errMsg => shr_log_errMsg - use pftconMod , only : noveg, npcropmin, pftcon, nc3crop, nc3irrig + use pftconMod , only : noveg, is_prognostic_crop, pftcon, nc3crop, nc3irrig use clm_varcon , only : spval, c3_r2, c4_r2, c14ratio use clm_varctl , only : iulog, use_cndv, use_crop use CNSharedParamsMod, only : use_matrixcn @@ -1532,7 +1532,7 @@ subroutine InitCold(this, bounds, ratio, carbon_type, c12_cnveg_carbonstate_inst this%matrix_cap_frootc_patch(p) = cnvegcstate_const%initial_vegC * ratio this%matrix_cap_frootc_storage_patch(p) = 0._r8 end if - else if (patch%itype(p) >= npcropmin) then ! prognostic crop types + else if (is_prognostic_crop(patch%itype(p))) then ! prognostic crop types this%leafc_patch(p) = 0._r8 this%leafc_storage_patch(p) = 0._r8 this%frootc_patch(p) = 0._r8 @@ -4578,7 +4578,7 @@ subroutine Summary_carbonstate(this, bounds, num_bgc_soilc, filter_bgc_soilc, nu this%gresp_storage_patch(p) + & this%gresp_xfer_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%storvegc_patch(p) = & this%storvegc_patch(p) + & diff --git a/src/biogeochem/CNVegMatrixMod.F90 b/src/biogeochem/CNVegMatrixMod.F90 index 5582afeffe..41339d3a2c 100644 --- a/src/biogeochem/CNVegMatrixMod.F90 +++ b/src/biogeochem/CNVegMatrixMod.F90 @@ -35,7 +35,7 @@ module CNVegMatrixMod ncphouttrans,nnphouttrans,ncgmouttrans,nngmouttrans,ncfiouttrans,nnfiouttrans use perf_mod , only : t_startf, t_stopf use PatchType , only : patch - use pftconMod , only : pftcon,npcropmin + use pftconMod , only : pftcon,is_prognostic_crop use CNVegCarbonStateType , only : cnveg_carbonstate_type use CNVegNitrogenStateType , only : cnveg_nitrogenstate_type use CNVegCarbonFluxType , only : cnveg_carbonflux_type !include: callocation,ctransfer, cturnover @@ -1140,7 +1140,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on Xvegc%V(p,igrain) = reproductivec(p,irepr) Xvegc%V(p,igrain_st) = reproductivec_storage(p,irepr) @@ -1173,7 +1173,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on Xveg13c%V(p,igrain) = cs13_veg%reproductivec_patch(p,irepr) Xveg13c%V(p,igrain_st) = cs13_veg%reproductivec_storage_patch(p,irepr) @@ -1207,7 +1207,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on Xveg14c%V(p,igrain) = cs14_veg%reproductivec_patch(p,irepr) Xveg14c%V(p,igrain_st) = cs14_veg%reproductivec_storage_patch(p,irepr) @@ -1241,7 +1241,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then Xvegn%V(p,igrain) = sum(reproductiven(p,:)) Xvegn%V(p,igrain_st) = sum(reproductiven_storage(p,:)) Xvegn%V(p,igrain_xf) = sum(reproductiven_xfer(p,:)) @@ -1279,7 +1279,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on reproc0(p) = max(reproductivec(p,irepr), epsi) reproc0_storage(p) = max(reproductivec_storage(p,irepr), epsi) @@ -1312,7 +1312,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on cs13_veg%reproc0_patch(p) = max(cs13_veg%reproductivec_patch(p,irepr), epsi) cs13_veg%reproc0_storage_patch(p) = max(cs13_veg%reproductivec_storage_patch(p,irepr), epsi) @@ -1346,7 +1346,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on cs14_veg%reproc0_patch(p) = max(cs14_veg%reproductivec_patch(p,irepr), epsi) cs14_veg%reproc0_storage_patch(p) = max(cs14_veg%reproductivec_storage_patch(p,irepr), epsi) @@ -1380,7 +1380,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! Use one index of the grain reproductive pools to operate on repron0(p) = max(reproductiven(p,irepr), epsi) repron0_storage(p) = max(reproductiven_storage(p,irepr), epsi) @@ -1730,7 +1730,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire end do do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_calloc_grain_acc(p) = matrix_calloc_grain_acc(p) + vegmatrixc_input%V(p,igrain) matrix_calloc_grainst_acc(p) = matrix_calloc_grainst_acc(p) + vegmatrixc_input%V(p,igrain_st) if(use_c13)then @@ -2052,7 +2052,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire end do do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_cturnover_grain_acc(p) = matrix_cturnover_grain_acc(p) & + (matrix_phturnover(p,igrain)+matrix_gmturnover(p,igrain)+matrix_fiturnover(p,igrain)) & * reproductivec(p,irepr) @@ -2110,7 +2110,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_nalloc_grain_acc(p) = matrix_nalloc_grain_acc(p) + vegmatrixn_input%V(p,igrain) matrix_nalloc_grainst_acc(p) = matrix_nalloc_grainst_acc(p) + vegmatrixn_input%V(p,igrain_st) end if @@ -2215,7 +2215,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_retransn_to_grain_acc(p) = matrix_ntransfer_retransn_to_grain_acc(p) & + matrix_nphtransfer(p,iretransn_to_igrain_phn) & * dt * retransn(p)!matrix_nphturnover(p,iretransn)*retransn(p) @@ -2287,7 +2287,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire end do do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_nturnover_grain_acc(p) = matrix_nturnover_grain_acc(p) & + (matrix_nphturnover(p,igrain)+matrix_ngmturnover(p,igrain)+matrix_nfiturnover(p,igrain)) & * reproductiven(p,irepr) @@ -2328,7 +2328,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! NOTE: This assumes only a single grain pool! (i.e nrepr is ! fixed at 1)! reproductivec(p,:) = Xvegc%V(p,igrain) @@ -2362,7 +2362,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! NOTE: This assumes only a single grain pool! (i.e nrepr is ! fixed at 1)! cs13_veg%reproductivec_patch(p,:) = Xveg13c%V(p,igrain) @@ -2396,7 +2396,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire end do do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then ! NOTE: This assumes only a single grain pool! (i.e nrepr is ! fixed at 1)! cs14_veg%reproductivec_patch(p,:) = Xveg14c%V(p,igrain) @@ -2431,7 +2431,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire do fp = 1,num_soilp p = filter_soilp(fp) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then reproductiven(p,:) = Xvegn%V(p,igrain) reproductiven_storage(p,:) = Xvegn%V(p,igrain_st) reproductiven_xfer(p,:) = Xvegn%V(p,igrain_xf) @@ -2457,7 +2457,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_calloc_acc(ilivecroot_st) = matrix_calloc_livecrootst_acc(p) matrix_calloc_acc(ideadcroot) = matrix_calloc_deadcroot_acc(p) matrix_calloc_acc(ideadcroot_st) = matrix_calloc_deadcrootst_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_calloc_acc(igrain) = matrix_calloc_grain_acc(p) matrix_calloc_acc(igrain_st) = matrix_calloc_grainst_acc(p) end if @@ -2474,7 +2474,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ctransfer_acc(ilivecroot,ilivecroot_xf) = matrix_ctransfer_livecrootxf_to_livecroot_acc(p) matrix_ctransfer_acc(ideadcroot_xf,ideadcroot_st) = matrix_ctransfer_deadcrootst_to_deadcrootxf_acc(p) matrix_ctransfer_acc(ideadcroot,ideadcroot_xf) = matrix_ctransfer_deadcrootxf_to_deadcroot_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ctransfer_acc(igrain_xf,igrain_st) = matrix_ctransfer_grainst_to_grainxf_acc(p) matrix_ctransfer_acc(igrain,igrain_xf) = matrix_ctransfer_grainxf_to_grain_acc(p) end if @@ -2499,7 +2499,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ctransfer_acc(ideadcroot,ideadcroot) = -matrix_cturnover_deadcroot_acc(p) matrix_ctransfer_acc(ideadcroot_st,ideadcroot_st) = -matrix_cturnover_deadcrootst_acc(p) matrix_ctransfer_acc(ideadcroot_xf,ideadcroot_xf) = -matrix_cturnover_deadcrootxf_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ctransfer_acc(igrain,igrain) = -matrix_cturnover_grain_acc(p) matrix_ctransfer_acc(igrain_st,igrain_st) = -matrix_cturnover_grainst_acc(p) matrix_ctransfer_acc(igrain_xf,igrain_xf) = -matrix_cturnover_grainxf_acc(p) @@ -2518,7 +2518,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c13alloc_acc(ilivecroot_st) = cs13_veg%matrix_calloc_livecrootst_acc_patch(p) matrix_c13alloc_acc(ideadcroot) = cs13_veg%matrix_calloc_deadcroot_acc_patch(p) matrix_c13alloc_acc(ideadcroot_st) = cs13_veg%matrix_calloc_deadcrootst_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c13alloc_acc(igrain) = cs13_veg%matrix_calloc_grain_acc_patch(p) matrix_c13alloc_acc(igrain_st) = cs13_veg%matrix_calloc_grainst_acc_patch(p) end if @@ -2535,7 +2535,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c13transfer_acc(ilivecroot,ilivecroot_xf) = cs13_veg%matrix_ctransfer_livecrootxf_to_livecroot_acc_patch(p) matrix_c13transfer_acc(ideadcroot_xf,ideadcroot_st) = cs13_veg%matrix_ctransfer_deadcrootst_to_deadcrootxf_acc_patch(p) matrix_c13transfer_acc(ideadcroot,ideadcroot_xf) = cs13_veg%matrix_ctransfer_deadcrootxf_to_deadcroot_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c13transfer_acc(igrain_xf,igrain_st) = cs13_veg%matrix_ctransfer_grainst_to_grainxf_acc_patch(p) matrix_c13transfer_acc(igrain,igrain_xf) = cs13_veg%matrix_ctransfer_grainxf_to_grain_acc_patch(p) end if @@ -2560,7 +2560,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c13transfer_acc(ideadcroot,ideadcroot) = -cs13_veg%matrix_cturnover_deadcroot_acc_patch(p) matrix_c13transfer_acc(ideadcroot_st,ideadcroot_st) = -cs13_veg%matrix_cturnover_deadcrootst_acc_patch(p) matrix_c13transfer_acc(ideadcroot_xf,ideadcroot_xf) = -cs13_veg%matrix_cturnover_deadcrootxf_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c13transfer_acc(igrain,igrain) = -cs13_veg%matrix_cturnover_grain_acc_patch(p) matrix_c13transfer_acc(igrain_st,igrain_st) = -cs13_veg%matrix_cturnover_grainst_acc_patch(p) matrix_c13transfer_acc(igrain_xf,igrain_xf) = -cs13_veg%matrix_cturnover_grainxf_acc_patch(p) @@ -2580,7 +2580,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c14alloc_acc(ilivecroot_st) = cs14_veg%matrix_calloc_livecrootst_acc_patch(p) matrix_c14alloc_acc(ideadcroot) = cs14_veg%matrix_calloc_deadcroot_acc_patch(p) matrix_c14alloc_acc(ideadcroot_st) = cs14_veg%matrix_calloc_deadcrootst_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c14alloc_acc(igrain) = cs14_veg%matrix_calloc_grain_acc_patch(p) matrix_c14alloc_acc(igrain_st) = cs14_veg%matrix_calloc_grainst_acc_patch(p) end if @@ -2597,7 +2597,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c14transfer_acc(ilivecroot,ilivecroot_xf) = cs14_veg%matrix_ctransfer_livecrootxf_to_livecroot_acc_patch(p) matrix_c14transfer_acc(ideadcroot_xf,ideadcroot_st) = cs14_veg%matrix_ctransfer_deadcrootst_to_deadcrootxf_acc_patch(p) matrix_c14transfer_acc(ideadcroot,ideadcroot_xf) = cs14_veg%matrix_ctransfer_deadcrootxf_to_deadcroot_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c14transfer_acc(igrain_xf,igrain_st) = cs14_veg%matrix_ctransfer_grainst_to_grainxf_acc_patch(p) matrix_c14transfer_acc(igrain,igrain_xf) = cs14_veg%matrix_ctransfer_grainxf_to_grain_acc_patch(p) end if @@ -2622,7 +2622,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c14transfer_acc(ideadcroot,ideadcroot) = -cs14_veg%matrix_cturnover_deadcroot_acc_patch(p) matrix_c14transfer_acc(ideadcroot_st,ideadcroot_st) = -cs14_veg%matrix_cturnover_deadcrootst_acc_patch(p) matrix_c14transfer_acc(ideadcroot_xf,ideadcroot_xf) = -cs14_veg%matrix_cturnover_deadcrootxf_acc_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c14transfer_acc(igrain,igrain) = -cs14_veg%matrix_cturnover_grain_acc_patch(p) matrix_c14transfer_acc(igrain_st,igrain_st) = -cs14_veg%matrix_cturnover_grainst_acc_patch(p) matrix_c14transfer_acc(igrain_xf,igrain_xf) = -cs14_veg%matrix_cturnover_grainxf_acc_patch(p) @@ -2641,7 +2641,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_nalloc_acc(ilivecroot_st) = matrix_nalloc_livecrootst_acc(p) matrix_nalloc_acc(ideadcroot) = matrix_nalloc_deadcroot_acc(p) matrix_nalloc_acc(ideadcroot_st) = matrix_nalloc_deadcrootst_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_nalloc_acc(igrain) = matrix_nalloc_grain_acc(p) matrix_nalloc_acc(igrain_st) = matrix_nalloc_grainst_acc(p) end if @@ -2658,7 +2658,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_acc(ilivecroot,ilivecroot_xf) = matrix_ntransfer_livecrootxf_to_livecroot_acc(p) matrix_ntransfer_acc(ideadcroot_xf,ideadcroot_st) = matrix_ntransfer_deadcrootst_to_deadcrootxf_acc(p) matrix_ntransfer_acc(ideadcroot,ideadcroot_xf) = matrix_ntransfer_deadcrootxf_to_deadcroot_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_acc(igrain_xf,igrain_st) = matrix_ntransfer_grainst_to_grainxf_acc(p) matrix_ntransfer_acc(igrain,igrain_xf) = matrix_ntransfer_grainxf_to_grain_acc(p) end if @@ -2677,7 +2677,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_acc(ilivecroot_st,iretransn) = matrix_ntransfer_retransn_to_livecrootst_acc(p) matrix_ntransfer_acc(ideadcroot,iretransn) = matrix_ntransfer_retransn_to_deadcroot_acc(p) matrix_ntransfer_acc(ideadcroot_st,iretransn) = matrix_ntransfer_retransn_to_deadcrootst_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_acc(igrain,iretransn) = matrix_ntransfer_retransn_to_grain_acc(p) matrix_ntransfer_acc(igrain_st,iretransn) = matrix_ntransfer_retransn_to_grainst_acc(p) end if @@ -2704,7 +2704,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_acc(ideadcroot,ideadcroot) = -matrix_nturnover_deadcroot_acc(p) matrix_ntransfer_acc(ideadcroot_st,ideadcroot_st) = -matrix_nturnover_deadcrootst_acc(p) matrix_ntransfer_acc(ideadcroot_xf,ideadcroot_xf) = -matrix_nturnover_deadcrootxf_acc(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_acc(igrain,igrain) = -matrix_nturnover_grain_acc(p) matrix_ntransfer_acc(igrain_st,igrain_st) = -matrix_nturnover_grainst_acc(p) matrix_ntransfer_acc(igrain_xf,igrain_xf) = -matrix_nturnover_grainxf_acc(p) @@ -2755,7 +2755,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ctransfer_acc(1:nvegcpool,ideadcroot) = matrix_ctransfer_acc(1:nvegcpool,ideadcroot) / deadcrootc0(p) matrix_ctransfer_acc(1:nvegcpool,ideadcroot_st) = matrix_ctransfer_acc(1:nvegcpool,ideadcroot_st) / deadcrootc0_storage(p) matrix_ctransfer_acc(1:nvegcpool,ideadcroot_xf) = matrix_ctransfer_acc(1:nvegcpool,ideadcroot_xf) / deadcrootc0_xfer(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ctransfer_acc(1:nvegcpool,igrain) = matrix_ctransfer_acc(1:nvegcpool,igrain) / reproc0(p) matrix_ctransfer_acc(1:nvegcpool,igrain_st) = matrix_ctransfer_acc(1:nvegcpool,igrain_st) / reproc0_storage(p) matrix_ctransfer_acc(1:nvegcpool,igrain_xf) = matrix_ctransfer_acc(1:nvegcpool,igrain_xf) / reproc0_xfer(p) @@ -2780,7 +2780,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c13transfer_acc(1:nvegcpool,ideadcroot) = matrix_c13transfer_acc(1:nvegcpool,ideadcroot) / cs13_veg%deadcrootc0_patch(p) matrix_c13transfer_acc(1:nvegcpool,ideadcroot_st) = matrix_c13transfer_acc(1:nvegcpool,ideadcroot_st) / cs13_veg%deadcrootc0_storage_patch(p) matrix_c13transfer_acc(1:nvegcpool,ideadcroot_xf) = matrix_c13transfer_acc(1:nvegcpool,ideadcroot_xf) / cs13_veg%deadcrootc0_xfer_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c13transfer_acc(1:nvegcpool,igrain) = matrix_c13transfer_acc(1:nvegcpool,igrain) / cs13_veg%reproc0_patch(p) matrix_c13transfer_acc(1:nvegcpool,igrain_st) = matrix_c13transfer_acc(1:nvegcpool,igrain_st) / cs13_veg%reproc0_storage_patch(p) matrix_c13transfer_acc(1:nvegcpool,igrain_xf) = matrix_c13transfer_acc(1:nvegcpool,igrain_xf) / cs13_veg%reproc0_xfer_patch(p) @@ -2806,7 +2806,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_c14transfer_acc(1:nvegcpool,ideadcroot) = matrix_c14transfer_acc(1:nvegcpool,ideadcroot) / cs14_veg%deadcrootc0_patch(p) matrix_c14transfer_acc(1:nvegcpool,ideadcroot_st) = matrix_c14transfer_acc(1:nvegcpool,ideadcroot_st) / cs14_veg%deadcrootc0_storage_patch(p) matrix_c14transfer_acc(1:nvegcpool,ideadcroot_xf) = matrix_c14transfer_acc(1:nvegcpool,ideadcroot_xf) / cs14_veg%deadcrootc0_xfer_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_c14transfer_acc(1:nvegcpool,igrain) = matrix_c14transfer_acc(1:nvegcpool,igrain) / cs14_veg%reproc0_patch(p) matrix_c14transfer_acc(1:nvegcpool,igrain_st) = matrix_c14transfer_acc(1:nvegcpool,igrain_st) / cs14_veg%reproc0_storage_patch(p) matrix_c14transfer_acc(1:nvegcpool,igrain_xf) = matrix_c14transfer_acc(1:nvegcpool,igrain_xf) / cs14_veg%reproc0_xfer_patch(p) @@ -2831,7 +2831,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_acc(1:nvegnpool,ideadcroot) = matrix_ntransfer_acc(1:nvegnpool,ideadcroot) / deadcrootn0(p) matrix_ntransfer_acc(1:nvegnpool,ideadcroot_st) = matrix_ntransfer_acc(1:nvegnpool,ideadcroot_st) / deadcrootn0_storage(p) matrix_ntransfer_acc(1:nvegnpool,ideadcroot_xf) = matrix_ntransfer_acc(1:nvegnpool,ideadcroot_xf) / deadcrootn0_xfer(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_acc(1:nvegnpool,igrain) = matrix_ntransfer_acc(1:nvegnpool,igrain) / repron0(p) matrix_ntransfer_acc(1:nvegnpool,igrain_st) = matrix_ntransfer_acc(1:nvegnpool,igrain_st) / repron0_storage(p) matrix_ntransfer_acc(1:nvegnpool,igrain_xf) = matrix_ntransfer_acc(1:nvegnpool,igrain_xf) / repron0_xfer(p) @@ -2923,7 +2923,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootc_SASUsave(p) = deadcrootc_SASUsave(p) + deadcrootc(p) deadcrootc_storage_SASUsave(p) = deadcrootc_storage_SASUsave(p) + deadcrootc_storage(p) deadcrootc_xfer_SASUsave(p) = deadcrootc_xfer_SASUsave(p) + deadcrootc_xfer(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then grainc_SASUsave(p) = grainc_SASUsave(p) + sum(reproductivec(p,:)) grainc_storage_SASUsave(p) = grainc_storage_SASUsave(p) + sum(reproductivec_storage(p,:)) end if @@ -2946,7 +2946,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%deadcrootc_SASUsave_patch(p) = cs13_veg%deadcrootc_SASUsave_patch(p) + cs13_veg%deadcrootc_patch(p) cs13_veg%deadcrootc_storage_SASUsave_patch(p) = cs13_veg%deadcrootc_storage_SASUsave_patch(p) + cs13_veg%deadcrootc_storage_patch(p) cs13_veg%deadcrootc_xfer_SASUsave_patch(p) = cs13_veg%deadcrootc_xfer_SASUsave_patch(p) + cs13_veg%deadcrootc_xfer_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%grainc_SASUsave_patch(p) = cs13_veg%grainc_SASUsave_patch(p) + cs13_veg%reproductivec_patch(p,irepr) cs13_veg%grainc_storage_SASUsave_patch(p) = cs13_veg%grainc_storage_SASUsave_patch(p) + cs13_veg%reproductivec_storage_patch(p,irepr) end if @@ -2970,7 +2970,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%deadcrootc_SASUsave_patch(p) = cs14_veg%deadcrootc_SASUsave_patch(p) + cs14_veg%deadcrootc_patch(p) cs14_veg%deadcrootc_storage_SASUsave_patch(p) = cs14_veg%deadcrootc_storage_SASUsave_patch(p) + cs14_veg%deadcrootc_storage_patch(p) cs14_veg%deadcrootc_xfer_SASUsave_patch(p) = cs14_veg%deadcrootc_xfer_SASUsave_patch(p) + cs14_veg%deadcrootc_xfer_patch(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%grainc_SASUsave_patch(p) = cs14_veg%grainc_SASUsave_patch(p) + cs14_veg%reproductivec_patch(p,irepr) cs14_veg%grainc_storage_SASUsave_patch(p) = cs14_veg%grainc_storage_SASUsave_patch(p) + cs14_veg%reproductivec_storage_patch(p,irepr) end if @@ -2993,7 +2993,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootn_SASUsave(p) = deadcrootn_SASUsave(p) + deadcrootn(p) deadcrootn_storage_SASUsave(p) = deadcrootn_storage_SASUsave(p) + deadcrootn_storage(p) deadcrootn_xfer_SASUsave(p) = deadcrootn_xfer_SASUsave(p) + deadcrootn_xfer(p) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then grainn_SASUsave(p) = grainn_SASUsave(p) + reproductiven(p,irepr) end if if(iyr .eq. nyr_forcing)then @@ -3015,7 +3015,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootc(p) = deadcrootc_SASUsave(p) / (nyr_forcing/nyr_SASU) deadcrootc_storage(p) = deadcrootc_storage_SASUsave(p) / (nyr_forcing/nyr_SASU) deadcrootc_xfer(p) = deadcrootc_xfer_SASUsave(p) / (nyr_forcing/nyr_SASU) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then reproductivec(p,:) = grainc_SASUsave(p) / (nyr_forcing/nyr_SASU) reproductivec_storage(p,:) = grainc_storage_SASUsave(p) / (nyr_forcing/nyr_SASU) end if @@ -3038,7 +3038,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%deadcrootc_patch(p) = cs13_veg%deadcrootc_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs13_veg%deadcrootc_storage_patch(p) = cs13_veg%deadcrootc_storage_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs13_veg%deadcrootc_xfer_patch(p) = cs13_veg%deadcrootc_xfer_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%reproductivec_patch(p,:) = cs13_veg%grainc_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs13_veg%reproductivec_storage_patch(p,:) = cs13_veg%grainc_storage_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) end if @@ -3062,7 +3062,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%deadcrootc_patch(p) = cs14_veg%deadcrootc_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs14_veg%deadcrootc_storage_patch(p) = cs14_veg%deadcrootc_storage_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs14_veg%deadcrootc_xfer_patch(p) = cs14_veg%deadcrootc_xfer_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%reproductivec_patch(p,:) = cs14_veg%grainc_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) cs14_veg%reproductivec_storage_patch(p,:) = cs14_veg%grainc_storage_SASUsave_patch(p) / (nyr_forcing/nyr_SASU) end if @@ -3085,7 +3085,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootn(p) = deadcrootn_SASUsave(p) / (nyr_forcing/nyr_SASU) deadcrootn_storage(p) = deadcrootn_storage_SASUsave(p) / (nyr_forcing/nyr_SASU) deadcrootn_xfer(p) = deadcrootn_xfer_SASUsave(p) / (nyr_forcing/nyr_SASU) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then reproductiven(p,:) = grainn_SASUsave(p) / (nyr_forcing/nyr_SASU) end if leafc_SASUsave(p) = 0 @@ -3106,7 +3106,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootc_SASUsave(p) = 0 deadcrootc_storage_SASUsave(p) = 0 deadcrootc_xfer_SASUsave(p) = 0 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then grainc_SASUsave(p) = 0 grainc_storage_SASUsave(p) = 0 end if @@ -3129,7 +3129,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%deadcrootc_SASUsave_patch(p) = 0 cs13_veg%deadcrootc_storage_SASUsave_patch(p) = 0 cs13_veg%deadcrootc_xfer_SASUsave_patch(p) = 0 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%grainc_SASUsave_patch(p) = 0 cs13_veg%grainc_storage_SASUsave_patch(p) = 0 end if @@ -3153,7 +3153,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%deadcrootc_SASUsave_patch(p) = 0 cs14_veg%deadcrootc_storage_SASUsave_patch(p) = 0 cs14_veg%deadcrootc_xfer_SASUsave_patch(p) = 0 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%grainc_SASUsave_patch(p) = 0 cs14_veg%grainc_storage_SASUsave_patch(p) = 0 end if @@ -3176,7 +3176,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire deadcrootn_SASUsave(p) = 0 deadcrootn_storage_SASUsave(p) = 0 deadcrootn_xfer_SASUsave(p) = 0 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then grainn_SASUsave(p) = 0 end if end if @@ -3204,7 +3204,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_cap_deadcrootc(p) = vegmatrixc_rt(ideadcroot) matrix_cap_deadcrootc_storage(p) = vegmatrixc_rt(ideadcroot_st) matrix_cap_deadcrootc_xfer(p) = vegmatrixc_rt(ideadcroot_xf) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_cap_reproc(p) = vegmatrixc_rt(igrain) matrix_cap_reproc_storage(p) = vegmatrixc_rt(igrain_st) matrix_cap_reproc_xfer(p) = vegmatrixc_rt(igrain_xf) @@ -3228,7 +3228,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%matrix_cap_deadcrootc_patch(p) = vegmatrixc13_rt(ideadcroot) cs13_veg%matrix_cap_deadcrootc_storage_patch(p) = vegmatrixc13_rt(ideadcroot_st) cs13_veg%matrix_cap_deadcrootc_xfer_patch(p) = vegmatrixc13_rt(ideadcroot_xf) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%matrix_cap_reproc_patch(p) = vegmatrixc13_rt(igrain) cs13_veg%matrix_cap_reproc_storage_patch(p) = vegmatrixc13_rt(igrain_st) cs13_veg%matrix_cap_reproc_xfer_patch(p) = vegmatrixc13_rt(igrain_xf) @@ -3253,7 +3253,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%matrix_cap_deadcrootc_patch(p) = vegmatrixc14_rt(ideadcroot) cs14_veg%matrix_cap_deadcrootc_storage_patch(p) = vegmatrixc14_rt(ideadcroot_st) cs14_veg%matrix_cap_deadcrootc_xfer_patch(p) = vegmatrixc14_rt(ideadcroot_xf) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%matrix_cap_reproc_patch(p) = vegmatrixc14_rt(igrain) cs14_veg%matrix_cap_reproc_storage_patch(p) = vegmatrixc14_rt(igrain_st) cs14_veg%matrix_cap_reproc_xfer_patch(p) = vegmatrixc14_rt(igrain_xf) @@ -3276,7 +3276,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_cap_livecrootn_xfer(p) = vegmatrixn_rt(ilivecroot_xf) matrix_cap_deadcrootn(p) = vegmatrixn_rt(ideadcroot) matrix_cap_deadcrootn_storage(p) = vegmatrixn_rt(ideadcroot_st) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_cap_repron(p) = vegmatrixn_rt(igrain) matrix_cap_repron_storage(p) = vegmatrixn_rt(igrain_st) matrix_cap_repron_xfer(p) = vegmatrixn_rt(igrain_xf) @@ -3296,7 +3296,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_calloc_livecrootst_acc(p) = 0._r8 matrix_calloc_deadcroot_acc(p) = 0._r8 matrix_calloc_deadcrootst_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_calloc_grain_acc(p) = 0._r8 matrix_calloc_grainst_acc(p) = 0._r8 end if @@ -3313,7 +3313,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ctransfer_livecrootxf_to_livecroot_acc(p) = 0._r8 matrix_ctransfer_deadcrootst_to_deadcrootxf_acc(p) = 0._r8 matrix_ctransfer_deadcrootxf_to_deadcroot_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ctransfer_grainst_to_grainxf_acc(p) = 0._r8 matrix_ctransfer_grainxf_to_grain_acc(p) = 0._r8 end if @@ -3338,7 +3338,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_cturnover_deadcroot_acc(p) = 0._r8 matrix_cturnover_deadcrootst_acc(p) = 0._r8 matrix_cturnover_deadcrootxf_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_cturnover_grain_acc(p) = 0._r8 matrix_cturnover_grainst_acc(p) = 0._r8 matrix_cturnover_grainxf_acc(p) = 0._r8 @@ -3357,7 +3357,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%matrix_calloc_livecrootst_acc_patch(p) = 0._r8 cs13_veg%matrix_calloc_deadcroot_acc_patch(p) = 0._r8 cs13_veg%matrix_calloc_deadcrootst_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%matrix_calloc_grain_acc_patch(p) = 0._r8 cs13_veg%matrix_calloc_grainst_acc_patch(p) = 0._r8 end if @@ -3374,7 +3374,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%matrix_ctransfer_livecrootxf_to_livecroot_acc_patch(p) = 0._r8 cs13_veg%matrix_ctransfer_deadcrootst_to_deadcrootxf_acc_patch(p) = 0._r8 cs13_veg%matrix_ctransfer_deadcrootxf_to_deadcroot_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%matrix_ctransfer_grainst_to_grainxf_acc_patch(p) = 0._r8 cs13_veg%matrix_ctransfer_grainxf_to_grain_acc_patch(p) = 0._r8 end if @@ -3399,7 +3399,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs13_veg%matrix_cturnover_deadcroot_acc_patch(p) = 0._r8 cs13_veg%matrix_cturnover_deadcrootst_acc_patch(p) = 0._r8 cs13_veg%matrix_cturnover_deadcrootxf_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs13_veg%matrix_cturnover_grain_acc_patch(p) = 0._r8 cs13_veg%matrix_cturnover_grainst_acc_patch(p) = 0._r8 cs13_veg%matrix_cturnover_grainxf_acc_patch(p) = 0._r8 @@ -3419,7 +3419,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%matrix_calloc_livecrootst_acc_patch(p) = 0._r8 cs14_veg%matrix_calloc_deadcroot_acc_patch(p) = 0._r8 cs14_veg%matrix_calloc_deadcrootst_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%matrix_calloc_grain_acc_patch(p) = 0._r8 cs14_veg%matrix_calloc_grainst_acc_patch(p) = 0._r8 end if @@ -3436,7 +3436,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%matrix_ctransfer_livecrootxf_to_livecroot_acc_patch(p) = 0._r8 cs14_veg%matrix_ctransfer_deadcrootst_to_deadcrootxf_acc_patch(p) = 0._r8 cs14_veg%matrix_ctransfer_deadcrootxf_to_deadcroot_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%matrix_ctransfer_grainst_to_grainxf_acc_patch(p) = 0._r8 cs14_veg%matrix_ctransfer_grainxf_to_grain_acc_patch(p) = 0._r8 end if @@ -3461,7 +3461,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire cs14_veg%matrix_cturnover_deadcroot_acc_patch(p) = 0._r8 cs14_veg%matrix_cturnover_deadcrootst_acc_patch(p) = 0._r8 cs14_veg%matrix_cturnover_deadcrootxf_acc_patch(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then cs14_veg%matrix_cturnover_grain_acc_patch(p) = 0._r8 cs14_veg%matrix_cturnover_grainst_acc_patch(p) = 0._r8 cs14_veg%matrix_cturnover_grainxf_acc_patch(p) = 0._r8 @@ -3480,7 +3480,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_nalloc_livecrootst_acc(p) = 0._r8 matrix_nalloc_deadcroot_acc(p) = 0._r8 matrix_nalloc_deadcrootst_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_nalloc_grain_acc(p) = 0._r8 matrix_nalloc_grainst_acc(p) = 0._r8 end if @@ -3497,7 +3497,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_livecrootxf_to_livecroot_acc(p) = 0._r8 matrix_ntransfer_deadcrootst_to_deadcrootxf_acc(p) = 0._r8 matrix_ntransfer_deadcrootxf_to_deadcroot_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_grainst_to_grainxf_acc(p) = 0._r8 matrix_ntransfer_grainxf_to_grain_acc(p) = 0._r8 end if @@ -3516,7 +3516,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_ntransfer_retransn_to_livecrootst_acc(p) = 0._r8 matrix_ntransfer_retransn_to_deadcroot_acc(p) = 0._r8 matrix_ntransfer_retransn_to_deadcrootst_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_ntransfer_retransn_to_grain_acc(p) = 0._r8 matrix_ntransfer_retransn_to_grainst_acc(p) = 0._r8 end if @@ -3543,7 +3543,7 @@ subroutine CNVegMatrix(bounds,num_soilp,filter_soilp,num_actfirep,filter_actfire matrix_nturnover_deadcroot_acc(p) = 0._r8 matrix_nturnover_deadcrootst_acc(p) = 0._r8 matrix_nturnover_deadcrootxf_acc(p) = 0._r8 - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then matrix_nturnover_grain_acc(p) = 0._r8 matrix_nturnover_grainst_acc(p) = 0._r8 matrix_nturnover_grainxf_acc(p) = 0._r8 diff --git a/src/biogeochem/CNVegNitrogenStateType.F90 b/src/biogeochem/CNVegNitrogenStateType.F90 index 7ff8e77df9..c343c45aca 100644 --- a/src/biogeochem/CNVegNitrogenStateType.F90 +++ b/src/biogeochem/CNVegNitrogenStateType.F90 @@ -10,7 +10,7 @@ module CNVegNitrogenStateType use clm_varctl , only : use_crop use CNSharedParamsMod , only : use_fun, use_matrixcn use decompMod , only : bounds_type - use pftconMod , only : npcropmin, noveg, pftcon + use pftconMod , only : is_prognostic_crop, noveg, pftcon use abortutils , only : endrun use spmdMod , only : masterproc use LandunitType , only : lun @@ -2251,7 +2251,7 @@ subroutine Summary_nitrogenstate(this, bounds, num_soilc, filter_soilc, num_soil this%npool_patch(p) + & this%retransn_patch(p) - if ( use_crop .and. patch%itype(p) >= npcropmin )then + if ( use_crop .and. is_prognostic_crop(patch%itype(p)) )then do k = 1, nrepr this%dispvegn_patch(p) = & this%dispvegn_patch(p) + & diff --git a/src/biogeochem/CNVegStructUpdateMod.F90 b/src/biogeochem/CNVegStructUpdateMod.F90 index 2e8ed8539b..007478b573 100644 --- a/src/biogeochem/CNVegStructUpdateMod.F90 +++ b/src/biogeochem/CNVegStructUpdateMod.F90 @@ -38,7 +38,7 @@ subroutine CNVegStructUpdate(bounds,num_soilp, filter_soilp, & ! ! !USES: use pftconMod , only : noveg, nc3crop, nc3irrig, nbrdlf_evr_shrub, nbrdlf_dcd_brl_shrub - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use pftconMod , only : ntmp_corn, nirrig_tmp_corn use pftconMod , only : ntrp_corn, nirrig_trp_corn use pftconMod , only : nsugarcane, nirrig_sugarcane @@ -232,7 +232,7 @@ subroutine CNVegStructUpdate(bounds,num_soilp, filter_soilp, & hbot(p) = max(0._r8, min(3._r8, htop(p)-1._r8)) - else if (ivt(p) >= npcropmin) then ! prognostic crops + else if (is_prognostic_crop(ivt(p))) then ! prognostic crops if (tlai(p) >= laimx(ivt(p))) peaklai(p) = 1 ! used in CNAllocation diff --git a/src/biogeochem/CNVegetationFacade.F90 b/src/biogeochem/CNVegetationFacade.F90 index 47099708f4..9ff26dd56d 100644 --- a/src/biogeochem/CNVegetationFacade.F90 +++ b/src/biogeochem/CNVegetationFacade.F90 @@ -96,6 +96,7 @@ module CNVegetationFacade use SoilBiogeochemPrecisionControlMod , only: SoilBiogeochemPrecisionControl use SoilWaterRetentionCurveMod , only : soil_water_retention_curve_type use CLMFatesInterfaceMod , only : hlm_fates_interface_type + use SoilHydrologyType , only : soilhydrology_type ! implicit none private @@ -204,10 +205,12 @@ subroutine Init(this, bounds, NLFilename, nskip_steps, params_ncid) ! ! !USES: use CNFireFactoryMod , only : create_cnfire_method + use CNFireNoFireMod , only : cnfire_nofire_type use clm_varcon , only : c13ratio, c14ratio use ncdio_pio , only : file_desc_t use filterMod , only : filter use decompMod , only : get_proc_clumps + ! ! !ARGUMENTS: class(cn_vegetation_type), intent(inout) :: this @@ -302,10 +305,21 @@ subroutine Init(this, bounds, NLFilename, nskip_steps, params_ncid) ! use_cndv is true so that it can be used in associate statements (nag compiler ! complains otherwise) call this%dgvs_inst%Init(bounds) - end if - call create_cnfire_method(NLFilename, this%cnfire_method) - call this%cnfire_method%CNFireReadParams( params_ncid ) + call create_cnfire_method( this%cnfire_method ) + call this%cnfire_method%FireInit( bounds ) + call this%cnfire_method%FireReadNML( bounds, NLFilename ) + call this%cnfire_method%CNFireReadParams( params_ncid ) + end if + + ! + ! For FATES we HAVE to allocate a cnfire_method even through it won't be used + ! cnfire_method is passed down to CN routines that are used for FATES + ! so there has to be something allocated that is passed down + ! + if ( use_fates_bgc )then + allocate(cnfire_nofire_type :: this%cnfire_method) + end if end subroutine Init @@ -584,7 +598,7 @@ subroutine Init2(this, bounds, NLFilename) character(len=*), parameter :: subname = 'Init2' !----------------------------------------------------------------------- - call CNDriverInit(bounds, NLFilename, this%cnfire_method) + call CNDriverInit(bounds, NLFilename) if (use_cndv) then call dynCNDV_init(bounds, this%dgvs_inst) @@ -1061,7 +1075,7 @@ subroutine EcosystemDynamicsPostDrainage(this, bounds, num_allc, filter_allc, & soilbiogeochem_carbonflux_inst, soilbiogeochem_carbonstate_inst, & c13_soilbiogeochem_carbonflux_inst, c13_soilbiogeochem_carbonstate_inst, & c14_soilbiogeochem_carbonflux_inst, c14_soilbiogeochem_carbonstate_inst, & - soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst) + soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst, soilhydrology_inst) ! ! !DESCRIPTION: ! Do the main science for CN vegetation that needs to be done after hydrology-drainage @@ -1100,6 +1114,7 @@ subroutine EcosystemDynamicsPostDrainage(this, bounds, num_allc, filter_allc, & type(soilbiogeochem_carbonstate_type) , intent(inout) :: c14_soilbiogeochem_carbonstate_inst type(soilbiogeochem_nitrogenflux_type) , intent(inout) :: soilbiogeochem_nitrogenflux_inst type(soilbiogeochem_nitrogenstate_type) , intent(inout) :: soilbiogeochem_nitrogenstate_inst + type(soilhydrology_type) , intent(in) :: soilhydrology_inst ! ! !LOCAL VARIABLES: @@ -1122,7 +1137,7 @@ subroutine EcosystemDynamicsPostDrainage(this, bounds, num_allc, filter_allc, & this%c13_cnveg_carbonstate_inst,this%c14_cnveg_carbonstate_inst, & this%c13_cnveg_carbonflux_inst,this%c14_cnveg_carbonflux_inst, & c13_soilbiogeochem_carbonstate_inst,c14_soilbiogeochem_carbonstate_inst,& - c13_soilbiogeochem_carbonflux_inst,c14_soilbiogeochem_carbonflux_inst) + c13_soilbiogeochem_carbonflux_inst,c14_soilbiogeochem_carbonflux_inst, soilhydrology_inst) ! Set controls on very low values in critical state variables diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90 index 54395c4668..48f9cb9cad 100644 --- a/src/biogeochem/CropType.F90 +++ b/src/biogeochem/CropType.F90 @@ -528,7 +528,7 @@ subroutine Restart(this, bounds, ncid, cnveg_state_inst, flag) use restUtilMod use ncdio_pio use PatchType, only : patch - use pftconMod, only : npcropmin, npcropmax + use pftconMod, only : is_prognostic_crop use clm_varpar, only : mxsowings, mxharvests ! BACKWARDS_COMPATIBILITY(wjs/ssr, 2023-01-09) use CNVegstateType, only : cnveg_state_type @@ -577,7 +577,7 @@ subroutine Restart(this, bounds, ncid, cnveg_state_inst, flag) interpinic_flag='copy', readvar=readvar, data=restyear) if (readvar) then do p = bounds%begp, bounds%endp - if (patch%itype(p) >= npcropmin .and. patch%itype(p) <= npcropmax .and. & + if (is_prognostic_crop(patch%itype(p)) .and. & patch%active(p)) then this%nyrs_crop_active_patch(p) = restyear end if diff --git a/src/biogeochem/DryDepVelocity.F90 b/src/biogeochem/DryDepVelocity.F90 index 07b3c40376..74957410f7 100644 --- a/src/biogeochem/DryDepVelocity.F90 +++ b/src/biogeochem/DryDepVelocity.F90 @@ -211,7 +211,7 @@ subroutine depvel_compute( bounds, & use pftconMod , only : nbrdlf_evr_shrub, nbrdlf_dcd_tmp_shrub use pftconMod , only : nbrdlf_dcd_brl_shrub,nc3_arctic_grass use pftconMod , only : nc3_nonarctic_grass, nc4_grass, nc3crop - use pftconMod , only : nc3irrig, npcropmin, npcropmax + use pftconMod , only : nc3irrig, is_prognostic_crop use clm_varcon , only : spval use clm_varctl , only : use_fates @@ -337,29 +337,40 @@ subroutine depvel_compute( bounds, & solar_flux = forc_solad(c,1) lat = grc%latdeg(g) lon = grc%londeg(g) - clmveg = patch%itype(pi) + soilw = h2osoi_vol(c,1) !map CLM veg type into Wesely veg type wesveg = wveg_unset - if (clmveg == noveg ) wesveg = 8 - if (clmveg == ndllf_evr_tmp_tree ) wesveg = 5 - if (clmveg == ndllf_evr_brl_tree ) wesveg = 5 - if (clmveg == ndllf_dcd_brl_tree ) wesveg = 5 - if (clmveg == nbrdlf_evr_trp_tree ) wesveg = 4 - if (clmveg == nbrdlf_evr_tmp_tree ) wesveg = 4 - if (clmveg == nbrdlf_dcd_trp_tree ) wesveg = 4 - if (clmveg == nbrdlf_dcd_tmp_tree ) wesveg = 4 - if (clmveg == nbrdlf_dcd_brl_tree ) wesveg = 4 - if (clmveg == nbrdlf_evr_shrub ) wesveg = 11 - if (clmveg == nbrdlf_dcd_tmp_shrub ) wesveg = 11 - if (clmveg == nbrdlf_dcd_brl_shrub ) wesveg = 11 - if (clmveg == nc3_arctic_grass ) wesveg = 3 - if (clmveg == nc3_nonarctic_grass ) wesveg = 3 - if (clmveg == nc4_grass ) wesveg = 3 - if (clmveg == nc3crop ) wesveg = 2 - if (clmveg == nc3irrig ) wesveg = 2 - if (clmveg >= npcropmin .and. clmveg <= npcropmax ) wesveg = 2 + clmveg = patch%itype(pi) ! this will be spval if fates is on. + if(use_fates)then + if(patch%is_fates(pi))then + wesveg = wesley_veg_index(pi) + else + wesveg = 8 !make bare ground for non-fates patches. Some of these are overwritten below. + endif + else + if (clmveg == noveg ) wesveg = 8 + if (clmveg == ndllf_evr_tmp_tree ) wesveg = 5 + if (clmveg == ndllf_evr_brl_tree ) wesveg = 5 + if (clmveg == ndllf_dcd_brl_tree ) wesveg = 5 + if (clmveg == nbrdlf_evr_trp_tree ) wesveg = 4 + if (clmveg == nbrdlf_evr_tmp_tree ) wesveg = 4 + if (clmveg == nbrdlf_dcd_trp_tree ) wesveg = 4 + if (clmveg == nbrdlf_dcd_tmp_tree ) wesveg = 4 + if (clmveg == nbrdlf_dcd_brl_tree ) wesveg = 4 + if (clmveg == nbrdlf_evr_shrub ) wesveg = 11 + if (clmveg == nbrdlf_dcd_tmp_shrub ) wesveg = 11 + if (clmveg == nbrdlf_dcd_brl_shrub ) wesveg = 11 + if (clmveg == nc3_arctic_grass ) wesveg = 3 + if (clmveg == nc3_nonarctic_grass ) wesveg = 3 + if (clmveg == nc4_grass ) wesveg = 3 + if (clmveg == nc3crop ) wesveg = 2 + if (clmveg == nc3irrig ) wesveg = 2 + if (is_prognostic_crop(clmveg)) wesveg = 2 + endif + + if (wesveg == wveg_unset )then write(iulog,*) 'clmveg = ', clmveg, 'lun%itype = ', lun%itype(l) call endrun(subgrid_index=pi, subgrid_level=subgrid_level_patch, & @@ -367,13 +378,6 @@ subroutine depvel_compute( bounds, & errMsg(sourcefile, __LINE__)) end if - if(use_fates)then - if(patch%is_fates(pi))then - wesveg = wesley_veg_index(pi) - else - wesveg = 8 !make bare ground for non-fates patches. Some of these are overwritten below. - endif - endif if(wesveg<0 .or. wesveg>11 )then call endrun(subgrid_index=pi, subgrid_level=subgrid_level_patch, & diff --git a/src/biogeochem/FATESFireFactoryMod.F90 b/src/biogeochem/FATESFireFactoryMod.F90 index 0352994e5f..94e3eee4c3 100644 --- a/src/biogeochem/FATESFireFactoryMod.F90 +++ b/src/biogeochem/FATESFireFactoryMod.F90 @@ -42,7 +42,8 @@ subroutine create_fates_fire_data_method( fates_fire_data_method ) ! The particular type is determined based on a namelist parameter. ! ! !USES: - use clm_varctl, only: fates_spitfire_mode + use clm_varctl, only: fates_spitfire_mode, use_fates_sp, use_fates_ed_st3 + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n use FATESFireBase, only: fates_fire_base_type use FATESFireNoDataMod, only: fates_fire_no_data_type use FATESFireDataMod, only: fates_fire_data_type @@ -51,25 +52,69 @@ subroutine create_fates_fire_data_method( fates_fire_data_method ) class(fates_fire_base_type), allocatable, intent(inout) :: fates_fire_data_method ! function result ! ! !LOCAL VARIABLES: - integer :: current_case - - character(len=*), parameter :: subname = 'create_fates_fire_data_method' !----------------------------------------------------------------------- - current_case = fates_spitfire_mode - - select case (current_case) + ! + ! For FATES options that bypass fire... + ! + if ( use_fates_sp .or. use_fates_ed_st3 )then + ! + ! Make sure fire-emissions is NOT on + ! + if ( shr_fire_emis_mechcomps_n > 0 )then + if ( use_fates_sp )then + write(iulog,*) "Fire emissions can NOT be on with FATES-SP mode: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES requires FATES to NOT be in Satellite Phenology (SP) mode" ) + else if ( use_fates_ed_st3 )then + write(iulog,*) "Fire emissions can NOT be on with FATES ST3 mode: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES requires FATES to NOT be in Static Stand Structure mode" ) + end if + ! For unit-testing return with a FATESFireData type, so there isn't a run-time error + ! Also do the FATESFireData type, as using FATESFireNoData type will fail with an error + allocate(fates_fire_data_type :: fates_fire_data_method) + return + end if + allocate(fates_fire_no_data_type :: fates_fire_data_method) + else + ! + ! For regular FATES options that include fire + ! + select case (fates_spitfire_mode) - case (no_fire:scalar_lightning) - allocate(fates_fire_no_data_type :: fates_fire_data_method) - case (lightning_from_data:anthro_suppression) - allocate(fates_fire_data_type :: fates_fire_data_method) + ! No-fire, scalar-lightning and successful_ignitions ALL do NOT need input data from the base class + case (no_fire:scalar_lightning) + allocate(fates_fire_no_data_type :: fates_fire_data_method) + case (successful_ignitions) + allocate(fates_fire_no_data_type :: fates_fire_data_method) + ! Lightning from data, and the anthro types (ignition and suppression) need lightning data from the base class + case (lightning_from_data) + allocate(fates_fire_data_type :: fates_fire_data_method) + case (anthro_ignitions:anthro_suppression) + allocate(fates_fire_data_type :: fates_fire_data_method) - case default - write(iulog,*) subname//' ERROR: unknown method: ', fates_spitfire_mode - call endrun(msg=errMsg(sourcefile, __LINE__)) + case default + write(iulog,*) 'Unrecognized fates_spitfire_mode option = ', fates_spitfire_mode, ' in: ', & + errMsg(sourcefile, __LINE__) + call endrun(msg="Unknown option for namelist item fates_spitfire_mode:") + ! For unit-testing, make sure a valid fates_fire_data_method is set and return, otherwise it fails with a seg-fault + allocate(fates_fire_no_data_type :: fates_fire_data_method) - end select + end select + ! ------------------------------------------------------------------------------------------------------- + ! For now we die with a error whenever fire-emissions are turned on -- because this isn't setup in FATES + ! + if ( fates_spitfire_mode /= no_fire ) then + if ( shr_fire_emis_mechcomps_n > 0 )then + write(iulog,*) "Fire emissions can NOT be on with FATES currently: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES can NOT currently be turned on (see issue #1045)" ) + return + end if + end if + ! ------------------------------------------------------------------------------------------------------- + end if end subroutine create_fates_fire_data_method diff --git a/src/biogeochem/FATESFireNoDataMod.F90 b/src/biogeochem/FATESFireNoDataMod.F90 index 4034b68e97..65b7bae5af 100644 --- a/src/biogeochem/FATESFireNoDataMod.F90 +++ b/src/biogeochem/FATESFireNoDataMod.F90 @@ -27,6 +27,8 @@ module FATESFireNoDataMod contains ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: FATESNoFireInit! Initialization + procedure, public :: FireInit => FATESNoFireInit procedure, public :: need_lightning_and_popdens procedure, public :: GetLight24 ! Return the 24-hour averaged lightning data procedure, public :: GetGDP ! Return the global gdp data @@ -40,6 +42,28 @@ module FATESFireNoDataMod contains + !----------------------------------------------------------------------- + subroutine FATESNoFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize No Fire data module for FATES + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod , only : errMsg => shr_log_errMsg + use clm_varctl , only : fates_spitfire_mode + ! !ARGUMENTS: + class(fates_fire_no_data_type) :: this + type(bounds_type), intent(in) :: bounds + + if ( (shr_fire_emis_mechcomps_n > 0) .and. (fates_spitfire_mode == 0) ) then + write(iulog,*) "Fire emissions can NOT be active for fates_spitfire_mode=0 (no_fire)", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Having fire emissions on requires fates_spitfire_mode to be something besides no_fire (0)" ) + return + end if + call this%CNFireInit( bounds ) + + end subroutine FATESNoFireInit + !------------------------------------------------------------------------ function need_lightning_and_popdens(this) ! !ARGUMENTS: diff --git a/src/biogeochem/NutrientCompetitionCLM45defaultMod.F90 b/src/biogeochem/NutrientCompetitionCLM45defaultMod.F90 index 82dceef664..95f0594a31 100644 --- a/src/biogeochem/NutrientCompetitionCLM45defaultMod.F90 +++ b/src/biogeochem/NutrientCompetitionCLM45defaultMod.F90 @@ -125,7 +125,7 @@ subroutine calc_plant_cn_alloc (this, bounds, num_soilp, filter_soilp, & c14_cnveg_carbonflux_inst, cnveg_nitrogenflux_inst, cnveg_nitrogenstate_inst, fpg_col) ! ! !USES: - use pftconMod , only : pftcon, npcropmin + use pftconMod , only : pftcon, is_prognostic_crop use clm_varctl , only : use_c13, use_c14 use CNVegStateType , only : cnveg_state_type use CropType , only : crop_type @@ -281,7 +281,7 @@ subroutine calc_plant_cn_alloc (this, bounds, num_soilp, filter_soilp, & cndw = deadwdcn(ivt(p)) fcur = fcur2(ivt(p)) - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops if (croplive(p).and.(.not.shr_infnan_isnan(aleaf(p)))) then f1 = aroot(p) / aleaf(p) f3 = astem(p) / aleaf(p) @@ -357,7 +357,7 @@ subroutine calc_plant_cn_alloc (this, bounds, num_soilp, filter_soilp, & cpool_to_deadcrootc(p) = nlc * f2 * f3 * (1._r8 - f4) * fcur cpool_to_deadcrootc_storage(p) = nlc * f2 * f3 * (1._r8 - f4) * (1._r8 - fcur) end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cpool_to_livestemc(p) = nlc * f3 * f4 * fcur cpool_to_livestemc_storage(p) = nlc * f3 * f4 * (1._r8 - fcur) cpool_to_deadstemc(p) = nlc * f3 * (1._r8 - f4) * fcur @@ -387,7 +387,7 @@ subroutine calc_plant_cn_alloc (this, bounds, num_soilp, filter_soilp, & npool_to_deadcrootn(p) = (nlc * f2 * f3 * (1._r8 - f4) / cndw) * fcur npool_to_deadcrootn_storage(p) = (nlc * f2 * f3 * (1._r8 - f4) / cndw) * (1._r8 - fcur) end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cng = graincn(ivt(p)) npool_to_livestemn(p) = (nlc * f3 * f4 / cnlw) * fcur npool_to_livestemn_storage(p) = (nlc * f3 * f4 / cnlw) * (1._r8 - fcur) @@ -420,7 +420,7 @@ subroutine calc_plant_cn_alloc (this, bounds, num_soilp, filter_soilp, & gresp_storage = gresp_storage + cpool_to_livecrootc_storage(p) gresp_storage = gresp_storage + cpool_to_deadcrootc_storage(p) end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops gresp_storage = gresp_storage + cpool_to_livestemc_storage(p) do k = 1, nrepr gresp_storage = gresp_storage + cpool_to_reproductivec_storage(p,k) @@ -505,7 +505,7 @@ subroutine calc_plant_nitrogen_demand(this, bounds, & ! - livestemn_to_retransn ! ! !USES: - use pftconMod , only : npcropmin, pftcon + use pftconMod , only : is_prognostic_crop, pftcon use pftconMod , only : ntmp_soybean, nirrig_tmp_soybean use pftconMod , only : ntrp_soybean, nirrig_trp_soybean use clm_time_manager , only : get_step_size_real diff --git a/src/biogeochem/NutrientCompetitionFlexibleCNMod.F90 b/src/biogeochem/NutrientCompetitionFlexibleCNMod.F90 index 00e8a00b77..d3f8753fd0 100644 --- a/src/biogeochem/NutrientCompetitionFlexibleCNMod.F90 +++ b/src/biogeochem/NutrientCompetitionFlexibleCNMod.F90 @@ -24,7 +24,7 @@ module NutrientCompetitionFlexibleCNMod use LandunitType , only : lun use ColumnType , only : col use PatchType , only : patch - use pftconMod , only : pftcon, npcropmin + use pftconMod , only : pftcon, is_prognostic_crop use NutrientCompetitionMethodMod, only : nutrient_competition_method_type use CropReprPoolsMod , only : nrepr use CNPhenologyMod , only : CropPhase @@ -413,7 +413,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & fcur = 0.0_r8 end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops if (croplive(p)) then f1 = aroot(p) / aleaf(p) f3 = astem(p) / aleaf(p) @@ -497,7 +497,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & + cpool_to_deadcrootc(p) + cpool_to_deadcrootc_storage(p) end if end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops cpool_to_livestemc(p) = nlc * f3 * f4 * fcur cpool_to_livestemc_storage(p) = nlc * f3 * f4 * (1._r8 - fcur) cpool_to_deadstemc(p) = nlc * f3 * (1._r8 - f4) * fcur @@ -548,7 +548,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & matrix_alloc(p,ideadcroot_st) = cpool_to_deadcrootc_storage(p) / cpool_to_veg end if end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops if(cpool_to_veg .ne. 0)then matrix_alloc(p,ilivestem) = cpool_to_livestemc(p) / cpool_to_veg matrix_alloc(p,ilivestem_st) = cpool_to_livestemc_storage(p) / cpool_to_veg @@ -586,7 +586,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & gresp_storage = gresp_storage + cpool_to_livecrootc_storage(p) gresp_storage = gresp_storage + cpool_to_deadcrootc_storage(p) end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops gresp_storage = gresp_storage + cpool_to_livestemc_storage(p) do k = 1, nrepr gresp_storage = gresp_storage + cpool_to_reproductivec_storage(p,k) @@ -594,7 +594,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & end if cpool_to_gresp_storage(p) = gresp_storage * g1 * (1._r8 - g2) - if (use_crop_agsys .and. ivt(p) >= npcropmin) then + if (use_crop_agsys .and. is_prognostic_crop(ivt(p))) then call calc_npool_to_components_agsys( & ! Inputs npool = npool(p), & @@ -708,7 +708,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & end if end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops if (cnveg_nitrogenstate_inst%livestemn_storage_patch(p) == 0.0_r8) then ! to avoid division by zero, and also to make livestemcn_actual(p) a very large number if livestemc(p) is zero @@ -795,7 +795,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & end if - if (ivt(p) >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt(p))) then ! skip 2 generic crops livewdcn_max = livewdcn(ivt(p)) + 15.0_r8 @@ -876,7 +876,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & + npool_to_deadstemn(p) + npool_to_deadstemn_storage(p) & + npool_to_livecrootn(p) + npool_to_livecrootn_storage(p) & + npool_to_deadcrootn(p) + npool_to_deadcrootn_storage(p) - if (ivt(p) >= npcropmin)then + if (is_prognostic_crop(ivt(p)))then npool_to_veg = npool_to_veg + npool_to_reproductiven(p,1) + npool_to_reproductiven_storage(p,1) end if if(npool_to_veg .ne. 0._r8)then @@ -892,7 +892,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & matrix_nalloc(p,ilivecroot_st ) = npool_to_livecrootn_storage(p) / npool_to_veg matrix_nalloc(p,ideadcroot ) = npool_to_deadcrootn(p) / npool_to_veg matrix_nalloc(p,ideadcroot_st ) = npool_to_deadcrootn_storage(p) / npool_to_veg - if (ivt(p) >= npcropmin)then + if (is_prognostic_crop(ivt(p)))then matrix_nalloc(p,igrain ) = npool_to_reproductiven(p,1) / npool_to_veg matrix_nalloc(p,igrain_st ) = npool_to_reproductiven_storage(p,1) / npool_to_veg end if @@ -916,7 +916,7 @@ subroutine calc_plant_cn_alloc(this, bounds, num_soilp, filter_soilp, & tmp = matrix_update_phn(p,iretransn_to_ilivecrootst ,matrix_nalloc(p,ilivecroot_st ) * retransn_to_npool(p) / retransn(p),dt,cnveg_nitrogenflux_inst,matrixcheck_ph,.True.) tmp = matrix_update_phn(p,iretransn_to_ideadcroot ,matrix_nalloc(p,ideadcroot ) * retransn_to_npool(p) / retransn(p),dt,cnveg_nitrogenflux_inst,matrixcheck_ph,.True.) tmp = matrix_update_phn(p,iretransn_to_ideadcrootst ,matrix_nalloc(p,ideadcroot_st ) * retransn_to_npool(p) / retransn(p),dt,cnveg_nitrogenflux_inst,matrixcheck_ph,.True.) - if(ivt(p) >= npcropmin)then + if(is_prognostic_crop(ivt(p)))then tmp = matrix_update_phn(p,iretransn_to_igrain ,matrix_nalloc(p,igrain ) * retransn_to_npool(p) / retransn(p),dt,cnveg_nitrogenflux_inst,matrixcheck_ph,.True.) tmp = matrix_update_phn(p,iretransn_to_igrainst ,matrix_nalloc(p,igrain_st ) * retransn_to_npool(p) / retransn(p),dt,cnveg_nitrogenflux_inst,matrixcheck_ph,.True.) end if @@ -1051,7 +1051,7 @@ subroutine calc_npool_to_components_flexiblecn( & npool_to_deadcrootn_demand = (nlc * f2 * f3 * (1._r8 - f4) / cndw) * fcur npool_to_deadcrootn_storage_demand = (nlc * f2 * f3 * (1._r8 - f4) / cndw) * (1._r8 - fcur) end if - if (ivt >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt)) then ! skip 2 generic crops cng = graincn(ivt) npool_to_livestemn_demand = (nlc * f3 * f4 / cnlw) * fcur @@ -1084,7 +1084,7 @@ subroutine calc_npool_to_components_flexiblecn( & npool_to_deadcrootn_storage_demand end if - if (ivt >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt)) then ! skip 2 generic crops npool_to_reproductiven_demand_tot = 0._r8 npool_to_reproductiven_storage_demand_tot = 0._r8 @@ -1122,7 +1122,7 @@ subroutine calc_npool_to_components_flexiblecn( & frNdemand_npool_to_deadcrootn = 0.0_r8 frNdemand_npool_to_deadcrootn_storage = 0.0_r8 end if - if (ivt >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt)) then ! skip 2 generic crops frNdemand_npool_to_livestemn = 0.0_r8 frNdemand_npool_to_livestemn_storage = 0.0_r8 @@ -1155,7 +1155,7 @@ subroutine calc_npool_to_components_flexiblecn( & frNdemand_npool_to_deadcrootn = npool_to_deadcrootn_demand / total_plant_Ndemand frNdemand_npool_to_deadcrootn_storage = npool_to_deadcrootn_storage_demand / total_plant_Ndemand end if - if (ivt >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt)) then ! skip 2 generic crops frNdemand_npool_to_livestemn = npool_to_livestemn_demand / total_plant_Ndemand frNdemand_npool_to_livestemn_storage = npool_to_livestemn_storage_demand / total_plant_Ndemand @@ -1194,7 +1194,7 @@ subroutine calc_npool_to_components_flexiblecn( & npool_to_deadcrootn = frNdemand_npool_to_deadcrootn * npool / dt npool_to_deadcrootn_storage = frNdemand_npool_to_deadcrootn_storage * npool / dt end if - if (ivt >= npcropmin) then ! skip 2 generic crops + if (is_prognostic_crop(ivt)) then ! skip 2 generic crops npool_to_livestemn = frNdemand_npool_to_livestemn * npool / dt npool_to_livestemn_storage = frNdemand_npool_to_livestemn_storage * npool / dt npool_to_deadstemn = frNdemand_npool_to_deadstemn * npool / dt diff --git a/src/biogeochem/SatellitePhenologyMod.F90 b/src/biogeochem/SatellitePhenologyMod.F90 index ba9610d536..3c6c9ba99b 100644 --- a/src/biogeochem/SatellitePhenologyMod.F90 +++ b/src/biogeochem/SatellitePhenologyMod.F90 @@ -13,11 +13,13 @@ module SatellitePhenologyMod use shr_log_mod , only : errMsg => shr_log_errMsg use decompMod , only : bounds_type use abortutils , only : endrun - use clm_varctl , only : iulog, use_lai_streams + use clm_varctl , only : iulog, use_lai_streams, single_column use perf_mod , only : t_startf, t_stopf use spmdMod , only : masterproc, mpicom, iam use laiStreamMod , only : lai_init, lai_advance, lai_interp - use ncdio_pio + use clm_varctl , only : use_fates + use ncdio_pio , only : ncd_pio_openfile, ncd_inqfdims, check_dim_size, ncd_io + use ncdio_pio , only : ncd_pio_closefile, file_desc_t ! ! !PUBLIC TYPES: implicit none @@ -55,6 +57,9 @@ subroutine SatellitePhenologyInit (bounds) ! ! !USES: use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod, only : errMsg => shr_log_errMsg + use clm_varctl, only : use_cn ! ! !ARGUMENTS: type(bounds_type), intent(in) :: bounds @@ -62,6 +67,12 @@ subroutine SatellitePhenologyInit (bounds) ! !LOCAL VARIABLES: integer :: ier ! error code !----------------------------------------------------------------------- + if ( (shr_fire_emis_mechcomps_n > 0) .and. (.not. use_cn) ) then + write(iulog,*) "Fire emissions can NOT be active for Satellite Phenology mode (SP)" // & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission requires BGC to be on rather than a Satelitte Pheonology (SP) case") + return + end if InterpMonths1 = -999 ! saved month index @@ -170,7 +181,7 @@ subroutine UpdateSatellitePhenologyCanopy(bounds, num_filter, filter, & use WaterDiagnosticBulkType , only : waterdiagnosticbulk_type use CanopyStateType , only : canopystate_type use PatchType , only : patch - use clm_varctl , only : use_fates + ! ! !ARGUMENTS: @@ -362,7 +373,7 @@ subroutine readAnnualVegetation (bounds, canopystate_inst) call ncd_pio_openfile (ncid, trim(locfn), 0) call ncd_inqfdims (ncid, isgrid2d, ni, nj, ns) - if (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj) then + if (.not. single_column .and. (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj)) then write(iulog,*)trim(subname), 'ldomain and input file do not match dims ' write(iulog,*)trim(subname), 'ldomain%ni,ni,= ',ldomain%ni,ni write(iulog,*)trim(subname), 'ldomain%nj,nj,= ',ldomain%nj,nj @@ -415,7 +426,9 @@ subroutine readMonthlyVegetation (bounds, fveg, months, canopystate_inst) use clm_time_manager , only : get_nstep use CanopyStateType , only : canopystate_type use PatchType , only : patch + use ColumnType , only : col use clm_varcon , only : grlnd + use clm_varpar , only : surfpft_lb,surfpft_ub use netcdf ! ! !ARGUMENTS: @@ -428,6 +441,7 @@ subroutine readMonthlyVegetation (bounds, fveg, months, canopystate_inst) character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id integer :: g,n,k,l,m,p,ni,nj,ns ! indices + integer :: c,ft ! indices integer :: dimid,varid ! input netCDF id's integer :: ntim ! number of input data time samples integer :: nlon_i ! number of input data longitudes @@ -485,25 +499,56 @@ subroutine readMonthlyVegetation (bounds, fveg, months, canopystate_inst) ! Assign lai/sai/hgtt/hgtb to the top [maxsoil_patches] patches ! as determined in subroutine surfrd - do p = bounds%begp,bounds%endp - g =patch%gridcell(p) - if (patch%itype(p) /= noveg) then ! vegetated pft - do l = 0, maxveg - if (l == patch%itype(p)) then - mlai2t(p,k) = mlai(g,l) - msai2t(p,k) = msai(g,l) - mhvt2t(p,k) = mhgtt(g,l) - mhvb2t(p,k) = mhgtb(g,l) - end if - end do - else ! non-vegetated pft - mlai2t(p,k) = 0._r8 - msai2t(p,k) = 0._r8 - mhvt2t(p,k) = 0._r8 - mhvb2t(p,k) = 0._r8 - end if - end do ! end of loop over patches - + if_fates: if(use_fates)then + do c = bounds%begc,bounds%endc + if(col%is_fates(c))then + do ft = surfpft_lb,surfpft_ub + p = ft + col%patchi(c) + g = patch%gridcell(p) + if (patch%is_fates(p)) then + mlai2t(p,k) = mlai(g,ft) + msai2t(p,k) = msai(g,ft) + mhvt2t(p,k) = mhgtt(g,ft) + mhvb2t(p,k) = mhgtb(g,ft) + else + ! Just in case if somehow a non-fates patch is on a fates column + mlai2t(p,k) = 0._r8 + msai2t(p,k) = 0._r8 + mhvt2t(p,k) = 0._r8 + mhvb2t(p,k) = 0._r8 + endif + end do + else + ! There are non-fates columns when you run with fates + ! gnu does not catch nans as fpes, so this is needed + ! to make intel happy. + mlai2t(col%patchi(c):col%patchf(c),k) = 0._r8 + msai2t(col%patchi(c):col%patchf(c),k) = 0._r8 + mhvt2t(col%patchi(c):col%patchf(c),k) = 0._r8 + mhvb2t(col%patchi(c):col%patchf(c),k) = 0._r8 + endif + end do + else + do p = bounds%begp,bounds%endp + g = patch%gridcell(p) + if (patch%itype(p) /= noveg ) then ! vegetated pft + do l = 0, maxveg + if (l == patch%itype(p)) then + mlai2t(p,k) = mlai(g,l) + msai2t(p,k) = msai(g,l) + mhvt2t(p,k) = mhgtt(g,l) + mhvb2t(p,k) = mhgtb(g,l) + end if + end do + else ! non-vegetated pft + mlai2t(p,k) = 0._r8 + msai2t(p,k) = 0._r8 + mhvt2t(p,k) = 0._r8 + mhvb2t(p,k) = 0._r8 + end if + end do ! end of loop over patches + end if if_fates + end do ! end of loop over months call ncd_pio_closefile(ncid) diff --git a/src/biogeochem/VOCEmissionMod.F90 b/src/biogeochem/VOCEmissionMod.F90 index 9fa48accb7..56481432f7 100644 --- a/src/biogeochem/VOCEmissionMod.F90 +++ b/src/biogeochem/VOCEmissionMod.F90 @@ -29,11 +29,11 @@ module VOCEmissionMod use SoilStateType , only : soilstate_type use SolarAbsorbedType , only : solarabs_type use TemperatureType , only : temperature_type - use PatchType , only : patch + use PatchType , only : patch use EnergyFluxType , only : energyflux_type ! implicit none - private + private ! ! !PUBLIC MEMBER FUNCTIONS: public :: VOCEmission @@ -44,21 +44,21 @@ module VOCEmissionMod real(r8) , pointer, private :: topt_out_patch (:) ! topt coefficient real(r8) , pointer, private :: alpha_out_patch (:) ! alpha coefficient real(r8) , pointer, private :: cp_out_patch (:) ! cp coefficient - real(r8) , pointer, private :: paru_out_patch (:) ! - real(r8) , pointer, private :: par24u_out_patch (:) ! - real(r8) , pointer, private :: par240u_out_patch (:) ! - real(r8) , pointer, private :: para_out_patch (:) ! - real(r8) , pointer, private :: par24a_out_patch (:) ! - real(r8) , pointer, private :: par240a_out_patch (:) ! - real(r8) , pointer, private :: gamma_out_patch (:) ! - real(r8) , pointer, private :: gammaL_out_patch (:) ! - real(r8) , pointer, private :: gammaT_out_patch (:) ! - real(r8) , pointer, private :: gammaP_out_patch (:) ! - real(r8) , pointer, private :: gammaA_out_patch (:) ! - real(r8) , pointer, private :: gammaS_out_patch (:) ! - real(r8) , pointer, private :: gammaC_out_patch (:) ! - real(r8) , pointer, private :: vocflx_tot_patch (:) ! total VOC flux into atmosphere [moles/m2/sec] - real(r8) , pointer, PUBLIC :: vocflx_patch (:,:) ! (num_mech_comps) MEGAN flux [moles/m2/sec] + real(r8) , pointer, private :: paru_out_patch (:) ! + real(r8) , pointer, private :: par24u_out_patch (:) ! + real(r8) , pointer, private :: par240u_out_patch (:) ! + real(r8) , pointer, private :: para_out_patch (:) ! + real(r8) , pointer, private :: par24a_out_patch (:) ! + real(r8) , pointer, private :: par240a_out_patch (:) ! + real(r8) , pointer, private :: gamma_out_patch (:) ! + real(r8) , pointer, private :: gammaL_out_patch (:) ! + real(r8) , pointer, private :: gammaT_out_patch (:) ! + real(r8) , pointer, private :: gammaP_out_patch (:) ! + real(r8) , pointer, private :: gammaA_out_patch (:) ! + real(r8) , pointer, private :: gammaS_out_patch (:) ! + real(r8) , pointer, private :: gammaC_out_patch (:) ! + real(r8) , pointer, private :: vocflx_tot_patch (:) ! total VOC flux into atmosphere [moles/m2/sec] + real(r8) , pointer, PUBLIC :: vocflx_patch (:,:) ! (num_mech_comps) MEGAN flux [moles/m2/sec] real(r8) , pointer, private :: efisop_grc (:,:) ! gridcell isoprene emission factors contains procedure, public :: Init @@ -75,6 +75,8 @@ module VOCEmissionMod type(megan_out_type), private, pointer :: meg_out(:) ! (n_megan_comps) points to output fluxes ! logical, parameter :: debug = .false. + logical :: megan_use_gamma_sm = .false. + real(r8) :: megan_min_gamma_sm = 0._r8 character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -83,12 +85,65 @@ module VOCEmissionMod contains !------------------------------------------------------------------------ - subroutine Init(this, bounds) + subroutine VOCReadNML(NLFilename) + ! + ! !DESCRIPTION: + ! Read the namelist for CropType + ! + ! !USES: + use fileutils , only : getavu, relavu, opnfil + use shr_nl_mod , only : shr_nl_find_group_name + use spmdMod , only : masterproc, mpicom + use shr_mpi_mod , only : shr_mpi_bcast + use clm_varctl , only : iulog + ! + ! !ARGUMENTS: + character(len=*), intent(in) :: NLFilename ! Namelist filename + ! + ! !LOCAL VARIABLES: + integer :: ierr ! error code + integer :: unitn ! unit for namelist file + + character(len=*), parameter :: subname = 'VOCReadNML' + character(len=*), parameter :: nmlname = 'megan_opts' + + namelist /megan_opts/ megan_use_gamma_sm, megan_min_gamma_sm + + if (masterproc) then + unitn = getavu() + write(iulog,*) 'Read in '//nmlname//' namelist' + call opnfil (NLFilename, unitn, 'F') + call shr_nl_find_group_name(unitn, nmlname, status=ierr) + if (ierr == 0) then + read(unitn, nml=megan_opts, iostat=ierr) + if (ierr /= 0) then + call endrun(msg="ERROR reading "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + end if + !else + ! call endrun(msg="ERROR could NOT find "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + end if + call relavu( unitn ) + end if + + call shr_mpi_bcast(megan_use_gamma_sm, mpicom) + call shr_mpi_bcast(megan_min_gamma_sm, mpicom) + + if (masterproc) then + write(iulog,*) ' ' + write(iulog,*) nmlname//' settings:' + write(iulog,nml=megan_opts) + write(iulog,*) ' ' + end if + + end subroutine VOCReadNML + + !------------------------------------------------------------------------ + subroutine Init(this, bounds, NLFilename) use clm_varctl, only : use_fates, use_fates_nocomp class(vocemis_type) :: this - type(bounds_type), intent(in) :: bounds - + type(bounds_type), intent(in) :: bounds + character(len=*) , intent(in) :: NLFilename ! Namelist filename if ( shr_megan_mechcomps_n > 0) then if (use_fates) then @@ -97,9 +152,13 @@ subroutine Init(this, bounds) errMsg(sourcefile, __LINE__)) end if end if - call this%InitAllocate(bounds) + call this%InitAllocate(bounds) call this%InitHistory(bounds) call this%InitCold(bounds) + + ! read run-time options + call VOCReadNML(NLFilename) + end if end subroutine Init @@ -115,7 +174,7 @@ subroutine InitAllocate(this, bounds) ! ! !ARGUMENTS: class(vocemis_type) :: this - type(bounds_type) , intent(in) :: bounds + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: i, imeg @@ -127,12 +186,11 @@ subroutine InitAllocate(this, bounds) type(shr_megan_megcomp_t), pointer :: meg_cmp !----------------------------------------------------------------------- - begg = bounds%begg; endg = bounds%endg begp = bounds%begp; endp = bounds%endp call megan_factors_init( shr_megan_factors_file ) - + meg_cmp => shr_megan_linkedlist do while(associated(meg_cmp)) allocate(meg_cmp%emis_factors(maxveg)) @@ -147,12 +205,12 @@ subroutine InitAllocate(this, bounds) allocate(this%topt_out_patch (begp:endp)) ; this%topt_out_patch (:) = nan allocate(this%topt_out_patch (begp:endp)) ; this%Eopt_out_patch (:) = nan allocate(this%alpha_out_patch (begp:endp)) ; this%alpha_out_patch (:) = nan - allocate(this%cp_out_patch (begp:endp)) ; this%cp_out_patch (:) = nan - allocate(this%para_out_patch (begp:endp)) ; this%para_out_patch (:) = nan - allocate(this%par24a_out_patch (begp:endp)) ; this%par24a_out_patch (:) = nan + allocate(this%cp_out_patch (begp:endp)) ; this%cp_out_patch (:) = nan + allocate(this%para_out_patch (begp:endp)) ; this%para_out_patch (:) = nan + allocate(this%par24a_out_patch (begp:endp)) ; this%par24a_out_patch (:) = nan allocate(this%par240a_out_patch (begp:endp)) ; this%par240a_out_patch (:) = nan - allocate(this%paru_out_patch (begp:endp)) ; this%paru_out_patch (:) = nan - allocate(this%par24u_out_patch (begp:endp)) ; this%par24u_out_patch (:) = nan + allocate(this%paru_out_patch (begp:endp)) ; this%paru_out_patch (:) = nan + allocate(this%par24u_out_patch (begp:endp)) ; this%par24u_out_patch (:) = nan allocate(this%par240u_out_patch (begp:endp)) ; this%par240u_out_patch (:) = nan allocate(this%gamma_out_patch (begp:endp)) ; this%gamma_out_patch (:) = nan allocate(this%gammaL_out_patch (begp:endp)) ; this%gammaL_out_patch (:) = nan @@ -165,13 +223,13 @@ subroutine InitAllocate(this, bounds) allocate(this%vocflx_tot_patch (begp:endp)); this%vocflx_tot_patch (:) = nan allocate(this%efisop_grc (6,begg:endg)); this%efisop_grc (:,:) = nan - allocate(meg_out(shr_megan_megcomps_n)) + allocate(meg_out(shr_megan_megcomps_n)) do i=1,shr_megan_megcomps_n allocate(meg_out(i)%flux_out(begp:endp)) meg_out(i)%flux_out(:) = 0._r8 end do - allocate(this%vocflx_patch(begp:endp,1:shr_megan_mechcomps_n)) + allocate(this%vocflx_patch(begp:endp,1:shr_megan_mechcomps_n)) this%vocflx_patch(:,1:shr_megan_mechcomps_n)= nan end subroutine InitAllocate @@ -182,13 +240,13 @@ subroutine InitHistory(this, bounds) ! !DESCRIPTION: ! Initialize history output fields for MEGAN emissions diagnositics ! - ! !USES + ! !USES use clm_varcon , only : spval use histFileMod , only : hist_addfld1d ! ! !ARGUMENTS: class(vocemis_type) :: this - type(bounds_type), intent(in) :: bounds + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES integer :: imeg, ii @@ -211,7 +269,7 @@ subroutine InitHistory(this, bounds) meg_cmp => meg_cmp%next_megcomp enddo - + this%vocflx_tot_patch(begp:endp)= spval call hist_addfld1d (fname='VOCFLXT', units='moles/m2/sec', & avgflag='A', long_name='total VOC flux into atmosphere', & @@ -267,17 +325,17 @@ subroutine InitHistory(this, bounds) avgflag='A', long_name='alpha coefficient for VOC calc', & ptr_patch=this%alpha_out_patch, set_lake=0._r8, default='inactive') - this%cp_out_patch(begp:endp) = spval + this%cp_out_patch(begp:endp) = spval call hist_addfld1d (fname='currentPatch', units='non', & avgflag='A', long_name='currentPatch coefficient for VOC calc', & ptr_patch=this%cp_out_patch, set_lake=0._r8, default='inactive') - this%paru_out_patch(begp:endp) = spval + this%paru_out_patch(begp:endp) = spval call hist_addfld1d (fname='PAR_sun', units='umol/m2/s', & avgflag='A', long_name='sunlit PAR', & ptr_patch=this%paru_out_patch, set_lake=0._r8, default='inactive') - this%par24u_out_patch(begp:endp) = spval + this%par24u_out_patch(begp:endp) = spval call hist_addfld1d (fname='PAR24_sun', units='umol/m2/s', & avgflag='A', long_name='sunlit PAR (24 hrs)', & ptr_patch=this%par24u_out_patch, set_lake=0._r8, default='inactive') @@ -287,12 +345,12 @@ subroutine InitHistory(this, bounds) avgflag='A', long_name='sunlit PAR (240 hrs)', & ptr_patch=this%par240u_out_patch, set_lake=0._r8, default='inactive') - this%para_out_patch(begp:endp) = spval + this%para_out_patch(begp:endp) = spval call hist_addfld1d (fname='PAR_shade', units='umol/m2/s', & avgflag='A', long_name='shade PAR', & ptr_patch=this%para_out_patch, set_lake=0._r8, default='inactive') - this%par24a_out_patch(begp:endp) = spval + this%par24a_out_patch(begp:endp) = spval call hist_addfld1d (fname='PAR24_shade', units='umol/m2/s', & avgflag='A', long_name='shade PAR (24 hrs)', & ptr_patch=this%par24a_out_patch, set_lake=0._r8, default='inactive') @@ -318,14 +376,14 @@ subroutine InitCold(this, bounds) ! ! !ARGUMENTS: class(vocemis_type) :: this - type(bounds_type), intent(in) :: bounds + type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: - logical :: readvar + logical :: readvar integer :: begg, endg type(file_desc_t) :: ncid ! netcdf id character(len=256) :: locfn ! local filename - real(r8) ,pointer :: temp_ef(:) ! read in - temporary EFs + real(r8) ,pointer :: temp_ef(:) ! read in - temporary EFs !----------------------------------------------------------------------- begg = bounds%begg; endg = bounds%endg @@ -387,7 +445,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & ! ! NEW DESCRIPTION ! Volatile organic compound emission ! This code simulates volatile organic compound emissions following - ! MEGAN (Model of Emissions of Gases and Aerosols from Nature) v2.1 + ! MEGAN (Model of Emissions of Gases and Aerosols from Nature) v2.1 ! for 20 compound classes. The original description of this ! algorithm (for isoprene only) can be found in Guenther et al., 2006 ! (we follow equations 2-9, 16-17, 20 for explicit canopy). @@ -397,12 +455,12 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & ! factors (epsilon) [ug m-2 h-1] which are specified for each of the 16 ! CLM Patches (in input file) OR in the case of isoprene, from ! mapped EFs for each PATCH which reflect species divergence of emissions, - ! particularly in North America. - ! The emission activity factor (gamma) [unitless] for includes + ! particularly in North America. + ! The emission activity factor (gamma) [unitless] for includes ! dependence on PPFT, temperature, LAI, leaf age and soil moisture. ! For isoprene only we also include the effect of CO2 inhibition as - ! described by Heald et al., 2009. - ! The canopy environment constant was calculated offline for CLM+CAM at + ! described by Heald et al., 2009. + ! The canopy environment constant was calculated offline for CLM+CAM at ! standard conditions. ! We assume that the escape efficiency (rho) here is unity following ! Guenther et al., 2006. @@ -410,7 +468,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & ! in preparation: Guenther, Heald et al., 2012 ! Subroutine written to operate at the patch level. ! - ! Input: to be read in with EFs and some parameters. + ! Input: to be read in with EFs and some parameters. ! Currently these are set in procedure init_EF_params ! Output: vocflx(shr_megan_mechcomps_n) !VOC flux [moles/m2/sec] ! @@ -420,7 +478,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & use GridcellType , only : grc ! ! !ARGUMENTS: - type(bounds_type) , intent(in) :: bounds + type(bounds_type) , intent(in) :: bounds integer , intent(in) :: num_soilp ! number of columns in soil patch filter integer , intent(in) :: filter_soilp(num_soilp) ! patch filter for soil type(atm2lnd_type) , intent(in) :: atm2lnd_inst @@ -451,7 +509,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & real(r8) :: par_sha ! temporary real(r8) :: par24_sha ! temporary real(r8) :: par240_sha ! temporary - + integer :: class_num, n_meg_comps, imech, imeg, ii integer :: l_pft_itype(bounds%begp:bounds%endp) ! local index of pft type ! that corresponds to pfts on megan factors @@ -462,6 +520,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & real(r8) :: co2_ppmv real(r8) :: vocflx_meg(shr_megan_megcomps_n) + real(r8) :: meg_coef ! factor used convert MEGAN units [micro-grams/m2/hr] to CAM srf emis units [g/m2/sec] real(r8), parameter :: megemis_units_factor = 1._r8/3600._r8/1.e6_r8 @@ -485,47 +544,47 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & call endrun( subname//' error: can NOT work without nlevcan == 1' ) end if - associate( & + associate( & btran => energyflux_inst%btran_patch , & ! Input: [real(r8) (:) ] transpiration wetness factor (0 to 1) - - forc_solad => atm2lnd_inst%forc_solad_downscaled_col, & ! Input: [real(r8) (:,:) ] direct beam radiation (visible only) - forc_solai => atm2lnd_inst%forc_solai_grc , & ! Input: [real(r8) (:,:) ] diffuse radiation (visible only) - forc_pbot => atm2lnd_inst%forc_pbot_downscaled_col , & ! Input: [real(r8) (:) ] downscaled atmospheric pressure (Pa) - forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) - forc_solad24 => atm2lnd_inst%fsd24_patch , & ! Input: [real(r8) (:) ] direct beam radiation last 24hrs (visible only) - forc_solad240 => atm2lnd_inst%fsd240_patch , & ! Input: [real(r8) (:) ] direct beam radiation last 240hrs (visible only) - forc_solai24 => atm2lnd_inst%fsi24_patch , & ! Input: [real(r8) (:) ] diffuse radiation last 24hrs (visible only) - forc_solai240 => atm2lnd_inst%fsi240_patch , & ! Input: [real(r8) (:) ] diffuse radiation last 240hrs (visible only) - - fsun => canopystate_inst%fsun_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy - fsun24 => canopystate_inst%fsun24_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy last 24 hrs - fsun240 => canopystate_inst%fsun240_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy last 240 hrs + + forc_solad => atm2lnd_inst%forc_solad_downscaled_col, & ! Input: [real(r8) (:,:) ] direct beam radiation (visible only) + forc_solai => atm2lnd_inst%forc_solai_grc , & ! Input: [real(r8) (:,:) ] diffuse radiation (visible only) + forc_pbot => atm2lnd_inst%forc_pbot_downscaled_col , & ! Input: [real(r8) (:) ] downscaled atmospheric pressure (Pa) + forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) + forc_solad24 => atm2lnd_inst%fsd24_patch , & ! Input: [real(r8) (:) ] direct beam radiation last 24hrs (visible only) + forc_solad240 => atm2lnd_inst%fsd240_patch , & ! Input: [real(r8) (:) ] direct beam radiation last 240hrs (visible only) + forc_solai24 => atm2lnd_inst%fsi24_patch , & ! Input: [real(r8) (:) ] diffuse radiation last 24hrs (visible only) + forc_solai240 => atm2lnd_inst%fsi240_patch , & ! Input: [real(r8) (:) ] diffuse radiation last 240hrs (visible only) + + fsun => canopystate_inst%fsun_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy + fsun24 => canopystate_inst%fsun24_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy last 24 hrs + fsun240 => canopystate_inst%fsun240_patch , & ! Input: [real(r8) (:) ] sunlit fraction of canopy last 240 hrs elai => canopystate_inst%elai_patch , & ! Input: [real(r8) (:) ] one-sided leaf area index with burying by snow elai240 => canopystate_inst%elai240_patch , & ! Input: [real(r8) (:) ] one-sided leaf area index with burying by snow last 240 hrs cisun_z => photosyns_inst%cisun_z_patch , & ! Input: [real(r8) (:,:) ] sunlit intracellular CO2 (Pa) cisha_z => photosyns_inst%cisha_z_patch , & ! Input: [real(r8) (:,:) ] shaded intracellular CO2 (Pa) - + t_veg => temperature_inst%t_veg_patch , & ! Input: [real(r8) (:) ] patch vegetation temperature (Kelvin) t_veg24 => temperature_inst%t_veg24_patch , & ! Input: [real(r8) (:) ] avg patch vegetation temperature for last 24 hrs t_veg240 => temperature_inst%t_veg240_patch , & ! Input: [real(r8) (:) ] avg patch vegetation temperature for last 240 hrs - - Eopt_out => vocemis_inst%Eopt_out_patch , & ! Output: [real(r8) (:) ] - topt_out => vocemis_inst%topt_out_patch , & ! Output: [real(r8) (:) ] - alpha_out => vocemis_inst%alpha_out_patch , & ! Output: [real(r8) (:) ] - cp_out => vocemis_inst%cp_out_patch , & ! Output: [real(r8) (:) ] - paru_out => vocemis_inst%paru_out_patch , & ! Output: [real(r8) (:) ] - par24u_out => vocemis_inst%par24u_out_patch , & ! Output: [real(r8) (:) ] - par240u_out => vocemis_inst%par240u_out_patch , & ! Output: [real(r8) (:) ] - para_out => vocemis_inst%para_out_patch , & ! Output: [real(r8) (:) ] - par24a_out => vocemis_inst%par24a_out_patch , & ! Output: [real(r8) (:) ] - par240a_out => vocemis_inst%par240a_out_patch , & ! Output: [real(r8) (:) ] - gammaL_out => vocemis_inst%gammaL_out_patch , & ! Output: [real(r8) (:) ] - gammaT_out => vocemis_inst%gammaT_out_patch , & ! Output: [real(r8) (:) ] - gammaP_out => vocemis_inst%gammaP_out_patch , & ! Output: [real(r8) (:) ] - gammaA_out => vocemis_inst%gammaA_out_patch , & ! Output: [real(r8) (:) ] - gammaS_out => vocemis_inst%gammaS_out_patch , & ! Output: [real(r8) (:) ] - gammaC_out => vocemis_inst%gammaC_out_patch , & ! Output: [real(r8) (:) ] - gamma_out => vocemis_inst%gamma_out_patch , & ! Output: [real(r8) (:) ] + + Eopt_out => vocemis_inst%Eopt_out_patch , & ! Output: [real(r8) (:) ] + topt_out => vocemis_inst%topt_out_patch , & ! Output: [real(r8) (:) ] + alpha_out => vocemis_inst%alpha_out_patch , & ! Output: [real(r8) (:) ] + cp_out => vocemis_inst%cp_out_patch , & ! Output: [real(r8) (:) ] + paru_out => vocemis_inst%paru_out_patch , & ! Output: [real(r8) (:) ] + par24u_out => vocemis_inst%par24u_out_patch , & ! Output: [real(r8) (:) ] + par240u_out => vocemis_inst%par240u_out_patch , & ! Output: [real(r8) (:) ] + para_out => vocemis_inst%para_out_patch , & ! Output: [real(r8) (:) ] + par24a_out => vocemis_inst%par24a_out_patch , & ! Output: [real(r8) (:) ] + par240a_out => vocemis_inst%par240a_out_patch , & ! Output: [real(r8) (:) ] + gammaL_out => vocemis_inst%gammaL_out_patch , & ! Output: [real(r8) (:) ] + gammaT_out => vocemis_inst%gammaT_out_patch , & ! Output: [real(r8) (:) ] + gammaP_out => vocemis_inst%gammaP_out_patch , & ! Output: [real(r8) (:) ] + gammaA_out => vocemis_inst%gammaA_out_patch , & ! Output: [real(r8) (:) ] + gammaS_out => vocemis_inst%gammaS_out_patch , & ! Output: [real(r8) (:) ] + gammaC_out => vocemis_inst%gammaC_out_patch , & ! Output: [real(r8) (:) ] + gamma_out => vocemis_inst%gamma_out_patch , & ! Output: [real(r8) (:) ] vocflx => vocemis_inst%vocflx_patch , & ! Output: [real(r8) (:,:) ] VOC flux [moles/m2/sec] vocflx_tot => vocemis_inst%vocflx_tot_patch & ! Output: [real(r8) (:) ] VOC flux [moles/m2/sec] ) @@ -565,9 +624,9 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & ! initialize EF epsilon=0._r8 - + ! initalize to zero since this might not alway get set - ! this needs to be within the fp loop ... + ! this needs to be within the fp loop ... vocflx_meg(:) = 0._r8 ! calculate VOC emissions for non-bare ground Patches @@ -590,7 +649,11 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & gamma_l = get_gamma_L(fsun240(p), elai(p)) ! Impact of soil moisture on isoprene emission - gamma_sm = get_gamma_SM(btran(p)) + if (megan_use_gamma_sm) then + gamma_sm = get_gamma_SM(btran(p)) + else + gamma_sm = 1._r8 + end if ! Loop through VOCs for light, temperature and leaf age activity factor & apply ! all final activity factors to baseline emission factors @@ -624,7 +687,7 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & gamma_a = get_gamma_A(l_pft_itype(p), elai240(p),elai(p),class_num) ! Activity factor for CO2 (only for isoprene) - if (trim(meg_cmp%name) == 'isoprene') then + if (trim(meg_cmp%name) == 'isoprene') then co2_ppmv = 1.e6_r8*forc_pco2(g)/forc_pbot(c) gamma_c = get_gamma_C(cisun_z(p,1),cisha_z(p,1),forc_pbot(c),fsun(p), co2_ppmv) ! Check of valid intercellular co2 pressure values. @@ -642,13 +705,14 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & if ( (gamma >=0.0_r8) .and. (gamma< 100._r8) ) then - vocflx_meg(imeg) = meg_cmp%coeff * epsilon * gamma * megemis_units_factor / meg_cmp%molec_weight ! moles/m2/sec + vocflx_meg(imeg) = epsilon * gamma * megemis_units_factor / meg_cmp%molec_weight ! moles/m2/sec ! assign to arrays for history file output (not weighted by landfrac) meg_out(imeg)%flux_out(p) = meg_out(imeg)%flux_out(p) & + epsilon * gamma * megemis_units_factor*1.e-3_r8 ! Kg/m2/sec - if (imeg==1) then - ! + + if (imeg==1) then + ! gamma_out(p)=gamma gammaP_out(p)=gamma_p gammaT_out(p)=gamma_t @@ -682,19 +746,26 @@ subroutine VOCEmission (bounds, num_soilp, filter_soilp, & meg_cmp => meg_cmp%next_megcomp enddo meg_cmp_loop - ! sum up the megan compound fluxes for the fluxes of chem mechanism compounds + ! sum up the megan compound fluxes for the fluxes of chem mechanism compounds do imech = 1,shr_megan_mechcomps_n n_meg_comps = shr_megan_mechcomps(imech)%n_megan_comps + if (debug) then + write(iulog,'(a,i4,3a)') 'MEGAN: imech ',imech,' atm chem tracer ',trim(shr_megan_mechcomps(imech)%name),' fluxes composed of: ' + endif do imeg = 1,n_meg_comps ! loop over number of megan compounds that make up the nth mechanism compoud + meg_coef = shr_megan_mechcomps(imech)%megan_comps(imeg)%coeff ii = shr_megan_mechcomps(imech)%megan_comps(imeg)%ptr%index - vocflx(p,imech) = vocflx(p,imech) + vocflx_meg(ii) + vocflx(p,imech) = vocflx(p,imech) + meg_coef*vocflx_meg(ii) + if (debug) then + write(iulog,'(a,f10.4,2a)') ' ',meg_coef, ' * ',trim(shr_megan_mechcomps(imech)%megan_comps(imeg)%ptr%name) + endif enddo vocflx_tot(p) = vocflx_tot(p) + vocflx(p,imech) ! moles/m2/sec enddo end if ! patch%itype(1:15 only) - enddo ! fp + enddo ! fp end associate @@ -706,7 +777,7 @@ function get_map_EF(ivt_in, g_in, vocemis_inst) ! Get mapped EF for isoprene ! Use gridded values for 6 Patches specified by MEGAN following ! Guenther et al. (2006). Map the maxveg CLM Patches to these 6. - ! Units: [ug m-2 h-1] + ! Units: [ug m-2 h-1] ! ! !ARGUMENTS: integer, intent(in) :: ivt_in @@ -720,6 +791,7 @@ function get_map_EF(ivt_in, g_in, vocemis_inst) ! vocemis_inst%efisop_patch ! Output: [real(r8) (:,:)] emission factors for isoprene for each patch [ug m-2 h-1] get_map_EF = 0._r8 + if ( ivt_in == ndllf_evr_tmp_tree & .or. ivt_in == ndllf_evr_brl_tree) then !fineleaf evergreen get_map_EF = vocemis_inst%efisop_grc(2,g_in) @@ -742,12 +814,12 @@ end function get_map_EF !----------------------------------------------------------------------- function get_gamma_P(par_sun_in, par24_sun_in, par240_sun_in, par_sha_in, par24_sha_in, par240_sha_in, & - fsun_in, fsun240_in, forc_solad240_in,forc_solai240_in, LDF_in, cp, alpha) + fsun_in, fsun240_in, forc_solad240_in,forc_solai240_in, LDF_in, cp, alpha) ! ! Activity factor for PPFD (Guenther et al., 2006): all light dependent species !------------------------- ! With distinction between sunlit and shaded leafs, weight scalings by - ! fsun and fshade + ! fsun and fshade ! Scale total incident par by fraction of sunlit leaves (added on 1/2002) ! fvitt -- forc_solad240, forc_solai240 can be zero when CLM finidat is specified @@ -799,11 +871,11 @@ function get_gamma_P(par_sun_in, par24_sun_in, par240_sun_in, par_sha_in, par24_ gamma_p_LDF = gamma_p_LDF + (1._r8-fsun_in) * (cp*alpha*par_sha_in*(1._r8 + alpha*alpha*par_sha_in*par_sha_in)**(-0.5_r8)) else ! With fixed alpha and cp (from MEGAN User's Guide): - ! SUN: direct + diffuse + ! SUN: direct + diffuse alpha = alpha_fix cp = cp_fix gamma_p_LDF = fsun_in * ( cp * alpha*par_sun_in * (1._r8 + alpha*alpha*par_sun_in*par_sun_in)**(-0.5_r8) ) - ! SHADE: diffuse + ! SHADE: diffuse gamma_p_LDF = gamma_p_LDF + (1._r8-fsun_in) * (cp*alpha*par_sha_in*(1._r8 + alpha*alpha*par_sha_in*par_sha_in)**(-0.5_r8)) end if @@ -832,7 +904,7 @@ function get_gamma_L(fsun240_in,elai_in) real(r8), parameter :: cce = 0.30_r8 ! factor to set emissions to unity @ std real(r8), parameter :: cce1 = 0.24_r8 ! same as Cce but for non-accumulated vars !----------------------------------------------------------------------- - if ( (fsun240_in > 0.0_r8) .and. (fsun240_in < 1.e30_r8) ) then + if ( (fsun240_in > 0.0_r8) .and. (fsun240_in < 1.e30_r8) ) then get_gamma_L = cce * elai_in else get_gamma_L = cce1 * elai_in @@ -866,6 +938,8 @@ function get_gamma_SM(btran_in) get_gamma_SM = 1._r8 / (1._r8 + b1 * exp(a1 * (btran_in - btran_threshold))) endif + get_gamma_SM = max(get_gamma_SM, megan_min_gamma_sm) + end function get_gamma_SM !----------------------------------------------------------------------- @@ -896,8 +970,8 @@ function get_gamma_T(t_veg240_in, t_veg24_in,t_veg_in, ct1_in, ct2_in, betaT_in, real(r8),intent(in) :: betaT_in real(r8),intent(in) :: LDF_in real(r8),intent(in) :: Ceo_in - real(r8),intent(out) :: Eopt ! temporary - real(r8),intent(out) :: topt ! temporary + real(r8),intent(out) :: Eopt ! temporary + real(r8),intent(out) :: topt ! temporary ! ! !LOCAL VARIABLES: real(r8) :: get_gamma_T @@ -924,7 +998,7 @@ function get_gamma_T(t_veg240_in, t_veg24_in,t_veg_in, ct1_in, ct2_in, betaT_in, !----------------------------------------------------------------------- ! Light dependent fraction (Guenther et al., 2006) - if ( (t_veg240_in > 0.0_r8) .and. (t_veg240_in < 1.e30_r8) ) then + if ( (t_veg240_in > 0.0_r8) .and. (t_veg240_in < 1.e30_r8) ) then ! topt and Eopt from eq 8 and 9: topt = co1 + (co2 * (t_veg240_in-tstd0)) if ( (ivt_in == nbrdlf_dcd_brl_shrub) ) then ! boreal-deciduous-shrub @@ -949,14 +1023,14 @@ function get_gamma_T(t_veg240_in, t_veg24_in,t_veg_in, ct1_in, ct2_in, betaT_in, else gamma_t_LDF = Eopt * ( ct2_in * exp(ct1_in * x) / (ct2_in - ct1_in * (1._r8 - exp(ct2_in * x))) ) endif - - + + ! Light independent fraction (of exp(beta T) form) gamma_t_LIF = exp(betaT_in * (t_veg_in - tstd)) - + ! Calculate total activity factor for light as a function of light-dependent fraction !-------------------------------- - get_gamma_T = (1-LDF_in)*gamma_T_LIF + LDF_in*gamma_T_LDF + get_gamma_T = (1-LDF_in)*gamma_T_LIF + LDF_in*gamma_T_LDF end function get_gamma_T @@ -967,8 +1041,8 @@ function get_gamma_A(ivt_in, elai240_in, elai_in, nclass_in) !----------------------------- ! If not CNDV elai is constant therefore gamma_a=1.0 ! gamma_a set to unity for evergreens (Patches 1, 2, 4, 5) - ! Note that we assume here that the time step is shorter than the number of - !days after budbreak required to induce isoprene emissions (ti=12 days) and + ! Note that we assume here that the time step is shorter than the number of + !days after budbreak required to induce isoprene emissions (ti=12 days) and ! the number of days after budbreak to reach peak emission (tm=28 days) ! ! !ARGUMENTS: @@ -985,7 +1059,7 @@ function get_gamma_A(ivt_in, elai240_in, elai_in, nclass_in) !----------------------------------------------------------------------- if ( (ivt_in == ndllf_dcd_brl_tree) .or. (ivt_in >= nbrdlf_dcd_trp_tree) ) then ! non-evergreen - if ( (elai240_in > 0.0_r8) .and. (elai240_in < 1.e30_r8) )then + if ( (elai240_in > 0.0_r8) .and. (elai240_in < 1.e30_r8) )then elai_prev = 2._r8*elai240_in-elai_in ! have accumulated average lai over last 10 days if (elai_prev == elai_in) then fnew = 0.0_r8 @@ -1003,27 +1077,27 @@ function get_gamma_A(ivt_in, elai240_in, elai_in, nclass_in) fmat = (elai_prev / elai_in) fold = 0.0_r8 end if - + get_gamma_A = fnew*Anew(nclass_in) + fgro*Agro(nclass_in) + fmat*Amat(nclass_in) + fold*Aold(nclass_in) else get_gamma_A = 1.0_r8 end if - + else get_gamma_A = 1.0_r8 end if - + end function get_gamma_A !----------------------------------------------------------------------- function get_gamma_C(cisun_in,cisha_in,forc_pbot_in,fsun_in, co2_ppmv) - + ! Activity factor for instantaneous CO2 changes (Heald et al., 2009) !------------------------- ! With distinction between sunlit and shaded leaves, weight scalings by - ! fsun and fshade + ! fsun and fshade ! ! !CALLED FROM: VOCEmission ! @@ -1049,9 +1123,9 @@ function get_gamma_C(cisun_in,cisha_in,forc_pbot_in,fsun_in, co2_ppmv) real(r8) :: get_gamma_C ! local variables - real(r8) :: Ismax ! empirical coeff for CO2 - real(r8) :: h ! empirical coeff for CO2 - real(r8) :: Cstar ! empirical coeff for CO2 + real(r8) :: Ismax ! empirical coeff for CO2 + real(r8) :: h ! empirical coeff for CO2 + real(r8) :: Cstar ! empirical coeff for CO2 real(r8) :: fint ! interpolation fraction for CO2 real(r8) :: ci ! temporary sunlight/shade weighted cisun & cisha (umolCO2/mol) real(r8) :: gamma_ci ! short-term exposure gamma @@ -1096,13 +1170,13 @@ function get_gamma_C(cisun_in,cisha_in,forc_pbot_in,fsun_in, co2_ppmv) ! pressure to get mixing ratio (umolCO2/mol) if ( (cisun_in .eq. cisun_in) .and. (cisha_in .eq. cisha_in) .and. (forc_pbot_in > 0._r8) .and. (fsun_in > 0._r8) ) then ci = ( fsun_in*cisun_in + (1._r8-fsun_in)*cisha_in )/forc_pbot_in * 1.e6_r8 - gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) + gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) else if ( (cisun_in > 0.0_r8) .and. (cisun_in < 1.e30_r8) .and. (forc_pbot_in > 0._r8) .and. (fsun_in .eq. 1._r8) ) then ci = cisun_in/forc_pbot_in * 1.e6_r8 - gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) + gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) else if ( (cisha_in > 0.0_r8) .and. (cisha_in < 1.e30_r8) .and. (forc_pbot_in > 0._r8) .and. (fsun_in .eq. 0._r8) ) then ci = cisha_in/forc_pbot_in * 1.e6_r8 - gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) + gamma_ci = Ismax - ( (Ismax*ci**h)/(Cstar**h+ci**h) ) else gamma_ci = 1._r8 end if @@ -1112,5 +1186,3 @@ function get_gamma_C(cisun_in,cisha_in,forc_pbot_in,fsun_in, co2_ppmv) end function get_gamma_C end module VOCEmissionMod - - diff --git a/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt b/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt new file mode 100644 index 0000000000..9a9885508c --- /dev/null +++ b/src/biogeochem/test/CIsoAtmTimeSeries_test/CMakeLists.txt @@ -0,0 +1,11 @@ +set(pfunit_sources + test_CIsoAtmTimeSeries.pf) + +add_pfunit_ctest(CIsoAtmTimeSeries + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share) + +# I don't think we need esmf here +# LINK_LIBRARIES clm csm_share esmf +# EXTRA_FINALIZE unittest_finalize_esmf +# EXTRA_USE unittestInitializeAndFinalize) diff --git a/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf b/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf new file mode 100644 index 0000000000..01866b7617 --- /dev/null +++ b/src/biogeochem/test/CIsoAtmTimeSeries_test/test_CIsoAtmTimeSeries.pf @@ -0,0 +1,277 @@ +module test_CIsoAtmTimeSeries + + ! Tests of CNCIsoAtmTimeSeriesReadMod + + use funit + use CIsoAtmTimeSeriesMod + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl, only : use_c13, use_c14 + + implicit none + + @TestCase + type, extends(TestCase) :: TestCIsoAtmTimeSeries + contains + procedure :: setUp + procedure :: tearDown + end type TestCIsoAtmTimeSeries + + character(len=200) :: expected_msg + +contains + + subroutine setUp(this) + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c13 = .true. + use_c14 = .true. + use_c13_timeseries = .true. + use_c14_bombspike = .true. + ! Set these filenames to /dev/null as it's a file guaranteed to exist on Linux systems + atm_c13_filename = '/dev/null' + atm_c14_filename = '/dev/null' + + end subroutine setUp + + subroutine tearDown(this) + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + end subroutine tearDown + + @Test + subroutine check_both_timeseries_on(this) + ! Check that it works when both timeseries are on without using streams + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_on + + @Test + subroutine check_both_timeseries_streams_on(this) + ! Check that it works when both timeseries are on and using streams + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = '/dev/null', & + stream_fldfilename_atm_c14_in = '/dev/null' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_streams_on + + @Test + subroutine check_both_timeseries_off(this) + ! Check that it works when both timeseries are off + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + use_c13_timeseries = .false. + use_c14_bombspike = .false. + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', & + stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_both_timeseries_off + + @Test + subroutine check_ciso_off(this) + ! Check that it works when both carbon isotopes are off + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + atm_c13_filename = '' + atm_c14_filename = '' + use_c13_timeseries = .false. + use_c14_bombspike = .false. + use_c13 = .false. + use_c14 = .false. + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', & + stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + call CIsoSetControl() + call CIsoLogControl() + + end subroutine check_ciso_off + + @Test + subroutine abort_if_both_c13_plain_and_stream_timeseries_files_set(this) + ! Check that it aborts if both the plain and stream C13 timeseries files are set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + @assertTrue(use_c13_timeseries) + atm_c13_filename = 'plain_c13_timeseries_file' + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + @assertTrue(len_trim(atm_c14_filename) /= 0) + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = 'stream_c13_timeseries_file', stream_fldfilename_atm_c14_in = ' ') + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries TRUE but both atm_c13_filename AND stream_fldfilename_atm_c13 are set and only one should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c13_plain_and_stream_timeseries_files_set + + @Test + subroutine abort_if_both_c14_plain_and_stream_timeseries_files_set(this) + ! Check that it aborts if both the plain and stream C14 timeseries files are set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + atm_c14_filename = 'plain_c14_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = 'stream_c14_timeseries_file' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike TRUE but both atm_c14_filename AND stream_fldfilename_atm_c14 are set and only one should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c14_plain_and_stream_timeseries_files_set + + @Test + subroutine abort_if_both_c13_plain_and_stream_timeseries_files_blank(this) + ! Check that it aborts if both the plain and stream C13 timeseries files are blank + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + @assertTrue(use_c13_timeseries) + atm_c13_filename = ' ' + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + @assertTrue(len_trim(atm_c14_filename) /= 0) + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ', stream_fldfilename_atm_c14_in = ' ') + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries TRUE but neither atm_c13_filename nor stream_fldfilename_atm_c13 are set and one or the other needs to be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c13_plain_and_stream_timeseries_files_blank + + @Test + subroutine abort_if_both_c14_plain_and_stream_timeseries_files_blank(this) + ! Check that it aborts if both the plain and stream C14 timeseries files are blank + class(TestCIsoAtmTimeSeries), intent(inout) :: this + @assertTrue(use_c14) + @assertTrue(use_c14_bombspike) + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike TRUE but neither atm_c14_filename nor stream_fldfilename_atm_c14 are set and one or the other needs to be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_both_c14_plain_and_stream_timeseries_files_blank + + @Test + subroutine abort_if_c13_timeseries_off_and_plain_timeseries_file_set(this) + ! Check that it aborts if c13 timeseries off, but the plain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + use_c13_timeseries = .false. + @assertFalse(use_c13_timeseries) + atm_c13_filename = 'plain_c13_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_timeseries_off_and_plain_timeseries_file_set + + @Test + subroutine abort_if_c13_timeseries_off_and_stream_timeseries_file_set(this) + ! Check that it aborts if c13 timeseries off, but the streamplain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + @assertTrue(use_c13) + use_c13_timeseries = .false. + @assertFalse(use_c13_timeseries) + atm_c13_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = 'stream_c13_filename ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13_timeseries is false but either atm_c13_filename or stream_fldfilename_atm_c13 are set and neither should be" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_timeseries_off_and_stream_timeseries_file_set + + @Test + subroutine abort_if_c14_bombspike_off_and_plain_timeseries_file_set(this) + ! Check that it aborts if c14 timeseries off, but the pain timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + integer :: i + + ! This should be true whether C14 is on or off + do i = 1, 2 + if ( i == 1 ) then + @assertTrue(use_c14) + else + use_c14 = .false. + end if + use_c14_bombspike = .false. + atm_c14_filename = 'plain_c14_timeseries_file' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be" + @assertExceptionRaised(expected_msg) + end do + + end subroutine abort_if_c14_bombspike_off_and_plain_timeseries_file_set + + @Test + subroutine abort_if_c14_bombspike_off_and_stream_timeseries_file_set(this) + ! Check that it aborts if c14 timeseries off, but the stream timeseries file is set + class(TestCIsoAtmTimeSeries), intent(inout) :: this + integer :: i + + ! This should be true whether C14 is on or off + do i = 1, 2 + if ( i == 1 ) then + @assertTrue(use_c14) + else + use_c14 = .false. + end if + use_c14_bombspike = .false. + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = 'stream_c14_filename' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14_bombspike false but either atm_c14_filename or stream_fldfilename_atm_c14 is set and neither should be" + @assertExceptionRaised(expected_msg) + end do + + end subroutine abort_if_c14_bombspike_off_and_stream_timeseries_file_set + + @Test + subroutine abort_if_c13_off_but_c13_timeseries_on(this) + ! Check that it aborts if c13 is off, but the c13 timeseries is on + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c13 = .false. + use_c13_timeseries = .true. + atm_c13_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c13_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c13 is false but use_c13_timeseries is TRUE (use_c13_timeseries can only be TRUE if use_c13 is TRUE)" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c13_off_but_c13_timeseries_on + + @Test + subroutine abort_if_c14_off_but_c14_bombspike_on(this) + ! Check that it aborts if c14 is off, but the c14 bombspike is on + class(TestCIsoAtmTimeSeries), intent(inout) :: this + + use_c14 = .false. + @assertFalse(use_c14) + use_c14_bombspike = .true. + atm_c14_filename = ' ' + call CIsoSetNMLInputs( stream_fldfilename_atm_c14_in = ' ' ) + call CIsoCheckNMLInputs() + expected_msg = "ABORTED: use_c14 is false but use_c14_bombspike is TRUE (use_c14_bombspike can only be TRUE if use_c14 is TRUE)" + @assertExceptionRaised(expected_msg) + + end subroutine abort_if_c14_off_but_c14_bombspike_on + +end module test_CIsoAtmTimeSeries diff --git a/src/biogeochem/test/CMakeLists.txt b/src/biogeochem/test/CMakeLists.txt index e22a720523..e47e2429b6 100644 --- a/src/biogeochem/test/CMakeLists.txt +++ b/src/biogeochem/test/CMakeLists.txt @@ -1,5 +1,8 @@ add_subdirectory(Species_test) +add_subdirectory(CIsoAtmTimeSeries_test) add_subdirectory(CNVegComputeSeed_test) add_subdirectory(CNPhenology_test) add_subdirectory(Latbaset_test) add_subdirectory(DustEmis_test) +add_subdirectory(CNFireFactory_test) +add_subdirectory(FATESFireFactory_test) diff --git a/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt b/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt new file mode 100644 index 0000000000..032e0fa953 --- /dev/null +++ b/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt @@ -0,0 +1,7 @@ +set (pfunit_sources + test_CNFireFactory.pf +) + +add_pfunit_ctest(CNFireFActory + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf) diff --git a/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf b/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf new file mode 100644 index 0000000000..5b0f52c8d4 --- /dev/null +++ b/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf @@ -0,0 +1,240 @@ +module test_CNFireFactory + + ! Tests of CNFireFactory + + use funit + use unittestSubgridMod, only : bounds + use FireMethodType , only : fire_method_type + use CNFireFactoryMod + use ESMF, only : ESMF_SUCCESS + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl, only : use_cn, iulog + + implicit none + + @TestCase + type, extends(TestCase) :: TestCNFireFactory + logical :: initialized = .false. + class(fire_method_type), allocatable :: cnfire_method + contains + procedure :: setUp + procedure :: tearDown + procedure :: FireFactInit + procedure :: turn_fire_emis_on + end type TestCNFireFactory + + contains + + !----------------------------------------------------------------------- + + subroutine setUp(this) + use shr_log_mod, only : shr_log_setLogUnit + use ESMF, only : ESMF_Initialize, ESMF_IsInitialized + use shr_sys_mod, only : shr_sys_system + class(TestCNFireFactory), intent(inout) :: this + + integer :: rc + logical :: esmf_initialized + + esmf_initialized = ESMF_IsInitialized( rc=rc ) + if (rc /= ESMF_SUCCESS) then + stop 'Error in ESMF_IsInitialized' + end if + if ( .not. esmf_initialized )then + call ESMF_Initialize( rc=rc ) + if (rc /= ESMF_SUCCESS) then + stop 'Error in ESMF_Initialize' + end if + end if + use_cn = .true. + iulog = 6 + call shr_log_setLogUnit(iulog) + this%initialized = .false. + + end subroutine setUp + !----------------------------------------------------------------------- + + subroutine tearDown(this) + use shr_sys_mod, only : shr_sys_system + use shr_log_mod, only : shr_log_setLogUnit + class(TestCNFireFactory), intent(inout) :: this + + integer :: rc + + ! A clean method should be added to the fire method class structures + if ( this%initialized )then + call this%cnfire_method%FireClean() + deallocate( this%cnfire_method ) + end if + ! IMPORTANT NOTE: DO NOT CALL ESMF_Finalize HERE! + ! Calling ESMF_Finalize here, with full ESMF, means you couldn't call ESMF_Initialize again + this%initialized = .false. + + end subroutine tearDown + + !----------------------------------------------------------------------- + + subroutine FireFactInit(this, fire_method) + class(TestCNFireFactory), intent(inout) :: this + character(len=*), intent(in) :: fire_method + + if ( trim(fire_method) /= "DO_NOT_SET") then + call CNFireSetFireMethod( fire_method_in=fire_method ) + end if + call create_cnfire_method(this%cnfire_method) + call this%cnfire_method%FireInit(bounds) + this%initialized = .true. + + end subroutine FireFactInit + + !----------------------------------------------------------------------- + + subroutine turn_fire_emis_on(this) + use shr_fire_emis_mod, only : shr_fire_emis_readnl, shr_fire_emis_mechcomps_n + use shr_sys_mod, only : shr_sys_system + class(TestCNFireFactory), intent(inout) :: this + + ! NOTE!: This is bad that this can be done directly without having it done through a namelist, or setter! + shr_fire_emis_mechcomps_n = 2 + end subroutine turn_fire_emis_on + + !----------------------------------------------------------------------- + + @Test + subroutine fire_method_not_set_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%FireFactInit( fire_method = "DO_NOT_SET") + expected_msg = "ABORTED: Unknown option for namelist item fire_method: UNSET" + @assertExceptionRaised(expected_msg) + + end subroutine fire_method_not_set_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fire_method_bad_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%FireFactInit( fire_method = "ZZTOP") ! Set to an invalid option + expected_msg = "ABORTED: Unknown option for namelist item fire_method: ZZTOP" + @assertExceptionRaised(expected_msg) + + end subroutine fire_method_bad_fails + + !----------------------------------------------------------------------- + + @Test + subroutine nofire_with_fire_emis_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%turn_fire_emis_on() + call this%FireFactInit( fire_method = "nofire") + expected_msg = "ABORTED: Having fire emissions on requires fire_method to be something besides nofire" + @assertExceptionRaised(expected_msg) + + end subroutine nofire_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine spcase_with_fire_emis_fails(this) + use SatellitePhenologyMod, only : SatellitePhenologyInit + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_cn = .false. + call this%turn_fire_emis_on() + call SatellitePhenologyInit( bounds ) + expected_msg = "ABORTED: Fire emission requires BGC to be on rather than a Satelitte Pheonology (SP) case" + @assertExceptionRaised(expected_msg) + + end subroutine spcase_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine li2014_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2014qianfrc") + + end subroutine li2014_works + + !----------------------------------------------------------------------- + + ! + ! Test that default settings with ALL of the Li Fire options work by default + ! (These tests are done one by one which makes them dead simple, but take up more code + ! see the looping option below) + ! + @Test + subroutine li2016_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2016crufrc") + + end subroutine li2016_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2021_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2021gswpfrc") + + end subroutine li2021_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2024_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2024gswpfrc") + + end subroutine li2024_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2024crujra_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2024crujra") + + end subroutine li2024crujra_works + + !----------------------------------------------------------------------- + + ! + ! Test that default settings with ALL of the Li Fire options work when fire emissions + ! are turned on. This test is done with a loop rather than one by one as above. + ! This cuts down on the total test code, but also means that setUp and tearDown have + ! to be explicitly called for example. Setup is always called before a test, and tearDown + ! after each test) + ! + + @Test + subroutine all_li_options_with_fire_emis_works(this) + class(TestCNFireFactory), intent(inout) :: this + integer, parameter :: noptions = 5 + integer :: i + character(len=*), parameter :: fire_method_options(noptions) = (/ 'li2014qianfrc', 'li2016crufrc ', 'li2021gswpfrc', 'li2024gswpfrc', 'li2024crujra '/) + + do i = 1, noptions + call this%setUp() ! This is needed because of the loop over all options + call this%turn_fire_emis_on() + call this%FireFactInit( fire_method = fire_method_options(i) ) + call this%tearDown() ! This is needed because of the loop over all options + end do + + end subroutine all_li_options_with_fire_emis_works + + !----------------------------------------------------------------------- + +end module test_CNFireFactory diff --git a/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt b/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt new file mode 100644 index 0000000000..80ac4114e7 --- /dev/null +++ b/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt @@ -0,0 +1,7 @@ +set (pfunit_sources + test_FATESFireFactory.pf +) + +add_pfunit_ctest(FATESFireFActory + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf) diff --git a/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf b/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf new file mode 100644 index 0000000000..fba39098a8 --- /dev/null +++ b/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf @@ -0,0 +1,167 @@ +module test_FATESFireFactory + + ! Tests of FATESFireFactory + + use funit + use unittestSubgridMod, only : bounds + use FATESFireBase, only : fates_fire_base_type + use FATESFireFactoryMod + use shr_kind_mod , only : r8 => shr_kind_r8, CS => shr_kind_CS + use clm_varctl, only : iulog, fates_spitfire_mode, use_fates, use_fates_sp, use_fates_ed_st3 + + implicit none + + @TestCase + type, extends(TestCase) :: TestFATESFireFactory + logical :: initialized = .false. + class(fates_fire_base_type), allocatable :: fates_fire_method + contains + procedure :: setUp + procedure :: tearDown + procedure :: FireFactInit + procedure :: turn_fire_emis_on + end type TestFATESFireFactory + + contains + + !----------------------------------------------------------------------- + + subroutine setUp(this) + use shr_log_mod, only : shr_log_setLogUnit + use ESMF, only : ESMF_Initialize + use shr_sys_mod, only : shr_sys_system + class(TestFATESFireFactory), intent(inout) :: this + + call ESMF_Initialize() + use_fates = .true. + use_fates_sp = .false. + use_fates_ed_st3 = .false. + fates_spitfire_mode = no_fire + iulog = 6 + call shr_log_setLogUnit(iulog) + this%initialized = .false. + + end subroutine setUp + !----------------------------------------------------------------------- + + subroutine tearDown(this) + use shr_sys_mod, only : shr_sys_system + use shr_log_mod, only : shr_log_setLogUnit + class(TestFATESFireFactory), intent(inout) :: this + + if ( this%initialized )then + call this%fates_fire_method%FireClean() + deallocate( this%fates_fire_method ) + end if + this%initialized = .false. + + end subroutine tearDown + + !----------------------------------------------------------------------- + + subroutine FireFactInit(this) + class(TestFATESFireFactory), intent(inout) :: this + + call create_fates_fire_data_method(this%fates_fire_method) + call this%fates_fire_method%FireInit(bounds) + this%initialized = .true. + + end subroutine FireFactInit + + !----------------------------------------------------------------------- + + subroutine turn_fire_emis_on(this) + use shr_fire_emis_mod, only : shr_fire_emis_readnl, shr_fire_emis_mechcomps_n + use shr_sys_mod, only : shr_sys_system + class(TestFATESFireFactory), intent(inout) :: this + + ! NOTE!: This is bad that this can be done directly without having it done through a namelist, or setter! + shr_fire_emis_mechcomps_n = 2 + end subroutine turn_fire_emis_on + + !----------------------------------------------------------------------- + + @Test + subroutine fates_spitfire_mode_bad_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + fates_spitfire_mode = -1 + call this%FireFactInit( ) + expected_msg = "ABORTED: Unknown option for namelist item fates_spitfire_mode:" + @assertExceptionRaised(expected_msg) + + end subroutine fates_spitfire_mode_bad_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_sp_case_with_fire_emis_fails(this) + use clm_varctl, only : use_fates_sp + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_fates_sp = .true. + call this%turn_fire_emis_on() + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES requires FATES to NOT be in Satellite Phenology (SP) mode" + @assertExceptionRaised(expected_msg) + + end subroutine fates_sp_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_st3_case_with_fire_emis_fails(this) + use clm_varctl, only : use_fates_ed_st3 + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_fates_ed_st3 = .true. + call this%turn_fire_emis_on() + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES requires FATES to NOT be in Static Stand Structure mode" + @assertExceptionRaised(expected_msg) + + end subroutine fates_st3_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_no_spitfire_case_with_fire_emis_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%turn_fire_emis_on() + fates_spitfire_mode = no_fire + call this%FireFactInit( ) + expected_msg = "ABORTED: Having fire emissions on requires fates_spitfire_mode to be something besides no_fire (0)" + @assertExceptionRaised(expected_msg) + + end subroutine fates_no_spitfire_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine all_fates_spitfire_options_with_fire_emis_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + integer, parameter :: noptions = anthro_suppression + integer :: i + character(100) :: expected_msg + + do i = scalar_lightning, noptions + call this%setUp() + call this%turn_fire_emis_on() + fates_spitfire_mode = i + use_fates_sp = .false. + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES can NOT currently be turned on (see issue #1045)" + @assertExceptionRaised(expected_msg) + call this%tearDown() + end do + + end subroutine all_fates_spitfire_options_with_fire_emis_fails + + !----------------------------------------------------------------------- + +end module test_FATESFireFactory diff --git a/src/biogeophys/CanopyFluxesMod.F90 b/src/biogeophys/CanopyFluxesMod.F90 index 93a4ff12b4..ee3f89f119 100644 --- a/src/biogeophys/CanopyFluxesMod.F90 +++ b/src/biogeophys/CanopyFluxesMod.F90 @@ -13,7 +13,7 @@ module CanopyFluxesMod use shr_kind_mod , only : r8 => shr_kind_r8 use shr_log_mod , only : errMsg => shr_log_errMsg use abortutils , only : endrun - use clm_varctl , only : iulog, use_cn, use_lch4, use_c13, use_c14, use_cndv, use_fates, & + use clm_varctl , only : iulog, use_cn, use_lch4, use_c13, use_cndv, use_fates, & use_luna, use_hydrstress, use_biomass_heat_storage, z0param_method use clm_varpar , only : nlevgrnd, nlevsno, nlevcan, mxpft use pftconMod , only : pftcon @@ -229,7 +229,7 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, use clm_time_manager , only : get_step_size_real, get_prev_date, is_near_local_noon use clm_varcon , only : sb, cpair, hvap, vkc, grav, denice, c_to_b use clm_varcon , only : denh2o, tfrz, tlsai_crit, alpha_aero - use clm_varcon , only : c14ratio, spval + use clm_varcon , only : spval use clm_varcon , only : c_water, c_dry_biomass, c_to_b use clm_varcon , only : nu_param, cd1_param use perf_mod , only : t_startf, t_stopf @@ -354,7 +354,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, real(r8) :: err(bounds%begp:bounds%endp) ! balance error real(r8) :: erre ! balance error real(r8) :: co2(bounds%begp:bounds%endp) ! atmospheric co2 partial pressure (pa) - real(r8) :: c13o2(bounds%begp:bounds%endp) ! atmospheric c13o2 partial pressure (pa) real(r8) :: o2(bounds%begp:bounds%endp) ! atmospheric o2 partial pressure (pa) real(r8) :: svpts(bounds%begp:bounds%endp) ! saturation vapor pressure at t_veg (pa) real(r8) :: eah(bounds%begp:bounds%endp) ! canopy air vapor pressure (pa) @@ -479,7 +478,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, forc_u => atm2lnd_inst%forc_u_grc , & ! Input: [real(r8) (:) ] atmospheric wind speed in east direction (m/s) forc_v => atm2lnd_inst%forc_v_grc , & ! Input: [real(r8) (:) ] atmospheric wind speed in north direction (m/s) forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) - forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc , & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) forc_po2 => atm2lnd_inst%forc_po2_grc , & ! Input: [real(r8) (:) ] partial pressure o2 (Pa) tc_ref2m => humanindex_inst%tc_ref2m_patch , & ! Output: [real(r8) (:) ] 2 m height surface air temperature (C) @@ -961,10 +959,6 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, co2(p) = forc_pco2(g) o2(p) = forc_po2(g) - if ( use_c13 ) then - c13o2(p) = forc_pc13o2(g) - end if - ! Initialize flux profile nmozsgn(p) = 0 @@ -1644,7 +1638,7 @@ subroutine CanopyFluxes(bounds, num_exposedvegp, filter_exposedvegp, ! Determine total photosynthesis - call PhotosynthesisTotal(fn, filterp, & + call PhotosynthesisTotal(bounds, fn, filterp, & atm2lnd_inst, canopystate_inst, photosyns_inst) ! Calculate water use efficiency diff --git a/src/biogeophys/PhotosynthesisMod.F90 b/src/biogeophys/PhotosynthesisMod.F90 index b8fd577382..098144446d 100644 --- a/src/biogeophys/PhotosynthesisMod.F90 +++ b/src/biogeophys/PhotosynthesisMod.F90 @@ -1,4 +1,4 @@ -module PhotosynthesisMod +module PhotosynthesisMod #include "shr_assert.h" @@ -21,7 +21,6 @@ module PhotosynthesisMod use decompMod , only : bounds_type, subgrid_level_patch use QuadraticMod , only : quadratic use pftconMod , only : pftcon - use CIsoAtmTimeseriesMod, only : C14BombSpike, use_c14_bombspike, C13TimeSeries, use_c13_timeseries, nsectors_c14 use atm2lndType , only : atm2lnd_type use CanopyStateType , only : canopystate_type use CNVegnitrogenstateType, only : cnveg_nitrogenstate_type @@ -172,6 +171,8 @@ module PhotosynthesisMod real(r8), pointer, public :: rc13_psnsun_patch (:) ! patch C13O2/C12O2 in sunlit canopy psn flux real(r8), pointer, public :: rc13_psnsha_patch (:) ! patch C13O2/C12O2 in shaded canopy psn flux + real(r8), pointer, public :: rc14_canair_patch (:) ! patch C14O2/C12O2 in canopy air + real(r8), pointer, public :: psnsun_patch (:) ! patch sunlit leaf photosynthesis (umol CO2/m**2/s) real(r8), pointer, public :: psnsha_patch (:) ! patch shaded leaf photosynthesis (umol CO2/m**2/s) real(r8), pointer, public :: c13_psnsun_patch (:) ! patch c13 sunlit leaf photosynthesis (umol 13CO2/m**2/s) @@ -346,6 +347,7 @@ subroutine InitAllocate(this, bounds) allocate(this%rc13_canair_patch (begp:endp)) ; this%rc13_canair_patch (:) = nan allocate(this%rc13_psnsun_patch (begp:endp)) ; this%rc13_psnsun_patch (:) = nan allocate(this%rc13_psnsha_patch (begp:endp)) ; this%rc13_psnsha_patch (:) = nan + allocate(this%rc14_canair_patch (begp:endp)) ; this%rc14_canair_patch (:) = nan allocate(this%cisun_z_patch (begp:endp,1:nlevcan)) ; this%cisun_z_patch (:,:) = nan allocate(this%cisha_z_patch (begp:endp,1:nlevcan)) ; this%cisha_z_patch (:,:) = nan @@ -449,6 +451,7 @@ subroutine Clean(this) deallocate(this%rc13_canair_patch ) deallocate(this%rc13_psnsun_patch ) deallocate(this%rc13_psnsha_patch ) + deallocate(this%rc14_canair_patch ) deallocate(this%cisun_z_patch ) deallocate(this%cisha_z_patch ) @@ -579,7 +582,7 @@ subroutine InitHistory(this, bounds) this%rc13_canair_patch(begp:endp) = spval call hist_addfld1d (fname='RC13_CANAIR', units='proportion', & avgflag='A', long_name='C13/C(12+13) for canopy air', & - ptr_patch=this%rc13_canair_patch, default='inactive') + ptr_patch=this%rc13_canair_patch, set_spec=spval, default='inactive') this%rc13_psnsun_patch(begp:endp) = spval call hist_addfld1d (fname='RC13_PSNSUN', units='proportion', & @@ -592,6 +595,13 @@ subroutine InitHistory(this, bounds) ptr_patch=this%rc13_psnsha_patch, default='inactive') endif + if ( use_c14 ) then + this%rc14_canair_patch(begp:endp) = spval + call hist_addfld1d (fname='RC14_CANAIR', units='proportion', & + avgflag='A', long_name='C14/C(12+13) for canopy air', & + ptr_patch=this%rc14_canair_patch, set_spec=spval, default='inactive') + end if + ! Canopy physiology if ( use_c13 ) then @@ -1034,6 +1044,11 @@ subroutine Restart(this, bounds, ncid, flag) dim1name='pft', long_name='', units='', & interpinic_flag='interp', readvar=readvar, data=this%rc13_psnsha_patch) endif + if ( use_c14 ) then + call restartvar(ncid=ncid, flag=flag, varname='rc14_canair', xtype=ncd_double, & + dim1name='pft', long_name='', units='', & + interpinic_flag='interp', readvar=readvar, data=this%rc14_canair_patch) + end if call restartvar(ncid=ncid, flag=flag, varname='GSSUN', xtype=ncd_double, & dim1name='pft', dim2name='levcan', switchdim=.true., & @@ -1173,10 +1188,13 @@ subroutine TimeStepInit (this, bounds) .or. lun%itype(l) == istice & .or. lun%itype(l) == istwet) then if (use_c13) then - this%rc13_canair_patch(p) = 0._r8 + this%rc13_canair_patch(p) = spval this%rc13_psnsun_patch(p) = 0._r8 this%rc13_psnsha_patch(p) = 0._r8 end if + if (use_c14) then + this%rc14_canair_patch(p) = spval + end if end if end do @@ -1197,11 +1215,15 @@ subroutine NewPatchInit (this, p) if ( use_c13 ) then this%alphapsnsun_patch(p) = 0._r8 this%alphapsnsha_patch(p) = 0._r8 - this%rc13_canair_patch(p) = 0._r8 + this%rc13_canair_patch(p) = spval this%rc13_psnsun_patch(p) = 0._r8 this%rc13_psnsha_patch(p) = 0._r8 endif + if ( use_c14 ) then + this%rc14_canair_patch(p) = spval + end if + this%psnsun_patch(p) = 0._r8 this%psnsha_patch(p) = 0._r8 @@ -1234,9 +1256,9 @@ subroutine Photosynthesis ( bounds, fn, filterp, & use clm_varcon , only : rgas, tfrz, spval use GridcellType , only : grc use clm_time_manager , only : get_step_size_real, is_near_local_noon - use clm_varctl , only : cnallocate_carbon_only + use clm_varctl , only : allocate_carbon_only use clm_varctl , only : lnc_opt, reduce_dayl_factor, vcmax_opt - use pftconMod , only : nbrdlf_dcd_tmp_shrub, npcropmin + use pftconMod , only : nbrdlf_dcd_tmp_shrub ! ! !ARGUMENTS: @@ -1619,7 +1641,7 @@ subroutine Photosynthesis ( bounds, fn, filterp, & if (.not. use_cn) then vcmax25top = vcmax25top * fnitr(patch%itype(p)) else - if ( CNAllocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) + if ( Allocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) end if else if (vcmax_opt == 3) then vcmax25top = ( i_vcad(patch%itype(p)) + s_vcad(patch%itype(p)) * lnc(p) ) * dayl_factor(p) @@ -2040,12 +2062,15 @@ subroutine Photosynthesis ( bounds, fn, filterp, & end subroutine Photosynthesis !------------------------------------------------------------------------------ - subroutine PhotosynthesisTotal (fn, filterp, & + subroutine PhotosynthesisTotal (bounds, fn, filterp, & atm2lnd_inst, canopystate_inst, photosyns_inst) ! ! Determine total photosynthesis ! + use CIsoAtmTimeseriesMod, only : C14BombSpike, C13TimeSeries + use CIsoAtmTimeseriesMod, only : rc13_atm_grc, rc14_atm_grc ! !ARGUMENTS: + type(bounds_type) , intent(in) :: bounds integer , intent(in) :: fn ! size of pft filter integer , intent(in) :: filterp(fn) ! patch filter type(atm2lnd_type) , intent(in) :: atm2lnd_inst @@ -2055,13 +2080,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & ! !LOCAL VARIABLES: integer :: f,fp,p,l,g ! indices - real(r8) :: rc14_atm(nsectors_c14), rc13_atm - integer :: sector_c14 !----------------------------------------------------------------------- associate( & forc_pco2 => atm2lnd_inst%forc_pco2_grc , & ! Input: [real(r8) (:) ] partial pressure co2 (Pa) - forc_pc13o2 => atm2lnd_inst%forc_pc13o2_grc , & ! Input: [real(r8) (:) ] partial pressure c13o2 (Pa) forc_po2 => atm2lnd_inst%forc_po2_grc , & ! Input: [real(r8) (:) ] partial pressure o2 (Pa) laisun => canopystate_inst%laisun_patch , & ! Input: [real(r8) (:) ] sunlit leaf area @@ -2072,6 +2094,7 @@ subroutine PhotosynthesisTotal (fn, filterp, & rc13_canair => photosyns_inst%rc13_canair_patch , & ! Output: [real(r8) (:) ] C13O2/C12O2 in canopy air rc13_psnsun => photosyns_inst%rc13_psnsun_patch , & ! Output: [real(r8) (:) ] C13O2/C12O2 in sunlit canopy psn flux rc13_psnsha => photosyns_inst%rc13_psnsha_patch , & ! Output: [real(r8) (:) ] C13O2/C12O2 in shaded canopy psn flux + rc14_canair => photosyns_inst%rc14_canair_patch , & ! Output: [real(r8) (:) ] C1342/C12O2 in canopy air alphapsnsun => photosyns_inst%alphapsnsun_patch , & ! Output: [real(r8) (:) ] fractionation factor in sunlit canopy psn flux alphapsnsha => photosyns_inst%alphapsnsha_patch , & ! Output: [real(r8) (:) ] fractionation factor in shaded canopy psn flux psnsun_wc => photosyns_inst%psnsun_wc_patch , & ! Output: [real(r8) (:) ] Rubsico-limited sunlit leaf photosynthesis (umol CO2 /m**2/ s) @@ -2090,19 +2113,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & fpsn_wp => photosyns_inst%fpsn_wp_patch & ! Output: [real(r8) (:) ] product-limited photosynthesis (umol CO2 /m**2 /s) ) - if ( use_c14 ) then - if (use_c14_bombspike) then - call C14BombSpike(rc14_atm) - else - rc14_atm(:) = c14ratio - end if - end if - - if ( use_c13 ) then - if (use_c13_timeseries) then - call C13TimeSeries(rc13_atm) - end if - end if + ! Get the current C13/C14 ratio in the atmosphere from timeseries data or the fixed values + ! These calls fill the data: rc13_atm_grc and rc14_atm_grc + if ( use_c14 ) call C14BombSpike(bounds) + if ( use_c13 ) call C13TimeSeries(bounds, atm2lnd_inst) do f = 1, fn p = filterp(f) @@ -2117,11 +2131,7 @@ subroutine PhotosynthesisTotal (fn, filterp, & if (use_cn) then if ( use_c13 ) then - if (use_c13_timeseries) then - rc13_canair(p) = rc13_atm - else - rc13_canair(p) = forc_pc13o2(g)/(forc_pco2(g) - forc_pc13o2(g)) - endif + rc13_canair(p) = rc13_atm_grc(g) rc13_psnsun(p) = rc13_canair(p)/alphapsnsun(p) rc13_psnsha(p) = rc13_canair(p)/alphapsnsha(p) c13_psnsun(p) = psnsun(p) * (rc13_psnsun(p)/(1._r8+rc13_psnsun(p))) @@ -2133,17 +2143,10 @@ subroutine PhotosynthesisTotal (fn, filterp, & endif if ( use_c14 ) then - ! determine latitute sector for radiocarbon bomb spike inputs - if ( grc%latdeg(g) .ge. 30._r8 ) then - sector_c14 = 1 - else if ( grc%latdeg(g) .ge. -30._r8 ) then - sector_c14 = 2 - else - sector_c14 = 3 - endif + rc14_canair(p) = rc14_atm_grc(g) - c14_psnsun(p) = rc14_atm(sector_c14) * psnsun(p) - c14_psnsha(p) = rc14_atm(sector_c14) * psnsha(p) + c14_psnsun(p) = rc14_atm_grc(g) * psnsun(p) + c14_psnsha(p) = rc14_atm_grc(g) * psnsha(p) endif end if @@ -2724,10 +2727,10 @@ subroutine PhotosynthesisHydraulicStress ( bounds, fn, filterp, & use clm_varcon , only : rgas, tfrz, rpi, spval use GridcellType , only : grc use clm_time_manager , only : get_step_size_real, is_near_local_noon - use clm_varctl , only : cnallocate_carbon_only + use clm_varctl , only : allocate_carbon_only use clm_varctl , only : lnc_opt, reduce_dayl_factor, vcmax_opt use clm_varpar , only : nlevsoi - use pftconMod , only : nbrdlf_dcd_tmp_shrub, npcropmin + use pftconMod , only : nbrdlf_dcd_tmp_shrub use ColumnType , only : col ! @@ -3241,7 +3244,7 @@ subroutine PhotosynthesisHydraulicStress ( bounds, fn, filterp, & if (.not. use_cn) then vcmax25top = vcmax25top * fnitr(patch%itype(p)) else - if ( CNAllocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) + if ( Allocate_Carbon_only() ) vcmax25top = vcmax25top * fnitr(patch%itype(p)) end if else if (vcmax_opt == 3) then vcmax25top = ( i_vcad(patch%itype(p)) + s_vcad(patch%itype(p)) * lnc(p) ) * dayl_factor(p) diff --git a/src/biogeophys/SoilHydrologyType.F90 b/src/biogeophys/SoilHydrologyType.F90 index 07ad2ca45b..9be9073bc0 100644 --- a/src/biogeophys/SoilHydrologyType.F90 +++ b/src/biogeophys/SoilHydrologyType.F90 @@ -51,6 +51,8 @@ Module SoilHydrologyType real(r8), pointer :: top_ice_col (:) ! col VIC ice len in top layers real(r8), pointer :: top_moist_limited_col(:) ! col VIC soil moisture in top layers, limited to no greater than top_max_moist_col real(r8), pointer :: ice_col (:,:) ! col VIC soil ice (kg/m2) for VIC soil layers + real(r8), pointer :: qout_col (:,:) ! flux of water out of soil layer [mm H2O/s] + real(r8), pointer :: qin_col (:,:) ! flux of water into soil layer [mm H2O/s] contains @@ -142,6 +144,8 @@ subroutine InitAllocate(this, bounds) allocate(this%top_ice_col (begc:endc)) ; this%top_ice_col (:) = nan allocate(this%top_moist_limited_col(begc:endc)) ; this%top_moist_limited_col(:) = nan allocate(this%ice_col (begc:endc,nlayert)) ; this%ice_col (:,:) = nan + allocate(this%qout_col (begc:endc,nlevsoi)) ; this%qout_col (:,:) = nan + allocate(this%qin_col (begc:endc,nlevsoi)) ; this%qin_col (:,:) = nan end subroutine InitAllocate @@ -149,7 +153,7 @@ end subroutine InitAllocate subroutine InitHistory(this, bounds, use_aquifer_layer) ! ! !USES: - use histFileMod , only : hist_addfld1d + use histFileMod , only : hist_addfld1d, hist_addfld2d ! ! !ARGUMENTS: class(soilhydrology_type) :: this @@ -192,6 +196,16 @@ subroutine InitHistory(this, bounds, use_aquifer_layer) avgflag='A', long_name='perched water table depth (natural vegetated and crop landunits only)', & ptr_col=this%zwt_perched_col, l2g_scale_type='veg') + this%qout_col(begc:endc, :) = spval + call hist_addfld2d (fname="QOUT", units='mm H2O/s', type2d='levsoi', & + avgflag='A', long_name='flux of water out of soil layer', & + ptr_col=this%qout_col, default='inactive') + + this%qin_col(begc:endc, :) = spval + call hist_addfld2d (fname="QIN", units='mm H2O/s', type2d='levsoi', & + avgflag='A', long_name='flux of water into soil layer', & + ptr_col=this%qin_col, default='inactive') + end subroutine InitHistory !----------------------------------------------------------------------- @@ -287,7 +301,6 @@ subroutine Restart(this, bounds, ncid, flag) character(len=*) , intent(in) :: flag ! 'read' or 'write' ! ! !LOCAL VARIABLES: - integer :: j,c ! indices logical :: readvar ! determine if variable is on initial file !----------------------------------------------------------------------- diff --git a/src/biogeophys/SoilWaterMovementMod.F90 b/src/biogeophys/SoilWaterMovementMod.F90 index 85bcf42c5e..dd4f48090f 100644 --- a/src/biogeophys/SoilWaterMovementMod.F90 +++ b/src/biogeophys/SoilWaterMovementMod.F90 @@ -579,7 +579,8 @@ subroutine soilwater_zengdecker2009(bounds, num_hydrologyc, filter_hydrologyc, & zwt => soilhydrology_inst%zwt_col , & ! Input: [real(r8) (:) ] water table depth (m) icefrac => soilhydrology_inst%icefrac_col , & ! Input: [real(r8) (:,:) ] fraction of ice hkdepth => soilhydrology_inst%hkdepth_col , & ! Input: [real(r8) (:) ] decay factor (m) - + qout_col => soilhydrology_inst%qout_col , & ! Output: [real(r8) (:,:) ] soil water out of the bottom, mm h2o/s + qin_col => soilhydrology_inst%qin_col , & ! Output: [real(r8) (:,:) ] soil water into the bottom, mm h2o/s smpmin => soilstate_inst%smpmin_col , & ! Input: [real(r8) (:) ] restriction for min of soil potential (mm) watsat => soilstate_inst%watsat_col , & ! Input: [real(r8) (:,:) ] volumetric soil water at saturation (porosity) hksat => soilstate_inst%hksat_col , & ! Input: [real(r8) (:,:) ] hydraulic conductivity at saturation (mm H2O /s) @@ -787,7 +788,8 @@ subroutine soilwater_zengdecker2009(bounds, num_hydrologyc, filter_hydrologyc, & amx(c,j) = 0._r8 bmx(c,j) = dzmm(c,j)*(sdamp+1._r8/dtime) + dqodw1(c,j) cmx(c,j) = dqodw2(c,j) - + qin_col(c,j) = qin(c,j) + qout_col(c,j) = qout(c,j) end do ! Nodes j=2 to j=nlevsoi-1 @@ -811,7 +813,8 @@ subroutine soilwater_zengdecker2009(bounds, num_hydrologyc, filter_hydrologyc, & amx(c,j) = -dqidw0(c,j) bmx(c,j) = dzmm(c,j)/dtime - dqidw1(c,j) + dqodw1(c,j) cmx(c,j) = dqodw2(c,j) - + qin_col(c,j) = qin(c,j) + qout_col(c,j) = qout(c,j) end do end do @@ -833,6 +836,8 @@ subroutine soilwater_zengdecker2009(bounds, num_hydrologyc, filter_hydrologyc, & amx(c,j) = -dqidw0(c,j) bmx(c,j) = dzmm(c,j)/dtime - dqidw1(c,j) + dqodw1(c,j) cmx(c,j) = 0._r8 + qin_col(c,j) = qin(c,j) + qout_col(c,j) = qout(c,j) ! next set up aquifer layer; hydrologically inactive rmx(c,j+1) = 0._r8 @@ -883,6 +888,8 @@ subroutine soilwater_zengdecker2009(bounds, num_hydrologyc, filter_hydrologyc, & amx(c,j+1) = -dqidw0(c,j+1) bmx(c,j+1) = dzmm(c,j+1)/dtime - dqidw1(c,j+1) + dqodw1(c,j+1) cmx(c,j+1) = 0._r8 + qin_col(c,j) = qin(c,j) + qout_col(c,j) = qout(c,j) endif end do @@ -1154,7 +1161,8 @@ subroutine soilwater_moisture_form(bounds, num_hydrologyc, & qcharge => soilhydrology_inst%qcharge_col , & ! Input: [real(r8) (:) ] aquifer recharge rate (mm/s) zwt => soilhydrology_inst%zwt_col , & ! Input: [real(r8) (:) ] water table depth (m) - + qout_col => soilhydrology_inst%qout_col , & ! Output: [real(r8) (:,:) ] soil water out of the bottom, mm h2o/s + qin_col => soilhydrology_inst%qin_col , & ! Output: [real(r8) (:,:) ] soil water into the bottom, mm h2o/s watsat => soilstate_inst%watsat_col , & ! Input: [real(r8) (:,:) ] volumetric soil water at saturation (porosity) smp_l => soilstate_inst%smp_l_col , & ! Input: [real(r8) (:,:) ] soil matrix potential [mm] hk_l => soilstate_inst%hk_l_col , & ! Input: [real(r8) (:,:) ] hydraulic conductivity (mm/s) @@ -1402,7 +1410,8 @@ subroutine soilwater_moisture_form(bounds, num_hydrologyc, & ! call endrun(subname // ':: negative soil moisture values found!') endif end do - + qin_col(c,1:nlayers) = qin(c,1:nlayers) + qout_col(c,1:nlayers) = qout(c,1:nlayers) end do ! spatial loop diff --git a/src/biogeophys/SurfaceAlbedoMod.F90 b/src/biogeophys/SurfaceAlbedoMod.F90 index dfcdda900b..d8d71ae41d 100644 --- a/src/biogeophys/SurfaceAlbedoMod.F90 +++ b/src/biogeophys/SurfaceAlbedoMod.F90 @@ -902,23 +902,6 @@ subroutine SurfaceAlbedo(bounds,nc, & end if end do - ! Weight reflectance/transmittance by lai and sai - ! Only perform on vegetated patches where coszen > 0 - - do fp = 1,num_vegsol - p = filter_vegsol(fp) - wl(p) = elai(p) / max( elai(p)+esai(p), mpe ) - ws(p) = esai(p) / max( elai(p)+esai(p), mpe ) - end do - - do ib = 1, numrad - do fp = 1,num_vegsol - p = filter_vegsol(fp) - rho(p,ib) = max( rhol(patch%itype(p),ib)*wl(p) + rhos(patch%itype(p),ib)*ws(p), mpe ) - tau(p,ib) = max( taul(patch%itype(p),ib)*wl(p) + taus(patch%itype(p),ib)*ws(p), mpe ) - end do - end do - ! Diagnose number of canopy layers for radiative transfer, in increments of dincmax. ! Add to number of layers so long as cumulative leaf+stem area does not exceed total ! leaf+stem area. Then add any remaining leaf+stem area to next layer and exit the loop. @@ -1086,7 +1069,24 @@ subroutine SurfaceAlbedo(bounds,nc, & call clm_fates%wrap_canopy_radiation(bounds, nc, fcansno(bounds%begp:bounds%endp), surfalb_inst) else - + + ! Weight reflectance/transmittance by lai and sai + ! Only perform on vegetated patches where coszen > 0 + + do fp = 1,num_vegsol + p = filter_vegsol(fp) + wl(p) = elai(p) / max( elai(p)+esai(p), mpe ) + ws(p) = esai(p) / max( elai(p)+esai(p), mpe ) + end do + + do ib = 1, numrad + do fp = 1,num_vegsol + p = filter_vegsol(fp) + rho(p,ib) = max( rhol(patch%itype(p),ib)*wl(p) + rhos(patch%itype(p),ib)*ws(p), mpe ) + tau(p,ib) = max( taul(patch%itype(p),ib)*wl(p) + taus(patch%itype(p),ib)*ws(p), mpe ) + end do + end do + call TwoStream (bounds, filter_vegsol, num_vegsol, & coszen_patch(bounds%begp:bounds%endp), & rho(bounds%begp:bounds%endp, :), & diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 899da6b882..58e4c93e7b 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1414,7 +1414,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ use accumulMod , only : update_accum_field, extract_accum_field, markreset_accum_field use clm_time_manager , only : is_doy_in_interval, get_curr_calday - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use CropType, only : crop_type ! ! !ARGUMENTS @@ -1485,7 +1485,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ((month > 9 .or. month < 4) .and. lat < 0._r8) ! Replace with read-in gdd20 accumulation season, if needed and valid ! (If these aren't being read in or they're invalid, they'll be -1) - if (stream_gdd20_seasons_tt .and. patch%itype(p) >= npcropmin) then + if (stream_gdd20_seasons_tt .and. is_prognostic_crop(patch%itype(p))) then gdd20_season_start = int(gdd20_season_starts(p)) gdd20_season_end = int(gdd20_season_ends(p)) if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then diff --git a/src/biogeophys/UrbanParamsType.F90 b/src/biogeophys/UrbanParamsType.F90 index 4b7b80e4fe..c6443897fe 100644 --- a/src/biogeophys/UrbanParamsType.F90 +++ b/src/biogeophys/UrbanParamsType.F90 @@ -9,9 +9,9 @@ module UrbanParamsType use shr_log_mod , only : errMsg => shr_log_errMsg use abortutils , only : endrun use decompMod , only : bounds_type, subgrid_level_gridcell, subgrid_level_landunit - use clm_varctl , only : iulog, fsurdat + use clm_varctl , only : iulog, fsurdat, single_column use clm_varcon , only : grlnd, spval - use LandunitType , only : lun + use LandunitType , only : lun ! implicit none save @@ -26,21 +26,21 @@ module UrbanParamsType ! ! !PRIVATE TYPE type urbinp_type - real(r8), pointer :: canyon_hwr (:,:) - real(r8), pointer :: wtlunit_roof (:,:) - real(r8), pointer :: wtroad_perv (:,:) - real(r8), pointer :: em_roof (:,:) - real(r8), pointer :: em_improad (:,:) - real(r8), pointer :: em_perroad (:,:) - real(r8), pointer :: em_wall (:,:) - real(r8), pointer :: alb_roof_dir (:,:,:) - real(r8), pointer :: alb_roof_dif (:,:,:) - real(r8), pointer :: alb_improad_dir (:,:,:) - real(r8), pointer :: alb_improad_dif (:,:,:) - real(r8), pointer :: alb_perroad_dir (:,:,:) - real(r8), pointer :: alb_perroad_dif (:,:,:) - real(r8), pointer :: alb_wall_dir (:,:,:) - real(r8), pointer :: alb_wall_dif (:,:,:) + real(r8), pointer :: canyon_hwr (:,:) + real(r8), pointer :: wtlunit_roof (:,:) + real(r8), pointer :: wtroad_perv (:,:) + real(r8), pointer :: em_roof (:,:) + real(r8), pointer :: em_improad (:,:) + real(r8), pointer :: em_perroad (:,:) + real(r8), pointer :: em_wall (:,:) + real(r8), pointer :: alb_roof_dir (:,:,:) + real(r8), pointer :: alb_roof_dif (:,:,:) + real(r8), pointer :: alb_improad_dir (:,:,:) + real(r8), pointer :: alb_improad_dif (:,:,:) + real(r8), pointer :: alb_perroad_dir (:,:,:) + real(r8), pointer :: alb_perroad_dif (:,:,:) + real(r8), pointer :: alb_wall_dir (:,:,:) + real(r8), pointer :: alb_wall_dif (:,:,:) real(r8), pointer :: ht_roof (:,:) real(r8), pointer :: wind_hgt_canyon (:,:) real(r8), pointer :: tk_wall (:,:,:) @@ -92,14 +92,14 @@ module UrbanParamsType real(r8), pointer :: eflx_traffic_factor (:) ! lun multiplicative traffic factor for sensible heat flux from urban traffic (-) contains - procedure, public :: Init - + procedure, public :: Init + end type urbanparams_type ! ! !Urban control variables - character(len= *), parameter, public :: urban_hac_off = 'OFF' - character(len= *), parameter, public :: urban_hac_on = 'ON' - character(len= *), parameter, public :: urban_wasteheat_on = 'ON_WASTEHEAT' + character(len= *), parameter, public :: urban_hac_off = 'OFF' + character(len= *), parameter, public :: urban_hac_on = 'ON' + character(len= *), parameter, public :: urban_wasteheat_on = 'ON_WASTEHEAT' character(len= 16), public :: urban_hac = urban_hac_off logical, public :: urban_explicit_ac = .true. ! whether to use explicit, time-varying AC adoption rate logical, public :: urban_traffic = .false. ! urban traffic fluxes @@ -112,7 +112,7 @@ module UrbanParamsType character(len=*), parameter, private :: sourcefile = & __FILE__ - !----------------------------------------------------------------------- + !----------------------------------------------------------------------- contains @@ -132,11 +132,11 @@ subroutine Init(this, bounds) ! ! !ARGUMENTS: class(urbanparams_type) :: this - type(bounds_type) , intent(in) :: bounds + type(bounds_type) , intent(in) :: bounds ! ! !LOCAL VARIABLES: integer :: j,l,c,p,g ! indices - integer :: nc,fl,ib ! indices + integer :: nc,fl,ib ! indices integer :: dindx ! urban density type index integer :: ier ! error status real(r8) :: sumvf ! sum of view factors for wall or road @@ -182,12 +182,12 @@ subroutine Init(this, bounds) allocate(this%em_perroad (begl:endl)) ; this%em_perroad (:) = nan allocate(this%em_wall (begl:endl)) ; this%em_wall (:) = nan allocate(this%alb_roof_dir (begl:endl,numrad)) ; this%alb_roof_dir (:,:) = nan - allocate(this%alb_roof_dif (begl:endl,numrad)) ; this%alb_roof_dif (:,:) = nan - allocate(this%alb_improad_dir (begl:endl,numrad)) ; this%alb_improad_dir (:,:) = nan - allocate(this%alb_perroad_dir (begl:endl,numrad)) ; this%alb_perroad_dir (:,:) = nan - allocate(this%alb_improad_dif (begl:endl,numrad)) ; this%alb_improad_dif (:,:) = nan - allocate(this%alb_perroad_dif (begl:endl,numrad)) ; this%alb_perroad_dif (:,:) = nan - allocate(this%alb_wall_dir (begl:endl,numrad)) ; this%alb_wall_dir (:,:) = nan + allocate(this%alb_roof_dif (begl:endl,numrad)) ; this%alb_roof_dif (:,:) = nan + allocate(this%alb_improad_dir (begl:endl,numrad)) ; this%alb_improad_dir (:,:) = nan + allocate(this%alb_perroad_dir (begl:endl,numrad)) ; this%alb_perroad_dir (:,:) = nan + allocate(this%alb_improad_dif (begl:endl,numrad)) ; this%alb_improad_dif (:,:) = nan + allocate(this%alb_perroad_dif (begl:endl,numrad)) ; this%alb_perroad_dif (:,:) = nan + allocate(this%alb_wall_dir (begl:endl,numrad)) ; this%alb_wall_dir (:,:) = nan allocate(this%alb_wall_dif (begl:endl,numrad)) ; this%alb_wall_dif (:,:) = nan allocate(this%eflx_traffic_factor (begl:endl)) ; this%eflx_traffic_factor (:) = nan @@ -261,7 +261,7 @@ subroutine Init(this, bounds) ! | \ vsr / | | r | | \ vww / s ! | \ / | h o w | \ / k ! wall | \ / | wall | a | | \ / y - ! |vwr \ / vwr| | d | |vrw \ / vsw + ! |vwr \ / vwr| | d | |vrw \ / vsw ! ------\/------ - - |-----\/----- ! road wall | ! <----- w ----> | @@ -272,20 +272,20 @@ subroutine Init(this, bounds) ! vsw = view factor of sky for wall ! vsr + vwr + vwr = 1 vrw + vww + vsw = 1 ! - ! Source: Masson, V. (2000) A physically-based scheme for the urban energy budget in + ! Source: Masson, V. (2000) A physically-based scheme for the urban energy budget in ! atmospheric models. Boundary-Layer Meteorology 94:357-397 ! ! - Calculate urban land unit aerodynamic constants using Macdonald (1998) as used in ! Grimmond and Oke (1999) ! --------------------------------------------------------------------------------------- - - ! road -- sky view factor -> 1 as building height -> 0 + + ! road -- sky view factor -> 1 as building height -> 0 ! and -> 0 as building height -> infinity this%vf_sr(l) = sqrt(lun%canyon_hwr(l)**2 + 1._r8) - lun%canyon_hwr(l) this%vf_wr(l) = 0.5_r8 * (1._r8 - this%vf_sr(l)) - ! one wall -- sky view factor -> 0.5 as building height -> 0 + ! one wall -- sky view factor -> 0.5 as building height -> 0 ! and -> 0 as building height -> infinity this%vf_sw(l) = 0.5_r8 * (lun%canyon_hwr(l) + 1._r8 - sqrt(lun%canyon_hwr(l)**2+1._r8)) / lun%canyon_hwr(l) @@ -311,7 +311,7 @@ subroutine Init(this, bounds) ! Grimmond and Oke (1999) !---------------------------------------------------------------------------------- - ! Calculate plan area index + ! Calculate plan area index plan_ai = lun%canyon_hwr(l)/(lun%canyon_hwr(l) + 1._r8) ! Building shape shortside/longside ratio (e.g. 1 = square ) @@ -344,7 +344,7 @@ subroutine Init(this, bounds) (1 - lun%z_d_town(l) / lun%ht_roof(l)) * frontal_ai)**(-0.5_r8)) end if - else ! Not urban point + else ! Not urban point this%eflx_traffic_factor(l) = spval this%t_building_min(l) = spval @@ -366,7 +366,7 @@ end subroutine Init !----------------------------------------------------------------------- subroutine UrbanInput(begg, endg, mode) ! - ! !DESCRIPTION: + ! !DESCRIPTION: ! Allocate memory and read in urban input data ! ! !USES: @@ -375,7 +375,7 @@ subroutine UrbanInput(begg, endg, mode) use fileutils , only : getavu, relavu, getfil, opnfil use spmdMod , only : masterproc use domainMod , only : ldomain - use ncdio_pio , only : file_desc_t, ncd_io, ncd_inqvdlen, ncd_inqfdims + use ncdio_pio , only : file_desc_t, ncd_io, ncd_inqvdlen, ncd_inqfdims use ncdio_pio , only : ncd_pio_openfile, ncd_pio_closefile, ncd_inqdid, ncd_inqdlen ! ! !ARGUMENTS: @@ -392,7 +392,7 @@ subroutine UrbanInput(begg, endg, mode) integer :: numrad_i ! input grid: number of solar bands (VIS/NIR) integer :: numurbl_i ! input grid: number of urban landunits integer :: ier,ret ! error status - logical :: isgrid2d ! true => file is 2d + logical :: isgrid2d ! true => file is 2d logical :: readvar ! true => variable is on dataset logical :: has_numurbl ! true => numurbl dimension is on dataset character(len=32) :: subname = 'UrbanInput' ! subroutine name @@ -403,11 +403,11 @@ subroutine UrbanInput(begg, endg, mode) if (mode == 'initialize') then ! Read urban data - + if (masterproc) then write(iulog,*)' Reading in urban input data from fsurdat file ...' end if - + call getfil (fsurdat, locfn, 0) call ncd_pio_openfile (ncid, locfn, 0) @@ -428,20 +428,20 @@ subroutine UrbanInput(begg, endg, mode) if ( nlevurb == 0 ) return ! Allocate dynamic memory - allocate(urbinp%canyon_hwr(begg:endg, numurbl), & - urbinp%wtlunit_roof(begg:endg, numurbl), & + allocate(urbinp%canyon_hwr(begg:endg, numurbl), & + urbinp%wtlunit_roof(begg:endg, numurbl), & urbinp%wtroad_perv(begg:endg, numurbl), & - urbinp%em_roof(begg:endg, numurbl), & - urbinp%em_improad(begg:endg, numurbl), & - urbinp%em_perroad(begg:endg, numurbl), & - urbinp%em_wall(begg:endg, numurbl), & - urbinp%alb_roof_dir(begg:endg, numurbl, numrad), & - urbinp%alb_roof_dif(begg:endg, numurbl, numrad), & - urbinp%alb_improad_dir(begg:endg, numurbl, numrad), & - urbinp%alb_perroad_dir(begg:endg, numurbl, numrad), & - urbinp%alb_improad_dif(begg:endg, numurbl, numrad), & - urbinp%alb_perroad_dif(begg:endg, numurbl, numrad), & - urbinp%alb_wall_dir(begg:endg, numurbl, numrad), & + urbinp%em_roof(begg:endg, numurbl), & + urbinp%em_improad(begg:endg, numurbl), & + urbinp%em_perroad(begg:endg, numurbl), & + urbinp%em_wall(begg:endg, numurbl), & + urbinp%alb_roof_dir(begg:endg, numurbl, numrad), & + urbinp%alb_roof_dif(begg:endg, numurbl, numrad), & + urbinp%alb_improad_dir(begg:endg, numurbl, numrad), & + urbinp%alb_perroad_dir(begg:endg, numurbl, numrad), & + urbinp%alb_improad_dif(begg:endg, numurbl, numrad), & + urbinp%alb_perroad_dif(begg:endg, numurbl, numrad), & + urbinp%alb_wall_dir(begg:endg, numurbl, numrad), & urbinp%alb_wall_dif(begg:endg, numurbl, numrad), & urbinp%ht_roof(begg:endg, numurbl), & urbinp%wind_hgt_canyon(begg:endg, numurbl), & @@ -461,7 +461,7 @@ subroutine UrbanInput(begg, endg, mode) endif call ncd_inqfdims (ncid, isgrid2d, ni, nj, ns) - if (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj) then + if (.not. single_column .and. (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj)) then write(iulog,*)trim(subname), 'ldomain and input file do not match dims ' write(iulog,*)trim(subname), 'ldomain%ni,ni,= ',ldomain%ni,ni write(iulog,*)trim(subname), 'ldomain%nj,nj,= ',ldomain%nj,nj @@ -655,7 +655,7 @@ subroutine UrbanInput(begg, endg, mode) call ncd_pio_closefile(ncid) if (masterproc) then - write(iulog,*)' Sucessfully read urban input data' + write(iulog,*)' Sucessfully read urban input data' write(iulog,*) end if @@ -955,7 +955,3 @@ end function IsProgBuildTemp !----------------------------------------------------------------------- end module UrbanParamsType - - - - diff --git a/src/cpl/nuopc/lnd_comp_nuopc.F90 b/src/cpl/nuopc/lnd_comp_nuopc.F90 index d8eb552c61..ae1792276d 100644 --- a/src/cpl/nuopc/lnd_comp_nuopc.F90 +++ b/src/cpl/nuopc/lnd_comp_nuopc.F90 @@ -409,6 +409,9 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' !------------------------------------------------------------------------------- + ! NOTE: Because this is an ESMF called subroutine -- do a timer over it's contents here rather than from the outside calls + call t_startf ('lc_lnd_init_realize') + rc = ESMF_SUCCESS call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO) @@ -431,6 +434,9 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) call NUOPC_CompAttributeGet(gcomp, name='single_column_lnd_domainfile', value=single_column_lnd_domainfile, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + ! NOTE: Now start the timer + !call t_startf ('lc_lnd_init_realize') + ! TODO: there is a problem retrieving scol_spval from the driver - for now ! hard-wire scol_spval - this needs to be fixed scol_spval = -999._r8 @@ -481,6 +487,8 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) end if enddo deallocate(lfieldnamelist) + ! Close the timer for the subroutine + call t_stopf ('lc_lnd_init_realize') ! ******************* ! *** RETURN HERE *** ! ******************* @@ -590,6 +598,8 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return if (isPresent .and. isSet) then if (trim(cvalue) .eq. '.true.') write_restart_at_endofrun = .true. + else + call shr_sys_abort( subname//'ERROR: write_restart_at_endofrun not isPresent or not isSet' ) end if ! --------------------- ! Initialize first phase of ctsm @@ -627,7 +637,9 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) hostname_in=hostname, & username_in=username) + call t_startf('clm_init1') call initialize1(dtime=dtime_sync) + call t_stopf('clm_init1') ! --------------------- ! Create ctsm decomp and domain info @@ -642,8 +654,10 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + call t_startf ('lc_lnd_set_decomp_and_domain_from_readmesh') call lnd_set_decomp_and_domain_from_readmesh(driver='cmeps', vm=vm, & meshfile_lnd=model_meshfile, meshfile_mask=meshfile_mask, mesh_ctsm=mesh, ni=ni, nj=nj, rc=rc) + call t_stopf ('lc_lnd_set_decomp_and_domain_from_readmesh') if (ChkErr(rc,__LINE__,u_FILE_u)) return end if @@ -659,7 +673,9 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) call ESMF_ClockGet(clock, currTime=currtime, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + call t_startf('clm_init2') call initialize2(ni, nj, currtime) + call t_stopf('clm_init2') !-------------------------------- ! Create land export state @@ -699,6 +715,7 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) #endif call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO) + call t_stopf ('lc_lnd_init_realize') end subroutine InitializeRealize @@ -810,11 +827,9 @@ subroutine ModelAdvance(gcomp, rc) ! Unpack import state !-------------------------------- - call t_startf ('lc_lnd_import') call import_fields( gcomp, bounds, glc_present, rof_prognostic, & atm2lnd_inst, glc2lnd_inst, water_inst%wateratm2lndbulk_inst, rc ) if (ChkErr(rc,__LINE__,u_FILE_u)) return - call t_stopf ('lc_lnd_import') !-------------------------------- ! Run model @@ -882,14 +897,12 @@ subroutine ModelAdvance(gcomp, rc) ! call ESMF_VMBarrier(vm, rc=rc) ! if (ChkErr(rc,__LINE__,u_FILE_u)) return - call t_startf ('shr_orb_decl') ! Note - the orbital inquiries set the values in clm_varorb via the module use statements call clm_orbital_update(clock, iulog, masterproc, eccen, obliqr, lambm0, mvelpp, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return calday = get_curr_calday(reuse_day_365_for_day_366=.true.) call shr_orb_decl( calday , eccen, mvelpp, lambm0, obliqr, declin , eccf ) call shr_orb_decl( nextsw_cday, eccen, mvelpp, lambm0, obliqr, declinp1, eccf ) - call t_stopf ('shr_orb_decl') call t_startf ('ctsm_run') ! Restart File - use nexttimestr rather than currtimestr here since that is the time at the end of @@ -906,20 +919,16 @@ subroutine ModelAdvance(gcomp, rc) ! Pack export state !-------------------------------- - call t_startf ('lc_lnd_export') call export_fields(gcomp, bounds, glc_present, rof_prognostic, & water_inst%waterlnd2atmbulk_inst, lnd2atm_inst, lnd2glc_inst, & soilbiogeochem_nitrogenflux_inst, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return - call t_stopf ('lc_lnd_export') !-------------------------------- ! Advance ctsm time step !-------------------------------- - call t_startf ('lc_ctsm2_adv_timestep') call advance_timestep() - call t_stopf ('lc_ctsm2_adv_timestep') ! Check that internal clock is in sync with master clock ! Note that the driver clock has not been updated yet - so at this point diff --git a/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 b/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 new file mode 100644 index 0000000000..174fa3554e --- /dev/null +++ b/src/cpl/share_esmf/CTSMForce2DStreamBaseType.F90 @@ -0,0 +1,269 @@ +module CTSMForce2DStreamBaseType + +! +! Description: +! +! Base module to handle 2D streams in CTSM. Specific streams extend this object +! for the details needed to handle a specific stream file. +! +! Having this base type allows the ESMF specific streams implementation to be isolated +! from the CTSM code. This allows the streams code this is based on to change in one place. +! It also makes it easier to unit-test extensions of this type as they become pretty standard +! CTSM code and there is a unit-tester stub for this code. +! + +#include "shr_assert.h" + + use ESMF, only : ESMF_LogFoundError, ESMF_LOGERR_PASSTHRU + use dshr_strdata_mod , only : shr_strdata_type + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use clm_varctl , only : iulog + use spmdMod , only : masterproc, mpicom, iam + use abortutils , only : endrun + use decompMod , only : bounds_type + use clm_varctl, only : FL => fname_len + + implicit none + private + + !----------------------------------------------------------------------- + ! Base 2D streams type + !----------------------------------------------------------------------- + type, abstract, public :: ctsm_force_2DStream_base_type + private + type(shr_strdata_type) :: sdat ! Stream data type + character(len=FL) :: stream_filename ! The stream data filename (also in sdat) + character(len=CL) :: stream_name ! The stream name (also in sdat) + contains + + ! PUBLIC METHODS + procedure(Init_interface) , public, deferred :: Init ! Initiale the extended type + procedure, public, non_overridable :: InitBase ! Initialize and read data in the streams + procedure(Clean_interface), public, deferred :: Clean ! Clean and deallocate the object class method + procedure, public, non_overridable :: CleanBase ! Clean method for the base type + procedure, public, non_overridable :: Advance ! Advance the streams data to the current model date + procedure, public :: GetPtr1D ! Get pointer to the 1D data array + procedure(Interp_interface), public, deferred :: Interp ! method in extensions to turn stream data into CTSM data + + end type ctsm_force_2DStream_base_type + !----------------------------------------------------------------------- + + !----------------------------------------------------------------------- + ! Interfaces that will be deferred to the extended type + !----------------------------------------------------------------------- + abstract interface + + !----------------------------------------------------------------------- + + subroutine Init_interface( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! Description: + ! + ! Initialize the specific stream type that extends the base type + ! Normally the extended type will call the InitBase as well as doing other initialization needed + ! + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + end subroutine Init_interface + + !----------------------------------------------------------------------- + + subroutine Clean_interface(this) + ! Description: + ! Clean up any memory allocated in the specific stream type that extends the base type + ! Normally the extended type will call the CleanBase method as well as other things needed. + ! Uses: + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + end subroutine Clean_interface + + !----------------------------------------------------------------------- + + subroutine Interp_interface(this, bounds) + ! Description: + ! Get the current time data from the streams and put it into the data of the extension. + ! What this looks like may vary with the streams extension, but in general it will use + ! The GetPtr1D method to get the streams data. + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + end subroutine Interp_interface + + end interface + !----------------------------------------------------------------------- + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + !----------------------------------------------------------------------- + contains + !----------------------------------------------------------------------- + + !----------------------------------------------------------------------- + + subroutine InitBase( this, bounds, varnames, fldfilename, meshfile, mapalgo, tintalgo, taxmode, name, & + year_first, year_last, model_year_align ) + ! + ! Description: + ! + ! Initialization of the base type. Extended types will normally call this as part of their initialization. + ! + ! Uses: + use lnd_comp_shr , only : mesh, model_clock + use dshr_strdata_mod , only : shr_strdata_init_from_inline + use decompMod , only : bounds_level_proc + use shr_log_mod , only : errMsg => shr_log_errMsg + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: varnames(:) ! variable names to read from stream file + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + character(*), intent(in) :: name ! name of stream + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + ! Local variables + integer, parameter :: offset = 0 ! time offset in seconds of stream data + integer :: rc ! error return code + + ! Some error checking... + SHR_ASSERT( bounds%level == bounds_level_proc, "InitBase should have a processor bounds, so we can do some checking"//errMsg( sourcefile, __LINE__) ) + SHR_ASSERT( bounds%begg == 1, "Make sure the starting bounds index is 1 so we know the mapping to gridcells is correct"//errMsg( sourcefile, __LINE__) ) + if ( len(fldfilename) >= FL )then + call endrun( 'stream field filename is too long:'//trim(fldfilename), file=sourcefile, line=__LINE__ ) + end if + this%stream_filename = fldfilename + this%stream_name = name + call shr_strdata_init_from_inline(this%sdat, & + my_task = iam, & + logunit = iulog, & + compname = 'LND', & + model_clock = model_clock,& + model_mesh = mesh, & + stream_meshfile = trim(meshfile), & + stream_lev_dimname = 'null', & + stream_mapalgo = mapalgo, & + stream_filenames = (/trim(fldfilename)/), & + stream_fldlistFile = varnames, & + stream_fldListModel = varnames, & + stream_yearFirst = year_first, & + stream_yearLast = year_last, & + stream_yearAlign = model_year_align, & + stream_offset = offset, & + stream_taxmode = taxmode, & + stream_dtlimit = 1.0e30_r8, & + stream_tintalgo = tintalgo, & + stream_name = name, & + rc = rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=sourcefile)) then + write(iulog,*) ' Streams initialization failing for ', trim(name), ' stream file = ', trim(fldfilename) + call endrun( 'CTSM forcing Streams initialization failing', file=sourcefile, line=__LINE__ ) + end if + end subroutine InitBase + + !----------------------------------------------------------------------- + + subroutine CleanBase( this ) + ! Description: + ! Clean up any memory in the base type as needed. + ! Normally types that extend this base type will call this as part of their clean operation + ! + ! Arguments: + class(ctsm_force_2DStream_base_type) , intent(inout) :: this + + integer :: ierr ! error code + + ! Currently no data to deallocate other than the stream data type + ! The stream data type doesn't have a clean method right now + ! So doing a few things manually here + end subroutine CleanBase + + !----------------------------------------------------------------------- + + subroutine Advance(this) + ! + ! Description: + ! + ! Advance the stream to the current time-step + ! + ! Uses: + use clm_time_manager , only : get_curr_date + use dshr_strdata_mod , only : shr_strdata_advance + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + ! !LOCAL VARIABLES: + integer :: year ! year (0, ...) for nstep+1 + integer :: mon ! month (1, ..., 12) for nstep+1 + integer :: day ! day of month (1, ..., 31) for nstep+1 + integer :: sec ! seconds into current date for nstep+1 + integer :: mcdate ! Current model date (yyyymmdd) + integer :: rc ! Error return code + + ! Advance sdat stream + call get_curr_date(year, mon, day, sec) + mcdate = year*10000 + mon*100 + day + call shr_strdata_advance(this%sdat, ymd=mcdate, tod=sec, logunit=iulog, istr='CTSMForce2DStreamBase', rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + write(iulog,*) ' Streams advance failing for ', trim(this%stream_name), ' stream file = ', trim(this%stream_filename) + call endrun( 'CTSM forcing Streams advance failing', file=sourcefile, line=__LINE__ ) + end if + end subroutine Advance + + !----------------------------------------------------------------------- + + subroutine GetPtr1D(this, fldname, dataptr1d) + ! + ! Description: + ! + ! Get the pointer to the 1D data array for the given field name + ! Normally stream extensions will use this in the Interp method to + ! save the stream data locally. + ! + ! Uses: + use dshr_methods_mod , only : dshr_fldbun_getfldptr + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + character(*), intent(in) :: fldname ! field name to get pointer for + real(r8), pointer :: dataptr1d(:) ! Pointer to the 1D data + + ! Local variables + integer :: rc ! error return code + + ! Get pointer for stream data that is time and spatially interpolated to model time and grid + call dshr_fldbun_getFldPtr(this%sdat%pstrm(1)%fldbun_model, fldname=fldname, fldptr1=dataptr1d, rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=sourcefile)) then + call endrun( 'Error getting field pointer for '//trim(fldname)//' from stream data', file=sourcefile, line=__LINE__ ) + end if + + end subroutine GetPtr1D + + !----------------------------------------------------------------------- + +end module CTSMForce2DStreamBaseType diff --git a/src/cpl/share_esmf/FireDataBaseType.F90 b/src/cpl/share_esmf/FireDataBaseType.F90 index b84e3bfa33..aa9395b770 100644 --- a/src/cpl/share_esmf/FireDataBaseType.F90 +++ b/src/cpl/share_esmf/FireDataBaseType.F90 @@ -26,23 +26,25 @@ module FireDataBaseType type, abstract, extends(fire_method_type) :: fire_base_type private ! !PRIVATE MEMBER DATA: - real(r8), public, pointer :: forc_hdm(:) ! Human population density - type(shr_strdata_type) :: sdat_hdm ! Human population density input data stream - real(r8), public, pointer :: forc_lnfm(:) ! Lightning frequency - type(shr_strdata_type) :: sdat_lnfm ! Lightning frequency input data stream + real(r8), public, pointer :: forc_hdm(:) => NULL() ! Human population density + type(shr_strdata_type) :: sdat_hdm ! Human population density input data stream + real(r8), public, pointer :: forc_lnfm(:) => NULL() ! Lightning frequency + type(shr_strdata_type) :: sdat_lnfm ! Lightning frequency input data stream - real(r8), public, pointer :: gdp_lf_col(:) ! col global real gdp data (k US$/capita) - real(r8), public, pointer :: peatf_lf_col(:) ! col global peatland fraction data (0-1) - integer , public, pointer :: abm_lf_col(:) ! col global peak month of crop fire emissions + real(r8), public, pointer :: gdp_lf_col(:) => NULL() ! col global real gdp data (k US$/capita) + real(r8), public, pointer :: peatf_lf_col(:) => NULL() ! col global peatland fraction data (0-1) + integer , public, pointer :: abm_lf_col(:) => NULL() ! col global peak month of crop fire emissions contains ! ! !PUBLIC MEMBER FUNCTIONS: - procedure, public :: FireInit => BaseFireInit ! Initialization of Fire procedure, public :: BaseFireInit ! Initialization of Fire + procedure, public :: FireInit => BaseFireInit ! Initialization of Fire + procedure, public :: BaseFireClean ! Clean up data and deallocate data + procedure, public :: FireClean => BaseFireClean ! Clean up data and deallocate data procedure, public :: FireInterp ! Interpolate fire data - procedure(FireReadNML_interface), public, deferred :: & - FireReadNML ! Read in namelist for Fire + procedure, public :: BaseFireReadNML ! Read in the namelist for fire + procedure, public :: ReadFireNML => BaseFireReadNML ! Read in the namelist for fire procedure(need_lightning_and_popdens_interface), public, deferred :: & need_lightning_and_popdens ! Returns true if need lightning & popdens ! @@ -78,7 +80,7 @@ end function need_lightning_and_popdens_interface contains !============================================================================== - subroutine FireReadNML_interface( this, NLFilename ) + subroutine BaseFireReadNML( this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read the namelist for Fire @@ -87,11 +89,21 @@ subroutine FireReadNML_interface( this, NLFilename ) ! ! !ARGUMENTS: class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds character(len=*), intent(in) :: NLFilename ! Namelist filename - end subroutine FireReadNML_interface + + ! Read the namelists for the fire data and do the preparation needed on them + if ( this%need_lightning_and_popdens() ) then + call this%hdm_init(bounds, NLFilename) + call this%hdm_interp(bounds) + call this%lnfm_init(bounds, NLFilename) + call this%lnfm_interp(bounds) + call this%surfdataread(bounds) + end if + end subroutine BaseFireReadNML !================================================================ - subroutine BaseFireInit( this, bounds, NLFilename ) + subroutine BaseFireInit( this, bounds ) ! ! !DESCRIPTION: ! Initialize CN Fire module @@ -101,9 +113,7 @@ subroutine BaseFireInit( this, bounds, NLFilename ) ! !ARGUMENTS: class(fire_base_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- - if ( this%need_lightning_and_popdens() ) then ! Allocate lightning forcing data allocate( this%forc_lnfm(bounds%begg:bounds%endg) ) @@ -118,16 +128,36 @@ subroutine BaseFireInit( this, bounds, NLFilename ) allocate(this%peatf_lf_col(bounds%begc:bounds%endc)) ! Allocates peak month of crop fire emissions allocate(this%abm_lf_col(bounds%begc:bounds%endc)) - - call this%hdm_init(bounds, NLFilename) - call this%hdm_interp(bounds) - call this%lnfm_init(bounds, NLFilename) - call this%lnfm_interp(bounds) - call this%surfdataread(bounds) end if end subroutine BaseFireInit + !================================================================ + subroutine BaseFireClean( this ) + ! + ! !DESCRIPTION: + ! Clean fire data + ! !USES: + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + deallocate( this%forc_lnfm ) + deallocate( this%forc_hdm ) + deallocate( this%gdp_lf_col ) + deallocate( this%peatf_lf_col ) + deallocate( this%abm_lf_col ) + this%forc_lnfm => NULL() + this%forc_hdm => NULL() + this%gdp_lf_col => NULL() + this%peatf_lf_col => NULL() + this%abm_lf_col => NULL() + end if + + end subroutine BaseFireClean + !================================================================ subroutine FireInterp(this,bounds) ! @@ -216,6 +246,7 @@ subroutine hdm_init( this, bounds, NLFilename ) call shr_mpi_bcast(stream_fldFileName_popdens , mpicom) call shr_mpi_bcast(stream_meshfile_popdens , mpicom) call shr_mpi_bcast(popdens_tintalgo , mpicom) + call shr_mpi_bcast(popdensmapalgo , mpicom) if (masterproc) then write(iulog,'(a)' ) ' ' @@ -307,6 +338,7 @@ subroutine hdm_interp( this, bounds) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%forc_hdm(g) = dataptr1d(ig) end do @@ -383,6 +415,7 @@ subroutine lnfm_init( this, bounds, NLFilename ) call shr_mpi_bcast(stream_fldFileName_lightng , mpicom) call shr_mpi_bcast(stream_meshfile_lightng , mpicom) call shr_mpi_bcast(lightng_tintalgo , mpicom) + call shr_mpi_bcast(lightngmapalgo , mpicom) if (masterproc) then write(iulog,'(a)') ' ' @@ -474,6 +507,7 @@ subroutine lnfm_interp(this, bounds ) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%forc_lnfm(g) = dataptr1d(ig) end do diff --git a/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 b/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 index afc65f2ece..158c75e434 100644 --- a/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 +++ b/src/cpl/share_esmf/PrigentRoughnessStreamType.F90 @@ -1,5 +1,6 @@ module PrigentRoughnessStreamType +#include "shr_assert.h" !----------------------------------------------------------------------- ! !DESCRIPTION: @@ -151,6 +152,7 @@ subroutine Init(this, bounds, NLFilename) ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) this%prigent_rghn(g) = dataptr1d(ig) end do diff --git a/src/cpl/share_esmf/UrbanTimeVarType.F90 b/src/cpl/share_esmf/UrbanTimeVarType.F90 index eb537a7031..1e6d004e96 100644 --- a/src/cpl/share_esmf/UrbanTimeVarType.F90 +++ b/src/cpl/share_esmf/UrbanTimeVarType.F90 @@ -174,6 +174,7 @@ subroutine urbantv_init(this, bounds, NLFilename) call shr_mpi_bcast(stream_year_first_urbantv , mpicom) call shr_mpi_bcast(stream_year_last_urbantv , mpicom) call shr_mpi_bcast(model_year_align_urbantv , mpicom) + call shr_mpi_bcast(urbantvmapalgo , mpicom) call shr_mpi_bcast(stream_fldFileName_urbantv , mpicom) call shr_mpi_bcast(stream_meshfile_urbantv , mpicom) call shr_mpi_bcast(urbantv_tintalgo , mpicom) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index b19612ca09..df48ef5e98 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -22,7 +22,8 @@ module cropcalStreamMod use clm_varpar , only : mxsowings use perf_mod , only : t_startf, t_stopf use spmdMod , only : masterproc, mpicom, iam - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop, get_crop_n_from_veg_type, get_veg_type_from_crop_n + use pftconMod , only : num_cfts_possible use CNPhenologyMod , only : generate_crop_gdds ! ! !PUBLIC TYPES: @@ -46,7 +47,6 @@ module cropcalStreamMod character(len=CS), allocatable :: stream_varnames_cultivar_gdds(:) character(len=CS), allocatable :: stream_varnames_gdd20_baseline(:) character(len=CS), allocatable :: stream_varnames_gdd20_season_enddate(:) ! start uses stream_varnames_sdate - integer :: ncft ! Number of crop functional types (excl. generic crops) logical :: allow_invalid_swindow_inputs ! Fall back on paramfile sowing windows in cases of invalid values in stream_fldFileName_swindow_start and _end? character(len=FL) :: stream_fldFileName_swindow_start ! sowing window start stream filename to read character(len=FL) :: stream_fldFileName_swindow_end ! sowing window end stream filename to read @@ -138,13 +138,12 @@ subroutine cropcal_init(bounds) stream_fldFileName_gdd20_season_start = '' stream_fldFileName_gdd20_season_end = '' ! Will need modification to work with mxsowings > 1 - ncft = mxpft - npcropmin + 1 ! Ignores generic crops - allocate(stream_varnames_sdate(ncft)) - allocate(stream_varnames_cultivar_gdds(ncft)) - allocate(stream_varnames_gdd20_baseline(ncft)) - allocate(stream_varnames_gdd20_season_enddate(ncft)) - do n = 1,ncft - ivt = npcropmin + n - 1 + allocate(stream_varnames_sdate(num_cfts_possible)) + allocate(stream_varnames_cultivar_gdds(num_cfts_possible)) + allocate(stream_varnames_gdd20_baseline(num_cfts_possible)) + allocate(stream_varnames_gdd20_season_enddate(num_cfts_possible)) + do n = 1,num_cfts_possible + ivt = get_veg_type_from_crop_n(n) write(stream_varnames_sdate(n),'(a,i0)') "sdate1_",ivt write(stream_varnames_cultivar_gdds(n),'(a,i0)') "gdd1_",ivt write(stream_varnames_gdd20_baseline(n),'(a,i0)') "gdd20bl_",ivt @@ -201,7 +200,7 @@ subroutine cropcal_init(bounds) write(iulog,'(a,l1)') ' allow_invalid_gdd20_season_inputs = ',allow_invalid_gdd20_season_inputs write(iulog,'(a,a)' ) ' stream_fldFileName_gdd20_season_start = ',stream_fldFileName_gdd20_season_start write(iulog,'(a,a)' ) ' stream_fldFileName_gdd20_season_end = ',stream_fldFileName_gdd20_season_end - do n = 1,ncft + do n = 1,num_cfts_possible write(iulog,'(a,a)' ) ' stream_varnames_sdate = ',trim(stream_varnames_sdate(n)) write(iulog,'(a,a)' ) ' stream_varnames_cultivar_gdds = ',trim(stream_varnames_cultivar_gdds(n)) write(iulog,'(a,a)' ) ' stream_varnames_gdd20_season_enddate = ',trim(stream_varnames_gdd20_season_enddate(n)) @@ -481,6 +480,7 @@ subroutine cropcal_advance( bounds ) do g = begg,endg ig = ig+1 g_to_ig(g) = ig + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) end do end if @@ -550,13 +550,13 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) dayspyr = get_curr_days_per_year() ! Read prescribed sowing window start dates from input files - allocate(dataptr2d_swindow_start(begg:endg, ncft)) + allocate(dataptr2d_swindow_start(begg:endg, num_cfts_possible)) dataptr2d_swindow_start(begg:endg,:) = -1._r8 - allocate(dataptr2d_swindow_end (begg:endg, ncft)) + allocate(dataptr2d_swindow_end (begg:endg, num_cfts_possible)) dataptr2d_swindow_end(begg:endg,:) = -1._r8 if (use_cropcal_rx_swindows) then ! Starting with npcropmin will skip generic crops - do n = 1, ncft + do n = 1, num_cfts_possible call dshr_fldbun_getFldPtr(sdat_cropcal_swindow_start%pstrm(1)%fldbun_model, trim(stream_varnames_sdate(n)), & fldptr1=dataptr1d_swindow_start, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then @@ -588,8 +588,8 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) p = filter_pcropp(fp) ivt = patch%itype(p) ! Will skip generic crops - if (ivt >= npcropmin) then - n = ivt - npcropmin + 1 + if (is_prognostic_crop(ivt)) then + n = get_crop_n_from_veg_type(ivt) ! vegetated pft ig = g_to_ig(patch%gridcell(p)) swindow_starts(p,1) = dataptr2d_swindow_start(ig,n) @@ -612,10 +612,13 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ! Handle invalid sowing window values if (any(swindow_starts(begp:endp,:) < 1 .or. swindow_ends(begp:endp,:) < 1)) then ! Fail if not allowing fallback to paramfile sowing windows - if ((.not. allow_invalid_swindow_inputs) .and. any(all(swindow_starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then + if ((.not. allow_invalid_swindow_inputs) .and. any(all(swindow_starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell(begp:endp) > 0._r8 .and. is_prognostic_crop(patch%itype(begp:endp)))) then write(iulog, *) 'At least one crop in one gridcell has invalid prescribed sowing window start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_swindow_inputs to .true.' write(iulog, *) 'Affected crops:' - do ivt = npcropmin, mxpft + do ivt = 1, mxpft + if (.not. is_prognostic_crop(ivt)) then + cycle + end if do fp = 1, num_pcropp p = filter_pcropp(fp) if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. all(swindow_starts(p,:) < 1)) then @@ -637,11 +640,11 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) deallocate(dataptr2d_swindow_start) deallocate(dataptr2d_swindow_end) - allocate(dataptr2d_cultivar_gdds(begg:endg, ncft)) + allocate(dataptr2d_cultivar_gdds(begg:endg, num_cfts_possible)) if (use_cropcal_rx_cultivar_gdds) then ! Read prescribed cultivar GDDs from input files ! Starting with npcropmin will skip generic crops - do n = 1, ncft + do n = 1, num_cfts_possible call dshr_fldbun_getFldPtr(sdat_cropcal_cultivar_gdds%pstrm(1)%fldbun_model, trim(stream_varnames_cultivar_gdds(n)), & fldptr1=dataptr1d_cultivar_gdds, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then @@ -667,11 +670,11 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ivt = patch%itype(p) ! Will skip generic crops - if (ivt >= npcropmin) then - n = ivt - npcropmin + 1 + if (is_prognostic_crop(ivt)) then + n = get_crop_n_from_veg_type(ivt) - if (n > ncft) then - write(iulog,'(a,i0,a,i0,a)') 'n (',n,') > ncft (',ncft,')' + if (n > num_cfts_possible) then + write(iulog,'(a,i0,a,i0,a)') 'n (',n,') > ncft (',num_cfts_possible,')' call ESMF_Finalize(endflag=ESMF_END_ABORT) end if @@ -694,11 +697,11 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) deallocate(dataptr2d_cultivar_gdds) - allocate(dataptr2d_gdd20_baseline(begg:endg, ncft)) + allocate(dataptr2d_gdd20_baseline(begg:endg, num_cfts_possible)) if (adapt_cropcal_rx_cultivar_gdds) then ! Read GDD20 baselines from input files ! Starting with npcropmin will skip generic crops - do n = 1, ncft + do n = 1, num_cfts_possible call dshr_fldbun_getFldPtr(sdat_cropcal_gdd20_baseline%pstrm(1)%fldbun_model, trim(stream_varnames_gdd20_baseline(n)), & fldptr1=dataptr1d_gdd20_baseline, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then @@ -718,11 +721,11 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ivt = patch%itype(p) ! Will skip generic crops - if (ivt >= npcropmin) then - n = ivt - npcropmin + 1 + if (is_prognostic_crop(ivt)) then + n = get_crop_n_from_veg_type(ivt) - if (n > ncft) then - write(iulog,'(a,i0,a,i0,a)') 'n (',n,') > ncft (',ncft,')' + if (n > num_cfts_possible) then + write(iulog,'(a,i0,a,i0,a)') 'n (',n,') > ncft (',num_cfts_possible,')' call ESMF_Finalize(endflag=ESMF_END_ABORT) end if @@ -747,13 +750,13 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ! Read prescribed gdd20 season start dates from input files - allocate(dataptr2d_gdd20_season_start(begg:endg, ncft)) + allocate(dataptr2d_gdd20_season_start(begg:endg, num_cfts_possible)) dataptr2d_gdd20_season_start(begg:endg,:) = -1._r8 - allocate(dataptr2d_gdd20_season_end (begg:endg, ncft)) + allocate(dataptr2d_gdd20_season_end (begg:endg, num_cfts_possible)) dataptr2d_gdd20_season_end(begg:endg,:) = -1._r8 if (stream_gdd20_seasons) then ! Starting with npcropmin will skip generic crops - do n = 1, ncft + do n = 1, num_cfts_possible call dshr_fldbun_getFldPtr(sdat_cropcal_gdd20_season_start%pstrm(1)%fldbun_model, trim(stream_varnames_sdate(n)), & fldptr1=dataptr1d_gdd20_season_start, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then @@ -788,8 +791,8 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) p = filter_pcropp(fp) ivt = patch%itype(p) ! Will skip generic crops - if (ivt >= npcropmin) then - n = ivt - npcropmin + 1 + if (is_prognostic_crop(ivt)) then + n = get_crop_n_from_veg_type(ivt) ! vegetated pft ig = g_to_ig(patch%gridcell(p)) @@ -805,10 +808,13 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) if (any(gdd20_season_starts(begp:endp) < 1._r8 .or. gdd20_season_ends(begp:endp) < 1._r8)) then ! Fail if not allowing fallback to paramfile sowing windows. Only need to check for ! values < 1 because values outside [1, 366] are set to -1 above. - if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1._r8 .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then + if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1._r8 .and. patch%wtgcell(begp:endp) > 0._r8 .and. is_prognostic_crop(patch%itype(begp:endp)))) then write(iulog, *) 'At least one crop in one gridcell has invalid gdd20 season start and/or end date(s). To ignore and fall back to paramfile sowing windows for such crop-gridcells, set allow_invalid_gdd20_season_inputs to .true.' write(iulog, *) 'Affected crops:' - do ivt = npcropmin, mxpft + do ivt = 1, mxpft + if (.not. is_prognostic_crop(ivt)) then + cycle + end if do fp = 1, num_pcropp p = filter_pcropp(fp) if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. gdd20_season_starts(p) < 1._r8) then diff --git a/src/cpl/share_esmf/lnd_set_decomp_and_domain.F90 b/src/cpl/share_esmf/lnd_set_decomp_and_domain.F90 index 0b066ceb5b..5a2536f63b 100644 --- a/src/cpl/share_esmf/lnd_set_decomp_and_domain.F90 +++ b/src/cpl/share_esmf/lnd_set_decomp_and_domain.F90 @@ -20,6 +20,7 @@ module lnd_set_decomp_and_domain use spmdMod , only : masterproc, mpicom use clm_varctl , only : iulog, inst_suffix, FL => fname_len use abortutils , only : endrun + use perf_mod , only : t_startf, t_stopf implicit none private ! except @@ -87,6 +88,7 @@ subroutine lnd_set_decomp_and_domain_from_readmesh(driver, vm, meshfile_lnd, mes real(r8) , pointer :: dataptr1d(:) !------------------------------------------------------------------------------- + call t_startf('lnd_set_decomp_and_domain_from_readmesh: setup') rc = ESMF_SUCCESS ! Write diag info @@ -105,7 +107,10 @@ subroutine lnd_set_decomp_and_domain_from_readmesh(driver, vm, meshfile_lnd, mes ! Determine global 2d sizes from read of dimensions of surface dataset and allocate global memory call lnd_get_global_dims(ni, nj, gsize, isgrid2d) + call t_stopf('lnd_set_decomp_and_domain_from_readmesh: setup') + ! Read in the land mesh from the file + call t_startf('lnd_set_decomp_and_domain_from_readmesh: ESMF mesh') mesh_lndinput = ESMF_MeshCreate(filename=trim(meshfile_lnd), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return @@ -142,9 +147,13 @@ subroutine lnd_set_decomp_and_domain_from_readmesh(driver, vm, meshfile_lnd, mes else call shr_sys_abort('driver '//trim(driver)//' is not supported, must be lilac or cmeps') end if + call t_stopf('lnd_set_decomp_and_domain_from_readmesh: ESMF mesh') + call t_startf ('lnd_set_decomp_and_domain_from_readmesh: final') ! Determine lnd decomposition that will be used by ctsm from lndmask_glob + call t_startf ('decompInit_lnd') call decompInit_lnd(lni=ni, lnj=nj, amask=lndmask_glob) + call t_stopf ('decompInit_lnd') ! Determine ocn decomposition that will be used to create the full mesh ! note that the memory for gindex_ocn will be allocated in the following call @@ -251,6 +260,8 @@ subroutine lnd_set_decomp_and_domain_from_readmesh(driver, vm, meshfile_lnd, mes deallocate(gindex_ocn) deallocate(gindex_ctsm) + call t_stopf('lnd_set_decomp_and_domain_from_readmesh: final') + end subroutine lnd_set_decomp_and_domain_from_readmesh !=============================================================================== @@ -315,7 +326,9 @@ subroutine lnd_set_decomp_and_domain_for_single_column(scol_lon, scol_lat, scol_ !------------------------------------------------------------------------------- ! Determine decomp and ldomain + call t_startf ('decompInit_lnd') call decompInit_lnd(lni=1, lnj=1, amask=(/1/)) + call t_stopf ('decompInit_lnd') ! Initialize processor bounds call get_proc_bounds(bounds) diff --git a/src/cpl/share_esmf/ndepStreamMod.F90 b/src/cpl/share_esmf/ndepStreamMod.F90 index ff91d5a202..860dd35641 100644 --- a/src/cpl/share_esmf/ndepStreamMod.F90 +++ b/src/cpl/share_esmf/ndepStreamMod.F90 @@ -1,5 +1,7 @@ module ndepStreamMod +#include "shr_assert.h" + !----------------------------------------------------------------------- ! !DESCRIPTION: ! Contains methods for reading in nitrogen deposition data file @@ -111,6 +113,7 @@ subroutine ndep_init(bounds, NLFilename) call shr_mpi_bcast(model_year_align_ndep , mpicom) call shr_mpi_bcast(ndep_varlist , mpicom) call shr_mpi_bcast(ndep_taxmode , mpicom) + call shr_mpi_bcast(ndepmapalgo , mpicom) call shr_mpi_bcast(ndep_tintalgo , mpicom) call shr_mpi_bcast(stream_fldFileName_ndep, mpicom) call shr_mpi_bcast(stream_meshfile_ndep , mpicom) @@ -255,12 +258,14 @@ subroutine ndep_interp(bounds, atm2lnd_inst) dayspyr = get_curr_days_per_year( ) do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) atm2lnd_inst%forc_ndep_grc(g) = dataptr1d(ig) / (secspday * dayspyr) end do else ig = 0 do g = bounds%begg,bounds%endg ig = ig+1 + SHR_ASSERT_FL( ig == g, sourcefile, __LINE__ ) atm2lnd_inst%forc_ndep_grc(g) = dataptr1d(ig) end do end if diff --git a/src/dyn_subgrid/dynFileMod.F90 b/src/dyn_subgrid/dynFileMod.F90 index d7f22f9dcd..ecb2d111d9 100644 --- a/src/dyn_subgrid/dynFileMod.F90 +++ b/src/dyn_subgrid/dynFileMod.F90 @@ -7,6 +7,7 @@ module dynFileMod ! ! !USES: use shr_log_mod , only : errMsg => shr_log_errMsg + use clm_varctl , only : fname_len use dynTimeInfoMod , only : time_info_type, year_position_type use ncdio_pio , only : file_desc_t, ncd_pio_openfile, ncd_inqdid, ncd_inqdlen, ncd_io use abortutils , only : endrun @@ -56,7 +57,7 @@ type(dyn_file_type) function constructor(filename, year_position) type(year_position_type) , intent(in) :: year_position ! ! !LOCAL VARIABLES: - character(len=256) :: locfn ! local file name + character(len=fname_len) :: locfn ! local file name integer :: ier ! error code integer :: ntimes ! number of time samples integer :: varid ! netcdf variable ID diff --git a/src/dyn_subgrid/dynSubgridControlMod.F90 b/src/dyn_subgrid/dynSubgridControlMod.F90 index 72e7229d0b..4cabcc168a 100644 --- a/src/dyn_subgrid/dynSubgridControlMod.F90 +++ b/src/dyn_subgrid/dynSubgridControlMod.F90 @@ -26,6 +26,7 @@ module dynSubgridControlMod public :: get_do_transient_crops ! return the value of the do_transient_crops control flag public :: get_do_transient_lakes ! return the value of the do_transient_lakes control flag public :: get_do_transient_urban ! return the value of the do_transient_urban control flag + public :: get_vars_1dwt_w_time ! return the value of the vars_1dwt_w_time control flag public :: run_has_transient_landcover ! returns true if any aspects of prescribed transient landcover are enabled public :: get_do_harvest ! return the value of the do_harvest control flag public :: get_do_grossunrep ! return the value of the do_grossunrep control flag @@ -47,6 +48,7 @@ module dynSubgridControlMod logical :: do_transient_urban = .false. ! whether to apply transient urban from dataset logical :: do_harvest = .false. ! whether to apply harvest from dataset logical :: do_grossunrep = .false. ! whether to apply gross unrepresented landcover change from dataset + logical :: vars_1dwt_w_time = .false. ! whether to add the time dimension to 1dwt variables, e.g. pfts1d_wtcol logical :: reset_dynbal_baselines = .false. ! whether to reset baseline values of total column water and energy in the first step of the run @@ -126,6 +128,7 @@ subroutine read_namelist( NLFilename ) logical :: do_transient_urban logical :: do_harvest logical :: do_grossunrep + logical :: vars_1dwt_w_time logical :: reset_dynbal_baselines logical :: for_testing_allow_non_annual_changes logical :: for_testing_zero_dynbal_fluxes @@ -144,6 +147,7 @@ subroutine read_namelist( NLFilename ) do_transient_urban, & do_harvest, & do_grossunrep, & + vars_1dwt_w_time, & reset_dynbal_baselines, & for_testing_allow_non_annual_changes, & for_testing_zero_dynbal_fluxes @@ -156,6 +160,7 @@ subroutine read_namelist( NLFilename ) do_transient_urban = .false. do_harvest = .false. do_grossunrep = .false. + vars_1dwt_w_time = .false. reset_dynbal_baselines = .false. for_testing_allow_non_annual_changes = .false. for_testing_zero_dynbal_fluxes = .false. @@ -183,6 +188,7 @@ subroutine read_namelist( NLFilename ) call shr_mpi_bcast (do_transient_urban, mpicom) call shr_mpi_bcast (do_harvest, mpicom) call shr_mpi_bcast (do_grossunrep, mpicom) + call shr_mpi_bcast (vars_1dwt_w_time, mpicom) call shr_mpi_bcast (reset_dynbal_baselines, mpicom) call shr_mpi_bcast (for_testing_allow_non_annual_changes, mpicom) call shr_mpi_bcast (for_testing_zero_dynbal_fluxes, mpicom) @@ -195,6 +201,7 @@ subroutine read_namelist( NLFilename ) do_transient_urban = do_transient_urban, & do_harvest = do_harvest, & do_grossunrep = do_grossunrep, & + vars_1dwt_w_time = vars_1dwt_w_time, & reset_dynbal_baselines = reset_dynbal_baselines, & for_testing_allow_non_annual_changes = for_testing_allow_non_annual_changes, & for_testing_zero_dynbal_fluxes = for_testing_zero_dynbal_fluxes) @@ -397,6 +404,18 @@ logical function get_do_transient_urban() end function get_do_transient_urban + !----------------------------------------------------------------------- + logical function get_vars_1dwt_w_time() + ! !DESCRIPTION: + ! Return the value of the vars_1dwt_w_time control flag + !----------------------------------------------------------------------- + + SHR_ASSERT_FL(dyn_subgrid_control_inst%initialized, sourcefile, __LINE__) + + get_vars_1dwt_w_time = dyn_subgrid_control_inst%vars_1dwt_w_time + + end function get_vars_1dwt_w_time + !----------------------------------------------------------------------- logical function run_has_transient_landcover() ! !DESCRIPTION: @@ -406,6 +425,7 @@ logical function run_has_transient_landcover() run_has_transient_landcover = & (get_do_transient_pfts() .or. & get_do_transient_crops() .or. & + get_do_transient_lakes() .or. & get_do_transient_urban()) end function run_has_transient_landcover diff --git a/src/fates b/src/fates index 855f4d4ab4..05612f93e7 160000 --- a/src/fates +++ b/src/fates @@ -1 +1 @@ -Subproject commit 855f4d4ab464cf5c3bac24e352916833984c4309 +Subproject commit 05612f93e701dea0501da87651aca81349b95cf4 diff --git a/src/init_interp/initInterp.F90 b/src/init_interp/initInterp.F90 index e0d56aed62..3ccdcd9b58 100644 --- a/src/init_interp/initInterp.F90 +++ b/src/init_interp/initInterp.F90 @@ -75,6 +75,9 @@ module initInterpMod ! patch-level variables) logical :: init_interp_fill_missing_with_natveg + ! If true, fill missing urban landunit type with closest urban high density (HD) landunit + logical :: init_interp_fill_missing_urban_with_HD + character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -106,11 +109,13 @@ subroutine initInterp_readnl(NLFilename) !----------------------------------------------------------------------- namelist /clm_initinterp_inparm/ & - init_interp_method, init_interp_fill_missing_with_natveg + init_interp_method, init_interp_fill_missing_with_natveg, & + init_interp_fill_missing_urban_with_HD ! Initialize options to default values, in case they are not specified in the namelist init_interp_method = ' ' init_interp_fill_missing_with_natveg = .false. + init_interp_fill_missing_urban_with_HD = .false. if (masterproc) then unitn = getavu() @@ -130,6 +135,7 @@ subroutine initInterp_readnl(NLFilename) call shr_mpi_bcast (init_interp_method, mpicom) call shr_mpi_bcast (init_interp_fill_missing_with_natveg, mpicom) + call shr_mpi_bcast (init_interp_fill_missing_urban_with_HD, mpicom) if (masterproc) then write(iulog,*) ' ' @@ -287,12 +293,36 @@ subroutine initInterp (filei, fileo, bounds, glc_behavior) status = pio_get_att(ncidi, pio_global, & 'icol_vegetated_or_bare_soil', & subgrid_special_indices%icol_vegetated_or_bare_soil) + status = pio_get_att(ncidi, pio_global, & + 'icol_urban_roof', & + subgrid_special_indices%icol_urban_roof) + status = pio_get_att(ncidi, pio_global, & + 'icol_urban_sunwall', & + subgrid_special_indices%icol_urban_sunwall) + status = pio_get_att(ncidi, pio_global, & + 'icol_urban_shadewall', & + subgrid_special_indices%icol_urban_shadewall) + status = pio_get_att(ncidi, pio_global, & + 'icol_urban_impervious_road', & + subgrid_special_indices%icol_urban_impervious_road) + status = pio_get_att(ncidi, pio_global, & + 'icol_urban_pervious_road', & + subgrid_special_indices%icol_urban_pervious_road) status = pio_get_att(ncidi, pio_global, & 'ilun_vegetated_or_bare_soil', & subgrid_special_indices%ilun_vegetated_or_bare_soil) status = pio_get_att(ncidi, pio_global, & 'ilun_crop', & subgrid_special_indices%ilun_crop) + status = pio_get_att(ncidi, pio_global, & + 'ilun_urban_tbd', & + subgrid_special_indices%ilun_urban_TBD) + status = pio_get_att(ncidi, pio_global, & + 'ilun_urban_hd', & + subgrid_special_indices%ilun_urban_HD) + status = pio_get_att(ncidi, pio_global, & + 'ilun_urban_md', & + subgrid_special_indices%ilun_urban_MD) ! BACKWARDS_COMPATIBILITY(wjs, 2021-04-16) ilun_landice_multiple_elevation_classes has ! been renamed to ilun_landice. For now we need to handle both possibilities for the @@ -321,10 +351,26 @@ subroutine initInterp (filei, fileo, bounds, glc_behavior) subgrid_special_indices%ipft_not_vegetated write(iulog,*)'icol_vegetated_or_bare_soil = ' , & subgrid_special_indices%icol_vegetated_or_bare_soil + write(iulog,*)'icol_urban_roof = ' , & + subgrid_special_indices%icol_urban_roof + write(iulog,*)'icol_urban_sunwall = ' , & + subgrid_special_indices%icol_urban_sunwall + write(iulog,*)'icol_urban_shadewall = ' , & + subgrid_special_indices%icol_urban_shadewall + write(iulog,*)'icol_urban_impervious_road = ' , & + subgrid_special_indices%icol_urban_impervious_road + write(iulog,*)'icol_urban_pervious_road = ' , & + subgrid_special_indices%icol_urban_pervious_road write(iulog,*)'ilun_vegetated_or_bare_soil = ' , & subgrid_special_indices%ilun_vegetated_or_bare_soil write(iulog,*)'ilun_crop = ' , & subgrid_special_indices%ilun_crop + write(iulog,*)'ilun_urban_tbd = ' , & + subgrid_special_indices%ilun_urban_TBD + write(iulog,*)'ilun_urban_hd = ' , & + subgrid_special_indices%ilun_urban_HD + write(iulog,*)'ilun_urban_md = ' , & + subgrid_special_indices%ilun_urban_MD write(iulog,*)'ilun_landice = ' , & subgrid_special_indices%ilun_landice write(iulog,*)'create_glacier_mec_landunits = ', & @@ -820,13 +866,13 @@ subroutine findMinDist( dimname, begi, endi, bego, endo, ncidi, ncido, & write(iulog,*)'calling set_subgrid_info for ',trim(dimname), ' for input' end if call set_subgrid_info(beg=begi, end=endi, dimname=dimname, use_glob=.true., & - ncid=ncidi, active=activei, subgrid=subgridi) + ncid=ncidi, active=activei, subgrid=subgridi, allow_scm=.false.) if (masterproc) then write(iulog,*)'calling set_subgrid_info for ',trim(dimname), ' for output' end if call set_subgrid_info(beg=bego, end=endo, dimname=dimname, use_glob=.false., & - ncid=ncido, active=activeo, subgrid=subgrido) + ncid=ncido, active=activeo, subgrid=subgrido, allow_scm=.true.) select case (interp_method) case (interp_method_general) @@ -839,6 +885,7 @@ subroutine findMinDist( dimname, begi, endi, bego, endo, ncidi, ncido, & glc_behavior=glc_behavior, & glc_elevclasses_same=glc_elevclasses_same, & fill_missing_with_natveg=init_interp_fill_missing_with_natveg, & + fill_missing_urban_with_HD=init_interp_fill_missing_urban_with_HD, & mindist_index=minindx) case (interp_method_finidat_areas) if (masterproc) then @@ -859,7 +906,7 @@ end subroutine findMinDist !======================================================================= - subroutine set_subgrid_info(beg, end, dimname, use_glob, ncid, active, subgrid) + subroutine set_subgrid_info(beg, end, dimname, use_glob, ncid, active, subgrid, allow_scm) ! -------------------------------------------------------------------- ! arguments @@ -869,6 +916,7 @@ subroutine set_subgrid_info(beg, end, dimname, use_glob, ncid, active, subgrid) logical , intent(in) :: use_glob ! if .true., use the 'glob' form of ncd_io logical , intent(out) :: active(beg:end) type(subgrid_type) , intent(inout) :: subgrid + logical , intent(in) :: allow_scm ! if .true., allow single column model subset of data ! ! local variables integer :: n @@ -896,32 +944,32 @@ subroutine set_subgrid_info(beg, end, dimname, use_glob, ncid, active, subgrid) end if if (dimname == 'pft') then - call read_var_double(ncid=ncid, varname='pfts1d_lon' , data=subgrid%lon , dim1name='pft', use_glob=use_glob) - call read_var_double(ncid=ncid, varname='pfts1d_lat' , data=subgrid%lat , dim1name='pft', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='pfts1d_itypveg', data=subgrid%ptype, dim1name='pft', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='pfts1d_itypcol', data=subgrid%ctype, dim1name='pft', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='pfts1d_ityplun', data=subgrid%ltype, dim1name='pft', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='pfts1d_active' , data=itemp , dim1name='pft', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='pfts1d_lon' , data=subgrid%lon , dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) + call read_var_double(ncid=ncid, varname='pfts1d_lat' , data=subgrid%lat , dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='pfts1d_itypveg', data=subgrid%ptype, dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='pfts1d_itypcol', data=subgrid%ctype, dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='pfts1d_ityplun', data=subgrid%ltype, dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='pfts1d_active' , data=itemp , dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) if (associated(subgrid%topoglc)) then - call read_var_double(ncid=ncid, varname='pfts1d_topoglc', data=subgrid%topoglc, dim1name='pft', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='pfts1d_topoglc', data=subgrid%topoglc, dim1name='pft', use_glob=use_glob, allow_scm=allow_scm) end if else if (dimname == 'column') then - call read_var_double(ncid=ncid, varname='cols1d_lon' , data=subgrid%lon , dim1name='column', use_glob=use_glob) - call read_var_double(ncid=ncid, varname='cols1d_lat' , data=subgrid%lat , dim1name='column', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='cols1d_ityp' , data=subgrid%ctype, dim1name='column', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='cols1d_ityplun', data=subgrid%ltype, dim1name='column', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='cols1d_active' , data=itemp , dim1name='column', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='cols1d_lon' , data=subgrid%lon , dim1name='column', use_glob=use_glob, allow_scm=allow_scm) + call read_var_double(ncid=ncid, varname='cols1d_lat' , data=subgrid%lat , dim1name='column', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='cols1d_ityp' , data=subgrid%ctype, dim1name='column', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='cols1d_ityplun', data=subgrid%ltype, dim1name='column', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='cols1d_active' , data=itemp , dim1name='column', use_glob=use_glob, allow_scm=allow_scm) if (associated(subgrid%topoglc)) then - call read_var_double(ncid=ncid, varname='cols1d_topoglc', data=subgrid%topoglc, dim1name='column', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='cols1d_topoglc', data=subgrid%topoglc, dim1name='column', use_glob=use_glob, allow_scm=allow_scm) end if else if (dimname == 'landunit') then - call read_var_double(ncid=ncid, varname='land1d_lon' , data=subgrid%lon , dim1name='landunit', use_glob=use_glob) - call read_var_double(ncid=ncid, varname='land1d_lat' , data=subgrid%lat , dim1name='landunit', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='land1d_ityplun', data=subgrid%ltype, dim1name='landunit', use_glob=use_glob) - call read_var_int(ncid=ncid, varname='land1d_active' , data=itemp , dim1name='landunit', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='land1d_lon' , data=subgrid%lon , dim1name='landunit', use_glob=use_glob, allow_scm=allow_scm) + call read_var_double(ncid=ncid, varname='land1d_lat' , data=subgrid%lat , dim1name='landunit', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='land1d_ityplun', data=subgrid%ltype, dim1name='landunit', use_glob=use_glob, allow_scm=allow_scm) + call read_var_int(ncid=ncid, varname='land1d_active' , data=itemp , dim1name='landunit', use_glob=use_glob, allow_scm=allow_scm) else if (dimname == 'gridcell') then - call read_var_double(ncid=ncid, varname='grid1d_lon' , data=subgrid%lon , dim1name='gridcell', use_glob=use_glob) - call read_var_double(ncid=ncid, varname='grid1d_lat' , data=subgrid%lat , dim1name='gridcell', use_glob=use_glob) + call read_var_double(ncid=ncid, varname='grid1d_lon' , data=subgrid%lon , dim1name='gridcell', use_glob=use_glob, allow_scm=allow_scm) + call read_var_double(ncid=ncid, varname='grid1d_lat' , data=subgrid%lat , dim1name='gridcell', use_glob=use_glob, allow_scm=allow_scm) ! All gridcells in the restart file are active itemp(beg:end) = 1 @@ -942,7 +990,7 @@ subroutine set_subgrid_info(beg, end, dimname, use_glob, ncid, active, subgrid) contains - subroutine read_var_double(ncid, varname, data, dim1name, use_glob) + subroutine read_var_double(ncid, varname, data, dim1name, use_glob, allow_scm) ! Wraps the ncd_io call, providing logic related to whether we're using the 'glob' ! form of ncd_io type(file_desc_t) , intent(inout) :: ncid @@ -950,15 +998,29 @@ subroutine read_var_double(ncid, varname, data, dim1name, use_glob) real(r8), pointer , intent(inout) :: data(:) character(len=*) , intent(in) :: dim1name logical , intent(in) :: use_glob ! if .true., use the 'glob' form of ncd_io + logical , intent(in) :: allow_scm ! if .true., allow single column model subset of data + + ! local + character(16) :: readflag + + if (allow_scm) then + readflag='read' + else + ! Flag to distinguish the times during IC interpolation when running in single column mode but + ! need to read the full data grid. Normally single_column means + ! "read the data grid and extract the closest column" but + ! during IC interpolation you need to read in the full grid to be interpolated regardless of the single_column flag. + readflag='read_noscm' + endif if (use_glob) then - call ncd_io(ncid=ncid, varname=varname, flag='read', data=data) + call ncd_io(ncid=ncid, varname=varname, flag=trim(readflag), data=data) else - call ncd_io(ncid=ncid, varname=varname, flag='read', data=data, dim1name=dim1name) + call ncd_io(ncid=ncid, varname=varname, flag=trim(readflag), data=data, dim1name=dim1name) end if end subroutine read_var_double - subroutine read_var_int(ncid, varname, data, dim1name, use_glob) + subroutine read_var_int(ncid, varname, data, dim1name, use_glob, allow_scm) ! Wraps the ncd_io call, providing logic related to whether we're using the 'glob' ! form of ncd_io type(file_desc_t) , intent(inout) :: ncid @@ -966,11 +1028,25 @@ subroutine read_var_int(ncid, varname, data, dim1name, use_glob) integer, pointer , intent(inout) :: data(:) character(len=*) , intent(in) :: dim1name logical , intent(in) :: use_glob ! if .true., use the 'glob' form of ncd_io + logical , intent(in) :: allow_scm ! if .true., allow single column model subset of data + + ! local + character(16) :: readflag + + if (allow_scm) then + readflag='read' + else + ! Flag to distinguish the times during IC interpolation when running in single column mode but + ! need to read the full data grid. Normally single_column means + ! "read the data grid and extract the closest column" but + ! during IC interpolation you need to read in the full grid to be interpolated regardless of the single_column flag. + readflag='read_noscm' + endif if (use_glob) then - call ncd_io(ncid=ncid, varname=varname, flag='read', data=data) + call ncd_io(ncid=ncid, varname=varname, flag=trim(readflag), data=data) else - call ncd_io(ncid=ncid, varname=varname, flag='read', data=data, dim1name=dim1name) + call ncd_io(ncid=ncid, varname=varname, flag=trim(readflag), data=data, dim1name=dim1name) end if end subroutine read_var_int @@ -1038,7 +1114,7 @@ subroutine interp_1d_double (varname, varname_i, dimname, begi, endi, bego, endo end if allocate (rbufsli(begi:endi), rbufslo(bego:endo)) - call ncd_io(ncid=ncidi, varname=trim(varname_i), flag='read', data=rbufsli) + call ncd_io(ncid=ncidi, varname=trim(varname_i), flag='read_noscm', data=rbufsli) call ncd_io(ncid=ncido, varname=trim(varname), flag='read', data=rbufslo, & dim1name=dimname) @@ -1080,7 +1156,7 @@ subroutine interp_1d_int (varname, varname_i, dimname, begi, endi, bego, endo, n allocate (ibufsli(begi:endi), ibufslo(bego:endo)) - call ncd_io(ncid=ncidi, varname=trim(varname_i), flag='read', & + call ncd_io(ncid=ncidi, varname=trim(varname_i), flag='read_noscm', & data=ibufsli) call ncd_io(ncid=ncido, varname=trim(varname), flag='read', & data=ibufslo, dim1name=dimname) diff --git a/src/init_interp/initInterpMindist.F90 b/src/init_interp/initInterpMindist.F90 index f6853b1cd3..9fdc9f81dd 100644 --- a/src/init_interp/initInterpMindist.F90 +++ b/src/init_interp/initInterpMindist.F90 @@ -32,11 +32,20 @@ module initInterpMindist type, public :: subgrid_special_indices_type integer :: ipft_not_vegetated integer :: icol_vegetated_or_bare_soil + integer :: icol_urban_roof + integer :: icol_urban_sunwall + integer :: icol_urban_shadewall + integer :: icol_urban_impervious_road + integer :: icol_urban_pervious_road integer :: ilun_vegetated_or_bare_soil integer :: ilun_crop integer :: ilun_landice + integer :: ilun_urban_TBD + integer :: ilun_urban_HD + integer :: ilun_urban_MD contains procedure :: is_vegetated_landunit ! returns true if the given landunit type is natural veg or crop + procedure :: is_urban_landunit ! returns true if the given landunit type is urban end type subgrid_special_indices_type type, public :: subgrid_type @@ -58,8 +67,10 @@ module initInterpMindist private :: set_glc_must_be_same_type private :: set_ice_adjustable_type private :: do_fill_missing_with_natveg + private :: do_fill_missing_urban_with_HD private :: is_sametype private :: is_baresoil + private :: is_urban_HD character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -147,7 +158,7 @@ end subroutine destroy_subgrid_type subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgrido, & subgrid_special_indices, glc_behavior, glc_elevclasses_same, & - fill_missing_with_natveg, mindist_index) + fill_missing_with_natveg, fill_missing_urban_with_HD, mindist_index) ! -------------------------------------------------------------------- ! arguments @@ -165,7 +176,7 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr logical , intent(in) :: glc_elevclasses_same ! If false: if an output type cannot be found in the input, code aborts - ! If true: if an output type cannot be found in the input, fill with closest natural + ! If true: if a non-urban output type cannot be found in the input, fill with closest natural ! veg column (using bare soil for patch-level variables) ! ! NOTE: always treated as true for natural veg and crop landunits/columns/patches in @@ -173,6 +184,11 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr ! use the closest natural veg column, regardless of the value of this flag. logical , intent(in) :: fill_missing_with_natveg + + ! If false: if an urban output type cannot be found in the input, code aborts + ! If true: if an urban output type cannot be found in the input, fill with closest urban HD + logical , intent(in) :: fill_missing_urban_with_HD + integer , intent(out) :: mindist_index(bego:endo) ! ! local variables @@ -187,6 +203,8 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr ! considered the same type. This is only valid for glc points, and is only valid ! for subgrid name = 'pft' or 'column'. logical :: glc_must_be_same_type_o(bego:endo) + + character(len=*), parameter :: subname = 'set_mindist' ! -------------------------------------------------------------------- if (associated(subgridi%topoglc) .and. associated(subgrido%topoglc)) then @@ -221,7 +239,8 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr subgridi = subgridi, subgrido = subgrido, & subgrid_special_indices = subgrid_special_indices, & glc_must_be_same_type = glc_must_be_same_type_o(no), & - veg_patch_just_considers_ptype = .true.)) then + veg_patch_just_considers_ptype = .true., & + do_fill_missing_urban_with_HD = .false.)) then dy = abs(subgrido%lat(no)-subgridi%lat(ni))*re dx = abs(subgrido%lon(no)-subgridi%lon(ni))*re * & 0.5_r8*(subgrido%coslat(no)+subgridi%coslat(ni)) @@ -260,7 +279,11 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr end if end do - ! If output type is not contained in input dataset, then use closest bare soil, + ! Note that do_fill_missing_with_natveg below will return .false. for pfts and columnns associated + ! with urban landunits so that the fill missing with bare soil will be implemented only for + ! non-urban types (pfts, columns, landunits, gridcells). + + ! If non-urban output type is not contained in input dataset, then use closest bare soil, ! if this point is one for which we fill missing with natveg. if ( distmin == spval .and. & do_fill_missing_with_natveg( & @@ -279,6 +302,50 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr end if end if end do + + ! If urban output type is not contained in input dataset, then use closest urban HD, + ! if this point is one for which we fill missing urban with urban HD. + else if (distmin == spval & + .and. do_fill_missing_urban_with_HD( & + fill_missing_urban_with_HD, no, subgrido, subgrid_special_indices)) then + do ni = begi, endi + if (activei(ni)) then + ! We need to call is_sametype for pfts and columns here to make sure that each + ! urban input pft and column type matches the output pft and column type. We don't + ! want to call it for landunits because they intentionally won't be the same type + ! (since we are filling missing urban landunits with HD) + if (subgrido%name .eq. 'landunit') then + if ( is_urban_HD(ni, subgridi, subgrid_special_indices)) then + dy = abs(subgrido%lat(no)-subgridi%lat(ni))*re + dx = abs(subgrido%lon(no)-subgridi%lon(ni))*re * & + 0.5_r8*(subgrido%coslat(no)+subgridi%coslat(ni)) + dist = dx*dx + dy*dy + if ( dist < distmin )then + distmin = dist + nmin = ni + end if + end if + else + if (is_sametype(ni = ni, no = no, & + subgridi = subgridi, subgrido = subgrido, & + subgrid_special_indices = subgrid_special_indices, & + glc_must_be_same_type = glc_must_be_same_type_o(no), & + veg_patch_just_considers_ptype = .false., & + do_fill_missing_urban_with_HD = .true.)) then + if ( is_urban_HD(ni, subgridi, subgrid_special_indices)) then + dy = abs(subgrido%lat(no)-subgridi%lat(ni))*re + dx = abs(subgrido%lon(no)-subgridi%lon(ni))*re * & + 0.5_r8*(subgrido%coslat(no)+subgridi%coslat(ni)) + dist = dx*dx + dy*dy + if ( dist < distmin )then + distmin = dist + nmin = ni + end if + end if + end if + end if + end if + end do end if ! Error conditions @@ -287,13 +354,29 @@ subroutine set_mindist(begi, endi, bego, endo, activei, activeo, subgridi, subgr &Cannot find any input points matching output point:' call subgrido%print_point(no, iulog) write(iulog,*) ' ' - write(iulog,*) 'Consider rerunning with the following in user_nl_clm:' + write(iulog,*) 'If this is an urban type' + write(iulog,*) '(ltype = ', subgrid_special_indices%ilun_urban_TBD, & + ',', subgrid_special_indices%ilun_urban_HD, & + ', or', subgrid_special_indices%ilun_urban_MD, ')' + write(iulog,*) 'then consider rerunning with the following in user_nl_clm:' + write(iulog,*) 'init_interp_fill_missing_urban_with_HD = .true.' + write(iulog,*) 'However, note that this will fill all urban missing types in the output' + write(iulog,*) 'with the closest urban high density (HD) type in the input' + write(iulog,*) 'So, you should consider whether that is what you want.' + write(iulog,*) ' ' + write(iulog,*) 'If this is a non-urban type' + write(iulog,*) '(ltype \= ',subgrid_special_indices%ilun_urban_TBD, & + ',', subgrid_special_indices%ilun_urban_HD, & + ', or', subgrid_special_indices%ilun_urban_MD, ')' + write(iulog,*) 'consider rerunning with the following in user_nl_clm:' write(iulog,*) 'init_interp_fill_missing_with_natveg = .true.' - write(iulog,*) 'However, note that this will fill all missing types in the output' + write(iulog,*) 'However, note that this will fill all non-urban missing types in the output' write(iulog,*) 'with the closest natural veg column in the input' write(iulog,*) '(using bare soil for patch-level variables).' write(iulog,*) 'So, you should consider whether that is what you want.' - call endrun(msg=errMsg(sourcefile, __LINE__)) + write(iulog,*) errMsg(sourcefile, __LINE__) + call endrun(msg=subname// & + ' ERROR: Cannot find any input points matching output point') end if mindist_index(no) = nmin @@ -378,7 +461,8 @@ subroutine set_single_match(begi, endi, bego, endo, activeo, subgridi, subgrido, subgridi = subgridi, subgrido = subgrido, & subgrid_special_indices = subgrid_special_indices, & glc_must_be_same_type = glc_must_be_same_type_o(no), & - veg_patch_just_considers_ptype = .false.) + veg_patch_just_considers_ptype = .false., & + do_fill_missing_urban_with_HD = .false.) if (ni_sametype) then if (found) then write(iulog,*) subname// & @@ -555,7 +639,7 @@ function do_fill_missing_with_natveg(fill_missing_with_natveg, & no, subgrido, subgrid_special_indices) ! ! !DESCRIPTION: - ! Returns true if the given output point, if missing, should be filled with the + ! Returns true if the given non-urban output point, if missing, should be filled with the ! closest natural veg point. ! ! !ARGUMENTS: @@ -576,8 +660,8 @@ function do_fill_missing_with_natveg(fill_missing_with_natveg, & if (subgrido%name == 'gridcell') then ! It makes no sense to try to fill missing with natveg for gridcell-level values do_fill_missing_with_natveg = .false. - else if (fill_missing_with_natveg) then - ! User has asked for all missing points to be filled with natveg + else if (fill_missing_with_natveg .and. .not. subgrid_special_indices%is_urban_landunit(subgrido%ltype(no))) then + ! User has asked for all non-urban missing points to be filled with natveg do_fill_missing_with_natveg = .true. else if (subgrid_special_indices%is_vegetated_landunit(subgrido%ltype(no))) then ! Even if user hasn't asked for it, we fill missing vegetated points (natural veg @@ -591,11 +675,46 @@ function do_fill_missing_with_natveg(fill_missing_with_natveg, & end function do_fill_missing_with_natveg + !----------------------------------------------------------------------- + function do_fill_missing_urban_with_HD(fill_missing_urban_with_HD, & + no, subgrido, subgrid_special_indices) + ! + ! !DESCRIPTION: + ! Returns true if the given urban output point, if missing, should be filled with the + ! closest urban HD point. + ! + ! !ARGUMENTS: + logical :: do_fill_missing_urban_with_HD ! function result + + ! whether we should fill ALL missing points with urban HD + logical, intent(in) :: fill_missing_urban_with_HD + + integer , intent(in) :: no + type(subgrid_type), intent(in) :: subgrido + type(subgrid_special_indices_type), intent(in) :: subgrid_special_indices + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'do_fill_missing_urban_with_HD' + !----------------------------------------------------------------------- + + if (subgrido%name == 'gridcell') then + ! It makes no sense to try to fill missing with urban HD for gridcell-level values + do_fill_missing_urban_with_HD = .false. + else if (fill_missing_urban_with_HD) then + ! User has asked for all missing urban points to be filled with urban HD + do_fill_missing_urban_with_HD = .true. + else + do_fill_missing_urban_with_HD = .false. + end if + + end function do_fill_missing_urban_with_HD !======================================================================= logical function is_sametype (ni, no, subgridi, subgrido, subgrid_special_indices, & - glc_must_be_same_type, veg_patch_just_considers_ptype) + glc_must_be_same_type, veg_patch_just_considers_ptype, & + do_fill_missing_urban_with_HD) ! -------------------------------------------------------------------- ! arguments @@ -620,6 +739,12 @@ logical function is_sametype (ni, no, subgridi, subgrido, subgrid_special_indice ! If false, then they need to have the same column and landunit types, too (as is the ! general case). logical, intent(in) :: veg_patch_just_considers_ptype + + ! If True, we allow for landunits to be different when checking if pft and column are + ! the same type, to allow for HD fill of missing urban output points. + logical, intent(in) :: do_fill_missing_urban_with_HD + + ! For urban columns/patches ! -------------------------------------------------------------------- is_sametype = .false. @@ -644,6 +769,10 @@ logical function is_sametype (ni, no, subgridi, subgrido, subgrid_special_indice subgridi%ptype(ni) == subgrido%ptype(no)) then is_sametype = .true. end if + else if (subgridi%ptype(ni) == subgrido%ptype(no) .and. & + subgridi%ctype(ni) == subgrido%ctype(no) .and. & + do_fill_missing_urban_with_HD) then + is_sametype = .true. else if (subgridi%ptype(ni) == subgrido%ptype(no) .and. & subgridi%ctype(ni) == subgrido%ctype(no) .and. & subgridi%ltype(ni) == subgrido%ltype(no)) then @@ -654,6 +783,9 @@ logical function is_sametype (ni, no, subgridi, subgrido, subgrid_special_indice subgridi%ltype(ni) == subgrid_special_indices%ilun_landice .and. & subgrido%ltype(no) == subgrid_special_indices%ilun_landice ) then is_sametype = .true. + else if (subgridi%ctype(ni) == subgrido%ctype(no) .and. & + do_fill_missing_urban_with_HD) then + is_sametype = .true. else if (subgridi%ctype(ni) == subgrido%ctype(no) .and. & subgridi%ltype(ni) == subgrido%ltype(no)) then is_sametype = .true. @@ -712,6 +844,31 @@ logical function is_baresoil (n, subgrid, subgrid_special_indices) end function is_baresoil + !----------------------------------------------------------------------- + logical function is_urban_HD (n, subgrid, subgrid_special_indices) + + ! -------------------------------------------------------------------- + ! arguments + integer , intent(in) :: n + type(subgrid_type), intent(in) :: subgrid + type(subgrid_special_indices_type), intent(in) :: subgrid_special_indices + ! -------------------------------------------------------------------- + + is_urban_HD = .false. + + if (subgrid%name == 'pft' .or. subgrid%name == 'column' .or. subgrid%name == 'landunit') then + if (subgrid%ltype(n) == subgrid_special_indices%ilun_urban_HD) then + is_urban_HD = .true. + end if + else + if (masterproc) then + write(iulog,*)'ERROR interpinic: is_urban_HD subgrid type ',subgrid%name,' not supported' + end if + call endrun(msg=errMsg(sourcefile, __LINE__)) + end if + + end function is_urban_HD + !----------------------------------------------------------------------- function is_vegetated_landunit(this, ltype) ! @@ -739,5 +896,30 @@ function is_vegetated_landunit(this, ltype) end function is_vegetated_landunit + function is_urban_landunit(this, ltype) + ! + ! !DESCRIPTION: + ! Returns true if the given landunit type is urban + ! + ! !USES: + ! + ! !ARGUMENTS: + logical :: is_urban_landunit ! function result + class(subgrid_special_indices_type), intent(in) :: this + integer, intent(in) :: ltype ! landunit type of interest + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'is_urban_landunit' + !----------------------------------------------------------------------- + + if (ltype == this%ilun_urban_TBD .or. ltype == this%ilun_urban_HD & + .or. ltype == this%ilun_urban_MD) then + is_urban_landunit = .true. + else + is_urban_landunit = .false. + end if + + end function is_urban_landunit end module initInterpMindist diff --git a/src/init_interp/initInterpMultilevelContainer.F90 b/src/init_interp/initInterpMultilevelContainer.F90 index 5a7b14832a..d26e51c71c 100644 --- a/src/init_interp/initInterpMultilevelContainer.F90 +++ b/src/init_interp/initInterpMultilevelContainer.F90 @@ -732,7 +732,7 @@ subroutine create_snow_interpolators(interp_multilevel_levsno, interp_multilevel ! Read snlsno_source_sgrid allocate(snlsno_source_sgrid(bounds_source%get_begc() : bounds_source%get_endc())) - call ncd_io(ncid=ncid_source, varname='SNLSNO', flag='read', & + call ncd_io(ncid=ncid_source, varname='SNLSNO', flag='read_noscm', & data=snlsno_source_sgrid) snlsno_source_sgrid(:) = abs(snlsno_source_sgrid(:)) diff --git a/src/init_interp/test/initInterpMindist_test/initInterpMindistTestUtils.pf b/src/init_interp/test/initInterpMindist_test/initInterpMindistTestUtils.pf index 04f09cb55d..7d277566af 100644 --- a/src/init_interp/test/initInterpMindist_test/initInterpMindistTestUtils.pf +++ b/src/init_interp/test/initInterpMindist_test/initInterpMindistTestUtils.pf @@ -19,12 +19,20 @@ module initInterpMindistTestUtils subgrid_special_indices_type( & ipft_not_vegetated = 0, & icol_vegetated_or_bare_soil = 10, & + icol_urban_roof = 71, & + icol_urban_sunwall = 72, & + icol_urban_shadewall = 73, & + icol_urban_impervious_road = 74, & + icol_urban_pervious_road = 75, & ilun_vegetated_or_bare_soil = 3, & ilun_crop = 4, & - ilun_landice = 5) + ilun_landice = 5, & + ilun_urban_TBD = 7, & + ilun_urban_HD = 8, & + ilun_urban_MD = 9) - ! value we can use for a special landunit; note that this just needs to differ from - ! ilun_vegetated_or_bare_soil and from ilun_crop + ! value we can use for a special landunit; note that this needs to differ from + ! ilun_vegetated_or_bare_soil, ilun_crop, ilun_urban_TBD, ilun_urban_HD, ilun_urban_MD integer, parameter, public :: ilun_special = 6 contains diff --git a/src/init_interp/test/initInterpMindist_test/test_set_mindist.pf b/src/init_interp/test/initInterpMindist_test/test_set_mindist.pf index 06ce20d7de..7a2c51456d 100644 --- a/src/init_interp/test/initInterpMindist_test/test_set_mindist.pf +++ b/src/init_interp/test/initInterpMindist_test/test_set_mindist.pf @@ -10,6 +10,7 @@ module test_set_mindist use clm_varcon , only: spval use unittestSimpleSubgridSetupsMod use unittestSubgridMod + use unittestUtils, only : endrun_msg use glcBehaviorMod, only: glc_behavior_type implicit none @@ -41,7 +42,7 @@ contains end subroutine tearDown subroutine wrap_set_mindist(subgridi, subgrido, mindist_index, activei, activeo, & - glc_behavior, glc_elevclasses_same, fill_missing_with_natveg) + glc_behavior, glc_elevclasses_same, fill_missing_with_natveg, fill_missing_urban_with_HD) ! Wrap the call to set_mindist. ! ! If activei / activeo are not provided, they are assumed to be .true. for all points. @@ -52,6 +53,7 @@ contains ! If glc_elevclasses_same is not present, it is assumed to be true. ! ! If fill_missing_with_natveg is not provided, it is assumed to be false + ! If fill_missing_urban_with_HD is not provided, it is assumed to be false ! Arguments: type(subgrid_type), intent(in) :: subgridi @@ -62,6 +64,7 @@ contains type(glc_behavior_type), intent(in), optional :: glc_behavior logical, intent(in), optional :: glc_elevclasses_same logical, intent(in), optional :: fill_missing_with_natveg + logical, intent(in), optional :: fill_missing_urban_with_HD ! Local variables: integer :: npts_i, npts_o @@ -71,6 +74,7 @@ contains type(glc_behavior_type) :: l_glc_behavior logical :: l_glc_elevclasses_same logical :: l_fill_missing_with_natveg + logical :: l_fill_missing_urban_with_HD !----------------------------------------------------------------------- @@ -115,12 +119,19 @@ contains l_fill_missing_with_natveg = .false. end if + if (present(fill_missing_urban_with_HD)) then + l_fill_missing_urban_with_HD = fill_missing_urban_with_HD + else + l_fill_missing_urban_with_HD = .false. + end if + call set_mindist(begi = 1, endi = npts_i, bego = bego, endo = endo, & activei = l_activei, activeo = l_activeo, subgridi = subgridi, subgrido = subgrido, & subgrid_special_indices = subgrid_special_indices, & glc_behavior = l_glc_behavior, & glc_elevclasses_same = l_glc_elevclasses_same, & fill_missing_with_natveg = l_fill_missing_with_natveg, & + fill_missing_urban_with_HD = l_fill_missing_urban_with_HD, & mindist_index = mindist_index) end subroutine wrap_set_mindist @@ -724,6 +735,186 @@ contains end associate end subroutine newveg_usesBaresoil + @Test + subroutine TBDurban_usesHDurban(this) + ! If there's a new urban TBD type, this should take inputs from the closest + ! HD type, if fill_missing_urban_with_HD = .true and fill_missing_with_natveg = .false. + ! + class(TestSetMindist), intent(inout) :: this + type(subgrid_type) :: subgridi, subgrido + real(r8), parameter :: my_lat = 31._r8 + real(r8), parameter :: my_lon = 41._r8 + integer :: i + integer :: mindist_index(1) + + associate( & + icol_urban_roof => subgrid_special_indices%icol_urban_roof, & + icol_urban_sunwall => subgrid_special_indices%icol_urban_sunwall, & + icol_urban_shadewall => subgrid_special_indices%icol_urban_shadewall, & + icol_urban_impervious_road => subgrid_special_indices%icol_urban_impervious_road, & + icol_urban_pervious_road => subgrid_special_indices%icol_urban_pervious_road, & + ilun_urban_TBD => subgrid_special_indices%ilun_urban_TBD, & + ilun_urban_HD => subgrid_special_indices%ilun_urban_HD, & + ilun_urban_MD => subgrid_special_indices%ilun_urban_MD & + ) + + call setup_landunit_ncols(ltype=ilun_urban_TBD, & + ctypes=[icol_urban_roof,icol_urban_sunwall,icol_urban_shadewall, & + icol_urban_impervious_road,icol_urban_pervious_road], & + cweights=[0.6_r8,0.1_r8,0.1_r8,0.1_r8,0.1_r8], & + ptype=0) + + call create_subgrid_info( & + subgrid_info = subgrido, & + npts = 1, & + beg = 1, & + name = 'landunit', & + ltype = [ilun_urban_TBD], & + lat = [my_lat], & + lon = [my_lon]) + + ! Input points differ in landunit type + call create_subgrid_info( & + subgrid_info = subgridi, & + npts = 2, & + name = 'landunit', & + ltype = [ilun_urban_MD, ilun_urban_HD], & + lat = [(my_lat, i=1,2)], & + lon = [(my_lon, i=1,2)]) + + call wrap_set_mindist(subgridi, subgrido, mindist_index, & + fill_missing_urban_with_HD = .true., & + fill_missing_with_natveg = .false.) + + ! Note that the mindist_index should return the second index of the + ! ltype array (2), not the actual value of ilun_urban_HD + @assertEqual(2, mindist_index(1)) + + end associate + end subroutine TBDurban_usesHDurban + + @Test + subroutine TBDurban_usesHDurban_aborts(this) + ! If there's a new urban TBD type, this should take inputs from the closest + ! HD type. This test will abort correctly if fill_missing_urban_with_HD = .false. + ! + class(TestSetMindist), intent(inout) :: this + type(subgrid_type) :: subgridi, subgrido + real(r8), parameter :: my_lat = 31._r8 + real(r8), parameter :: my_lon = 41._r8 + integer :: i + integer :: mindist_index(1) + character(len=:), allocatable :: expected_msg + + associate( & + icol_urban_roof => subgrid_special_indices%icol_urban_roof, & + icol_urban_sunwall => subgrid_special_indices%icol_urban_sunwall, & + icol_urban_shadewall => subgrid_special_indices%icol_urban_shadewall, & + icol_urban_impervious_road => subgrid_special_indices%icol_urban_impervious_road, & + icol_urban_pervious_road => subgrid_special_indices%icol_urban_pervious_road, & + ilun_urban_TBD => subgrid_special_indices%ilun_urban_TBD, & + ilun_urban_HD => subgrid_special_indices%ilun_urban_HD, & + ilun_urban_MD => subgrid_special_indices%ilun_urban_MD & + ) + + call setup_landunit_ncols(ltype=ilun_urban_TBD, & + ctypes=[icol_urban_roof,icol_urban_sunwall,icol_urban_shadewall, & + icol_urban_impervious_road,icol_urban_pervious_road], & + cweights=[0.6_r8,0.1_r8,0.1_r8,0.1_r8,0.1_r8], & + ptype=0) + + call create_subgrid_info( & + subgrid_info = subgrido, & + npts = 1, & + beg = 1, & + name = 'landunit', & + ltype = [ilun_urban_TBD], & + lat = [my_lat], & + lon = [my_lon]) + + ! Input points differ in landunit type + call create_subgrid_info( & + subgrid_info = subgridi, & + npts = 2, & + name = 'landunit', & + ltype = [ilun_urban_MD, ilun_urban_HD], & + lat = [(my_lat, i=1,2)], & + lon = [(my_lon, i=1,2)]) + + call wrap_set_mindist(subgridi, subgrido, mindist_index, & + fill_missing_urban_with_HD = .false.) + + expected_msg = endrun_msg( & + 'set_mindist ERROR: Cannot find any input points matching output point') + @assertExceptionRaised(expected_msg) + + end associate + end subroutine TBDurban_usesHDurban_aborts + + @Test + subroutine urbanlandunits_NotFilled_with_natveg_aborts(this) + ! With fill_missing_urban_with_HD = .false. and fill_missing_with_natveg = .true., + ! urban landunit should not be filled with natveg, and an error in set_mindist will be + ! thrown, and this test should pass. + ! + class(TestSetMindist), intent(inout) :: this + type(subgrid_type) :: subgridi, subgrido + real(r8), parameter :: my_lat = 31._r8 + real(r8), parameter :: my_lon = 41._r8 + integer :: i + integer :: mindist_index(1) + character(len=:), allocatable :: expected_msg + + associate( & + ipft_bare => subgrid_special_indices%ipft_not_vegetated, & + icol_urban_roof => subgrid_special_indices%icol_urban_roof, & + icol_urban_sunwall => subgrid_special_indices%icol_urban_sunwall, & + icol_urban_shadewall => subgrid_special_indices%icol_urban_shadewall, & + icol_urban_impervious_road => subgrid_special_indices%icol_urban_impervious_road, & + icol_urban_pervious_road => subgrid_special_indices%icol_urban_pervious_road, & + icol_natveg => subgrid_special_indices%icol_vegetated_or_bare_soil, & + ilun_natveg => subgrid_special_indices%ilun_vegetated_or_bare_soil, & + ilun_urban_TBD => subgrid_special_indices%ilun_urban_TBD & + ) + + call setup_landunit_ncols(ltype=ilun_urban_TBD, & + ctypes=[icol_urban_roof,icol_urban_sunwall,icol_urban_shadewall, & + icol_urban_impervious_road,icol_urban_pervious_road], & + cweights=[0.6_r8,0.1_r8,0.1_r8,0.1_r8,0.1_r8], & + ptype=0) + + call create_subgrid_info( & + subgrid_info = subgrido, & + npts = 1, & + beg = 1, & + name = 'pft', & + ptype = [0], & + ctype = [icol_urban_roof], & + ltype = [ilun_urban_TBD], & + lat = [my_lat], & + lon = [my_lon]) + + call create_subgrid_info( & + subgrid_info = subgridi, & + npts = 1, & + name = 'pft', & + ptype = [ipft_bare], & + ctype = [icol_natveg], & + ltype = [ilun_natveg], & + lat = [my_lat], & + lon = [my_lon]) + + call wrap_set_mindist(subgridi, subgrido, mindist_index, & + fill_missing_urban_with_HD = .false., & + fill_missing_with_natveg = .true.) + + expected_msg = endrun_msg( & + 'set_mindist ERROR: Cannot find any input points matching output point') + @assertExceptionRaised(expected_msg) + + end associate + end subroutine urbanlandunits_NotFilled_with_natveg_aborts + @Test subroutine baresoil_ignoresSpecialLandunits(this) ! This test ensures that, when finding a match for a bare soil patch, we ignore diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 53a6edb8a5..fc324efeb9 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -20,6 +20,7 @@ list(APPEND clm_sources column_varcon.F90 decompMod.F90 filterColMod.F90 + FireMethodType.F90 glc2lndMod.F90 glcBehaviorMod.F90 initSubgridMod.F90 diff --git a/src/main/FireMethodType.F90 b/src/main/FireMethodType.F90 index 978450e65f..5f90dea893 100644 --- a/src/main/FireMethodType.F90 +++ b/src/main/FireMethodType.F90 @@ -34,6 +34,9 @@ module FireMethodType ! Figure out the fire fluxes procedure(CNFireFluxes_interface) , public, deferred :: CNFireFluxes + ! Deallocate the fire datasets + procedure(FireClean_interface) , public, deferred :: FireClean + end type fire_method_type abstract interface @@ -52,7 +55,7 @@ module FireMethodType ! consistent between different implementations. ! !--------------------------------------------------------------------------- - subroutine FireInit_interface(this, bounds, NLFilename ) + subroutine FireInit_interface(this, bounds ) ! ! !DESCRIPTION: ! Initialize Fire datasets @@ -63,20 +66,21 @@ subroutine FireInit_interface(this, bounds, NLFilename ) ! !ARGUMENTS: class(fire_method_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- end subroutine FireInit_interface - subroutine FireReadNML_interface(this, NLFilename ) + subroutine FireReadNML_interface(this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read general fire namelist ! ! USES + use decompMod , only : bounds_type import :: fire_method_type ! !ARGUMENTS: class(fire_method_type) :: this + type(bounds_type), intent(in) :: bounds character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- @@ -97,6 +101,20 @@ subroutine FireInterp_interface(this, bounds) end subroutine FireInterp_interface + !----------------------------------------------------------------------- + subroutine FireClean_interface(this) + ! + ! !DESCRIPTION: + ! Deallocate Fire datasets + ! + ! USES + import :: fire_method_type + ! !ARGUMENTS: + class(fire_method_type) :: this + !----------------------------------------------------------------------- + + end subroutine FireClean_interface + !----------------------------------------------------------------------- subroutine CNFireReadParams_interface( this, ncid ) ! diff --git a/src/main/abortutils.F90 b/src/main/abortutils.F90 index c93fd761bf..8afa4ef195 100644 --- a/src/main/abortutils.F90 +++ b/src/main/abortutils.F90 @@ -10,6 +10,8 @@ module abortutils ! in conjunction with aborting the model, or at least issuing a warning. !----------------------------------------------------------------------- + use shr_log_mod , only : errMsg => shr_log_errMsg + implicit none private @@ -27,23 +29,25 @@ module abortutils contains !----------------------------------------------------------------------- - subroutine endrun_vanilla(msg, additional_msg) + subroutine endrun_vanilla(msg, additional_msg, line, file) !----------------------------------------------------------------------- ! !DESCRIPTION: ! Abort the model for abnormal termination ! - use shr_sys_mod , only: shr_sys_abort + use shr_abort_mod , only: shr_abort_abort use clm_varctl , only: iulog ! ! !ARGUMENTS: ! Generally you want to at least provide msg. The main reason to separate msg from ! additional_msg is to supported expected-exception unit testing: you can put ! volatile stuff in additional_msg, as in: - ! call endrun(msg='Informative message', additional_msg=errmsg(__FILE__, __LINE__)) + ! call endrun(msg='Informative message', additional_msg=datetime ) ! and then just assert against msg. - character(len=*), intent(in), optional :: msg ! string to be passed to shr_sys_abort - character(len=*), intent(in), optional :: additional_msg ! string to be printed, but not passed to shr_sys_abort + character(len=*), intent(in), optional :: msg ! string to be passed to shr_abort + character(len=*), intent(in), optional :: additional_msg ! string to be printed, but not passed to shr_abort + integer , intent(in), optional :: line ! Line number for the endrun call + character(len=*), intent(in), optional :: file ! file for the endrun call !----------------------------------------------------------------------- if (present (additional_msg)) then @@ -52,12 +56,16 @@ subroutine endrun_vanilla(msg, additional_msg) write(iulog,*)'ENDRUN:' end if - call shr_sys_abort(msg) + ! Don't pass file and line to shr_abort_abort since the PFUNIT test version doesn't have those options + if ( present(file) .and. present(line) ) then + write(iulog,*) errMsg(file, line) + end if + call shr_abort_abort(string=msg) end subroutine endrun_vanilla !----------------------------------------------------------------------- - subroutine endrun_write_point_context(subgrid_index, subgrid_level, msg, additional_msg) + subroutine endrun_write_point_context(subgrid_index, subgrid_level, msg, additional_msg, line, file) !----------------------------------------------------------------------- ! Description: @@ -65,21 +73,16 @@ subroutine endrun_write_point_context(subgrid_index, subgrid_level, msg, additio ! ! This version also prints additional information about the point causing the error. ! - use shr_sys_mod , only: shr_sys_abort use clm_varctl , only: iulog use decompMod , only: subgrid_level_unspecified ! ! Arguments: integer , intent(in) :: subgrid_index ! index of interest (can be at any subgrid level or gridcell level) integer , intent(in) :: subgrid_level ! one of the subgrid_level_* constants defined in decompMod; subgrid_level_unspecified is allowed here, in which case the additional information will not be printed - - ! Generally you want to at least provide msg. The main reason to separate msg from - ! additional_msg is to supported expected-exception unit testing: you can put - ! volatile stuff in additional_msg, as in: - ! call endrun(msg='Informative message', additional_msg=errmsg(__FILE__, __LINE__)) - ! and then just assert against msg. - character(len=*), intent(in), optional :: msg ! string to be passed to shr_sys_abort - character(len=*), intent(in), optional :: additional_msg ! string to be printed, but not passed to shr_sys_abort + integer , intent(in), optional :: line ! Line number for the endrun call + character(len=*), intent(in), optional :: file !file for the endrun call + character(len=*), intent(in), optional :: msg ! string to be passed to shr_abort + character(len=*), intent(in), optional :: additional_msg ! string to be printed, but not passed to shr_abort ! ! Local Variables: integer :: igrc, ilun, icol @@ -89,13 +92,7 @@ subroutine endrun_write_point_context(subgrid_index, subgrid_level, msg, additio call write_point_context(subgrid_index, subgrid_level) end if - if (present (additional_msg)) then - write(iulog,*)'ENDRUN: ', additional_msg - else - write(iulog,*)'ENDRUN:' - end if - - call shr_sys_abort(msg) + call endrun_vanilla(msg=msg, additional_msg=additional_msg, line=line, file=file) end subroutine endrun_write_point_context @@ -107,8 +104,8 @@ subroutine write_point_context(subgrid_index, subgrid_level) ! Write various information giving context for the given index at the given subgrid ! level, including global index information and more. ! - use shr_sys_mod , only : shr_sys_flush, shr_sys_abort - use shr_log_mod , only : errMsg => shr_log_errMsg + ! NOTE: DO NOT CALL AN ABORT FROM HERE AS THAT WOULD SHORT CIRUIT THE ERROR REPORTING!! + ! use clm_varctl , only : iulog use decompMod , only : subgrid_level_gridcell, subgrid_level_landunit, subgrid_level_column, subgrid_level_patch use decompMod , only : get_global_index @@ -123,43 +120,105 @@ subroutine write_point_context(subgrid_index, subgrid_level) integer , intent(in) :: subgrid_level ! one of the subgrid_level_* constants defined in decompMod ! ! Local Variables: - integer :: igrc, ilun, icol, ipft + integer, parameter :: unset = -9999 ! Unset value for an index + integer :: igrc=unset, ilun=unset, icol=unset, ipft=unset ! Local index for grid-cell, landunit, column, and patch + integer :: ggrc=unset, glun=unset, gcol=unset, gpft=unset ! Global index for grid-cell, landunit, column, and patch + logical :: bad_point = .false. ! Flag to indicate if the point is bad (i.e., global index is -1) !----------------------------------------------------------------------- if (subgrid_level == subgrid_level_gridcell) then igrc = subgrid_index + ggrc = get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell, donot_abort_on_badindex=.true.) + + else if (subgrid_level == subgrid_level_landunit) then + + ilun = subgrid_index + glun = get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit, donot_abort_on_badindex=.true.) + if ( glun /= -1 ) then + igrc = lun%gridcell(ilun) + ggrc = get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell, donot_abort_on_badindex=.true.) + else + bad_point = .true. + end if + + else if (subgrid_level == subgrid_level_column) then + + icol = subgrid_index + gcol = get_global_index(subgrid_index=icol, subgrid_level=subgrid_level_column, donot_abort_on_badindex=.true.) + if ( gcol /= -1 ) then + ilun = col%landunit(icol) + igrc = col%gridcell(icol) + ggrc = get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell, donot_abort_on_badindex=.true.) + glun = get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit, donot_abort_on_badindex=.true.) + else + bad_point = .true. + end if + + else if (subgrid_level == subgrid_level_patch) then + + ipft = subgrid_index + gpft = get_global_index(subgrid_index=ipft, subgrid_level=subgrid_level_patch, donot_abort_on_badindex=.true.) + if ( gpft /= -1 ) then + icol = patch%column(ipft) + ilun = patch%landunit(ipft) + igrc = patch%gridcell(ipft) + ggrc = get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell, donot_abort_on_badindex=.true.) + glun = get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit, donot_abort_on_badindex=.true.) + gcol = get_global_index(subgrid_index=icol, subgrid_level=subgrid_level_column, donot_abort_on_badindex=.true.) + else + bad_point = .true. + end if + + end if + + ! + ! Badpoint should already be determined, but check again in case one of the subsequent + ! calls to get_global_index returns -1 + ! If one of the global indices is -1 then this is a bad point, so flag a bad-point + if ( igrc /= unset) then + if ( ggrc == -1 ) bad_point = .true. + end if + if ( ilun /= unset) then + if ( glun == -1 ) bad_point = .true. + end if + if ( icol /= unset) then + if ( gcol == -1 ) bad_point = .true. + end if + if ( ipft /= unset) then + if ( gpft == -1 ) bad_point = .true. + end if + + if (bad_point) then + write(iulog,*) 'A bad input point was given: subgrid_index = ', subgrid_index, & + ', subgrid_level = ', subgrid_level + write(iulog,*) errMsg(sourcefile, __LINE__) + write(iulog,*) 'Continuing the endrun without writing point context information' + return + end if + + if (subgrid_level == subgrid_level_gridcell) then + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': local gridcell index = ', igrc - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', & - get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell) + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', ggrc write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell longitude = ', grc%londeg(igrc) write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell latitude = ', grc%latdeg(igrc) else if (subgrid_level == subgrid_level_landunit) then - ilun = subgrid_index - igrc = lun%gridcell(ilun) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': local landunit index = ', ilun - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', & - get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', & - get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell) + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', glun + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', ggrc write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell longitude = ', grc%londeg(igrc) write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell latitude = ', grc%latdeg(igrc) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': landunit type = ', lun%itype(subgrid_index) else if (subgrid_level == subgrid_level_column) then - icol = subgrid_index - ilun = col%landunit(icol) - igrc = col%gridcell(icol) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': local column index = ', icol - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global column index = ', & - get_global_index(subgrid_index=icol, subgrid_level=subgrid_level_column) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', & - get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', & - get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell) + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global column index = ', gcol + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', glun + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', ggrc write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell longitude = ', grc%londeg(igrc) write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell latitude = ', grc%latdeg(igrc) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': column type = ', col%itype(icol) @@ -167,19 +226,11 @@ subroutine write_point_context(subgrid_index, subgrid_level) else if (subgrid_level == subgrid_level_patch) then - ipft = subgrid_index - icol = patch%column(ipft) - ilun = patch%landunit(ipft) - igrc = patch%gridcell(ipft) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': local patch index = ', ipft - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global patch index = ', & - get_global_index(subgrid_index=ipft, subgrid_level=subgrid_level_patch) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global column index = ', & - get_global_index(subgrid_index=icol, subgrid_level=subgrid_level_column) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', & - get_global_index(subgrid_index=ilun, subgrid_level=subgrid_level_landunit) - write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', & - get_global_index(subgrid_index=igrc, subgrid_level=subgrid_level_gridcell) + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global patch index = ', gpft + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global column index = ', gcol + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global landunit index = ', glun + write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': global gridcell index = ', ggrc write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell longitude = ', grc%londeg(igrc) write(iulog,'(a, i0, a, f12.7)') 'iam = ', iam, ': gridcell latitude = ', grc%latdeg(igrc) write(iulog,'(a, i0, a, i0)') 'iam = ', iam, ': pft type = ', patch%itype(ipft) @@ -188,11 +239,11 @@ subroutine write_point_context(subgrid_index, subgrid_level) else write(iulog,*) 'subgrid_level not supported: ', subgrid_level - call shr_sys_abort('subgrid_level not supported '//errmsg(sourcefile, __LINE__)) + write(iulog,*) errMsg(sourcefile, __LINE__) + write(iulog,*) 'Continuing the endrun without writing point context information' + return end if - call shr_sys_flush(iulog) - end subroutine write_point_context end module abortutils diff --git a/src/main/clm_driver.F90 b/src/main/clm_driver.F90 index 8c5b2fe612..1e54591569 100644 --- a/src/main/clm_driver.F90 +++ b/src/main/clm_driver.F90 @@ -18,6 +18,7 @@ module clm_driver use clm_time_manager , only : get_nstep, is_beg_curr_day, is_beg_curr_year use clm_time_manager , only : get_prev_date, is_first_step use clm_varpar , only : nlevsno, nlevgrnd + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) use clm_varorb , only : obliqr use spmdMod , only : masterproc, mpicom use decompMod , only : get_proc_clumps, get_clump_bounds, get_proc_bounds, bounds_type @@ -727,16 +728,30 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro ! bugs. allocate(downreg_patch(bounds_clump%begp:bounds_clump%endp)) allocate(leafn_patch(bounds_clump%begp:bounds_clump%endp)) - downreg_patch = bgc_vegetation_inst%get_downreg_patch(bounds_clump) - leafn_patch = bgc_vegetation_inst%get_leafn_patch(bounds_clump) - allocate(froot_carbon(bounds_clump%begp:bounds_clump%endp)) allocate(croot_carbon(bounds_clump%begp:bounds_clump%endp)) - froot_carbon = bgc_vegetation_inst%get_froot_carbon_patch( & - bounds_clump, canopystate_inst%tlai_patch(bounds_clump%begp:bounds_clump%endp)) - croot_carbon = bgc_vegetation_inst%get_croot_carbon_patch( & - bounds_clump, canopystate_inst%tlai_patch(bounds_clump%begp:bounds_clump%endp)) + ! The get functions for these four patch arrays are relevant only + ! for native cn vegetation. More importantly, they utilize patch%itype(p) + ! which is invalid for fates patches and cannot be accessed without failure + ! These arrays must be passed as arguments, so we clearly fill them here + ! with unusuable special values when fates is active, and meaningful values + ! when fates is not active. + + if(use_fates)then + downreg_patch(:) = nan + leafn_patch(:) = nan + froot_carbon(:) = nan + croot_carbon(:) = nan + else + downreg_patch = bgc_vegetation_inst%get_downreg_patch(bounds_clump) + leafn_patch = bgc_vegetation_inst%get_leafn_patch(bounds_clump) + froot_carbon = bgc_vegetation_inst%get_froot_carbon_patch( & + bounds_clump, canopystate_inst%tlai_patch(bounds_clump%begp:bounds_clump%endp)) + croot_carbon = bgc_vegetation_inst%get_croot_carbon_patch( & + bounds_clump, canopystate_inst%tlai_patch(bounds_clump%begp:bounds_clump%endp)) + end if + call CanopyFluxes(bounds_clump, & filter(nc)%num_exposedvegp, filter(nc)%exposedvegp, & clm_fates,nc, & @@ -1145,7 +1160,7 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro soilbiogeochem_carbonflux_inst, soilbiogeochem_carbonstate_inst, & c13_soilbiogeochem_carbonflux_inst, c13_soilbiogeochem_carbonstate_inst, & c14_soilbiogeochem_carbonflux_inst, c14_soilbiogeochem_carbonstate_inst, & - soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst) + soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst, soilhydrology_inst) call t_stopf('EcosysDynPostDrainage') end if @@ -1254,14 +1269,16 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro ! This is only relevant to fates two stream to not break sun fraction calculations ! on the second timestep after start from finidat or for hybrid run. - if (use_fates .and. .not. use_fates_sp .and. fates_radiation_model == 'twostream') then - if (.not. doalb .and. get_nstep() == 1) then - if (.not. is_cold_start .and. nsrest == nsrStartup) then - call UpdateZenithAngles(bounds_clump, surfalb_inst, nextsw_cday, declinp1) - call clm_fates%wrap_canopy_radiation(bounds_clump, nc, & + ! The first clause is to maintain b4b with base, but is not necessary. + + if (use_fates .and. .not.doalb ) then + if ( (is_cold_start .and. get_nstep() == 1) .or. & + ((fates_radiation_model == 'twostream') .and. (get_nstep()== 1) .and. (.not.use_fates_sp) & + .and. (.not.is_cold_start) .and. (nsrest == nsrStartup)) ) then + call UpdateZenithAngles(bounds_clump, surfalb_inst, nextsw_cday, declinp1) + call clm_fates%wrap_canopy_radiation(bounds_clump, nc, & water_inst%waterdiagnosticbulk_inst%fcansno_patch(bounds_clump%begp:bounds_clump%endp), & surfalb_inst) - endif endif endif @@ -1352,7 +1369,8 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro call get_clump_bounds(nc, bounds_clump) call clm_fates%wrap_co2_to_atm(bounds_clump, & filter_inactive_and_active(nc)%num_bgc_soilc, filter_inactive_and_active(nc)%bgc_soilc, & - soilbiogeochem_carbonflux_inst, net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg)) + soilbiogeochem_carbonflux_inst , bgc_vegetation_inst%c_products_inst, & + net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg)) end do !$OMP END PARALLEL DO endif diff --git a/src/main/clm_initializeMod.F90 b/src/main/clm_initializeMod.F90 index 0cc6f93e5a..b3f016d479 100644 --- a/src/main/clm_initializeMod.F90 +++ b/src/main/clm_initializeMod.F90 @@ -86,8 +86,6 @@ subroutine initialize1(dtime) character(len=32) :: subname = 'initialize1' ! subroutine name !----------------------------------------------------------------------- - call t_startf('clm_init1') - ! Initialize run control variables, timestep if ( masterproc )then @@ -120,8 +118,6 @@ subroutine initialize1(dtime) call crop_repr_pools_init() call hillslope_properties_init(NLFilename) - call t_stopf('clm_init1') - end subroutine initialize1 !----------------------------------------------------------------------- @@ -144,6 +140,7 @@ subroutine initialize2(ni,nj, currtime) use clm_varctl , only : use_hillslope use clm_varorb , only : eccen, mvelpp, lambm0, obliqr use clm_varctl , only : use_cropcal_streams + use clm_varctl , only : use_noio use landunit_varcon , only : landunit_varcon_init, max_lunit, numurbl use pftconMod , only : pftcon use decompInitMod , only : decompInit_clumps, decompInit_glcp @@ -160,6 +157,7 @@ subroutine initialize2(ni,nj, currtime) use clm_time_manager , only : get_curr_date, get_nstep, advance_timestep use clm_time_manager , only : timemgr_init, timemgr_restart_io, timemgr_restart, is_restart use CIsoAtmTimeseriesMod , only : C14_init_BombSpike, use_c14_bombspike, C13_init_TimeSeries, use_c13_timeseries + use CIsoAtmTimeseriesMod , only : CIsoAtmReadNML use DaylengthMod , only : InitDaylength use dynSubgridDriverMod , only : dynSubgrid_init use dynConsBiogeophysMod , only : dyn_hwcontent_set_baselines @@ -223,8 +221,7 @@ subroutine initialize2(ni,nj, currtime) character(len=32) :: subname = 'initialize2' ! subroutine name !----------------------------------------------------------------------- - call t_startf('clm_init2') - + call t_startf('clm_init2_part1') ! Get processor bounds for gridcells call get_proc_bounds(bounds_proc) begg = bounds_proc%begg; endg = bounds_proc%endg @@ -277,9 +274,13 @@ subroutine initialize2(ni,nj, currtime) call CLMFatesGlobals2() end if + call t_stopf('clm_init2_part1') + call t_startf('clm_init2_part2') ! Determine decomposition of subgrid scale landunits, columns, patches + call t_startf('clm_decompInit_clumps') call decompInit_clumps(ni, nj, glc_behavior) + call t_stopf('clm_decompInit_clumps') ! *** Get ALL processor bounds - for gridcells, landunit, columns and patches *** call get_proc_bounds(bounds_proc) @@ -304,7 +305,9 @@ subroutine initialize2(ni,nj, currtime) !$OMP END PARALLEL DO ! Set global seg maps for gridcells, landlunits, columns and patches + call t_startf('clm_decompInit_glcp') call decompInit_glcp(ni, nj, glc_behavior) + call t_stopf('clm_decompInit_glcp') if (use_hillslope) then ! Initialize hillslope properties @@ -365,20 +368,20 @@ subroutine initialize2(ni,nj, currtime) if (use_fates) call CLMFatesTimesteps() ! Initialize daylength from the previous time step (needed so prev_dayl can be set correctly) - call t_startf('init_orbd') calday = get_curr_calday(reuse_day_365_for_day_366=.true.) call shr_orb_decl( calday, eccen, mvelpp, lambm0, obliqr, declin, eccf ) dtime = get_step_size_real() caldaym1 = get_curr_calday(offset=-int(dtime), reuse_day_365_for_day_366=.true.) call shr_orb_decl( caldaym1, eccen, mvelpp, lambm0, obliqr, declinm1, eccf ) - call t_stopf('init_orbd') call InitDaylength(bounds_proc, declin=declin, declinm1=declinm1, obliquity=obliqr) + call t_stopf('clm_init2_part2') + call t_startf('clm_init2_part3') ! Initialize Balance checking (after time-manager) call BalanceCheckInit() ! History file variables - if (use_cn) then + if (use_cn .and. .not. use_noio ) then call hist_addfld1d (fname='DAYL', units='s', & avgflag='A', long_name='daylength', & ptr_gcell=grc%dayl, default='inactive') @@ -394,21 +397,23 @@ subroutine initialize2(ni,nj, currtime) ! First put in history calls for subgrid data structures - these cannot appear in the ! module for the subgrid data definition due to circular dependencies that are introduced - data2dptr => col%dz(:,-nlevsno+1:0) - col%dz(bounds_proc%begc:bounds_proc%endc,:) = spval - call hist_addfld2d (fname='SNO_Z', units='m', type2d='levsno', & - avgflag='A', long_name='Snow layer thicknesses', & - ptr_col=data2dptr, no_snow_behavior=no_snow_normal, default='inactive') - - call hist_addfld2d (fname='SNO_Z_ICE', units='m', type2d='levsno', & - avgflag='A', long_name='Snow layer thicknesses (ice landunits only)', & - ptr_col=data2dptr, no_snow_behavior=no_snow_normal, & - l2g_scale_type='ice', default='inactive') - - col%zii(bounds_proc%begc:bounds_proc%endc) = spval - call hist_addfld1d (fname='ZII', units='m', & - avgflag='A', long_name='convective boundary height', & - ptr_col=col%zii, default='inactive') + if ( .not. use_noio )then + data2dptr => col%dz(:,-nlevsno+1:0) + col%dz(bounds_proc%begc:bounds_proc%endc,:) = spval + call hist_addfld2d (fname='SNO_Z', units='m', type2d='levsno', & + avgflag='A', long_name='Snow layer thicknesses', & + ptr_col=data2dptr, no_snow_behavior=no_snow_normal, default='inactive') + + call hist_addfld2d (fname='SNO_Z_ICE', units='m', type2d='levsno', & + avgflag='A', long_name='Snow layer thicknesses (ice landunits only)', & + ptr_col=data2dptr, no_snow_behavior=no_snow_normal, & + l2g_scale_type='ice', default='inactive') + + col%zii(bounds_proc%begc:bounds_proc%endc) = spval + call hist_addfld1d (fname='ZII', units='m', & + avgflag='A', long_name='convective boundary height', & + ptr_col=col%zii, default='inactive') + end if ! Initialize instances of all derived types as well as time constant variables call clm_instInit(bounds_proc) @@ -423,10 +428,8 @@ subroutine initialize2(ni,nj, currtime) ! Initializate dynamic subgrid weights (for prescribed transient Patches, CNDV ! and/or dynamic landunits); note that these will be overwritten in a restart run - call t_startf('init_dyn_subgrid') call init_subgrid_weights_mod(bounds_proc) call dynSubgrid_init(bounds_proc, glc_behavior, crop_inst) - call t_stopf('init_dyn_subgrid') ! Initialize fates LUH2 usage if (use_fates_luh) then @@ -467,26 +470,24 @@ subroutine initialize2(ni,nj, currtime) ! NOTE(wjs, 2016-02-23) Maybe the rest of the body of this conditional should also ! be moved into bgc_vegetation_inst%Init2 - if (n_drydep > 0) then - ! Must do this also when drydeposition is used so that estimates of monthly - ! differences in LAI can be computed - ! Also do this for FATES see below + if (n_drydep > 0 .and. (.not. use_fates)) then + ! Fates no longer need satephen for dry deposition + ! Only FATES-SP is getting it in esle below call SatellitePhenologyInit(bounds_proc) end if - if ( use_c14 .and. use_c14_bombspike ) then - call C14_init_BombSpike() + if ( use_c13 .or. use_c14 ) call CIsoAtmReadNML( NLFilename ) + if ( use_c14 ) then + call C14_init_BombSpike( bounds_proc ) end if - if ( use_c13 .and. use_c13_timeseries ) then - call C13_init_TimeSeries() + if ( use_c13 ) then + call C13_init_TimeSeries( bounds_proc ) end if else ! FATES OR Satellite phenology - ! For FATES-SP or FATES-NOCOMP Initialize SP - ! Also for FATES with Dry-Deposition on as well (see above) - ! For now don't allow for dry-deposition with full fates - ! because of issues in #1044 EBK Jun/17/2022 - if( use_fates_sp .or. (.not. use_fates )) then + ! For FATES-SP + ! Drydep with fates no longer needs LAI to be read. + if( use_fates_sp .or. (.not. use_fates) ) then if (masterproc) then write(iulog,'(a)')'Initializing Satellite Phenology' end if @@ -549,6 +550,7 @@ subroutine initialize2(ni,nj, currtime) ! If appropriate, create interpolated initial conditions if (nsrest == nsrStartup .and. finidat_interp_source /= ' ') then + call t_startf('clm_init2_init_interp') ! Check that finidat is not cold start - abort if it is if (finidat /= ' ') then call endrun(msg='ERROR clm_initializeMod: '//& @@ -598,6 +600,7 @@ subroutine initialize2(ni,nj, currtime) close(iun) write(iulog,'(a)')' Successfully wrote finidat status file '//trim(locfn) end if + call t_stopf('clm_init2_init_interp') end if ! If requested, reset dynbal baselines @@ -649,17 +652,14 @@ subroutine initialize2(ni,nj, currtime) ! Initialize nitrogen deposition if (use_cn ) then !.or. use_fates_bgc) then (ndep with fates will be added soon RGK) - call t_startf('init_ndep') if (.not. ndep_from_cpl) then call ndep_init(bounds_proc, NLFilename) call ndep_interp(bounds_proc, atm2lnd_inst) end if - call t_stopf('init_ndep') end if ! Initialize crop calendars if (use_crop) then - call t_startf('init_cropcal') call cropcal_init(bounds_proc) if (use_cropcal_streams) then call cropcal_advance( bounds_proc ) @@ -671,7 +671,6 @@ subroutine initialize2(ni,nj, currtime) end do !$OMP END PARALLEL DO end if - call t_stopf('init_cropcal') end if ! Initialize active history fields. @@ -717,10 +716,8 @@ subroutine initialize2(ni,nj, currtime) ! Determine gridcell averaged properties to send to atm if (nsrest == nsrStartup) then - call t_startf('init_map2gc') call lnd2atm_minimal(bounds_proc, & water_inst, surfalb_inst, energyflux_inst, lnd2atm_inst) - call t_stopf('init_map2gc') end if ! Initialize sno export state to send to glc @@ -728,12 +725,10 @@ subroutine initialize2(ni,nj, currtime) do nc = 1,nclumps call get_clump_bounds(nc, bounds_clump) - call t_startf('init_lnd2glc') call lnd2glc_inst%update_lnd2glc(bounds_clump, & filter(nc)%num_do_smb_c, filter(nc)%do_smb_c, & temperature_inst, water_inst%waterfluxbulk_inst, topo_inst, & init=.true.) - call t_stopf('init_lnd2glc') end do !$OMP END PARALLEL DO @@ -773,7 +768,6 @@ subroutine initialize2(ni,nj, currtime) deallocate(topo_glc_mec, fert_cft, irrig_method) ! Write log output for end of initialization - call t_startf('init_wlog') if (masterproc) then write(iulog,*) 'Successfully initialized the land model' if (nsrest == nsrStartup) then @@ -788,7 +782,6 @@ subroutine initialize2(ni,nj, currtime) write(iulog,'(72a1)') ("*",i=1,60) write(iulog,*) endif - call t_stopf('init_wlog') if (water_inst%DoConsistencyCheck()) then !$OMP PARALLEL DO PRIVATE (nc, bounds_clump) @@ -799,7 +792,7 @@ subroutine initialize2(ni,nj, currtime) !$OMP END PARALLEL DO end if - call t_stopf('clm_init2') + call t_stopf('clm_init2_part3') end subroutine initialize2 diff --git a/src/main/clm_instMod.F90 b/src/main/clm_instMod.F90 index 210cff2c2e..7d9a0f6ad2 100644 --- a/src/main/clm_instMod.F90 +++ b/src/main/clm_instMod.F90 @@ -24,14 +24,14 @@ module clm_instMod ! Constants !----------------------------------------- - use UrbanParamsType , only : urbanparams_type ! Constants + use UrbanParamsType , only : urbanparams_type ! Constants use UrbanParamsType , only : IsSimpleBuildTemp, IsProgBuildTemp use UrbanTimeVarType , only : urbantv_type use SoilBiogeochemDecompCascadeConType , only : decomp_cascade_con - use CNDVType , only : dgv_ecophyscon ! Constants + use CNDVType , only : dgv_ecophyscon ! Constants !----------------------------------------- - ! Definition of component types + ! Definition of component types !----------------------------------------- use ActiveLayerMod , only : active_layer_type @@ -71,14 +71,14 @@ module clm_instMod use CNFireEmissionsMod , only : fireemis_type use atm2lndType , only : atm2lnd_type use lnd2atmType , only : lnd2atm_type - use lnd2glcMod , only : lnd2glc_type + use lnd2glcMod , only : lnd2glc_type use glc2lndMod , only : glc2lnd_type use glcBehaviorMod , only : glc_behavior_type use TopoMod , only : topo_type use GridcellType , only : grc - use LandunitType , only : lun - use ColumnType , only : col - use PatchType , only : patch + use LandunitType , only : lun + use ColumnType , only : col + use PatchType , only : patch use CLMFatesInterfaceMod , only : hlm_fates_interface_type use SnowCoverFractionBaseMod , only : snow_cover_fraction_base_type use SnowCoverFractionFactoryMod , only : CreateAndInitSnowCoverFraction @@ -87,8 +87,8 @@ module clm_instMod ! use SoilStateInitTimeConstMod , only : SoilStateInitTimeConst use SoilHydrologyInitTimeConstMod , only : SoilHydrologyInitTimeConst - use SurfaceAlbedoMod , only : SurfaceAlbedoInitTimeConst - use LakeCon , only : LakeConInit + use SurfaceAlbedoMod , only : SurfaceAlbedoInitTimeConst + use LakeCon , only : LakeConInit use SoilBiogeochemPrecisionControlMod, only: SoilBiogeochemPrecisionControlInit use SoilWaterMovementMod , only : use_aquifer_layer ! @@ -99,7 +99,7 @@ module clm_instMod ! Instances of component types !----------------------------------------- - ! Physics types + ! Physics types type(active_layer_type), public :: active_layer_inst type(aerosol_type), public :: aerosol_inst type(canopystate_type), public :: canopystate_inst @@ -138,7 +138,7 @@ module clm_instMod class(nutrient_competition_method_type), public, allocatable :: nutrient_competition_method - ! Soil biogeochem types + ! Soil biogeochem types type(soilbiogeochem_state_type) , public :: soilbiogeochem_state_inst type(soilbiogeochem_carbonstate_type) , public :: soilbiogeochem_carbonstate_inst type(soilbiogeochem_carbonstate_type) , public :: c13_soilbiogeochem_carbonstate_inst @@ -171,7 +171,7 @@ module clm_instMod !----------------------------------------------------------------------- subroutine clm_instReadNML( NLFilename ) ! - ! !ARGUMENTS + ! !ARGUMENTS implicit none character(len=*), intent(IN) :: NLFilename ! Namelist filename ! Read in any namelists that must be read for any clm object instances that need it @@ -186,7 +186,7 @@ end subroutine clm_instReadNML !----------------------------------------------------------------------- subroutine clm_instInit(bounds) ! - ! !USES: + ! !USES: use clm_varpar , only : nlevsno use controlMod , only : nlfilename, fsurdat, hillslope_file use domainMod , only : ldomain @@ -196,10 +196,10 @@ subroutine clm_instInit(bounds) use SoilBiogeochemCompetitionMod , only : SoilBiogeochemCompetitionInit use clm_varctl , only : use_excess_ice use ExcessIceStreamType , only : excessicestream_type, UseExcessIceStreams - + use initVerticalMod , only : initVertical use SnowHydrologyMod , only : InitSnowLayers - use accumulMod , only : print_accum_fields + use accumulMod , only : print_accum_fields use SoilWaterRetentionCurveFactoryMod , only : create_soil_water_retention_curve use decompMod , only : get_proc_bounds use BalanceCheckMod , only : GetBalanceCheckSkipSteps @@ -209,7 +209,7 @@ subroutine clm_instInit(bounds) use initVerticalMod , only : setSoilLayerClass use DustEmisFactory , only : create_dust_emissions ! - ! !ARGUMENTS + ! !ARGUMENTS type(bounds_type), intent(in) :: bounds ! processor bounds ! ! !LOCAL VARIABLES: @@ -230,10 +230,10 @@ subroutine clm_instInit(bounds) !---------------------------------------------------------------------- ! Note: h2osno_col and snow_depth_col are initialized as local variables - ! since they are needed to initialize vertical data structures + ! since they are needed to initialize vertical data structures - begp = bounds%begp; endp = bounds%endp - begc = bounds%begc; endc = bounds%endc + begp = bounds%begp; endp = bounds%endp + begc = bounds%begc; endc = bounds%endc begl = bounds%begl; endl = bounds%endl call getfil (paramfile, locfn, 0) @@ -255,7 +255,7 @@ subroutine clm_instInit(bounds) ! all of the year. if (lun%itype(l)==istice) then h2osno_col(c) = 100._r8 - else if (lun%itype(l)==istsoil .and. abs(grc%latdeg(g)) >= 60._r8) then + else if (lun%itype(l)==istsoil .and. abs(grc%latdeg(g)) >= 60._r8) then h2osno_col(c) = 100._r8 else h2osno_col(c) = 0._r8 @@ -271,7 +271,7 @@ subroutine clm_instInit(bounds) ! Initialize urban time varying data call urbantv_inst%Init(bounds, NLFilename) - ! Initialize vertical data components + ! Initialize vertical data components call initVertical(bounds, & glc_behavior, & @@ -287,7 +287,7 @@ subroutine clm_instInit(bounds) endif !----------------------------------------------- - ! Set cold-start values for snow levels, snow layers and snow interfaces + ! Set cold-start values for snow levels, snow layers and snow interfaces !----------------------------------------------- call InitSnowLayers(bounds, snow_depth_col(bounds%begc:bounds%endc)) @@ -395,7 +395,7 @@ subroutine clm_instInit(bounds) ! Note - always initialize the memory for ch4_inst call ch4_inst%Init(bounds, soilstate_inst%cellorg_col(begc:endc, 1:), fsurdat, nlfilename) - call vocemis_inst%Init(bounds) + call vocemis_inst%Init(bounds, NLFilename) call fireemis_inst%Init(bounds) @@ -408,7 +408,7 @@ subroutine clm_instInit(bounds) call soilbiogeochem_state_inst%Init(bounds) ! Initialize decompcascade constants - ! Note that init_decompcascade_bgc need + ! Note that init_decompcascade_bgc need ! soilbiogeochem_state_inst to be initialized call init_decomp_cascade_constants( ) @@ -432,7 +432,7 @@ subroutine clm_instInit(bounds) c12_soilbiogeochem_carbonstate_inst=soilbiogeochem_carbonstate_inst) end if - call soilbiogeochem_carbonflux_inst%Init(bounds, carbon_type='c12') + call soilbiogeochem_carbonflux_inst%Init(bounds, carbon_type='c12') if (use_c13) then call c13_soilbiogeochem_carbonflux_inst%Init(bounds, carbon_type='c13') end if @@ -447,7 +447,7 @@ subroutine clm_instInit(bounds) soilbiogeochem_carbonstate_inst%decomp_cpools_col(begc:endc,1:ndecomp_pools), & soilbiogeochem_carbonstate_inst%decomp_cpools_1m_col(begc:endc, 1:ndecomp_pools)) - call soilbiogeochem_nitrogenflux_inst%Init(bounds) + call soilbiogeochem_nitrogenflux_inst%Init(bounds) ! Initialize precision control for soil biogeochemistry call SoilBiogeochemPrecisionControlInit( soilbiogeochem_carbonstate_inst, c13_soilbiogeochem_carbonstate_inst, & @@ -463,9 +463,9 @@ subroutine clm_instInit(bounds) call crop_inst%Init(bounds) end if - + ! Initialize the Functionaly Assembled Terrestrial Ecosystem Simulator (FATES) - ! + ! if (use_fates) then call clm_fates%Init(bounds, flandusepftdat) end if @@ -479,14 +479,14 @@ subroutine clm_instInit(bounds) ! ------------------------------------------------------------------------ ! The time manager needs to be initialized before this called is made, since - ! the step size is needed. + ! the step size is needed. call t_startf('init_accflds') call atm2lnd_inst%InitAccBuffer(bounds) call temperature_inst%InitAccBuffer(bounds) - + call water_inst%InitAccBuffer(bounds) call energyflux_inst%InitAccBuffer(bounds) @@ -525,10 +525,10 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file) ! Define/write/read CLM restart file. ! ! !ARGUMENTS: - type(bounds_type) , intent(in) :: bounds - + type(bounds_type) , intent(in) :: bounds + type(file_desc_t) , intent(inout) :: ncid ! netcdf id - character(len=*) , intent(in) :: flag ! 'define', 'write', 'read' + character(len=*) , intent(in) :: flag ! 'define', 'write', 'read' logical , intent(in) :: writing_finidat_interp_dest_file ! true if we are writing a finidat_interp_dest file (ignored for flag=='read') ! Local variables @@ -566,7 +566,7 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file) call water_inst%restart(bounds, ncid, flag=flag, & writing_finidat_interp_dest_file = writing_finidat_interp_dest_file, & watsat_col = soilstate_inst%watsat_col(bounds%begc:bounds%endc,:), & - t_soisno_col=temperature_inst%t_soisno_col(bounds%begc:bounds%endc, -nlevsno+1:), & + t_soisno_col=temperature_inst%t_soisno_col(bounds%begc:bounds%endc, -nlevsno+1:), & altmax_lastyear_indx=active_layer_inst%altmax_lastyear_indx_col(bounds%begc:bounds%endc)) call irrigation_inst%restart (bounds, ncid, flag=flag) @@ -623,7 +623,7 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file) canopystate_inst=canopystate_inst, & soilstate_inst=soilstate_inst, & active_layer_inst=active_layer_inst, & - soilbiogeochem_carbonflux_inst=soilbiogeochem_carbonflux_inst, & + soilbiogeochem_carbonflux_inst=soilbiogeochem_carbonflux_inst, & soilbiogeochem_nitrogenflux_inst=soilbiogeochem_nitrogenflux_inst) end if @@ -631,4 +631,3 @@ subroutine clm_instRest(bounds, ncid, flag, writing_finidat_interp_dest_file) end subroutine clm_instRest end module clm_instMod - diff --git a/src/main/clm_varcon.F90 b/src/main/clm_varcon.F90 index e45f5c440e..234b89c797 100644 --- a/src/main/clm_varcon.F90 +++ b/src/main/clm_varcon.F90 @@ -127,26 +127,27 @@ module clm_varcon real(r8), public, parameter :: g_to_mg = 1.0e3_r8 ! coefficient to convert g to mg real(r8), public, parameter :: cm3_to_m3 = 1.0e-6_r8 ! coefficient to convert cm3 to m3 real(r8), public, parameter :: pct_to_frac = 1.0e-2_r8 ! coefficient to convert % to fraction + real(r8), public, parameter :: mmh2o_to_m3h2o_per_m2 = 1.0e-3_r8 ! coefficient to convert mm H2O to m3 H2O/m2 !!! C13 real(r8), public, parameter :: preind_atm_del13c = -6.0_r8 ! preindustrial value for atmospheric del13C - real(r8), public, parameter :: preind_atm_ratio = SHR_CONST_PDB + (preind_atm_del13c * SHR_CONST_PDB)/1000.0_r8 ! 13C/12C + real(r8), private, parameter :: preind_atm_ratio = SHR_CONST_PDB + (preind_atm_del13c * SHR_CONST_PDB)/1000.0_r8 ! 13C/12C real(r8), public :: c13ratio = preind_atm_ratio/(1.0_r8+preind_atm_ratio) ! 13C/(12+13)C preind atmosphere ! typical del13C for C3 photosynthesis (permil, relative to PDB) - real(r8), public, parameter :: c3_del13c = -28._r8 + real(r8), private, parameter :: c3_del13c = -28._r8 ! typical del13C for C4 photosynthesis (permil, relative to PDB) - real(r8), public, parameter :: c4_del13c = -13._r8 + real(r8), private, parameter :: c4_del13c = -13._r8 ! isotope ratio (13c/12c) for C3 photosynthesis - real(r8), public, parameter :: c3_r1 = SHR_CONST_PDB + ((c3_del13c*SHR_CONST_PDB)/1000._r8) + real(r8), private, parameter :: c3_r1 = SHR_CONST_PDB + ((c3_del13c*SHR_CONST_PDB)/1000._r8) ! isotope ratio (13c/[12c+13c]) for C3 photosynthesis real(r8), public, parameter :: c3_r2 = c3_r1/(1._r8 + c3_r1) ! isotope ratio (13c/12c) for C4 photosynthesis - real(r8), public, parameter :: c4_r1 = SHR_CONST_PDB + ((c4_del13c*SHR_CONST_PDB)/1000._r8) + real(r8), private, parameter :: c4_r1 = SHR_CONST_PDB + ((c4_del13c*SHR_CONST_PDB)/1000._r8) ! isotope ratio (13c/[12c+13c]) for C4 photosynthesis real(r8), public, parameter :: c4_r2 = c4_r1/(1._r8 + c4_r1) diff --git a/src/main/clm_varctl.F90 b/src/main/clm_varctl.F90 index 41978ae695..83133acf2b 100644 --- a/src/main/clm_varctl.F90 +++ b/src/main/clm_varctl.F90 @@ -11,8 +11,8 @@ module clm_varctl ! !PUBLIC MEMBER FUNCTIONS: implicit none public :: clm_varctl_set ! Set variables - public :: cnallocate_carbon_only_set - public :: cnallocate_carbon_only + public :: allocate_carbon_only_set + public :: allocate_carbon_only ! private save @@ -331,6 +331,7 @@ module clm_varctl ! 0 for no fire; 1 for constant ignitions; ! > 1 for external data (lightning and/or anthropogenic ignitions) ! see bld/namelist_files/namelist_definition_clm4_5.xml for details + logical, public :: use_fates_managed_fire = .false. ! true => turn on managed fire logical, public :: use_fates_tree_damage = .false. ! true => turn on tree damage module character(len=256), public :: fates_harvest_mode = '' ! five different harvest modes; see namelist definition character(len=256), public :: fates_stomatal_model = '' ! stomatal conductance model, Ball-berry or Medlyn @@ -521,6 +522,7 @@ module clm_varctl logical, public :: use_lch4 = .true. logical, public :: use_nitrif_denitrif = .true. + logical, public :: use_nvmovement = .false. logical, public :: use_extralakelayers = .false. logical, public :: use_vichydro = .false. logical, public :: use_cn = .false. @@ -583,15 +585,15 @@ subroutine clm_varctl_set( caseid_in, ctitle_in, brnch_retain_casename_in, & end subroutine clm_varctl_set - ! Set module carbon_only flag - subroutine cnallocate_carbon_only_set(carbon_only_in) + ! Set module carbon_only flag (applies to both CN and FATES) + subroutine allocate_carbon_only_set(carbon_only_in) logical, intent(in) :: carbon_only_in carbon_only = carbon_only_in - end subroutine cnallocate_carbon_only_set + end subroutine allocate_carbon_only_set - ! Get module carbon_only flag - logical function CNAllocate_Carbon_only() - cnallocate_carbon_only = carbon_only - end function CNAllocate_Carbon_only + ! Get module carbon_only flag (applies to both CN and FATES) + logical function Allocate_Carbon_only() + allocate_carbon_only = carbon_only + end function Allocate_Carbon_only end module clm_varctl diff --git a/src/main/controlMod.F90 b/src/main/controlMod.F90 index 6d363a9a6e..089503dc8b 100644 --- a/src/main/controlMod.F90 +++ b/src/main/controlMod.F90 @@ -257,7 +257,8 @@ subroutine control_init(dtime) use_fates_tree_damage, & use_fates_daylength_factor, & fates_photosynth_acclimation, & - fates_history_dimlevel + fates_history_dimlevel, & + use_fates_managed_fire ! Ozone vegetation stress method namelist / clm_inparm / o3_veg_stress_method @@ -318,7 +319,7 @@ subroutine control_init(dtime) use_lch4, use_nitrif_denitrif, use_extralakelayers, & use_vichydro, use_cn, use_cndv, use_crop, use_fertilizer, & use_grainproduct, use_snicar_frc, use_vancouver, use_mexicocity, use_noio, & - use_nguardrail, crop_residue_removal_frac, flush_gdd20 + use_nguardrail, crop_residue_removal_frac, flush_gdd20, use_nvmovement ! SNICAR namelist /clm_inparm/ & @@ -729,6 +730,7 @@ subroutine control_spmd() call mpi_bcast (use_lch4, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_nitrif_denitrif, 1, MPI_LOGICAL, 0, mpicom, ier) + call mpi_bcast (use_nvmovement, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_extralakelayers, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_vichydro, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_cn, 1, MPI_LOGICAL, 0, mpicom, ier) @@ -844,6 +846,7 @@ subroutine control_spmd() call mpi_bcast (fates_paramfile, len(fates_paramfile) , MPI_CHARACTER, 0, mpicom, ier) call mpi_bcast (fluh_timeseries, len(fluh_timeseries) , MPI_CHARACTER, 0, mpicom, ier) call mpi_bcast (flandusepftdat, len(flandusepftdat) , MPI_CHARACTER, 0, mpicom, ier) + call mpi_bcast (use_fates_managed_fire, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (fates_parteh_mode, 1, MPI_INTEGER, 0, mpicom, ier) call mpi_bcast (fates_seeddisp_cadence, 1, MPI_INTEGER, 0, mpicom, ier) @@ -1026,6 +1029,7 @@ subroutine control_print () write(iulog,*) 'process control parameters:' write(iulog,*) ' use_lch4 = ', use_lch4 write(iulog,*) ' use_nitrif_denitrif = ', use_nitrif_denitrif + write(iulog,*) ' use_nvmovement = ', use_nvmovement write(iulog,*) ' use_extralakelayers = ', use_extralakelayers write(iulog,*) ' use_vichydro = ', use_vichydro write(iulog,*) ' use_excess_ice = ', use_excess_ice @@ -1259,6 +1263,7 @@ subroutine control_print () write(iulog, *) ' fates_seeddisp_cadence = ', fates_seeddisp_cadence write(iulog, *) ' fates_seeddisp_cadence: 0, 1, 2, 3 => off, daily, monthly, or yearly dispersal' write(iulog, *) ' fates_inventory_ctrl_filename = ', trim(fates_inventory_ctrl_filename) + write(iulog, *) ' use_fates_managed_fire= ', use_fates_managed_fire end if end subroutine control_print diff --git a/src/main/decompInitMod.F90 b/src/main/decompInitMod.F90 index bebcd9d358..aa575bd787 100644 --- a/src/main/decompInitMod.F90 +++ b/src/main/decompInitMod.F90 @@ -73,30 +73,19 @@ subroutine decompInit_lnd(lni, lnj, amask) integer, allocatable :: gdc2glo(:)! used to create gindex_global type(bounds_type) :: bounds ! contains subgrid bounds data !------------------------------------------------------------------------------ + ! Set some global scalars: nclumps, numg and lns + call decompInit_lnd_set_nclumps_numg_lns( ) - lns = lni * lnj + ! Do some error checking + call decompInit_lnd_check_errors( ier ) + if (ier /= 0) return - !--- set and verify nclumps --- - if (clump_pproc > 0) then - nclumps = clump_pproc * npes - if (nclumps < npes) then - write(iulog,*) 'decompInit_lnd(): Number of gridcell clumps= ',nclumps, & - ' is less than the number of processes = ', npes - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if - else - write(iulog,*)'clump_pproc= ',clump_pproc,' must be greater than 0' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if + call decompInit_lnd_allocate( ier ) + if (ier /= 0) return - ! allocate and initialize procinfo and clumps + ! Initialize procinfo and clumps ! beg and end indices initialized for simple addition of cells later - allocate(procinfo%cid(clump_pproc), stat=ier) - if (ier /= 0) then - write(iulog,*) 'decompInit_lnd(): allocation error for procinfo%cid' - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif procinfo%nclumps = clump_pproc procinfo%cid(:) = -1 procinfo%ncells = 0 @@ -115,11 +104,6 @@ subroutine decompInit_lnd(lni, lnj, amask) procinfo%endp = 0 procinfo%endCohort = 0 - allocate(clumps(nclumps), stat=ier) - if (ier /= 0) then - write(iulog,*) 'decompInit_lnd(): allocation error for clumps' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if clumps(:)%owner = -1 clumps(:)%ncells = 0 clumps(:)%nlunits = 0 @@ -156,25 +140,6 @@ subroutine decompInit_lnd(lni, lnj, amask) endif enddo - ! count total land gridcells - numg = 0 - do ln = 1,lns - if (amask(ln) == 1) then - numg = numg + 1 - endif - enddo - - if (npes > numg) then - write(iulog,*) 'decompInit_lnd(): Number of processes exceeds number ', & - 'of land grid cells',npes,numg - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if - if (nclumps > numg) then - write(iulog,*) 'decompInit_lnd(): Number of clumps exceeds number ', & - 'of land grid cells',nclumps,numg - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if - if (float(numg)/float(nclumps) < float(nsegspc)) then seglen1 = .true. seglen = 1.0_r8 @@ -191,7 +156,6 @@ subroutine decompInit_lnd(lni, lnj, amask) ! Assign gridcells to clumps (and thus pes) --- - allocate(lcid(lns)) lcid(:) = 0 ng = 0 do ln = 1,lns @@ -238,21 +202,7 @@ subroutine decompInit_lnd(lni, lnj, amask) end if enddo - ! Set gindex_global - - allocate(gdc2glo(numg), stat=ier) - if (ier /= 0) then - write(iulog,*) 'decompInit_lnd(): allocation error1 for gdc2glo , etc' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if - gdc2glo(:) = 0 - allocate(clumpcnt(nclumps),stat=ier) - if (ier /= 0) then - write(iulog,*) 'decompInit_lnd(): allocation error1 for clumpcnt' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if - - ! clumpcnt is the start gdc index of each clump + ! clumpcnt is the ending gdc index of each clump ag = 0 clumpcnt = 0 @@ -283,16 +233,17 @@ subroutine decompInit_lnd(lni, lnj, amask) ! Initialize global gindex (non-compressed, includes ocean points) ! Note that gindex_global goes from (1:endg) + call get_proc_bounds(bounds) ! This has to be done after procinfo is finalized + call decompInit_lnd_gindex_global_allocate( bounds, ier ) ! This HAS to be done after prcoinfo is finalized + if (ier /= 0) return + nglob_x = lni ! decompMod module variables nglob_y = lnj ! decompMod module variables - call get_proc_bounds(bounds) - allocate(gindex_global(1:bounds%endg)) do n = procinfo%begg,procinfo%endg gindex_global(n-procinfo%begg+1) = gdc2glo(n) enddo - deallocate(clumpcnt) - deallocate(gdc2glo) + call decompInit_lnd_clean() ! Diagnostic output if (masterproc) then @@ -306,6 +257,174 @@ subroutine decompInit_lnd(lni, lnj, amask) end if call shr_sys_flush(iulog) + !------------------------------------------------------------------------------ + ! Internal subroutines for this subroutine + contains + !------------------------------------------------------------------------------ + + !------------------------------------------------------------------------------ + subroutine decompInit_lnd_allocate( ier ) + ! Allocate the temporary and long term variables set and used in decompInit_lnd + integer, intent(out) :: ier ! error code + ! + ! Long-term allocation: + ! Arrays from decompMod are allocated here + ! TODO: This should move to a method in decompMod + ! as should the deallocates + ! + ! Temporary allocation: + ! Allocate some temporaries used only in decompInit_lnd + ! + ! NOTE: nclumps, numg, and lns must be set before calling this routine! + ! So decompInit_lnd_set_nclumps_numg_lns must be called first + + ! Allocate the longer term decompMod data + allocate(procinfo%cid(clump_pproc), stat=ier) + if (ier /= 0) then + call endrun(msg='allocation error for procinfo%cid', file=sourcefile, line=__LINE__) + return + endif + + if ( nclumps < 1 )then + call endrun(msg="nclumps is NOT set before allocation", file=sourcefile, line=__LINE__) + return + end if + allocate(clumps(nclumps), stat=ier) + if (ier /= 0) then + write(iulog,*) 'allocation error for clumps: nclumps, ier=', nclumps, ier + call endrun(msg='allocation error for clumps', file=sourcefile, line=__LINE__) + return + end if + + if ( numg < 1 )then + call endrun(msg="numg is NOT set before allocation", file=sourcefile, line=__LINE__) + return + end if + allocate(gdc2glo(numg), stat=ier) + if (ier /= 0) then + call endrun(msg="allocation error for gdc2glo", file=sourcefile, line=__LINE__) + return + end if + + ! Temporary arrays that are just used in decompInit_lnd + if ( lns < 1 )then + call endrun(msg="lns is NOT set before allocation", file=sourcefile, line=__LINE__) + return + end if + allocate(lcid(lns), stat=ier) + if (ier /= 0) then + call endrun(msg="allocation error for lcid", file=sourcefile, line=__LINE__) + return + end if + allocate(clumpcnt(nclumps),stat=ier) + if (ier /= 0) then + call endrun(msg="allocation error for clumpcnt", file=sourcefile, line=__LINE__) + return + end if + + end subroutine decompInit_lnd_allocate + + !------------------------------------------------------------------------------ + + subroutine decompInit_lnd_gindex_global_allocate( bounds, ier ) + ! Allocate gindex_global which requires that bounds gridcell begg to endg be set first + integer, intent(out) :: ier ! error code + type(bounds_type), intent(in) :: bounds ! contains subgrid bounds data + + ier = 0 + if ( bounds%endg < 1 )then + ier = 1 + call endrun(msg="endg is NOT set before allocation", file=sourcefile, line=__LINE__) + return + end if + allocate(gindex_global(1:bounds%endg), stat=ier) + if (ier /= 0) then + call endrun(msg="allocation error for gindex_global", file=sourcefile, line=__LINE__) + return + end if + end subroutine decompInit_lnd_gindex_global_allocate + + !------------------------------------------------------------------------------ + + subroutine decompInit_lnd_clean() + ! Deallocate the temporary variables used in decompInit_lnd + deallocate(clumpcnt) + deallocate(gdc2glo) + !deallocate(lcid) + end subroutine decompInit_lnd_clean + + !------------------------------------------------------------------------------ + + subroutine decompInit_lnd_set_nclumps_numg_lns( ) + ! Set nclumps, numg, and lns + ! Because of this -- it HAS to be called before decompInit_lnd_allocate + + ! Set total clumps and total cells over grid + nclumps = clump_pproc * npes + + lns = lni * lnj + + ! count total land gridcells + numg = 0 + do ln = 1,lns + if (amask(ln) == 1) then + numg = numg + 1 + endif + enddo + + end subroutine decompInit_lnd_set_nclumps_numg_lns + + !------------------------------------------------------------------------------ + + subroutine decompInit_lnd_check_errors( ier ) + ! Do some general error checking on input options + integer, intent(out) :: ier ! error code + + ier = 0 + if (nsegspc < 1) then + ier = 1 + write(iulog,*) 'nsegspc bad = ', nsegspc + call endrun(msg="Number of segments per clump (nsegspc) is less than 1 and can NOT be", & + file=sourcefile, line=__LINE__) + return + end if + + !--- set and verify nclumps --- + if (clump_pproc > 0) then + if (nclumps < npes) then + ier = 1 + write(iulog,*) 'Number of gridcell clumps= ',nclumps, & + ' is less than the number of processes = ', npes + call endrun(msg="Number of clumps exceeds number of processes", & + file=sourcefile, line=__LINE__) + return + end if + else + ier = 1 + write(iulog,*) 'ERROR: Bad clump_pproc=', clump_pproc + call endrun(msg='clump_pproc must be greater than 0', file=sourcefile, line=__LINE__) + return + end if + + if (npes > numg) then + ier = 1 + write(iulog,*) 'Number of processes > gridcells: npes=',npes,' num gridcells = ', numg + call endrun(msg="Number of processes exceeds number of land grid cells", & + file=sourcefile, line=__LINE__) + return + end if + if (nclumps > numg) then + ier = 1 + write(iulog,*) 'Number of clumps > gridcells nclumps = ', & + nclumps, ' num gridcells = ', numg + call endrun(msg="Number of clumps exceeds number of land grid cells", & + file=sourcefile, line=__LINE__) + return + end if + end subroutine decompInit_lnd_check_errors + + !------------------------------------------------------------------------------ + end subroutine decompInit_lnd !------------------------------------------------------------------------------ @@ -625,7 +744,6 @@ subroutine decompInit_glcp(lni,lnj,glc_behavior) integer :: gsize Character(len=32), parameter :: subname = 'decompInit_glcp' !------------------------------------------------------------------------------ - ! Get processor bounds call get_proc_bounds(bounds) diff --git a/src/main/decompMod.F90 b/src/main/decompMod.F90 index 940ba724bf..adf85fa5b7 100644 --- a/src/main/decompMod.F90 +++ b/src/main/decompMod.F90 @@ -46,6 +46,7 @@ module decompMod public :: get_subgrid_level_from_name ! Given a name like nameg, return a subgrid level index like subgrid_level_gridcell public :: get_subgrid_level_gsize ! get global size associated with subgrid_level public :: get_subgrid_level_gindex ! get global index array associated with subgrid_level + public :: decompmod_clean ! Deallocate memory used by decompMod ! !PRIVATE MEMBER FUNCTIONS: ! @@ -361,7 +362,7 @@ integer function get_proc_clumps() end function get_proc_clumps !----------------------------------------------------------------------- - integer function get_global_index(subgrid_index, subgrid_level) + integer function get_global_index(subgrid_index, subgrid_level, donot_abort_on_badindex) !---------------------------------------------------------------- ! Description @@ -373,23 +374,47 @@ integer function get_global_index(subgrid_index, subgrid_level) ! Arguments integer , intent(in) :: subgrid_index ! index of interest (can be at any subgrid level or gridcell level) integer , intent(in) :: subgrid_level ! one of the subgrid_level_* constants defined above + logical , intent(in), optional :: donot_abort_on_badindex ! Don't abort if given a bad index ! ! Local Variables: type(bounds_type) :: bounds_proc ! processor bounds integer :: beg_index ! beginning proc index for subgrid_level + integer :: end_index ! ending proc index for subgrid_level + integer :: index ! index of the point to get integer, pointer :: gindex(:) + logical :: abort_on_badindex = .true. !---------------------------------------------------------------- + if (present(donot_abort_on_badindex)) then + abort_on_badindex = .not. donot_abort_on_badindex + end if call get_proc_bounds(bounds_proc, allow_call_from_threaded_region=.true.) beg_index = get_beg(bounds_proc, subgrid_level) + end_index = get_end(bounds_proc, subgrid_level) if (beg_index == -1) then write(iulog,*) 'get_global_index: subgrid_level not supported: ', subgrid_level - call shr_sys_abort('subgrid_level not supported' // & - errmsg(sourcefile, __LINE__)) + if (abort_on_badindex) then + call shr_sys_abort('subgrid_level not supported') + else + get_global_index = -1 + return + end if end if call get_subgrid_level_gindex(subgrid_level=subgrid_level, gindex=gindex) - get_global_index = gindex(subgrid_index - beg_index + 1) + index = subgrid_index - beg_index + 1 + if ( (index < beg_index) .or. (index > end_index) ) then + if (abort_on_badindex) then + write(iulog,*) 'get_global_index: subgrid_index out of bounds: ', & + 'subgrid_index = ', subgrid_index, ', beg_index = ', beg_index, & + ', end_index = ', end_index, ', subgrid_level = ', subgrid_level + call shr_sys_abort('subgrid_index out of bounds') + else + get_global_index = -1 + return + end if + end if + get_global_index = gindex(index) end function get_global_index @@ -537,9 +562,52 @@ subroutine get_subgrid_level_gindex (subgrid_level, gindex) gindex => gindex_cohort case default write(iulog,*) 'get_subgrid_level_gindex: unknown subgrid_level: ', subgrid_level - call shr_sys_abort() + call shr_sys_abort('bad subgrid_level') end select end subroutine get_subgrid_level_gindex + !----------------------------------------------------------------------- + subroutine decompmod_clean() + ! Deallocate the decompMod long-term variables created in decompInit_lnd + + ! Set the total counts to zero + nclumps = 0 + numg = 0 + numl = 0 + numc = 0 + nump = 0 + numCohort = 0 + + ! Deallocate and set the pointers to null + if ( allocated(clumps) )then + deallocate(clumps) + end if + if ( associated(procinfo%cid) )then + deallocate(procinfo%cid) + procinfo%cid => null() + end if + if ( associated(gindex_global) )then + deallocate(gindex_global) + gindex_global => null() + end if + if ( associated(gindex_grc) )then + deallocate( gindex_grc ) + gindex_grc => null() + end if + if ( associated(gindex_lun) )then + deallocate( gindex_lun ) + gindex_lun => null() + end if + if ( associated(gindex_col) )then + deallocate( gindex_col ) + gindex_col => null() + end if + if ( associated(gindex_patch) )then + deallocate( gindex_patch ) + gindex_patch => null() + end if + end subroutine decompMod_clean + !----------------------------------------------------------------------- + end module decompMod diff --git a/src/main/filterMod.F90 b/src/main/filterMod.F90 index 2fb7d23079..6540021923 100644 --- a/src/main/filterMod.F90 +++ b/src/main/filterMod.F90 @@ -316,7 +316,7 @@ subroutine setFiltersOneGroup(bounds, this_filter, include_inactive, glc_behavio ! ! !USES: use decompMod , only : bounds_level_clump - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use landunit_varcon , only : istsoil, istcrop, istice ! ! !ARGUMENTS: @@ -479,7 +479,7 @@ subroutine setFiltersOneGroup(bounds, this_filter, include_inactive, glc_behavio do p = bounds%begp,bounds%endp if(.not.use_fates)then if (patch%active(p) .or. include_inactive) then - if (patch%itype(p) >= npcropmin) then !skips 2 generic crop types + if (is_prognostic_crop(patch%itype(p))) then !skips 2 generic crop types fl = fl + 1 this_filter(nc)%pcropp(fl) = p else diff --git a/src/main/histFileMod.F90 b/src/main/histFileMod.F90 index f5147559d9..6e2af830c9 100644 --- a/src/main/histFileMod.F90 +++ b/src/main/histFileMod.F90 @@ -46,9 +46,12 @@ module histFileMod integer , public, parameter :: max_tapes = 10 ! max number of history tapes integer , public, parameter :: max_flds = 2500 ! max number of history fields integer , public, parameter :: max_namlen = 64 ! maximum number of characters for field name - integer , public, parameter :: scale_type_strlen = 32 ! maximum number of characters for scale types + integer , private, parameter :: scale_type_strlen = 32 ! maximum number of characters for scale types integer , private, parameter :: avgflag_strlen = 10 ! maximum number of characters for avgflag integer , private, parameter :: hist_dim_name_length = 16 ! lenngth of character strings in dimension names + integer , private, parameter :: max_split_files = 2 ! max number of files per tape + integer , private, parameter :: accumulated_file_index = 1 ! non-instantaneous file identifier + integer , private, parameter :: instantaneous_file_index = 2 ! instantaneous file identifier ! Possible ways to treat multi-layer snow fields at times when no snow is present in a ! given layer. Note that the public parameters are the only ones that can be used by @@ -141,7 +144,7 @@ module histFileMod fexcl(max_flds,max_tapes) ! copy of hist_fexcl* fields in 2-D format. Note Fortran ! used to have a bug in 2-D namelists, thus this workaround. - logical, private :: if_disphist(max_tapes) ! restart, true => save history file + logical, private :: if_disphist(max_tapes, max_split_files) ! restart, true => save history file ! ! !PUBLIC MEMBER FUNCTIONS: (in rough call order) public :: hist_addfld1d ! Add a 1d single-level field to the list of all history fields @@ -258,7 +261,7 @@ end subroutine copy_entry_interface ! practice are all disabled. Fields for those tapes have to be specified ! explicitly and manually via hist_fincl2 et al. type, extends(entry_base) :: allhistfldlist_entry - logical :: actflag(max_tapes) ! which history tapes to write to. + logical :: actflag(max_tapes,max_split_files) ! which history tapes to write to character(len=avgflag_strlen) :: avgflag(max_tapes) ! type of time averaging contains procedure :: copy => copy_allhistfldlist_entry @@ -280,16 +283,15 @@ end subroutine copy_entry_interface ! tapes is assembled in the 'allhistfldlist' variable. Note that the first history tape is index 1 in ! the code but contains 'h0' in its output filenames (see set_hist_filename method). type history_tape - integer :: nflds ! number of active fields on tape - integer :: ntimes ! current number of time samples on tape + integer :: nflds(max_split_files) ! number of active fields on file + integer :: ntimes(max_split_files) ! current number of time samples on tape; although ntimes is an array, all its values are the same integer :: mfilt ! maximum number of time samples per tape integer :: nhtfrq ! number of time samples per tape integer :: ncprec ! netcdf output precision logical :: dov2xy ! true => do xy average for all fields logical :: is_endhist ! true => current time step is end of history interval real(r8) :: begtime ! time at beginning of history averaging interval - type (history_entry) :: hlist(max_flds) ! array of active history tape entries. - ! The ordering matches the allhistfldlist's. + type (history_entry) :: hlist(max_flds, max_split_files) ! array of active history tape and file entries listed in the same order as in allhistfldlist, but hlist contains the active subset of all the fields end type history_tape type clmpoint_rs ! Pointer to real scalar data (1D) @@ -312,10 +314,10 @@ end subroutine copy_entry_interface ! type (allhistfldlist_entry) :: allhistfldlist(max_flds) ! list of all history fields ! - ! Whether each history tape is in use in this run. If history_tape_in_use(i) is false, - ! then data in tape(i) is undefined and should not be referenced. + ! Whether each history tape is in use in this run. If history_tape_in_use(i,j) is false, + ! then data in [tape(i), file(j)] is undefined and should not be referenced. ! - logical :: history_tape_in_use(max_tapes) ! whether each history tape is in use in this run + logical :: history_tape_in_use(max_tapes, max_split_files) ! history tape is/isn't in use in this run ! ! The actual (accumulated) history data for all active fields in each in-use tape. See ! 'history_tape_in_use' for in-use tapes, and 'allhistfldlist' for active fields. See also @@ -331,14 +333,14 @@ end subroutine copy_entry_interface ! ! Other variables ! - character(len=max_length_filename) :: locfnh(max_tapes) ! local history file names - character(len=max_length_filename) :: locfnhr(max_tapes) ! local history restart file names + character(len=max_length_filename) :: locfnh(max_tapes, max_split_files) ! local history file names + character(len=max_length_filename) :: locfnhr(max_tapes, max_split_files) ! local history restart file names logical :: htapes_defined = .false. ! flag indicates history output fields have been defined ! ! NetCDF Id's ! - type(file_desc_t), target :: nfid(max_tapes) ! file ids - type(file_desc_t), target :: ncid_hist(max_tapes) ! file ids for history restart files + type(file_desc_t), target :: nfid(max_tapes, max_split_files) ! file ids + type(file_desc_t), target :: ncid_hist(max_tapes, max_split_files) ! file ids for history restart files integer :: time_dimid ! time dimension id integer :: nbnd_dimid ! time bounds dimension id integer :: strlen_dimid ! string dimension id @@ -372,7 +374,7 @@ subroutine hist_printflds() ! !ARGUMENTS: ! ! !LOCAL VARIABLES: - integer, parameter :: ncol = 5 ! number of table columns + integer, parameter :: ncol = 6 ! number of table columns integer nf, i, j ! do-loop counters integer hist_fields_file ! file unit number integer width_col(ncol) ! widths of table columns @@ -401,7 +403,7 @@ subroutine hist_printflds() ! the CTSM's web-based documentation. ! First sort the list to be in alphabetical order - call sort_hist_list(1, nallhistflds, allhistfldlist) + call sort_hist_list(nallhistflds, allhistfldlist) if (masterproc .and. hist_fields_list_file) then ! Hardwired table column widths to fit the table on a computer @@ -413,7 +415,8 @@ subroutine hist_printflds() width_col(2) = hist_dim_name_length ! level dimension column width_col(3) = 94 ! long description column width_col(4) = 65 ! units column - width_col(5) = 7 ! active (T or F) column + width_col(5) = 10 ! active (T or F) column + width_col(6) = 12 ! active (T or F) column width_col_sum = sum(width_col) + ncol - 1 ! sum of widths & blank spaces ! Convert integer widths to strings for use in format statements @@ -467,9 +470,9 @@ subroutine hist_printflds() fmt_txt = '('//str_w_col_sum//'a)' write(hist_fields_file,fmt_txt) ('-', i=1, width_col_sum) ! Concatenate strings needed in format statement - fmt_txt = '(a'//str_width_col(1)//',x,a'//str_width_col(2)//',x,a'//str_width_col(3)//',x,a'//str_width_col(4)//',x,a'//str_width_col(5)//')' + fmt_txt = '(a'//str_width_col(1)//',x,a'//str_width_col(2)//',x,a'//str_width_col(3)//',x,a'//str_width_col(4)//',x,a'//str_width_col(5)//',x,a'//str_width_col(6)//')' write(hist_fields_file,fmt_txt) 'Variable Name', & - 'Level Dim.', 'Long Description', 'Units', 'Active?' + 'Level Dim.', 'Long Description', 'Units', "Active 'I'", "Act. not 'I'" ! End header, same as header ! Concatenate strings needed in format statement @@ -481,14 +484,14 @@ subroutine hist_printflds() ! Main table ! Concatenate strings needed in format statement - fmt_txt = '(a'//str_width_col(1)//',x,a'//str_width_col(2)//',x,a'//str_width_col(3)//',x,a'//str_width_col(4)//',l'//str_width_col(5)//')' + fmt_txt = '(a'//str_width_col(1)//',x,a'//str_width_col(2)//',x,a'//str_width_col(3)//',x,a'//str_width_col(4)//',l'//str_width_col(5)//',l'//str_width_col(6)//')' do nf = 1,nallhistflds write(hist_fields_file,fmt_txt) & allhistfldlist(nf)%field%name, & allhistfldlist(nf)%field%type2d, & allhistfldlist(nf)%field%long_name, & allhistfldlist(nf)%field%units, & - allhistfldlist(nf)%actflag(1) + allhistfldlist(nf)%actflag(1,:) end do ! Table footer, same as header @@ -538,7 +541,7 @@ subroutine allhistfldlist_addfld (fname, numdims, type1d, type1d_out, & ! ! !LOCAL VARIABLES: integer :: n ! loop index - integer :: f ! allhistfldlist index + integer :: fld ! allhistfldlist index integer :: numa ! total number of atm cells across all processors integer :: numg ! total number of gridcells across all processors integer :: numl ! total number of landunits across all processors @@ -583,7 +586,7 @@ subroutine allhistfldlist_addfld (fname, numdims, type1d, type1d_out, & ! Increase number of fields on list of all history fields nallhistflds = nallhistflds + 1 - f = nallhistflds + fld = nallhistflds ! Check number of fields in list against maximum number @@ -595,49 +598,49 @@ subroutine allhistfldlist_addfld (fname, numdims, type1d, type1d_out, & ! Add field to list of all history fields - allhistfldlist(f)%field%name = fname - allhistfldlist(f)%field%long_name = long_name - allhistfldlist(f)%field%units = units - allhistfldlist(f)%field%type1d = type1d - allhistfldlist(f)%field%type1d_out = type1d_out - allhistfldlist(f)%field%type2d = type2d - allhistfldlist(f)%field%numdims = numdims - allhistfldlist(f)%field%num2d = num2d - allhistfldlist(f)%field%hpindex = hpindex - allhistfldlist(f)%field%p2c_scale_type = p2c_scale_type - allhistfldlist(f)%field%c2l_scale_type = c2l_scale_type - allhistfldlist(f)%field%l2g_scale_type = l2g_scale_type + allhistfldlist(fld)%field%name = fname + allhistfldlist(fld)%field%long_name = long_name + allhistfldlist(fld)%field%units = units + allhistfldlist(fld)%field%type1d = type1d + allhistfldlist(fld)%field%type1d_out = type1d_out + allhistfldlist(fld)%field%type2d = type2d + allhistfldlist(fld)%field%numdims = numdims + allhistfldlist(fld)%field%num2d = num2d + allhistfldlist(fld)%field%hpindex = hpindex + allhistfldlist(fld)%field%p2c_scale_type = p2c_scale_type + allhistfldlist(fld)%field%c2l_scale_type = c2l_scale_type + allhistfldlist(fld)%field%l2g_scale_type = l2g_scale_type select case (type1d) case (grlnd) - allhistfldlist(f)%field%beg1d = bounds%begg - allhistfldlist(f)%field%end1d = bounds%endg - allhistfldlist(f)%field%num1d = numg + allhistfldlist(fld)%field%beg1d = bounds%begg + allhistfldlist(fld)%field%end1d = bounds%endg + allhistfldlist(fld)%field%num1d = numg case (nameg) - allhistfldlist(f)%field%beg1d = bounds%begg - allhistfldlist(f)%field%end1d = bounds%endg - allhistfldlist(f)%field%num1d = numg + allhistfldlist(fld)%field%beg1d = bounds%begg + allhistfldlist(fld)%field%end1d = bounds%endg + allhistfldlist(fld)%field%num1d = numg case (namel) - allhistfldlist(f)%field%beg1d = bounds%begl - allhistfldlist(f)%field%end1d = bounds%endl - allhistfldlist(f)%field%num1d = numl + allhistfldlist(fld)%field%beg1d = bounds%begl + allhistfldlist(fld)%field%end1d = bounds%endl + allhistfldlist(fld)%field%num1d = numl case (namec) - allhistfldlist(f)%field%beg1d = bounds%begc - allhistfldlist(f)%field%end1d = bounds%endc - allhistfldlist(f)%field%num1d = numc + allhistfldlist(fld)%field%beg1d = bounds%begc + allhistfldlist(fld)%field%end1d = bounds%endc + allhistfldlist(fld)%field%num1d = numc case (namep) - allhistfldlist(f)%field%beg1d = bounds%begp - allhistfldlist(f)%field%end1d = bounds%endp - allhistfldlist(f)%field%num1d = nump + allhistfldlist(fld)%field%beg1d = bounds%begp + allhistfldlist(fld)%field%end1d = bounds%endp + allhistfldlist(fld)%field%num1d = nump case default write(iulog,*) trim(subname),' ERROR: unknown 1d output type= ',type1d call endrun(msg=errMsg(sourcefile, __LINE__)) end select if (present(no_snow_behavior)) then - allhistfldlist(f)%field%no_snow_behavior = no_snow_behavior + allhistfldlist(fld)%field%no_snow_behavior = no_snow_behavior else - allhistfldlist(f)%field%no_snow_behavior = no_snow_unset + allhistfldlist(fld)%field%no_snow_behavior = no_snow_unset end if ! The following two fields are used only in list of all history fields, @@ -645,8 +648,8 @@ subroutine allhistfldlist_addfld (fname, numdims, type1d, type1d_out, & ! ALL FIELDS IN THE FORMER ARE INITIALIZED WITH THE ACTIVE ! FLAG SET TO FALSE - allhistfldlist(f)%avgflag(:) = avgflag - allhistfldlist(f)%actflag(:) = .false. + allhistfldlist(fld)%avgflag(:) = avgflag + allhistfldlist(fld)%actflag(:,:) = .false. end subroutine allhistfldlist_addfld @@ -704,7 +707,7 @@ subroutine hist_htapes_build () ! Note - with netcdf, only 1 (ncd_double) and 2 (ncd_float) are allowed do t=1,ntapes - tape(t)%ntimes = 0 + tape(t)%ntimes(:) = 0 tape(t)%dov2xy = hist_dov2xy(t) tape(t)%nhtfrq = hist_nhtfrq(t) tape(t)%mfilt = hist_mfilt(t) @@ -744,7 +747,7 @@ subroutine allhistfldlist_make_active (name, tape_index, avgflag) character(len=*), intent(in), optional :: avgflag ! time averaging flag ! ! !LOCAL VARIABLES: - integer :: f ! field index + integer :: fld ! field index logical :: found ! flag indicates field found in allhistfldlist character(len=*),parameter :: subname = 'allhistfldlist_make_active' !----------------------------------------------------------------------- @@ -768,11 +771,15 @@ subroutine allhistfldlist_make_active (name, tape_index, avgflag) ! Also reset averaging flag if told to use other than default. found = .false. - do f = 1,nallhistflds - if (trim(name) == trim(allhistfldlist(f)%field%name)) then - allhistfldlist(f)%actflag(tape_index) = .true. + do fld = 1, nallhistflds + if (trim(name) == trim(allhistfldlist(fld)%field%name)) then if (present(avgflag)) then - if (avgflag/= ' ') allhistfldlist(f)%avgflag(tape_index) = avgflag + if (avgflag /= ' ') allhistfldlist(fld)%avgflag(tape_index) = avgflag + end if + if (allhistfldlist(fld)%avgflag(tape_index) == 'I') then + allhistfldlist(fld)%actflag(tape_index,instantaneous_file_index) = .true. + else + allhistfldlist(fld)%actflag(tape_index,accumulated_file_index) = .true. end if found = .true. exit @@ -796,7 +803,7 @@ subroutine allhistfldlist_change_timeavg (t) integer, intent(in) :: t ! history tape index ! ! !LOCAL VARIABLES: - integer :: f ! field index + integer :: fld ! field index character(len=avgflag_strlen) :: avgflag ! local equiv of hist_avgflag_pertape(t) character(len=*),parameter :: subname = 'allhistfldlist_change_timeavg' !----------------------------------------------------------------------- @@ -807,8 +814,8 @@ subroutine allhistfldlist_change_timeavg (t) call endrun(msg=errMsg(sourcefile, __LINE__)) end if - do f = 1,nallhistflds - allhistfldlist(f)%avgflag(t) = avgflag + do fld = 1, nallhistflds + allhistfldlist(fld)%avgflag(t) = avgflag end do end subroutine allhistfldlist_change_timeavg @@ -828,7 +835,8 @@ subroutine htapes_fieldlist() ! !ARGUMENTS: ! ! !LOCAL VARIABLES: - integer :: t, f ! tape, field indices + class(entry_base), pointer :: tmp_hlist(:) ! temporary subset of hlist to pass as call argument + integer :: t, f, fld ! tape, file, field indices integer :: ff ! index into include, exclude and fprec list character(len=max_namlen) :: name ! field name portion of fincl (i.e. no avgflag separator) character(len=max_namlen) :: allhistfldname ! name from allhistfldlist field @@ -872,40 +880,40 @@ subroutine htapes_fieldlist() ! First ensure contents of fincl and fexcl are valid names - do t = 1,max_tapes - f = 1 - do while (f < max_flds .and. fincl(f,t) /= ' ') - name = getname (fincl(f,t)) + tape_loop1: do t = 1, max_tapes + fld = 1 + do while (fld < max_flds .and. fincl(fld,t) /= ' ') + name = getname (fincl(fld,t)) do ff = 1,nallhistflds allhistfldname = allhistfldlist(ff)%field%name if (name == allhistfldname) exit end do if (name /= allhistfldname) then - write(iulog,*) trim(subname),' ERROR: ', trim(name), ' in fincl(', f, ') ',& + write(iulog,*) trim(subname),' ERROR: ', trim(name), ' in fincl(', fld, ') ',& 'for history tape ',t,' not found' call endrun(msg=errMsg(sourcefile, __LINE__)) end if - f = f + 1 + fld = fld + 1 end do - f = 1 - do while (f < max_flds .and. fexcl(f,t) /= ' ') + fld = 1 + do while (fld < max_flds .and. fexcl(fld,t) /= ' ') do ff = 1,nallhistflds allhistfldname = allhistfldlist(ff)%field%name - if (fexcl(f,t) == allhistfldname) exit + if (fexcl(fld,t) == allhistfldname) exit end do - if (fexcl(f,t) /= allhistfldname) then - write(iulog,*) trim(subname),' ERROR: ', fexcl(f,t), ' in fexcl(', f, ') ', & + if (fexcl(fld,t) /= allhistfldname) then + write(iulog,*) trim(subname),' ERROR: ', fexcl(fld,t), ' in fexcl(', fld, ') ', & 'for history tape ',t,' not found' call endrun(msg=errMsg(sourcefile, __LINE__)) end if - f = f + 1 + fld = fld + 1 end do - end do + history_tape_in_use(t,:) = .false. + tape(t)%nflds(:) = 0 + end do tape_loop1 - history_tape_in_use(:) = .false. - tape(:)%nflds = 0 - do t = 1,max_tapes + tape_loop2: do t = 1, max_tapes ! Loop through the allhistfldlist set of field names and determine if any of those ! are in the FINCL or FEXCL arrays @@ -914,68 +922,101 @@ subroutine htapes_fieldlist() ! Add the field to the tape if specified via namelist (FINCL[1-max_tapes]), ! or if it is on by default and was not excluded via namelist (FEXCL[1-max_tapes]). - do f = 1,nallhistflds - allhistfldname = allhistfldlist(f)%field%name - call list_index (fincl(1,t), allhistfldname, ff) + file_loop1: do f = 1, max_split_files + fld_loop: do fld = 1, nallhistflds + allhistfldname = allhistfldlist(fld)%field%name + call list_index (fincl(1,t), allhistfldname, ff) - if (ff > 0) then + ff_gt_0: if (ff > 0) then - ! if field is in include list, ff > 0 and htape_addfld - ! will be called for field + ! if field is in include list, ff > 0 and htape_addfld + ! will be called for field - avgflag = getflag (fincl(ff,t)) - call htape_addfld (t, f, avgflag) + avgflag = getflag (fincl(ff,t)) - else if (.not. hist_empty_htapes) then + ! Set time averaging flag based on allhistfldlist setting or + ! override the default averaging flag with namelist setting - ! find index of field in exclude list + if (.not. avgflag_valid(avgflag, blank_valid=.true.)) then + write(iulog,*) trim(subname),' ERROR: unknown avgflag=', avgflag + call endrun(msg=errMsg(sourcefile, __LINE__)) + end if - call list_index (fexcl(1,t), allhistfldname, ff) + if (avgflag == ' ') then + avgflag = allhistfldlist(fld)%avgflag(t) + end if - ! if field is in exclude list, ff > 0 and htape_addfld - ! will not be called for field - ! if field is not in exclude list, ff =0 and htape_addfld - ! will be called for field (note that htape_addfld will be - ! called below only if field is not in exclude list OR in - ! include list + ! This if-statement is in a loop of f (instantaneous_ or + ! accumulated_file_index) so it matters whether f is one + ! or the other when going through here. Otherwise all fields + ! would end up on all files, which is not the intent. + if (f == instantaneous_file_index .and. avgflag == 'I') then + call htape_addfld (t, f, fld, avgflag) + else if (f == accumulated_file_index .and. avgflag /= 'I') then + call htape_addfld (t, f, fld, avgflag) + else if (f /= instantaneous_file_index .and. f /= accumulated_file_index) then + write(iulog,*) trim(subname),' ERROR: invalid f =', f, ' should be one of these values:', accumulated_file_index, instantaneous_file_index + call endrun(msg=errMsg(sourcefile, __LINE__)) + end if - if (ff == 0 .and. allhistfldlist(f)%actflag(t)) then - call htape_addfld (t, f, ' ') - end if + else if (.not. hist_empty_htapes) then - end if - end do + ! find index of field in exclude list - ! Specification of tape contents now complete. - ! Sort each list of active entries - call sort_hist_list(t, tape(t)%nflds, tape(t)%hlist) + call list_index (fexcl(1,t), allhistfldname, ff) - if (masterproc) then - if (tape(t)%nflds > 0) then - write(iulog,*) trim(subname),' : Included fields tape ',t,'=',tape(t)%nflds + ! if field is in exclude list, ff > 0 and htape_addfld + ! will not be called for field + ! if field is not in exclude list, ff =0 and htape_addfld + ! will be called for field (note that htape_addfld will be + ! called below only if field is not in exclude list OR in + ! include list + + if (ff == 0 .and. allhistfldlist(fld)%actflag(t,f)) then + call htape_addfld (t, f, fld, ' ') + end if + + end if ff_gt_0 + end do fld_loop + + ! Specification of tape contents now complete. + ! Sort each list of active entries + associate(tmp_hlist => tape(t)%hlist(:,f)) + call sort_hist_list(tape(t)%nflds(f), tmp_hlist(:)) + end associate + + if (masterproc) then + if (tape(t)%nflds(f) > 0) then + write(iulog,*) trim(subname),' : Included fields tape ', t, '=',tape(t)%nflds(f) + end if + do fld = 1, tape(t)%nflds(f) + write(iulog,*) fld, ' ', tape(t)%hlist(fld,f)%field%name, & + tape(t)%hlist(fld,f)%field%num2d, ' ', tape(t)%hlist(fld,f)%avgflag + end do + call shr_sys_flush(iulog) end if - do f = 1,tape(t)%nflds - write(iulog,*) f,' ',tape(t)%hlist(f)%field%name, & - tape(t)%hlist(f)%field%num2d,' ',tape(t)%hlist(f)%avgflag - end do - call shr_sys_flush(iulog) - end if - end do + end do file_loop1 + end do tape_loop2 ! Determine index of max active history tape, and whether each tape is in use ntapes = 0 do t = max_tapes,1,-1 - if (tape(t)%nflds > 0) then - ntapes = t - exit - end if + do f = 1, max_split_files + if (tape(t)%nflds(f) > 0) then + ntapes = t + exit + end if + end do + if (ntapes > 0) exit end do do t = 1, ntapes - if (tape(t)%nflds > 0) then - history_tape_in_use(t) = .true. - end if + do f = 1, max_split_files + if (tape(t)%nflds(f) > 0) then + history_tape_in_use(t,f) = .true. + end if + end do end do ! Change 1d output per tape output flag if requested - only for history @@ -996,7 +1037,7 @@ subroutine htapes_fieldlist() if (masterproc) then write(iulog,*) 'There will be a total of ',ntapes,' history tapes' - do t=1,ntapes + tape_loop3: do t = 1, ntapes write(iulog,*) if (hist_nhtfrq(t) == 0) then write(iulog,*)'History tape ',t,' write frequency is MONTHLY' @@ -1010,12 +1051,14 @@ subroutine htapes_fieldlist() end if write(iulog,*)'Number of time samples on history tape ',t,' is ',hist_mfilt(t) write(iulog,*)'Output precision on history tape ',t,'=',hist_ndens(t) - if (.not. history_tape_in_use(t)) then - write(iulog,*) 'History tape ',t,' does not have any fields,' - write(iulog,*) 'so it will not be written!' - end if + file_loop2: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + write(iulog,*) 'History tape ', t,' and file ', f, ' has no fields,' + write(iulog,*) 'so it will not be written!' + end if + end do file_loop2 write(iulog,*) - end do + end do tape_loop3 call shr_sys_flush(iulog) end if @@ -1065,19 +1108,18 @@ subroutine copy_history_entry(this, other) end subroutine copy_history_entry !----------------------------------------------------------------------- - subroutine sort_hist_list(t, n_fields, hist_list) + subroutine sort_hist_list(n_fields, hist_list) ! !DESCRIPTION: ! Sort list of history variable names hist_list in alphabetical ! order. ! !ARGUMENTS: - integer, intent(in) :: t ! tape index integer, intent(in) :: n_fields ! number of fields class(entry_base), intent(inout) :: hist_list(:) ! !LOCAL VARIABLES: - integer :: f, ff ! field indices + integer :: fld, ff ! field indices class(entry_base), allocatable :: tmp character(len=*), parameter :: subname = 'sort_hist_list' @@ -1091,8 +1133,8 @@ subroutine sort_hist_list(t, n_fields, hist_list) allocate(tmp, source = hist_list(1)) - do f = n_fields-1, 1, -1 - do ff = 1, f + do fld = n_fields-1, 1, -1 + do ff = 1, fld ! First sort by the name of the level dimension; then, within the list of ! fields with the same level dimension, sort by field name. Sorting first by ! the level dimension gives a significant performance improvement especially @@ -1147,14 +1189,15 @@ logical function is_mapping_upto_subgrid( type1d, type1d_out ) result ( mapping) end function is_mapping_upto_subgrid !----------------------------------------------------------------------- - subroutine htape_addfld (t, f, avgflag) + subroutine htape_addfld (t, f, fld, avgflag) ! ! !DESCRIPTION: ! Add a field to a history tape, copying metadata from the list of all history fields ! ! !ARGUMENTS: integer, intent(in) :: t ! history tape index - integer, intent(in) :: f ! field index from list of all history fields + integer, intent(in) :: f ! history file index + integer, intent(in) :: fld ! field index from list of all history fields character(len=*), intent(in) :: avgflag ! time averaging flag ! ! !LOCAL VARIABLES: @@ -1179,16 +1222,16 @@ subroutine htape_addfld (t, f, avgflag) if (htapes_defined) then write(iulog,*) trim(subname),' ERROR: attempt to add field ', & - allhistfldlist(f)%field%name, ' after history files are set' + allhistfldlist(fld)%field%name, ' after history files are set' call endrun(msg=errMsg(sourcefile, __LINE__)) end if - tape(t)%nflds = tape(t)%nflds + 1 - n = tape(t)%nflds + tape(t)%nflds(f) = tape(t)%nflds(f) + 1 + n = tape(t)%nflds(f) ! Copy field information - tape(t)%hlist(n)%field = allhistfldlist(f)%field + tape(t)%hlist(n,f)%field = allhistfldlist(fld)%field ! Determine bounds @@ -1203,16 +1246,16 @@ subroutine htape_addfld (t, f, avgflag) ! ***NOTE- the following logic is what permits non lat/lon grids to ! be written to clm history file - type1d = tape(t)%hlist(n)%field%type1d + type1d = tape(t)%hlist(n,f)%field%type1d if (type1d == nameg .or. & type1d == namel .or. & type1d == namec .or. & type1d == namep) then - tape(t)%hlist(n)%field%type1d_out = grlnd + tape(t)%hlist(n,f)%field%type1d_out = grlnd end if if (type1d == grlnd) then - tape(t)%hlist(n)%field%type1d_out = grlnd + tape(t)%hlist(n,f)%field%type1d_out = grlnd end if else if (hist_type1d_pertape(t) /= ' ') then @@ -1220,17 +1263,17 @@ subroutine htape_addfld (t, f, avgflag) ! Set output 1d type based on namelist setting of hist_type1d_pertape ! Only applies to tapes when xy output is not required - type1d = tape(t)%hlist(n)%field%type1d + type1d = tape(t)%hlist(n,f)%field%type1d select case (trim(hist_type1d_pertape(t))) case('GRID') - tape(t)%hlist(n)%field%type1d_out = nameg + tape(t)%hlist(n,f)%field%type1d_out = nameg case('LAND') - tape(t)%hlist(n)%field%type1d_out = namel + tape(t)%hlist(n,f)%field%type1d_out = namel case('COLS') - tape(t)%hlist(n)%field%type1d_out = namec + tape(t)%hlist(n,f)%field%type1d_out = namec case ('PFTS') - tape(t)%hlist(n)%field%type1d_out = namep + tape(t)%hlist(n,f)%field%type1d_out = namep case default write(iulog,*) trim(subname),' ERROR: unknown input hist_type1d_pertape= ', hist_type1d_pertape(t) call endrun(msg=errMsg(sourcefile, __LINE__)) @@ -1240,7 +1283,7 @@ subroutine htape_addfld (t, f, avgflag) ! Determine output 1d dimensions - type1d_out = tape(t)%hlist(n)%field%type1d_out + type1d_out = tape(t)%hlist(n,f)%field%type1d_out if (type1d_out == grlnd) then beg1d_out = bounds%begg end1d_out = bounds%endg @@ -1267,26 +1310,26 @@ subroutine htape_addfld (t, f, avgflag) end if ! Output bounds for the field - tape(t)%hlist(n)%field%beg1d_out = beg1d_out - tape(t)%hlist(n)%field%end1d_out = end1d_out - tape(t)%hlist(n)%field%num1d_out = num1d_out + tape(t)%hlist(n,f)%field%beg1d_out = beg1d_out + tape(t)%hlist(n,f)%field%end1d_out = end1d_out + tape(t)%hlist(n,f)%field%num1d_out = num1d_out ! Fields native bounds - beg1d = allhistfldlist(f)%field%beg1d - end1d = allhistfldlist(f)%field%end1d + beg1d = allhistfldlist(fld)%field%beg1d + end1d = allhistfldlist(fld)%field%end1d - ! Alloccate and initialize history buffer and related info + ! Allocate and initialize history buffer and related info - num2d = tape(t)%hlist(n)%field%num2d + num2d = tape(t)%hlist(n,f)%field%num2d if ( is_mapping_upto_subgrid( type1d, type1d_out ) ) then - allocate (tape(t)%hlist(n)%hbuf(beg1d_out:end1d_out,num2d)) - allocate (tape(t)%hlist(n)%nacs(beg1d_out:end1d_out,num2d)) + allocate (tape(t)%hlist(n,f)%hbuf(beg1d_out:end1d_out,num2d)) + allocate (tape(t)%hlist(n,f)%nacs(beg1d_out:end1d_out,num2d)) else - allocate (tape(t)%hlist(n)%hbuf(beg1d:end1d,num2d)) - allocate (tape(t)%hlist(n)%nacs(beg1d:end1d,num2d)) + allocate (tape(t)%hlist(n,f)%hbuf(beg1d:end1d,num2d)) + allocate (tape(t)%hlist(n,f)%nacs(beg1d:end1d,num2d)) end if - tape(t)%hlist(n)%hbuf(:,:) = 0._r8 - tape(t)%hlist(n)%nacs(:,:) = 0 + tape(t)%hlist(n,f)%hbuf(:,:) = 0._r8 + tape(t)%hlist(n,f)%nacs(:,:) = 0 ! Set time averaging flag based on allhistfldlist setting or ! override the default averaging flag with namelist setting @@ -1297,9 +1340,9 @@ subroutine htape_addfld (t, f, avgflag) end if if (avgflag == ' ') then - tape(t)%hlist(n)%avgflag = allhistfldlist(f)%avgflag(t) + tape(t)%hlist(n,f)%avgflag = allhistfldlist(fld)%avgflag(t) else - tape(t)%hlist(n)%avgflag = avgflag + tape(t)%hlist(n,f)%avgflag = avgflag end if ! Override this tape's avgflag if nhtfrq == 1 @@ -1312,7 +1355,7 @@ subroutine htape_addfld (t, f, avgflag) ! - local time (L) avgflag_temp = hist_avgflag_pertape(t) if (avgflag_temp == 'I' .or. avgflag_temp(1:1) == 'L') then - tape(t)%hlist(n)%avgflag = avgflag_temp + tape(t)%hlist(n,f)%avgflag = avgflag_temp end if end subroutine htape_addfld @@ -1329,33 +1372,36 @@ subroutine hist_update_hbuf(bounds) ! ! !LOCAL VARIABLES: integer :: t ! tape index - integer :: f ! field index + integer :: f ! file index + integer :: fld ! field index integer :: num2d ! size of second dimension (e.g. number of vertical levels) integer :: numdims ! number of dimensions character(len=*),parameter :: subname = 'hist_update_hbuf' character(len=hist_dim_name_length) :: type2d ! hbuf second dimension type ["levgrnd","levlak","numrad","ltype","natpft","cft","glc_nec","elevclas","subname(n)","mxsowings","mxharvests"] !----------------------------------------------------------------------- - do t = 1,ntapes -!$OMP PARALLEL DO PRIVATE (f, num2d, numdims) - do f = 1,tape(t)%nflds + tape_loop: do t = 1, ntapes + file_loop: do f = 1, max_split_files +!$OMP PARALLEL DO PRIVATE (fld, num2d, numdims) + do fld = 1, tape(t)%nflds(f) - numdims = tape(t)%hlist(f)%field%numdims + numdims = tape(t)%hlist(fld,f)%field%numdims - if ( numdims == 1) then - call hist_update_hbuf_field_1d (t, f, bounds) - else - num2d = tape(t)%hlist(f)%field%num2d - call hist_update_hbuf_field_2d (t, f, bounds, num2d) - end if - end do + if ( numdims == 1) then + call hist_update_hbuf_field_1d (t, f, fld, bounds) + else + num2d = tape(t)%hlist(fld,f)%field%num2d + call hist_update_hbuf_field_2d (t, f, fld, bounds, num2d) + end if + end do !$OMP END PARALLEL DO - end do + end do file_loop + end do tape_loop end subroutine hist_update_hbuf !----------------------------------------------------------------------- - subroutine hist_update_hbuf_field_1d (t, f, bounds) + subroutine hist_update_hbuf_field_1d (t, f, fld, bounds) ! ! !DESCRIPTION: ! Accumulate (or take min, max, etc. as appropriate) input field @@ -1372,7 +1418,8 @@ subroutine hist_update_hbuf_field_1d (t, f, bounds) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index - integer, intent(in) :: f ! field index + integer, intent(in) :: f ! file index + integer, intent(in) :: fld ! field index type(bounds_type), intent(in) :: bounds ! ! !LOCAL VARIABLES: @@ -1412,19 +1459,19 @@ subroutine hist_update_hbuf_field_1d (t, f, bounds) SHR_ASSERT_FL(bounds%level == bounds_level_proc, sourcefile, __LINE__) - avgflag = tape(t)%hlist(f)%avgflag - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf - beg1d = tape(t)%hlist(f)%field%beg1d - end1d = tape(t)%hlist(f)%field%end1d - beg1d_out = tape(t)%hlist(f)%field%beg1d_out - end1d_out = tape(t)%hlist(f)%field%end1d_out - type1d = tape(t)%hlist(f)%field%type1d - type1d_out = tape(t)%hlist(f)%field%type1d_out - p2c_scale_type = tape(t)%hlist(f)%field%p2c_scale_type - c2l_scale_type = tape(t)%hlist(f)%field%c2l_scale_type - l2g_scale_type = tape(t)%hlist(f)%field%l2g_scale_type - hpindex = tape(t)%hlist(f)%field%hpindex + avgflag = tape(t)%hlist(fld,f)%avgflag + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf + beg1d = tape(t)%hlist(fld,f)%field%beg1d + end1d = tape(t)%hlist(fld,f)%field%end1d + beg1d_out = tape(t)%hlist(fld,f)%field%beg1d_out + end1d_out = tape(t)%hlist(fld,f)%field%end1d_out + type1d = tape(t)%hlist(fld,f)%field%type1d + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + p2c_scale_type = tape(t)%hlist(fld,f)%field%p2c_scale_type + c2l_scale_type = tape(t)%hlist(fld,f)%field%c2l_scale_type + l2g_scale_type = tape(t)%hlist(fld,f)%field%l2g_scale_type + hpindex = tape(t)%hlist(fld,f)%field%hpindex field => clmptr_rs(hpindex)%ptr call get_curr_date (year, month, day, secs) @@ -1718,7 +1765,7 @@ subroutine hist_update_hbuf_field_1d (t, f, bounds) end subroutine hist_update_hbuf_field_1d !----------------------------------------------------------------------- - subroutine hist_update_hbuf_field_2d (t, f, bounds, num2d) + subroutine hist_update_hbuf_field_2d (t, f, fld, bounds, num2d) ! ! !DESCRIPTION: ! Accumulate (or take min, max, etc. as appropriate) input field @@ -1736,7 +1783,8 @@ subroutine hist_update_hbuf_field_2d (t, f, bounds, num2d) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index - integer, intent(in) :: f ! field index + integer, intent(in) :: f ! file index + integer, intent(in) :: fld ! field index type(bounds_type), intent(in) :: bounds integer, intent(in) :: num2d ! size of second dimension ! @@ -1779,20 +1827,20 @@ subroutine hist_update_hbuf_field_2d (t, f, bounds, num2d) SHR_ASSERT_FL(bounds%level == bounds_level_proc, sourcefile, __LINE__) - avgflag = tape(t)%hlist(f)%avgflag - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf - beg1d = tape(t)%hlist(f)%field%beg1d - end1d = tape(t)%hlist(f)%field%end1d - beg1d_out = tape(t)%hlist(f)%field%beg1d_out - end1d_out = tape(t)%hlist(f)%field%end1d_out - type1d = tape(t)%hlist(f)%field%type1d - type1d_out = tape(t)%hlist(f)%field%type1d_out - p2c_scale_type = tape(t)%hlist(f)%field%p2c_scale_type - c2l_scale_type = tape(t)%hlist(f)%field%c2l_scale_type - l2g_scale_type = tape(t)%hlist(f)%field%l2g_scale_type - no_snow_behavior = tape(t)%hlist(f)%field%no_snow_behavior - hpindex = tape(t)%hlist(f)%field%hpindex + avgflag = tape(t)%hlist(fld,f)%avgflag + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf + beg1d = tape(t)%hlist(fld,f)%field%beg1d + end1d = tape(t)%hlist(fld,f)%field%end1d + beg1d_out = tape(t)%hlist(fld,f)%field%beg1d_out + end1d_out = tape(t)%hlist(fld,f)%field%end1d_out + type1d = tape(t)%hlist(fld,f)%field%type1d + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + p2c_scale_type = tape(t)%hlist(fld,f)%field%p2c_scale_type + c2l_scale_type = tape(t)%hlist(fld,f)%field%c2l_scale_type + l2g_scale_type = tape(t)%hlist(fld,f)%field%l2g_scale_type + no_snow_behavior = tape(t)%hlist(fld,f)%field%no_snow_behavior + hpindex = tape(t)%hlist(fld,f)%field%hpindex call get_curr_date (year, month, day, secs) @@ -2253,7 +2301,7 @@ end subroutine hist_set_snow_field_2d !----------------------------------------------------------------------- - subroutine hfields_normalize (t) + subroutine hfields_normalize (t, f) ! ! !DESCRIPTION: ! Normalize fields on a history file by the number of accumulations. @@ -2262,9 +2310,10 @@ subroutine hfields_normalize (t) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index ! ! !LOCAL VARIABLES: - integer :: f ! field index + integer :: fld ! field index integer :: k ! 1d index integer :: j ! 2d index logical :: aflag ! averaging flag @@ -2278,18 +2327,18 @@ subroutine hfields_normalize (t) ! Normalize by number of accumulations for time averaged case - do f = 1,tape(t)%nflds - avgflag = tape(t)%hlist(f)%avgflag - if ( is_mapping_upto_subgrid(tape(t)%hlist(f)%field%type1d, tape(t)%hlist(f)%field%type1d_out) )then - beg1d = tape(t)%hlist(f)%field%beg1d_out - end1d = tape(t)%hlist(f)%field%end1d_out + do fld = 1, tape(t)%nflds(f) + avgflag = tape(t)%hlist(fld,f)%avgflag + if ( is_mapping_upto_subgrid(tape(t)%hlist(fld,f)%field%type1d, tape(t)%hlist(fld,f)%field%type1d_out) )then + beg1d = tape(t)%hlist(fld,f)%field%beg1d_out + end1d = tape(t)%hlist(fld,f)%field%end1d_out else - beg1d = tape(t)%hlist(f)%field%beg1d - end1d = tape(t)%hlist(f)%field%end1d + beg1d = tape(t)%hlist(fld,f)%field%beg1d + end1d = tape(t)%hlist(fld,f)%field%end1d end if - num2d = tape(t)%hlist(f)%field%num2d - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf + num2d = tape(t)%hlist(fld,f)%field%num2d + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf if (avgflag == 'A' .or. avgflag(1:1) == 'L') then aflag = .true. @@ -2311,7 +2360,7 @@ subroutine hfields_normalize (t) end subroutine hfields_normalize !----------------------------------------------------------------------- - subroutine hfields_zero (t) + subroutine hfields_zero (t, f) ! ! !DESCRIPTION: ! Zero out accumulation and history buffers for a given history tape. @@ -2319,21 +2368,22 @@ subroutine hfields_zero (t) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index ! ! !LOCAL VARIABLES: - integer :: f ! field index + integer :: fld ! field index character(len=*),parameter :: subname = 'hfields_zero' !----------------------------------------------------------------------- - do f = 1,tape(t)%nflds - tape(t)%hlist(f)%hbuf(:,:) = 0._r8 - tape(t)%hlist(f)%nacs(:,:) = 0 + do fld = 1,tape(t)%nflds(f) + tape(t)%hlist(fld,f)%hbuf(:,:) = 0._r8 + tape(t)%hlist(fld,f)%nacs(:,:) = 0 end do end subroutine hfields_zero !----------------------------------------------------------------------- - subroutine htape_create (t, histrest) + subroutine htape_create (t, f, histrest) ! ! !DESCRIPTION: ! Define netcdf metadata of history file t. @@ -2351,10 +2401,10 @@ subroutine htape_create (t, histrest) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index logical, intent(in), optional :: histrest ! if creating the history restart file ! ! !LOCAL VARIABLES: - integer :: f ! field index integer :: p,c,l,n ! indices integer :: ier ! error code integer :: num2d ! size of second dimension (e.g. number of vertical levels) @@ -2394,9 +2444,9 @@ subroutine htape_create (t, histrest) ncprec = tape(t)%ncprec if (lhistrest) then - lnfid => ncid_hist(t) + lnfid => ncid_hist(t,f) else - lnfid => nfid(t) + lnfid => nfid(t,f) endif ! Create new netCDF file. It will be in define mode @@ -2404,20 +2454,20 @@ subroutine htape_create (t, histrest) if ( .not. lhistrest )then if (masterproc) then write(iulog,*) trim(subname),' : Opening netcdf htape ', & - trim(locfnh(t)) + trim(locfnh(t,f)) call shr_sys_flush(iulog) end if - call ncd_pio_createfile(lnfid, trim(locfnh(t))) + call ncd_pio_createfile(lnfid, trim(locfnh(t,f))) call ncd_putatt(lnfid, ncd_global, 'title', 'CLM History file information' ) call ncd_putatt(lnfid, ncd_global, 'comment', & "NOTE: None of the variables are weighted by land fraction!" ) else if (masterproc) then write(iulog,*) trim(subname),' : Opening netcdf rhtape ', & - trim(locfnhr(t)) + trim(locfnhr(t,f)) call shr_sys_flush(iulog) end if - call ncd_pio_createfile(lnfid, trim(locfnhr(t))) + call ncd_pio_createfile(lnfid, trim(locfnhr(t,f))) call ncd_putatt(lnfid, ncd_global, 'title', & 'CLM Restart History information, required to continue a simulation' ) call ncd_putatt(lnfid, ncd_global, 'comment', & @@ -2542,7 +2592,7 @@ subroutine htape_create (t, histrest) call ncd_defdim(lnfid, 'time', ncd_unlimited, time_dimid) if (masterproc)then write(iulog,*) trim(subname), & - ' : Successfully defined netcdf history file ',t + ' : Successfully defined netcdf history file ', t, f call shr_sys_flush(iulog) end if else @@ -2665,7 +2715,7 @@ subroutine htape_add_cft_metadata(lnfid) end subroutine htape_add_cft_metadata !----------------------------------------------------------------------- - subroutine htape_timeconst3D(t, & + subroutine htape_timeconst3D(t, f, & bounds, watsat_col, sucsat_col, bsw_col, hksat_col, & cellsand_col, cellclay_col, mode) ! @@ -2684,6 +2734,7 @@ subroutine htape_timeconst3D(t, & ! ! !ARGUMENTS: integer , intent(in) :: t ! tape index + integer , intent(in) :: f ! file index type(bounds_type) , intent(in) :: bounds real(r8) , intent(in) :: watsat_col( bounds%begc:,1: ) real(r8) , intent(in) :: sucsat_col( bounds%begc:,1: ) @@ -2786,20 +2837,20 @@ subroutine htape_timeconst3D(t, & end if if (tape(t)%dov2xy) then if (ldomain%isgrid2d) then - call ncd_defvar(ncid=nfid(t), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec,& + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec,& dim1name='lon', dim2name='lat', dim3name='levgrnd', & long_name=long_name, units=units, missing_value=spval, fill_value=spval, & varid=varid) else - call ncd_defvar(ncid=nfid(t), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec, & dim1name=grlnd, dim2name='levgrnd', & long_name=long_name, units=units, missing_value=spval, fill_value=spval, & varid=varid) end if - call add_landunit_mask_metadata(nfid(t), varid, l2g_scale_type(ifld)) + call add_landunit_mask_metadata(nfid(t,f), varid, l2g_scale_type(ifld)) else - call ncd_defvar(ncid=nfid(t), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnames(ifld)), xtype=tape(t)%ncprec, & dim1name=namec, dim2name='levgrnd', & long_name=long_name, units=units, missing_value=spval, fill_value=spval) end if @@ -2849,14 +2900,14 @@ subroutine htape_timeconst3D(t, & if (ldomain%isgrid2d) then call ncd_io(varname=trim(varnames(ifld)), dim1name=grlnd, & - data=histo, ncid=nfid(t), flag='write') + data=histo, ncid=nfid(t,f), flag='write') else call ncd_io(varname=trim(varnames(ifld)), dim1name=grlnd, & - data=histo, ncid=nfid(t), flag='write') + data=histo, ncid=nfid(t,f), flag='write') end if else call ncd_io(varname=trim(varnames(ifld)), dim1name=namec, & - data=histi, ncid=nfid(t), flag='write') + data=histi, ncid=nfid(t,f), flag='write') end if end do @@ -2877,20 +2928,20 @@ subroutine htape_timeconst3D(t, & end if if (tape(t)%dov2xy) then if (ldomain%isgrid2d) then - call ncd_defvar(ncid=nfid(t), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec,& + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec,& dim1name='lon', dim2name='lat', dim3name='levlak', & long_name=long_name, units=units, missing_value=spval, fill_value=spval, & varid=varid) else - call ncd_defvar(ncid=nfid(t), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec, & dim1name=grlnd, dim2name='levlak', & long_name=long_name, units=units, missing_value=spval, fill_value=spval, & varid=varid) end if - call add_landunit_mask_metadata(nfid(t), varid, l2g_scale_typel(ifld)) + call add_landunit_mask_metadata(nfid(t,f), varid, l2g_scale_typel(ifld)) else - call ncd_defvar(ncid=nfid(t), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamesl(ifld)), xtype=tape(t)%ncprec, & dim1name=namec, dim2name='levlak', & long_name=long_name, units=units, missing_value=spval, fill_value=spval) end if @@ -2935,14 +2986,14 @@ subroutine htape_timeconst3D(t, & c2l_scale_type='unity', l2g_scale_type=l2g_scale_typel(ifld)) if (ldomain%isgrid2d) then call ncd_io(varname=trim(varnamesl(ifld)), dim1name=grlnd, & - data=histol, ncid=nfid(t), flag='write') + data=histol, ncid=nfid(t,f), flag='write') else call ncd_io(varname=trim(varnamesl(ifld)), dim1name=grlnd, & - data=histol, ncid=nfid(t), flag='write') + data=histol, ncid=nfid(t,f), flag='write') end if else call ncd_io(varname=trim(varnamesl(ifld)), dim1name=namec, & - data=histil, ncid=nfid(t), flag='write') + data=histil, ncid=nfid(t,f), flag='write') end if end do @@ -2963,16 +3014,16 @@ subroutine htape_timeconst3D(t, & end if if (tape(t)%dov2xy) then if (ldomain%isgrid2d) then - call ncd_defvar(ncid=nfid(t), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec,& + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec,& dim1name='lon', dim2name='lat', dim3name='levsoi', & long_name=long_name, units=units, missing_value=spval, fill_value=spval) else - call ncd_defvar(ncid=nfid(t), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec, & dim1name=grlnd, dim2name='levsoi', & long_name=long_name, units=units, missing_value=spval, fill_value=spval) end if else - call ncd_defvar(ncid=nfid(t), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=trim(varnamest(ifld)), xtype=tape(t)%ncprec, & dim1name=namec, dim2name='levsoi', & long_name=long_name, units=units, missing_value=spval, fill_value=spval) end if @@ -3014,14 +3065,14 @@ subroutine htape_timeconst3D(t, & c2l_scale_type='unity', l2g_scale_type='veg') if (ldomain%isgrid2d) then call ncd_io(varname=trim(varnamest(ifld)), dim1name=grlnd, & - data=histot, ncid=nfid(t), flag='write') + data=histot, ncid=nfid(t,f), flag='write') else call ncd_io(varname=trim(varnamest(ifld)), dim1name=grlnd, & - data=histot, ncid=nfid(t), flag='write') + data=histot, ncid=nfid(t,f), flag='write') end if else call ncd_io(varname=trim(varnamest(ifld)), dim1name=namec, & - data=histit, ncid=nfid(t), flag='write') + data=histit, ncid=nfid(t,f), flag='write') end if end do @@ -3033,7 +3084,7 @@ subroutine htape_timeconst3D(t, & end subroutine htape_timeconst3D !----------------------------------------------------------------------- - subroutine htape_timeconst(t, mode) + subroutine htape_timeconst(t, f, mode) ! ! !DESCRIPTION: ! Write time constant values to primary history tape. @@ -3095,6 +3146,7 @@ subroutine htape_timeconst(t, mode) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index integer :: dtime ! timestep size character(len=*), intent(in) :: mode ! 'define' or 'write' ! @@ -3140,147 +3192,147 @@ subroutine htape_timeconst(t, mode) call get_proc_bounds(bounds) - if (tape(t)%ntimes == 1) then + if (tape(t)%ntimes(f) == 1) then if (mode == 'define') then call ncd_defvar(varname='levgrnd', xtype=tape(t)%ncprec, & dim1name='levgrnd', & - long_name='coordinate ground levels', units='m', ncid=nfid(t)) + long_name='coordinate ground levels', units='m', ncid=nfid(t,f)) call ncd_defvar(varname='levsoi', xtype=tape(t)%ncprec, & dim1name='levsoi', & - long_name='coordinate soil levels (equivalent to top nlevsoi levels of levgrnd)', units='m', ncid=nfid(t)) + long_name='coordinate soil levels (equivalent to top nlevsoi levels of levgrnd)', units='m', ncid=nfid(t,f)) call ncd_defvar(varname='levlak', xtype=tape(t)%ncprec, & dim1name='levlak', & - long_name='coordinate lake levels', units='m', ncid=nfid(t)) + long_name='coordinate lake levels', units='m', ncid=nfid(t,f)) call ncd_defvar(varname='levdcmp', xtype=tape(t)%ncprec, dim1name='levdcmp', & - long_name='coordinate levels for soil decomposition variables', units='m', ncid=nfid(t)) + long_name='coordinate levels for soil decomposition variables', units='m', ncid=nfid(t,f)) if (use_hillslope .and. .not.tape(t)%dov2xy)then call ncd_defvar(varname='hillslope_distance', xtype=ncd_double, & dim1name=namec, long_name='hillslope column distance', & - units='m', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_width', xtype=ncd_double, & dim1name=namec, long_name='hillslope column width', & - units='m', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_area', xtype=ncd_double, & dim1name=namec, long_name='hillslope column area', & - units='m2', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_elev', xtype=ncd_double, & dim1name=namec, long_name='hillslope column elevation', & - units='m', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_slope', xtype=ncd_double, & dim1name=namec, long_name='hillslope column slope', & - units='m/m', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_aspect', xtype=ncd_double, & dim1name=namec, long_name='hillslope column aspect', & - units='radians', ncid=nfid(t)) + units='m', ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_index', xtype=ncd_int, & dim1name=namec, long_name='hillslope index', & - ncid=nfid(t)) + ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_cold', xtype=ncd_int, & dim1name=namec, long_name='hillslope downhill column index', & - ncid=nfid(t)) + ncid=nfid(t,f)) call ncd_defvar(varname='hillslope_colu', xtype=ncd_int, & dim1name=namec, long_name='hillslope uphill column index', & - ncid=nfid(t)) + ncid=nfid(t,f)) end if if(use_fates)then call ncd_defvar(varname='fates_levscls', xtype=tape(t)%ncprec, dim1name='fates_levscls', & - long_name='FATES diameter size class lower bound', units='cm', ncid=nfid(t)) + long_name='FATES diameter size class lower bound', units='cm', ncid=nfid(t,f)) call ncd_defvar(varname='fates_scmap_levscag', xtype=ncd_int, dim1name='fates_levscag', & - long_name='FATES size-class map into size x patch age', units='-', ncid=nfid(t)) + long_name='FATES size-class map into size x patch age', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_agmap_levscag', xtype=ncd_int, dim1name='fates_levscag', & - long_name='FATES age-class map into size x patch age', units='-', ncid=nfid(t)) + long_name='FATES age-class map into size x patch age', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levscpf',xtype=ncd_int, dim1name='fates_levscpf', & - long_name='FATES pft index of the combined pft-size class dimension', units='-', ncid=nfid(t)) + long_name='FATES pft index of the combined pft-size class dimension', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_scmap_levscpf',xtype=ncd_int, dim1name='fates_levscpf', & - long_name='FATES size index of the combined pft-size class dimension', units='-', ncid=nfid(t)) + long_name='FATES size index of the combined pft-size class dimension', units='-', ncid=nfid(t,f)) ! Units are dash here with units of yr added to the long name so ! that postprocessors (like ferret) won't get confused with what ! the time coordinate is. EBK Nov/3/2021 (see #1540) call ncd_defvar(varname='fates_levcacls', xtype=tape(t)%ncprec, dim1name='fates_levcacls', & - long_name='FATES cohort age class lower bound (yr)', units='-', ncid=nfid(t)) + long_name='FATES cohort age class lower bound (yr)', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levcapf',xtype=ncd_int, dim1name='fates_levcapf', & - long_name='FATES pft index of the combined pft-cohort age class dimension', units='-', ncid=nfid(t)) + long_name='FATES pft index of the combined pft-cohort age class dimension', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_camap_levcapf',xtype=ncd_int, dim1name='fates_levcapf', & - long_name='FATES cohort age index of the combined pft-cohort age dimension', units='-', ncid=nfid(t)) + long_name='FATES cohort age index of the combined pft-cohort age dimension', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levage',xtype=tape(t)%ncprec, dim1name='fates_levage', & - long_name='FATES patch age (yr)', ncid=nfid(t)) + long_name='FATES patch age (yr)', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levheight',xtype=tape(t)%ncprec, dim1name='fates_levheight', & - long_name='FATES height (m)', ncid=nfid(t)) + long_name='FATES height (m)', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levpft',xtype=ncd_int, dim1name='fates_levpft', & - long_name='FATES pft number', ncid=nfid(t)) + long_name='FATES pft number', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levfuel',xtype=ncd_int, dim1name='fates_levfuel', & - long_name='FATES fuel index', ncid=nfid(t)) + long_name='FATES fuel index', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levcwdsc',xtype=ncd_int, dim1name='fates_levcwdsc', & - long_name='FATES cwd size class', ncid=nfid(t)) + long_name='FATES cwd size class', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levcan',xtype=ncd_int, dim1name='fates_levcan', & - long_name='FATES canopy level', ncid=nfid(t)) + long_name='FATES canopy level', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levleaf',xtype=ncd_int, dim1name='fates_levleaf', & - long_name='FATES leaf+stem level', units='VAI', ncid=nfid(t)) + long_name='FATES leaf+stem level', units='VAI', ncid=nfid(t,f)) call ncd_defvar(varname='fates_canmap_levcnlf',xtype=ncd_int, dim1name='fates_levcnlf', & - long_name='FATES canopy level of combined canopy-leaf dimension', ncid=nfid(t)) + long_name='FATES canopy level of combined canopy-leaf dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_lfmap_levcnlf',xtype=ncd_int, dim1name='fates_levcnlf', & - long_name='FATES leaf level of combined canopy-leaf dimension', ncid=nfid(t)) + long_name='FATES leaf level of combined canopy-leaf dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_canmap_levcnlfpf',xtype=ncd_int, dim1name='fates_levcnlfpf', & - long_name='FATES canopy level of combined canopy x leaf x pft dimension', ncid=nfid(t)) + long_name='FATES canopy level of combined canopy x leaf x pft dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_lfmap_levcnlfpf',xtype=ncd_int, dim1name='fates_levcnlfpf', & - long_name='FATES leaf level of combined canopy x leaf x pft dimension', ncid=nfid(t)) + long_name='FATES leaf level of combined canopy x leaf x pft dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levcnlfpf',xtype=ncd_int, dim1name='fates_levcnlfpf', & - long_name='FATES PFT level of combined canopy x leaf x pft dimension', ncid=nfid(t)) + long_name='FATES PFT level of combined canopy x leaf x pft dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_scmap_levscagpft', xtype=ncd_int, dim1name='fates_levscagpf', & - long_name='FATES size-class map into size x patch age x pft', units='-', ncid=nfid(t)) + long_name='FATES size-class map into size x patch age x pft', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_agmap_levscagpft', xtype=ncd_int, dim1name='fates_levscagpf', & - long_name='FATES age-class map into size x patch age x pft', units='-', ncid=nfid(t)) + long_name='FATES age-class map into size x patch age x pft', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levscagpft', xtype=ncd_int, dim1name='fates_levscagpf', & - long_name='FATES pft map into size x patch age x pft', units='-', ncid=nfid(t)) + long_name='FATES pft map into size x patch age x pft', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levagepft', xtype=ncd_int, dim1name='fates_levagepft', & - long_name='FATES pft map into patch age x pft', units='-', ncid=nfid(t)) + long_name='FATES pft map into patch age x pft', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_agmap_levagepft', xtype=ncd_int, dim1name='fates_levagepft', & - long_name='FATES age-class map into patch age x pft', units='-', ncid=nfid(t)) + long_name='FATES age-class map into patch age x pft', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_agmap_levagefuel', xtype=ncd_int, dim1name='fates_levagefuel', & - long_name='FATES age-class map into patch age x fuel size', units='-', ncid=nfid(t)) + long_name='FATES age-class map into patch age x fuel size', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_fscmap_levagefuel', xtype=ncd_int, dim1name='fates_levagefuel', & - long_name='FATES fuel size-class map into patch age x fuel size', units='-', ncid=nfid(t)) + long_name='FATES fuel size-class map into patch age x fuel size', units='-', ncid=nfid(t,f)) call ncd_defvar(varname='fates_cdmap_levcdsc',xtype=ncd_int, dim1name='fates_levcdsc', & - long_name='FATES damage index of the combined damage-size dimension', ncid=nfid(t)) + long_name='FATES damage index of the combined damage-size dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_scmap_levcdsc',xtype=ncd_int, dim1name='fates_levcdsc', & - long_name='FATES size index of the combined damage-size dimension', ncid=nfid(t)) + long_name='FATES size index of the combined damage-size dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_cdmap_levcdpf',xtype=ncd_int, dim1name='fates_levcdpf', & - long_name='FATES damage index of the combined damage-size-PFT dimension', ncid=nfid(t)) + long_name='FATES damage index of the combined damage-size-PFT dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_scmap_levcdpf',xtype=ncd_int, dim1name='fates_levcdpf', & - long_name='FATES size index of the combined damage-size-PFT dimension', ncid=nfid(t)) + long_name='FATES size index of the combined damage-size-PFT dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_pftmap_levcdpf',xtype=ncd_int, dim1name='fates_levcdpf', & - long_name='FATES pft index of the combined damage-size-PFT dimension', ncid=nfid(t)) + long_name='FATES pft index of the combined damage-size-PFT dimension', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levcdam', xtype=tape(t)%ncprec, dim1name='fates_levcdam', & - long_name='FATES damage class lower bound', units='unitless', ncid=nfid(t)) + long_name='FATES damage class lower bound', units='unitless', ncid=nfid(t,f)) call ncd_defvar(varname='fates_levlanduse',xtype=ncd_int, dim1name='fates_levlanduse', & - long_name='FATES land use label', ncid=nfid(t)) + long_name='FATES land use label', ncid=nfid(t,f)) end if elseif (mode == 'write') then if ( masterproc ) write(iulog, *) ' zsoi:',zsoi - call ncd_io(varname='levgrnd', data=zsoi, ncid=nfid(t), flag='write') - call ncd_io(varname='levsoi', data=zsoi(1:nlevsoi), ncid=nfid(t), flag='write') - call ncd_io(varname='levlak' , data=zlak, ncid=nfid(t), flag='write') + call ncd_io(varname='levgrnd', data=zsoi, ncid=nfid(t,f), flag='write') + call ncd_io(varname='levsoi', data=zsoi(1:nlevsoi), ncid=nfid(t,f), flag='write') + call ncd_io(varname='levlak' , data=zlak, ncid=nfid(t,f), flag='write') if ( decomp_method /= no_soil_decomp )then - call ncd_io(varname='levdcmp', data=zsoi, ncid=nfid(t), flag='write') + call ncd_io(varname='levdcmp', data=zsoi, ncid=nfid(t,f), flag='write') else zsoi_1d(1) = 1._r8 - call ncd_io(varname='levdcmp', data=zsoi_1d, ncid=nfid(t), flag='write') + call ncd_io(varname='levdcmp', data=zsoi_1d, ncid=nfid(t,f), flag='write') end if if (use_hillslope .and. .not.tape(t)%dov2xy) then - call ncd_io(varname='hillslope_distance' , data=col%hill_distance, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_width' , data=col%hill_width, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_area' , data=col%hill_area, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_elev' , data=col%hill_elev, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_slope' , data=col%hill_slope, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_aspect' , data=col%hill_aspect, dim1name=namec, ncid=nfid(t), flag='write') - call ncd_io(varname='hillslope_index' , data=col%hillslope_ndx, dim1name=namec, ncid=nfid(t), flag='write') + call ncd_io(varname='hillslope_distance' , data=col%hill_distance, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_width' , data=col%hill_width, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_area' , data=col%hill_area, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_elev' , data=col%hill_elev, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_slope' , data=col%hill_slope, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_aspect' , data=col%hill_aspect, dim1name=namec, ncid=nfid(t,f), flag='write') + call ncd_io(varname='hillslope_index' , data=col%hillslope_ndx, dim1name=namec, ncid=nfid(t,f), flag='write') ! write global indices rather than local indices allocate(icarr(bounds%begc:bounds%endc),stat=ier) @@ -3296,7 +3348,7 @@ subroutine htape_timeconst(t, mode) endif enddo - call ncd_io(varname='hillslope_cold' , data=icarr, dim1name=namec, ncid=nfid(t), flag='write') + call ncd_io(varname='hillslope_cold' , data=icarr, dim1name=namec, ncid=nfid(t,f), flag='write') do c = bounds%begc,bounds%endc if (col%colu(c) /= ispval) then @@ -3306,45 +3358,45 @@ subroutine htape_timeconst(t, mode) endif enddo - call ncd_io(varname='hillslope_colu' , data=icarr, dim1name=namec, ncid=nfid(t), flag='write') + call ncd_io(varname='hillslope_colu' , data=icarr, dim1name=namec, ncid=nfid(t,f), flag='write') deallocate(icarr) endif if(use_fates)then - call ncd_io(varname='fates_scmap_levscag',data=fates_hdim_scmap_levscag, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_agmap_levscag',data=fates_hdim_agmap_levscag, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levscls',data=fates_hdim_levsclass, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levcacls',data=fates_hdim_levcoage, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levscpf',data=fates_hdim_pfmap_levscpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_scmap_levscpf',data=fates_hdim_scmap_levscpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levcapf',data=fates_hdim_pfmap_levcapf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_camap_levcapf',data=fates_hdim_camap_levcapf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levage',data=fates_hdim_levage, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levheight',data=fates_hdim_levheight, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levpft',data=fates_hdim_levpft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levfuel',data=fates_hdim_levfuel, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levcdam',data=fates_hdim_levdamage, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levcwdsc',data=fates_hdim_levcwdsc, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levcan',data=fates_hdim_levcan, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levleaf',data=fates_hdim_levleaf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_canmap_levcnlf',data=fates_hdim_canmap_levcnlf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_lfmap_levcnlf',data=fates_hdim_lfmap_levcnlf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_canmap_levcnlfpf',data=fates_hdim_canmap_levcnlfpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_lfmap_levcnlfpf',data=fates_hdim_lfmap_levcnlfpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levcnlfpf',data=fates_hdim_pftmap_levcnlfpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_scmap_levscagpft',data=fates_hdim_scmap_levscagpft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_agmap_levscagpft',data=fates_hdim_agmap_levscagpft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levscagpft',data=fates_hdim_pftmap_levscagpft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levagepft',data=fates_hdim_pftmap_levagepft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_agmap_levagepft',data=fates_hdim_agmap_levagepft, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_agmap_levagefuel',data=fates_hdim_agmap_levagefuel, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_fscmap_levagefuel',data=fates_hdim_fscmap_levagefuel, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_scmap_levcdsc',data=fates_hdim_scmap_levcdsc, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_cdmap_levcdsc',data=fates_hdim_cdmap_levcdsc, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_scmap_levcdpf',data=fates_hdim_scmap_levcdpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_cdmap_levcdpf',data=fates_hdim_cdmap_levcdpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_pftmap_levcdpf',data=fates_hdim_pftmap_levcdpf, ncid=nfid(t), flag='write') - call ncd_io(varname='fates_levlanduse',data=fates_hdim_levlanduse, ncid=nfid(t), flag='write') + call ncd_io(varname='fates_scmap_levscag',data=fates_hdim_scmap_levscag, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_agmap_levscag',data=fates_hdim_agmap_levscag, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levscls',data=fates_hdim_levsclass, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levcacls',data=fates_hdim_levcoage, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levscpf',data=fates_hdim_pfmap_levscpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_scmap_levscpf',data=fates_hdim_scmap_levscpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levcapf',data=fates_hdim_pfmap_levcapf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_camap_levcapf',data=fates_hdim_camap_levcapf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levage',data=fates_hdim_levage, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levheight',data=fates_hdim_levheight, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levpft',data=fates_hdim_levpft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levfuel',data=fates_hdim_levfuel, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levcdam',data=fates_hdim_levdamage, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levcwdsc',data=fates_hdim_levcwdsc, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levcan',data=fates_hdim_levcan, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levleaf',data=fates_hdim_levleaf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_canmap_levcnlf',data=fates_hdim_canmap_levcnlf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_lfmap_levcnlf',data=fates_hdim_lfmap_levcnlf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_canmap_levcnlfpf',data=fates_hdim_canmap_levcnlfpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_lfmap_levcnlfpf',data=fates_hdim_lfmap_levcnlfpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levcnlfpf',data=fates_hdim_pftmap_levcnlfpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_scmap_levscagpft',data=fates_hdim_scmap_levscagpft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_agmap_levscagpft',data=fates_hdim_agmap_levscagpft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levscagpft',data=fates_hdim_pftmap_levscagpft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levagepft',data=fates_hdim_pftmap_levagepft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_agmap_levagepft',data=fates_hdim_agmap_levagepft, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_agmap_levagefuel',data=fates_hdim_agmap_levagefuel, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_fscmap_levagefuel',data=fates_hdim_fscmap_levagefuel, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_scmap_levcdsc',data=fates_hdim_scmap_levcdsc, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_cdmap_levcdsc',data=fates_hdim_cdmap_levcdsc, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_scmap_levcdpf',data=fates_hdim_scmap_levcdpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_cdmap_levcdpf',data=fates_hdim_cdmap_levcdpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_pftmap_levcdpf',data=fates_hdim_pftmap_levcdpf, ncid=nfid(t,f), flag='write') + call ncd_io(varname='fates_levlanduse',data=fates_hdim_levlanduse, ncid=nfid(t,f), flag='write') end if endif @@ -3355,7 +3407,7 @@ subroutine htape_timeconst(t, mode) !------------------------------------------------------------------------------- ! For define mode -- only do this for first time-sample - if (mode == 'define' .and. tape(t)%ntimes == 1) then + if (mode == 'define' .and. tape(t)%ntimes(f) == 1) then call get_ref_date(yr, mon, day, nbsec) nstep = get_nstep() hours = nbsec / 3600 @@ -3368,16 +3420,16 @@ subroutine htape_timeconst(t, mode) dim1id(1) = time_dimid str = 'days since ' // basedate // " " // basesec - if (hist_avgflag_pertape(t) /= 'I') then ! NOT instantaneous fields tape + if (f == accumulated_file_index) then step_or_bounds = 'time_bounds' long_name = 'time at exact middle of ' // step_or_bounds - call ncd_defvar(nfid(t), 'time', tape(t)%ncprec, 1, dim1id, varid, & + call ncd_defvar(nfid(t,f), 'time', tape(t)%ncprec, 1, dim1id, varid, & long_name=long_name, units=str) - call ncd_putatt(nfid(t), varid, 'bounds', 'time_bounds') - else ! instantaneous fields tape + call ncd_putatt(nfid(t,f), varid, 'bounds', 'time_bounds') + else ! instantaneous file step_or_bounds = 'time step' long_name = 'time at end of ' // step_or_bounds - call ncd_defvar(nfid(t), 'time', tape(t)%ncprec, 1, dim1id, varid, & + call ncd_defvar(nfid(t,f), 'time', tape(t)%ncprec, 1, dim1id, varid, & long_name=long_name, units=str) end if cal = get_calendar() @@ -3386,13 +3438,13 @@ subroutine htape_timeconst(t, mode) else if ( trim(cal) == GREGORIAN_C )then caldesc = "gregorian" end if - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) dim1id(1) = time_dimid long_name = 'current date (YYYYMMDD) at end of ' // step_or_bounds - call ncd_defvar(nfid(t) , 'mcdate', ncd_int, 1, dim1id , varid, & + call ncd_defvar(nfid(t,f) , 'mcdate', ncd_int, 1, dim1id , varid, & long_name = long_name) - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) ! ! add global attribute time_period_freq ! @@ -3416,42 +3468,42 @@ subroutine htape_timeconst(t, mode) end if 999 format(a,i0) - call ncd_putatt(nfid(t), ncd_global, 'time_period_freq', & + call ncd_putatt(nfid(t,f), ncd_global, 'time_period_freq', & trim(time_period_freq)) long_name = 'current seconds of current date at end of ' // step_or_bounds - call ncd_defvar(nfid(t) , 'mcsec' , ncd_int, 1, dim1id , varid, & + call ncd_defvar(nfid(t,f) , 'mcsec' , ncd_int, 1, dim1id , varid, & long_name = long_name, units='s') - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) long_name = 'current day (from base day) at end of ' // step_or_bounds - call ncd_defvar(nfid(t) , 'mdcur' , ncd_int, 1, dim1id , varid, & + call ncd_defvar(nfid(t,f) , 'mdcur' , ncd_int, 1, dim1id , varid, & long_name = long_name) - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) long_name = 'current seconds of current day at end of ' // step_or_bounds - call ncd_defvar(nfid(t) , 'mscur' , ncd_int, 1, dim1id , varid, & + call ncd_defvar(nfid(t,f) , 'mscur' , ncd_int, 1, dim1id , varid, & long_name = long_name) - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) - call ncd_defvar(nfid(t) , 'nstep' , ncd_int, 1, dim1id , varid, & + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) + call ncd_defvar(nfid(t,f) , 'nstep' , ncd_int, 1, dim1id , varid, & long_name = 'time step') dim2id(1) = nbnd_dimid; dim2id(2) = time_dimid - if (hist_avgflag_pertape(t) /= 'I') then ! NOT instantaneous fields tape - call ncd_defvar(nfid(t), 'time_bounds', ncd_double, 2, dim2id, varid, & + if (f == accumulated_file_index) then + call ncd_defvar(nfid(t,f), 'time_bounds', ncd_double, 2, dim2id, varid, & long_name = 'time interval endpoints', & units = str) - call ncd_putatt(nfid(t), varid, 'calendar', caldesc) + call ncd_putatt(nfid(t,f), varid, 'calendar', caldesc) end if dim2id(1) = strlen_dimid; dim2id(2) = time_dimid - call ncd_defvar(nfid(t), 'date_written', ncd_char, 2, dim2id, varid) - call ncd_defvar(nfid(t), 'time_written', ncd_char, 2, dim2id, varid) + call ncd_defvar(nfid(t,f), 'date_written', ncd_char, 2, dim2id, varid) + call ncd_defvar(nfid(t,f), 'time_written', ncd_char, 2, dim2id, varid) if ( len_trim(TimeConst3DVars_Filename) > 0 )then - call ncd_putatt(nfid(t), ncd_global, 'Time_constant_3Dvars_filename', & + call ncd_putatt(nfid(t,f), ncd_global, 'Time_constant_3Dvars_filename', & trim(TimeConst3DVars_Filename)) end if if ( len_trim(TimeConst3DVars) > 0 )then - call ncd_putatt(nfid(t), ncd_global, 'Time_constant_3Dvars', & + call ncd_putatt(nfid(t,f), ncd_global, 'Time_constant_3Dvars', & trim(TimeConst3DVars)) end if @@ -3462,26 +3514,26 @@ subroutine htape_timeconst(t, mode) mcdate = yr*10000 + mon*100 + day nstep = get_nstep() - call ncd_io('mcdate', mcdate, 'write', nfid(t), nt=tape(t)%ntimes) - call ncd_io('mcsec' , mcsec , 'write', nfid(t), nt=tape(t)%ntimes) - call ncd_io('mdcur' , mdcur , 'write', nfid(t), nt=tape(t)%ntimes) - call ncd_io('mscur' , mscur , 'write', nfid(t), nt=tape(t)%ntimes) - call ncd_io('nstep' , nstep , 'write', nfid(t), nt=tape(t)%ntimes) + call ncd_io('mcdate', mcdate, 'write', nfid(t,f), nt=tape(t)%ntimes(f)) + call ncd_io('mcsec' , mcsec , 'write', nfid(t,f), nt=tape(t)%ntimes(f)) + call ncd_io('mdcur' , mdcur , 'write', nfid(t,f), nt=tape(t)%ntimes(f)) + call ncd_io('mscur' , mscur , 'write', nfid(t,f), nt=tape(t)%ntimes(f)) + call ncd_io('nstep' , nstep , 'write', nfid(t,f), nt=tape(t)%ntimes(f)) timedata(1) = tape(t)%begtime ! beginning time timedata(2) = mdcur + mscur/secspday ! end time - if (hist_avgflag_pertape(t) /= 'I') then ! NOT instantaneous fields tape + if (f == accumulated_file_index) then time = (timedata(1) + timedata(2)) * 0.5_r8 - call ncd_io('time_bounds', timedata, 'write', nfid(t), nt=tape(t)%ntimes) - else + call ncd_io('time_bounds', timedata, 'write', nfid(t,f), nt=tape(t)%ntimes(f)) + else ! instantaneous file time = timedata(2) end if - call ncd_io('time' , time , 'write', nfid(t), nt=tape(t)%ntimes) + call ncd_io('time' , time , 'write', nfid(t,f), nt=tape(t)%ntimes(f)) call getdatetime (cdate, ctime) - call ncd_io('date_written', cdate, 'write', nfid(t), nt=tape(t)%ntimes) + call ncd_io('date_written', cdate, 'write', nfid(t,f), nt=tape(t)%ntimes(f)) - call ncd_io('time_written', ctime, 'write', nfid(t), nt=tape(t)%ntimes) + call ncd_io('time_written', ctime, 'write', nfid(t,f), nt=tape(t)%ntimes(f)) endif @@ -3489,96 +3541,96 @@ subroutine htape_timeconst(t, mode) !*** Grid definition variables *** !------------------------------------------------------------------------------- ! For define mode -- only do this for first time-sample - if (mode == 'define' .and. tape(t)%ntimes == 1) then + if (mode == 'define' .and. tape(t)%ntimes(f) == 1) then if (ldomain%isgrid2d) then call ncd_defvar(varname='lon', xtype=tape(t)%ncprec, dim1name='lon', & long_name='coordinate longitude', units='degrees_east', & - ncid=nfid(t), missing_value=spval, fill_value=spval) + ncid=nfid(t,f), missing_value=spval, fill_value=spval) else call ncd_defvar(varname='lon', xtype=tape(t)%ncprec, & dim1name=grlnd, & - long_name='coordinate longitude', units='degrees_east', ncid=nfid(t), & + long_name='coordinate longitude', units='degrees_east', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) end if if (ldomain%isgrid2d) then call ncd_defvar(varname='lat', xtype=tape(t)%ncprec, dim1name='lat', & long_name='coordinate latitude', units='degrees_north', & - ncid=nfid(t), missing_value=spval, fill_value=spval) + ncid=nfid(t,f), missing_value=spval, fill_value=spval) else call ncd_defvar(varname='lat', xtype=tape(t)%ncprec, & dim1name=grlnd, & - long_name='coordinate latitude', units='degrees_north', ncid=nfid(t), & + long_name='coordinate latitude', units='degrees_north', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) end if if (ldomain%isgrid2d) then call ncd_defvar(varname='area', xtype=tape(t)%ncprec, & dim1name='lon', dim2name='lat',& - long_name='grid cell areas', units='km^2', ncid=nfid(t), & + long_name='grid cell areas', units='km^2', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) else call ncd_defvar(varname='area', xtype=tape(t)%ncprec, & dim1name=grlnd, & - long_name='grid cell areas', units='km^2', ncid=nfid(t), & + long_name='grid cell areas', units='km^2', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) end if if (ldomain%isgrid2d) then call ncd_defvar(varname='landfrac', xtype=tape(t)%ncprec, & dim1name='lon', dim2name='lat', & - long_name='land fraction', ncid=nfid(t), & + long_name='land fraction', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) else call ncd_defvar(varname='landfrac', xtype=tape(t)%ncprec, & dim1name=grlnd, & - long_name='land fraction', ncid=nfid(t), & + long_name='land fraction', ncid=nfid(t,f), & missing_value=spval, fill_value=spval) end if if (ldomain%isgrid2d) then call ncd_defvar(varname='landmask', xtype=ncd_int, & dim1name='lon', dim2name='lat', & - long_name='land/ocean mask (0.=ocean and 1.=land)', ncid=nfid(t), & + long_name='land/ocean mask (0.=ocean and 1.=land)', ncid=nfid(t,f), & imissing_value=ispval, ifill_value=ispval) else call ncd_defvar(varname='landmask', xtype=ncd_int, & dim1name=grlnd, & - long_name='land/ocean mask (0.=ocean and 1.=land)', ncid=nfid(t), & + long_name='land/ocean mask (0.=ocean and 1.=land)', ncid=nfid(t,f), & imissing_value=ispval, ifill_value=ispval) end if if (ldomain%isgrid2d) then call ncd_defvar(varname='nbedrock' , xtype=ncd_int, & dim1name='lon', dim2name='lat', & - long_name='index of shallowest bedrock layer', ncid=nfid(t), & + long_name='index of shallowest bedrock layer', ncid=nfid(t,f), & imissing_value=ispval, ifill_value=ispval) else call ncd_defvar(varname='nbedrock' , xtype=ncd_int, & dim1name=grlnd, & - long_name='index of shallowest bedrock layer', ncid=nfid(t), & + long_name='index of shallowest bedrock layer', ncid=nfid(t,f), & imissing_value=ispval, ifill_value=ispval) end if else if (mode == 'write') then - ! Most of this is constant and only needs to be done on tape(t)%ntimes=1 + ! Most of this is constant and only needs to be done on tape(t)%ntimes(f)=1 ! But, some may change for dynamic PATCH mode for example if (ldomain%isgrid2d) then - call ncd_io(varname='lon', data=lon1d, ncid=nfid(t), flag='write') - call ncd_io(varname='lat', data=lat1d, ncid=nfid(t), flag='write') + call ncd_io(varname='lon', data=lon1d, ncid=nfid(t,f), flag='write') + call ncd_io(varname='lat', data=lat1d, ncid=nfid(t,f), flag='write') else - call ncd_io(varname='lon', data=ldomain%lonc, dim1name=grlnd, ncid=nfid(t), flag='write') - call ncd_io(varname='lat', data=ldomain%latc, dim1name=grlnd, ncid=nfid(t), flag='write') + call ncd_io(varname='lon', data=ldomain%lonc, dim1name=grlnd, ncid=nfid(t,f), flag='write') + call ncd_io(varname='lat', data=ldomain%latc, dim1name=grlnd, ncid=nfid(t,f), flag='write') end if - call ncd_io(varname='area' , data=ldomain%area, dim1name=grlnd, ncid=nfid(t), flag='write') - call ncd_io(varname='landfrac', data=ldomain%frac, dim1name=grlnd, ncid=nfid(t), flag='write') - call ncd_io(varname='landmask', data=ldomain%mask, dim1name=grlnd, ncid=nfid(t), flag='write') - call ncd_io(varname='nbedrock' , data=grc%nbedrock, dim1name=grlnd, ncid=nfid(t), flag='write') + call ncd_io(varname='area' , data=ldomain%area, dim1name=grlnd, ncid=nfid(t,f), flag='write') + call ncd_io(varname='landfrac', data=ldomain%frac, dim1name=grlnd, ncid=nfid(t,f), flag='write') + call ncd_io(varname='landmask', data=ldomain%mask, dim1name=grlnd, ncid=nfid(t,f), flag='write') + call ncd_io(varname='nbedrock' , data=grc%nbedrock, dim1name=grlnd, ncid=nfid(t,f), flag='write') end if ! (define/write mode end subroutine htape_timeconst !----------------------------------------------------------------------- - subroutine hfields_write(t, mode) + subroutine hfields_write(t, f, mode) ! ! !DESCRIPTION: ! Write history tape. Issue the call to write the variable. @@ -3588,10 +3640,11 @@ subroutine hfields_write(t, mode) ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index character(len=*), intent(in) :: mode ! 'define' or 'write' ! ! !LOCAL VARIABLES: - integer :: f ! field index + integer :: fld ! field index integer :: k ! 1d index integer :: c,l,p ! indices integer :: beg1d ! on-node 1d field pointer start index @@ -3624,34 +3677,34 @@ subroutine hfields_write(t, mode) if (.not. tape(t)%dov2xy) then if (mode == 'define') then - call hfields_1dinfo(t, mode='define') + call hfields_1dinfo(t, f, mode='define') else if (mode == 'write') then - call hfields_1dinfo(t, mode='write') + call hfields_1dinfo(t, f, mode='write') end if end if ! Define time-dependent variables create variables and attributes for field list - do f = 1,tape(t)%nflds + fld_loop: do fld = 1, tape(t)%nflds(f) ! Set history field variables - varname = tape(t)%hlist(f)%field%name - long_name = tape(t)%hlist(f)%field%long_name - units = tape(t)%hlist(f)%field%units - avgflag = tape(t)%hlist(f)%avgflag - type1d = tape(t)%hlist(f)%field%type1d - type1d_out = tape(t)%hlist(f)%field%type1d_out - beg1d = tape(t)%hlist(f)%field%beg1d - end1d = tape(t)%hlist(f)%field%end1d - beg1d_out = tape(t)%hlist(f)%field%beg1d_out - end1d_out = tape(t)%hlist(f)%field%end1d_out - num1d_out = tape(t)%hlist(f)%field%num1d_out - type2d = tape(t)%hlist(f)%field%type2d - numdims = tape(t)%hlist(f)%field%numdims - num2d = tape(t)%hlist(f)%field%num2d - l2g_scale_type = tape(t)%hlist(f)%field%l2g_scale_type - nt = tape(t)%ntimes + varname = tape(t)%hlist(fld,f)%field%name + long_name = tape(t)%hlist(fld,f)%field%long_name + units = tape(t)%hlist(fld,f)%field%units + avgflag = tape(t)%hlist(fld,f)%avgflag + type1d = tape(t)%hlist(fld,f)%field%type1d + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + beg1d = tape(t)%hlist(fld,f)%field%beg1d + end1d = tape(t)%hlist(fld,f)%field%end1d + beg1d_out = tape(t)%hlist(fld,f)%field%beg1d_out + end1d_out = tape(t)%hlist(fld,f)%field%end1d_out + num1d_out = tape(t)%hlist(fld,f)%field%num1d_out + type2d = tape(t)%hlist(fld,f)%field%type2d + numdims = tape(t)%hlist(fld,f)%field%numdims + num2d = tape(t)%hlist(fld,f)%field%num2d + l2g_scale_type = tape(t)%hlist(fld,f)%field%l2g_scale_type + nt = tape(t)%ntimes(f) if (mode == 'define') then @@ -3685,13 +3738,13 @@ subroutine hfields_write(t, mode) if (dim2name == 'undefined') then if (numdims == 1) then - call ncd_defvar(ncid=nfid(t), varname=varname, xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=varname, xtype=tape(t)%ncprec, & dim1name=dim1name, dim2name='time', & long_name=long_name, units=units, cell_method=avgstr, & missing_value=spval, fill_value=spval, & varid=varid) else - call ncd_defvar(ncid=nfid(t), varname=varname, xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=varname, xtype=tape(t)%ncprec, & dim1name=dim1name, dim2name=type2d, dim3name='time', & long_name=long_name, units=units, cell_method=avgstr, & missing_value=spval, fill_value=spval, & @@ -3699,13 +3752,13 @@ subroutine hfields_write(t, mode) end if else if (numdims == 1) then - call ncd_defvar(ncid=nfid(t), varname=varname, xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=varname, xtype=tape(t)%ncprec, & dim1name=dim1name, dim2name=dim2name, dim3name='time', & long_name=long_name, units=units, cell_method=avgstr, & missing_value=spval, fill_value=spval, & varid=varid) else - call ncd_defvar(ncid=nfid(t), varname=varname, xtype=tape(t)%ncprec, & + call ncd_defvar(ncid=nfid(t,f), varname=varname, xtype=tape(t)%ncprec, & dim1name=dim1name, dim2name=dim2name, dim3name=type2d, dim4name='time', & long_name=long_name, units=units, cell_method=avgstr, & missing_value=spval, fill_value=spval, & @@ -3714,14 +3767,14 @@ subroutine hfields_write(t, mode) endif if (type1d_out == nameg .or. type1d_out == grlnd) then - call add_landunit_mask_metadata(nfid(t), varid, l2g_scale_type) + call add_landunit_mask_metadata(nfid(t,f), varid, l2g_scale_type) end if else if (mode == 'write') then ! Determine output buffer - histo => tape(t)%hlist(f)%hbuf + histo => tape(t)%hlist(fld,f)%hbuf ! Allocate dynamic memory @@ -3738,10 +3791,10 @@ subroutine hfields_write(t, mode) if (numdims == 1) then call ncd_io(flag='write', varname=varname, & - dim1name=type1d_out, data=hist1do, ncid=nfid(t), nt=nt) + dim1name=type1d_out, data=hist1do, ncid=nfid(t,f), nt=nt) else call ncd_io(flag='write', varname=varname, & - dim1name=type1d_out, data=histo, ncid=nfid(t), nt=nt) + dim1name=type1d_out, data=histo, ncid=nfid(t,f), nt=nt) end if @@ -3753,12 +3806,12 @@ subroutine hfields_write(t, mode) end if - end do + end do fld_loop end subroutine hfields_write !----------------------------------------------------------------------- - subroutine hfields_1dinfo(t, mode) + subroutine hfields_1dinfo(t, f, mode) ! ! !DESCRIPTION: ! Write/define 1d info for history tape. @@ -3766,13 +3819,14 @@ subroutine hfields_1dinfo(t, mode) ! !USES: use decompMod , only : gindex_global use domainMod , only : ldomain, ldomain + use dynSubgridControlMod, only : run_has_transient_landcover, get_vars_1dwt_w_time ! ! !ARGUMENTS: integer, intent(in) :: t ! tape index + integer, intent(in) :: f ! file index character(len=*), intent(in) :: mode ! 'define' or 'write' ! ! !LOCAL VARIABLES: - integer :: f ! field index integer :: k ! 1d index integer :: g,c,l,p ! indices integer :: ier ! errir status @@ -3792,7 +3846,7 @@ subroutine hfields_1dinfo(t, mode) call get_proc_bounds(bounds) - ncid => nfid(t) + ncid => nfid(t,f) if (mode == 'define') then @@ -3827,9 +3881,6 @@ subroutine hfields_1dinfo(t, mode) call ncd_defvar(varname='land1d_gi', xtype=ncd_int, dim1name=namel, & long_name='1d grid index of corresponding landunit', ifill_value=ispval, ncid=ncid) - call ncd_defvar(varname='land1d_wtgcell', xtype=ncd_double, dim1name=namel, & - long_name='landunit weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) - call ncd_defvar(varname='land1d_ityplunit', xtype=ncd_int, dim1name=namel, & long_name='landunit type (vegetated,urban,lake,wetland,glacier or glacier_mec)', & ifill_value=ispval, ncid=ncid) @@ -3857,12 +3908,6 @@ subroutine hfields_1dinfo(t, mode) call ncd_defvar(varname='cols1d_li', xtype=ncd_int, dim1name=namec, & long_name='1d landunit index of corresponding column', ifill_value=ispval, ncid=ncid) - call ncd_defvar(varname='cols1d_wtgcell', xtype=ncd_double, dim1name=namec, & - long_name='column weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) - - call ncd_defvar(varname='cols1d_wtlunit', xtype=ncd_double, dim1name=namec, & - long_name='column weight relative to corresponding landunit', fill_value=spval, ncid=ncid) - call ncd_defvar(varname='cols1d_itype_col', xtype=ncd_int, dim1name=namec, & long_name='column type (see global attributes)', ifill_value=ispval, ncid=ncid) @@ -3899,15 +3944,6 @@ subroutine hfields_1dinfo(t, mode) call ncd_defvar(varname='pfts1d_ci', xtype=ncd_int, dim1name=namep, & long_name='1d column index of corresponding pft', ifill_value=ispval, ncid=ncid) - call ncd_defvar(varname='pfts1d_wtgcell', xtype=ncd_double, dim1name=namep, & - long_name='pft weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) - - call ncd_defvar(varname='pfts1d_wtlunit', xtype=ncd_double, dim1name=namep, & - long_name='pft weight relative to corresponding landunit', fill_value=spval, ncid=ncid) - - call ncd_defvar(varname='pfts1d_wtcol', xtype=ncd_double, dim1name=namep, & - long_name='pft weight relative to corresponding column', fill_value=spval, ncid=ncid) - call ncd_defvar(varname='pfts1d_itype_veg', xtype=ncd_int, dim1name=namep, & long_name='pft vegetation type', ifill_value=ispval, ncid=ncid) @@ -3921,6 +3957,45 @@ subroutine hfields_1dinfo(t, mode) call ncd_defvar(varname='pfts1d_active', xtype=ncd_log, dim1name=namep, & ifill_value=0, long_name='true => do computations on this pft', ncid=ncid) + ! group the wt variables together in an if-statement + if (run_has_transient_landcover() .or. get_vars_1dwt_w_time()) then ! transient simulation + call ncd_defvar(varname='land1d_wtgcell', xtype=ncd_double, dim1name=namel, dim2name='time', & + long_name='landunit weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='cols1d_wtgcell', xtype=ncd_double, dim1name=namec, dim2name='time', & + long_name='column weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='cols1d_wtlunit', xtype=ncd_double, dim1name=namec, dim2name='time', & + long_name='column weight relative to corresponding landunit', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtgcell', xtype=ncd_double, dim1name=namep, dim2name='time', & + long_name='pft weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtlunit', xtype=ncd_double, dim1name=namep, dim2name='time', & + long_name='pft weight relative to corresponding landunit', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtcol', xtype=ncd_double, dim1name=namep, dim2name='time', & + long_name='pft weight relative to corresponding column', fill_value=spval, ncid=ncid) + else + call ncd_defvar(varname='land1d_wtgcell', xtype=ncd_double, dim1name=namel, & + long_name='landunit weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='cols1d_wtgcell', xtype=ncd_double, dim1name=namec, & + long_name='column weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='cols1d_wtlunit', xtype=ncd_double, dim1name=namec, & + long_name='column weight relative to corresponding landunit', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtgcell', xtype=ncd_double, dim1name=namep, & + long_name='pft weight relative to corresponding gridcell', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtlunit', xtype=ncd_double, dim1name=namep, & + long_name='pft weight relative to corresponding landunit', fill_value=spval, ncid=ncid) + + call ncd_defvar(varname='pfts1d_wtcol', xtype=ncd_double, dim1name=namep, & + long_name='pft weight relative to corresponding column', fill_value=spval, ncid=ncid) + end if + else if (mode == 'write') then ! Determine bounds @@ -3982,7 +4057,6 @@ subroutine hfields_1dinfo(t, mode) ilarr = get_global_index_array(lun%gridcell(bounds%begl:bounds%endl), bounds%begl, bounds%endl, & subgrid_level=subgrid_level_gridcell) call ncd_io(varname='land1d_gi' , data=ilarr, dim1name=namel, ncid=ncid, flag='write') - call ncd_io(varname='land1d_wtgcell' , data=lun%wtgcell , dim1name=namel, ncid=ncid, flag='write') call ncd_io(varname='land1d_ityplunit', data=lun%itype , dim1name=namel, ncid=ncid, flag='write') call ncd_io(varname='land1d_active' , data=lun%active , dim1name=namel, ncid=ncid, flag='write') @@ -4013,14 +4087,12 @@ subroutine hfields_1dinfo(t, mode) subgrid_level=subgrid_level_landunit) call ncd_io(varname='cols1d_li', data=icarr , dim1name=namec, ncid=ncid, flag='write') - call ncd_io(varname='cols1d_wtgcell', data=col%wtgcell , dim1name=namec, ncid=ncid, flag='write') - call ncd_io(varname='cols1d_wtlunit', data=col%wtlunit , dim1name=namec, ncid=ncid, flag='write') call ncd_io(varname='cols1d_itype_col', data=col%itype , dim1name=namec, ncid=ncid, flag='write') do c = bounds%begc,bounds%endc icarr(c) = lun%itype(col%landunit(c)) enddo - call ncd_io(varname='cols1d_itype_lunit', data=icarr , dim1name=namec, ncid=ncid, flag='write') + call ncd_io(varname='cols1d_itype_lunit', data=icarr , dim1name=namec, ncid=ncid, flag='write') call ncd_io(varname='cols1d_active' , data=col%active , dim1name=namec, ncid=ncid, flag='write') call ncd_io(varname='cols1d_nbedrock', data=col%nbedrock , dim1name=namec, ncid=ncid, flag='write') @@ -4056,9 +4128,6 @@ subroutine hfields_1dinfo(t, mode) subgrid_level=subgrid_level_column) call ncd_io(varname='pfts1d_ci' , data=iparr , dim1name=namep, ncid=ncid, flag='write') - call ncd_io(varname='pfts1d_wtgcell' , data=patch%wtgcell , dim1name=namep, ncid=ncid, flag='write') - call ncd_io(varname='pfts1d_wtlunit' , data=patch%wtlunit , dim1name=namep, ncid=ncid, flag='write') - call ncd_io(varname='pfts1d_wtcol' , data=patch%wtcol , dim1name=namep, ncid=ncid, flag='write') call ncd_io(varname='pfts1d_itype_veg', data=patch%itype , dim1name=namep, ncid=ncid, flag='write') do p = bounds%begp,bounds%endp @@ -4069,10 +4138,27 @@ subroutine hfields_1dinfo(t, mode) do p = bounds%begp,bounds%endp iparr(p) = lun%itype(patch%landunit(p)) enddo - call ncd_io(varname='pfts1d_itype_lunit', data=iparr , dim1name=namep, ncid=ncid, flag='write') + call ncd_io(varname='pfts1d_itype_lunit', data=iparr , dim1name=namep, ncid=ncid, flag='write') call ncd_io(varname='pfts1d_active' , data=patch%active , dim1name=namep, ncid=ncid, flag='write') + ! group the wt variables together in an if-statement + if (run_has_transient_landcover() .or. get_vars_1dwt_w_time()) then ! transient simulation + call ncd_io(varname='land1d_wtgcell' , data=lun%wtgcell , dim1name=namel, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + call ncd_io(varname='cols1d_wtgcell', data=col%wtgcell , dim1name=namec, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + call ncd_io(varname='cols1d_wtlunit', data=col%wtlunit , dim1name=namec, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + call ncd_io(varname='pfts1d_wtgcell' , data=patch%wtgcell , dim1name=namep, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + call ncd_io(varname='pfts1d_wtlunit' , data=patch%wtlunit , dim1name=namep, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + call ncd_io(varname='pfts1d_wtcol' , data=patch%wtcol , dim1name=namep, ncid=ncid, flag='write', nt=tape(t)%ntimes(f)) + else + call ncd_io(varname='land1d_wtgcell' , data=lun%wtgcell , dim1name=namel, ncid=ncid, flag='write') + call ncd_io(varname='cols1d_wtgcell', data=col%wtgcell , dim1name=namec, ncid=ncid, flag='write') + call ncd_io(varname='cols1d_wtlunit', data=col%wtlunit , dim1name=namec, ncid=ncid, flag='write') + call ncd_io(varname='pfts1d_wtgcell' , data=patch%wtgcell , dim1name=namep, ncid=ncid, flag='write') + call ncd_io(varname='pfts1d_wtlunit' , data=patch%wtlunit , dim1name=namep, ncid=ncid, flag='write') + call ncd_io(varname='pfts1d_wtcol' , data=patch%wtcol , dim1name=namep, ncid=ncid, flag='write') + end if + deallocate(rgarr,rlarr,rcarr,rparr) deallocate(igarr,ilarr,icarr,iparr) @@ -4123,7 +4209,8 @@ subroutine hist_htapes_wrapup( rstwr, nlend, bounds, & ! ! !LOCAL VARIABLES: integer :: t ! tape index - integer :: f ! field index + integer :: f ! file index + integer :: fld ! field index integer :: ier ! error code integer :: nstep ! current step integer :: day ! current day (1 -> 31) @@ -4166,151 +4253,157 @@ subroutine hist_htapes_wrapup( rstwr, nlend, bounds, & ! Loop over active history tapes, create new history files if necessary ! and write data to history files if end of history interval. - do t = 1, ntapes - - if (.not. history_tape_in_use(t)) then - cycle - end if - - ! Determine if end of history interval - tape(t)%is_endhist = .false. - if (tape(t)%nhtfrq==0) then !monthly average - if (mon /= monm1) tape(t)%is_endhist = .true. - else - if (mod(nstep,tape(t)%nhtfrq) == 0) tape(t)%is_endhist = .true. - end if + tape_loop1: do t = 1, ntapes + file_loop1: do f = 1, max_split_files - ! If end of history interval + if (.not. history_tape_in_use(t,f)) then + cycle + end if - if (tape(t)%is_endhist) then + ! Determine if end of history interval + tape(t)%is_endhist = .false. + if (tape(t)%nhtfrq==0) then !monthly average + if (mon /= monm1) tape(t)%is_endhist = .true. + else + if (mod(nstep,tape(t)%nhtfrq) == 0) tape(t)%is_endhist = .true. + end if - ! Normalize history buffer if time averaged + ! If end of history interval - call hfields_normalize(t) + if (tape(t)%is_endhist) then - ! Increment current time sample counter. + ! Normalize history buffer if time averaged - tape(t)%ntimes = tape(t)%ntimes + 1 + call hfields_normalize(t, f) - ! Create history file if appropriate and build time comment + ! Increment current time sample counter. - ! If first time sample, generate unique history file name, open file, - ! define dims, vars, etc. + tape(t)%ntimes(f) = tape(t)%ntimes(f) + 1 + ! Create history file if appropriate and build time comment - if (tape(t)%ntimes == 1) then - call t_startf('hist_htapes_wrapup_define') - locfnh(t) = set_hist_filename (hist_freq=tape(t)%nhtfrq, & - hist_mfilt=tape(t)%mfilt, hist_file=t) - if (masterproc) then - write(iulog,*) trim(subname),' : Creating history file ', trim(locfnh(t)), & - ' at nstep = ',get_nstep() - write(iulog,*)'calling htape_create for file t = ',t - endif - call htape_create (t) + ! If first time sample, generate unique history file name, open file, + ! define dims, vars, etc. - ! Define time-constant field variables - call htape_timeconst(t, mode='define') + if (tape(t)%ntimes(f) == 1) then + call t_startf('hist_htapes_wrapup_define') + locfnh(t,f) = set_hist_filename (hist_freq=tape(t)%nhtfrq, & + hist_mfilt=tape(t)%mfilt, hist_file=t, f_index=f) + if (masterproc) then + write(iulog,*) trim(subname),' : Creating history file ', trim(locfnh(t,f)), & + ' at nstep = ',get_nstep() + write(iulog,*)'calling htape_create for tape t and file f = ', t, f + endif + call htape_create (t, f) - ! Define 3D time-constant field variables on first history tapes - if ( do_3Dtconst .and. t == 1) then - call htape_timeconst3D(t, & - bounds, watsat_col, sucsat_col, bsw_col, hksat_col, & - cellsand_col, cellclay_col, mode='define') - TimeConst3DVars_Filename = trim(locfnh(t)) - end if + ! Define time-constant field variables + call htape_timeconst(t, f, mode='define') - ! Define model field variables - call hfields_write(t, mode='define') + ! Define 3D time-constant field variables on first history tapes + if ( do_3Dtconst .and. t == 1) then + call htape_timeconst3D(t, f, & + bounds, watsat_col, sucsat_col, bsw_col, hksat_col, & + cellsand_col, cellclay_col, mode='define') + TimeConst3DVars_Filename = trim(locfnh(t,f)) + end if - ! Exit define model - call ncd_enddef(nfid(t)) - call t_stopf('hist_htapes_wrapup_define') - endif + ! Define model field variables + call hfields_write(t, f, mode='define') - call t_startf('hist_htapes_wrapup_tconst') - ! Write time constant history variables - call htape_timeconst(t, mode='write') + ! Exit define model + call ncd_enddef(nfid(t,f)) + call t_stopf('hist_htapes_wrapup_define') + endif - ! Write 3D time constant history variables to first history tapes - if ( do_3Dtconst .and. t == 1 .and. tape(t)%ntimes == 1 )then - call htape_timeconst3D(t, & - bounds, watsat_col, sucsat_col, bsw_col, hksat_col, & - cellsand_col, cellclay_col, mode='write') - do_3Dtconst = .false. - end if + call t_startf('hist_htapes_wrapup_tconst') + ! Write time constant history variables + call htape_timeconst(t, f, mode='write') - if (masterproc) then - write(iulog,*) - write(iulog,*) trim(subname),' : Writing current time sample to local history file ', & - trim(locfnh(t)),' at nstep = ',get_nstep(), & - ' for history time interval beginning at ', tape(t)%begtime, & - ' and ending at ',time - write(iulog,*) - call shr_sys_flush(iulog) - endif + ! Write 3D time constant history variables to first history tapes + if ( do_3Dtconst .and. t == 1 .and. tape(t)%ntimes(f) == 1 )then + call htape_timeconst3D(t, f, & + bounds, watsat_col, sucsat_col, bsw_col, hksat_col, & + cellsand_col, cellclay_col, mode='write') + do_3Dtconst = .false. + end if - ! Update beginning time of next interval - tape(t)%begtime = time - call t_stopf('hist_htapes_wrapup_tconst') + if (masterproc) then + write(iulog,*) + write(iulog,*) trim(subname),' : Writing current time sample to local history file ', & + trim(locfnh(t,f)),' at nstep = ',get_nstep(), & + ' for history time interval beginning at ', tape(t)%begtime, & + ' and ending at ',time + write(iulog,*) + call shr_sys_flush(iulog) + endif - ! Write history time samples - call t_startf('hist_htapes_wrapup_write') - call hfields_write(t, mode='write') - call t_stopf('hist_htapes_wrapup_write') + ! Update beginning time of next interval + tape(t)%begtime = time + call t_stopf('hist_htapes_wrapup_tconst') - ! Zero necessary history buffers - call hfields_zero(t) + ! Write history time samples + call t_startf('hist_htapes_wrapup_write') + call hfields_write(t, f, mode='write') + call t_stopf('hist_htapes_wrapup_write') - end if + ! Zero necessary history buffers + call hfields_zero(t, f) - end do ! end loop over history tapes + end if + end do file_loop1 + end do tape_loop1 ! Determine if file needs to be closed - call hist_do_disp (ntapes, tape(:)%ntimes, tape(:)%mfilt, if_stop, if_disphist, rstwr, nlend) + file_loop1b: do f = 1, max_split_files + call hist_do_disp (ntapes, tape(:)%ntimes(f), tape(:)%mfilt, if_stop, if_disphist(:,f), rstwr, nlend) + end do file_loop1b ! Close open history file ! Auxilary files may have been closed and saved off without being full, ! must reopen the files - do t = 1, ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if + tape_loop2: do t = 1, ntapes + file_loop2: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - if (if_disphist(t)) then - if (tape(t)%ntimes /= 0) then - if (masterproc) then - write(iulog,*) - write(iulog,*) trim(subname),' : Closing local history file ',& - trim(locfnh(t)),' at nstep = ', get_nstep() - write(iulog,*) - endif + if (if_disphist(t,f)) then + if (tape(t)%ntimes(f) /= 0) then + if (masterproc) then + write(iulog,*) + write(iulog,*) trim(subname),' : Closing local history file ',& + trim(locfnh(t,f)),' at nstep = ', get_nstep() + write(iulog,*) + end if - call ncd_pio_closefile(nfid(t)) + call ncd_pio_closefile(nfid(t,f)) - if (.not.if_stop .and. (tape(t)%ntimes/=tape(t)%mfilt)) then - call ncd_pio_openfile (nfid(t), trim(locfnh(t)), ncd_write) - end if - else - if (masterproc) then - write(iulog,*) trim(subname),' : history tape ',t,': no open file to close' - end if + if (.not.if_stop .and. (tape(t)%ntimes(f)/=tape(t)%mfilt)) then + call ncd_pio_openfile (nfid(t,f), trim(locfnh(t,f)), ncd_write) + end if + else + if (masterproc) then + write(iulog,*) trim(subname),' : history tape ',t,': no open file to close' + end if + endif endif - endif - end do + end do file_loop2 + end do tape_loop2 ! Reset number of time samples to zero if file is full do t = 1, ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if + do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - if (if_disphist(t) .and. tape(t)%ntimes==tape(t)%mfilt) then - tape(t)%ntimes = 0 - end if + if (if_disphist(t,f) .and. tape(t)%ntimes(f)==tape(t)%mfilt) then + tape(t)%ntimes(f) = 0 + end if + end do end do end subroutine hist_htapes_wrapup @@ -4349,6 +4442,7 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) integer :: numl ! total number of landunits across all processors integer :: numc ! total number of columns across all processors integer :: nump ! total number of pfts across all processors + integer :: counter ! loop counter character(len=max_namlen) :: name ! variable name character(len=max_namlen) :: name_acc ! accumulator variable name character(len=max_namlen) :: long_name ! long name of variable @@ -4356,7 +4450,9 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) character(len=max_chars) :: units ! units of variable character(len=max_chars) :: units_acc ! accumulator units character(len=max_chars) :: fname ! full name of history file - character(len=max_chars) :: locrest(max_tapes) ! local history restart file names + character(len=max_chars) :: locrest(max_tapes, max_split_files) ! local history restart file names + character(len=max_chars) :: locrest_onfile(max_split_files, max_tapes) ! history restart file names on file, dims flipped + character(len=max_chars) :: locfnh_onfile(max_split_files, max_tapes) ! history file names on file, dims flipped character(len=max_length_filename) :: my_locfnh ! temporary version of locfnh character(len=max_length_filename) :: my_locfnhr ! temporary version of locfnhr @@ -4369,6 +4465,7 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) character(len=avgflag_strlen), allocatable :: tavgflag(:) integer :: start(2) + character(len=1) :: file_index ! instantaneous or accumulated_file_index character(len=1) :: hnum ! history file index character(len=hist_dim_name_length) :: type1d ! clm pointer 1d type character(len=hist_dim_name_length) :: type1d_out ! history buffer 1d type @@ -4389,11 +4486,12 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) integer :: dimid ! dimension ID integer :: k ! 1d index integer :: ntapes_onfile ! number of history tapes on the restart file - logical, allocatable :: history_tape_in_use_onfile(:) ! whether a given history tape is in use, according to the restart file + logical, allocatable :: history_tape_in_use_onfile(:) ! history tape is/isn't in use according to the restart file integer :: nflds_onfile ! number of history fields on the restart file logical :: readvar ! whether a variable was read successfully integer :: t ! tape index - integer :: f ! field index + integer :: f ! file index + integer :: fld ! field index integer :: varid ! variable id integer, allocatable :: itemp(:) ! temporary real(r8), pointer :: hbuf(:,:) ! history buffer @@ -4412,7 +4510,7 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) if (flag == 'read') then if (nsrest == nsrBranch) then do t = 1,ntapes - tape(t)%ntimes = 0 + tape(t)%ntimes(:) = 0 end do return end if @@ -4428,7 +4526,7 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! First when writing out and in define mode, create files and define all variables ! !================================================ - if (flag == 'define') then + define_read_write: if (flag == 'define') then !================================================ if (.not. present(rdate)) then @@ -4441,25 +4539,27 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! and then add the history and history restart filenames ! call ncd_defdim( ncid, 'ntapes' , ntapes , dimid) + call ncd_defdim( ncid, 'max_split_files', max_split_files, dimid) + call ncd_defdim( ncid, 'ntapes_multiply_by_max_split_files', ntapes * max_split_files, dimid) call ncd_defdim( ncid, 'max_chars' , max_chars , dimid) call ncd_defvar(ncid=ncid, varname='history_tape_in_use', xtype=ncd_log, & - long_name="Whether this history tape is in use", & - dim1name="ntapes") + long_name="Whether this history tape is/isn't in use", & + dim1name="ntapes_multiply_by_max_split_files") ier = PIO_inq_varid(ncid, 'history_tape_in_use', vardesc) ier = PIO_put_att(ncid, vardesc%varid, 'interpinic_flag', iflag_skip) call ncd_defvar(ncid=ncid, varname='locfnh', xtype=ncd_char, & long_name="History filename", & comment="This variable NOT needed for startup or branch simulations", & - dim1name='max_chars', dim2name="ntapes" ) + dim1name='max_chars', dim2name="ntapes_multiply_by_max_split_files" ) ier = PIO_inq_varid(ncid, 'locfnh', vardesc) ier = PIO_put_att(ncid, vardesc%varid, 'interpinic_flag', iflag_skip) call ncd_defvar(ncid=ncid, varname='locfnhr', xtype=ncd_char, & long_name="Restart history filename", & comment="This variable NOT needed for startup or branch simulations", & - dim1name='max_chars', dim2name="ntapes" ) + dim1name='max_chars', dim2name="ntapes_multiply_by_max_split_files" ) ier = PIO_inq_varid(ncid, 'locfnhr', vardesc) ier = PIO_put_att(ncid, vardesc%varid, 'interpinic_flag', iflag_skip) @@ -4471,172 +4571,183 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! Loop over tapes - write out namelist information to each restart-history tape ! only read/write accumulators and counters if needed - do t = 1,ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if - - ! Create the restart history filename and open it - write(hnum,'(i1.1)') t-1 - locfnhr(t) = "./" // trim(caseid) //"."// trim(compname) // trim(inst_suffix) & - // ".rh" // hnum //"."// trim(rdate) //".nc" - - call htape_create( t, histrest=.true. ) - - ! Add read/write accumultators and counters if needed - if (.not. tape(t)%is_endhist) then - do f = 1,tape(t)%nflds - name = tape(t)%hlist(f)%field%name - long_name = tape(t)%hlist(f)%field%long_name - units = tape(t)%hlist(f)%field%units - name_acc = trim(name) // "_acc" - units_acc = "unitless positive integer" - long_name_acc = trim(long_name) // " accumulator number of samples" - type1d_out = tape(t)%hlist(f)%field%type1d_out - type2d = tape(t)%hlist(f)%field%type2d - num2d = tape(t)%hlist(f)%field%num2d - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf - - if (type1d_out == grlnd) then - if (ldomain%isgrid2d) then - dim1name = 'lon' ; dim2name = 'lat' - else - dim1name = trim(grlnd); dim2name = 'undefined' - end if - else - dim1name = type1d_out ; dim2name = 'undefined' - endif + tape_loop1: do t = 1, ntapes + file_loop1: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - if (dim2name == 'undefined') then - if (num2d == 1) then - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name), xtype=ncd_double, & - dim1name=dim1name, & - long_name=trim(long_name), units=trim(units)) - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name_acc), xtype=ncd_int, & - dim1name=dim1name, & - long_name=trim(long_name_acc), units=trim(units_acc)) + ! Create the restart history filename and open it + write(hnum,'(i1.1)') t-1 + if (f == instantaneous_file_index) then + file_index = 'i' ! instantaneous file_index + else if (f == accumulated_file_index) then + file_index = 'a' ! accumulated file_index + else + write(iulog,*) trim(subname),' ERROR: f index =', f, ' but model expected f = ', instantaneous_file_index, ' (instantaneous file index) or ', accumulated_file_index, ' (accumulated file index)' + write(iulog,*) errMsg(sourcefile, __LINE__) + call endrun(msg="ERROR: file index not in range") + end if + locfnhr(t,f) = "./" // trim(caseid) //"."// trim(compname) // trim(inst_suffix) & + // ".rh" // hnum // file_index //"."// trim(rdate) //".nc" + + call htape_create( t, f, histrest=.true. ) + + ! Add read/write accumultators and counters if needed + not_endhist: if (.not. tape(t)%is_endhist) then + fld_loop1: do fld = 1, tape(t)%nflds(f) + name = tape(t)%hlist(fld,f)%field%name + long_name = tape(t)%hlist(fld,f)%field%long_name + units = tape(t)%hlist(fld,f)%field%units + name_acc = trim(name) // "_acc" + units_acc = "unitless positive integer" + long_name_acc = trim(long_name) // " accumulator number of samples" + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + type2d = tape(t)%hlist(fld,f)%field%type2d + num2d = tape(t)%hlist(fld,f)%field%num2d + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf + + if (type1d_out == grlnd) then + if (ldomain%isgrid2d) then + dim1name = 'lon' ; dim2name = 'lat' + else + dim1name = trim(grlnd); dim2name = 'undefined' + end if else - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name), xtype=ncd_double, & - dim1name=dim1name, dim2name=type2d, & - long_name=trim(long_name), units=trim(units)) - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name_acc), xtype=ncd_int, & - dim1name=dim1name, dim2name=type2d, & - long_name=trim(long_name_acc), units=trim(units_acc)) - end if - else - if (num2d == 1) then - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name), xtype=ncd_double, & - dim1name=dim1name, dim2name=dim2name, & - long_name=trim(long_name), units=trim(units)) - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name_acc), xtype=ncd_int, & - dim1name=dim1name, dim2name=dim2name, & - long_name=trim(long_name_acc), units=trim(units_acc)) + dim1name = type1d_out ; dim2name = 'undefined' + endif + + if (dim2name == 'undefined') then + if (num2d == 1) then + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name), xtype=ncd_double, & + dim1name=dim1name, & + long_name=trim(long_name), units=trim(units)) + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name_acc), xtype=ncd_int, & + dim1name=dim1name, & + long_name=trim(long_name_acc), units=trim(units_acc)) + else + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name), xtype=ncd_double, & + dim1name=dim1name, dim2name=type2d, & + long_name=trim(long_name), units=trim(units)) + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name_acc), xtype=ncd_int, & + dim1name=dim1name, dim2name=type2d, & + long_name=trim(long_name_acc), units=trim(units_acc)) + end if else - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name), xtype=ncd_double, & - dim1name=dim1name, dim2name=dim2name, dim3name=type2d, & - long_name=trim(long_name), units=trim(units)) - call ncd_defvar(ncid=ncid_hist(t), varname=trim(name_acc), xtype=ncd_int, & - dim1name=dim1name, dim2name=dim2name, dim3name=type2d, & - long_name=trim(long_name_acc), units=trim(units_acc)) - end if - endif - end do - endif - - ! - ! Add namelist information to each restart history tape - ! - call ncd_defdim( ncid_hist(t), 'fname_lenp2' , max_namlen+2, dimid) - call ncd_defdim( ncid_hist(t), 'fname_len' , max_namlen , dimid) - call ncd_defdim( ncid_hist(t), 'avgflag_len' , avgflag_strlen, dimid) - call ncd_defdim( ncid_hist(t), 'scalar' , 1 , dimid) - call ncd_defdim( ncid_hist(t), 'max_chars' , max_chars , dimid) - call ncd_defdim( ncid_hist(t), 'max_nflds' , max_nflds , dimid) - call ncd_defdim( ncid_hist(t), 'max_flds' , max_flds , dimid) - - call ncd_defvar(ncid=ncid_hist(t), varname='nhtfrq', xtype=ncd_int, & - long_name="Frequency of history writes", & - comment="Namelist item", & - units="absolute value of negative is in hours, 0=monthly, positive is time-steps", & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='mfilt', xtype=ncd_int, & - long_name="Number of history time samples on a file", units="unitless", & - comment="Namelist item", & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='ncprec', xtype=ncd_int, & - long_name="Flag for data precision", flag_values=(/1,2/), & - comment="Namelist item", & - nvalid_range=(/1,2/), & - flag_meanings=(/"single-precision", "double-precision"/), & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='dov2xy', xtype=ncd_log, & - long_name="Output on 2D grid format (TRUE) or vector format (FALSE)", & - comment="Namelist item", & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='fincl', xtype=ncd_char, & - comment="Namelist item", & - long_name="Fieldnames to include", & - dim1name='fname_lenp2', dim2name='max_flds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='fexcl', xtype=ncd_char, & - comment="Namelist item", & - long_name="Fieldnames to exclude", & - dim1name='fname_lenp2', dim2name='max_flds' ) - - call ncd_defvar(ncid=ncid_hist(t), varname='nflds', xtype=ncd_int, & - long_name="Number of fields on file", units="unitless", & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='ntimes', xtype=ncd_int, & - long_name="Number of time steps on file", units="time-step", & - dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='is_endhist', xtype=ncd_log, & - long_name="End of history file", dim1name='scalar') - call ncd_defvar(ncid=ncid_hist(t), varname='begtime', xtype=ncd_double, & - long_name="Beginning time", units="time units", & - dim1name='scalar') - - call ncd_defvar(ncid=ncid_hist(t), varname='num2d', xtype=ncd_int, & - long_name="Size of second dimension", units="unitless", & - dim1name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='hpindex', xtype=ncd_int, & - long_name="History pointer index", units="unitless", & - dim1name='max_nflds' ) - - call ncd_defvar(ncid=ncid_hist(t), varname='avgflag', xtype=ncd_char, & - long_name="Averaging flag", & - units="A=Average, X=Maximum, M=Minimum, I=Instantaneous, SUM=Sum", & - dim1name='avgflag_len', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='name', xtype=ncd_char, & - long_name="Fieldnames", & - dim1name='fname_len', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='long_name', xtype=ncd_char, & - long_name="Long descriptive names for fields", & - dim1name='max_chars', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='units', xtype=ncd_char, & - long_name="Units for each history field output", & - dim1name='max_chars', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='type1d', xtype=ncd_char, & - long_name="1st dimension type", & - dim1name='string_length', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='type1d_out', xtype=ncd_char, & - long_name="1st output dimension type", & - dim1name='string_length', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='type2d', xtype=ncd_char, & - long_name="2nd dimension type", & - dim1name='string_length', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='p2c_scale_type', xtype=ncd_char, & - long_name="PFT to column scale type", & - dim1name='scale_type_string_length', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='c2l_scale_type', xtype=ncd_char, & - long_name="column to landunit scale type", & - dim1name='scale_type_string_length', dim2name='max_nflds' ) - call ncd_defvar(ncid=ncid_hist(t), varname='l2g_scale_type', xtype=ncd_char, & - long_name="landunit to gridpoint scale type", & - dim1name='scale_type_string_length', dim2name='max_nflds' ) - - call ncd_enddef(ncid_hist(t)) - - end do ! end of ntapes loop + if (num2d == 1) then + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name), xtype=ncd_double, & + dim1name=dim1name, dim2name=dim2name, & + long_name=trim(long_name), units=trim(units)) + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name_acc), xtype=ncd_int, & + dim1name=dim1name, dim2name=dim2name, & + long_name=trim(long_name_acc), units=trim(units_acc)) + else + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name), xtype=ncd_double, & + dim1name=dim1name, dim2name=dim2name, dim3name=type2d, & + long_name=trim(long_name), units=trim(units)) + call ncd_defvar(ncid=ncid_hist(t,f), varname=trim(name_acc), xtype=ncd_int, & + dim1name=dim1name, dim2name=dim2name, dim3name=type2d, & + long_name=trim(long_name_acc), units=trim(units_acc)) + end if + endif + end do fld_loop1 + end if not_endhist + + ! + ! Add namelist information to each restart history tape + ! + call ncd_defdim( ncid_hist(t,f), 'fname_lenp2' , max_namlen+2, dimid) + call ncd_defdim( ncid_hist(t,f), 'fname_len' , max_namlen , dimid) + call ncd_defdim( ncid_hist(t,f), 'avgflag_len' , avgflag_strlen, dimid) + call ncd_defdim( ncid_hist(t,f), 'scalar' , 1 , dimid) + call ncd_defdim( ncid_hist(t,f), 'max_chars' , max_chars , dimid) + call ncd_defdim( ncid_hist(t,f), 'max_nflds' , max_nflds , dimid) + call ncd_defdim( ncid_hist(t,f), 'max_flds' , max_flds , dimid) + + call ncd_defvar(ncid=ncid_hist(t,f), varname='nhtfrq', xtype=ncd_int, & + long_name="Frequency of history writes", & + comment="Namelist item", & + units="absolute value of negative is in hours, 0=monthly, positive is time-steps", & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='mfilt', xtype=ncd_int, & + long_name="Number of history time samples on a file", units="unitless", & + comment="Namelist item", & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='ncprec', xtype=ncd_int, & + long_name="Flag for data precision", flag_values=(/1,2/), & + comment="Namelist item", & + nvalid_range=(/1,2/), & + flag_meanings=(/"single-precision", "double-precision"/), & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='dov2xy', xtype=ncd_log, & + long_name="Output on 2D grid format (TRUE) or vector format (FALSE)", & + comment="Namelist item", & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='fincl', xtype=ncd_char, & + comment="Namelist item", & + long_name="Fieldnames to include", & + dim1name='fname_lenp2', dim2name='max_flds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='fexcl', xtype=ncd_char, & + comment="Namelist item", & + long_name="Fieldnames to exclude", & + dim1name='fname_lenp2', dim2name='max_flds' ) + + call ncd_defvar(ncid=ncid_hist(t,f), varname='nflds', xtype=ncd_int, & + long_name="Number of fields on file", units="unitless", & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='ntimes', xtype=ncd_int, & + long_name="Number of time steps on file", units="time-step", & + dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='is_endhist', xtype=ncd_log, & + long_name="End of history file", dim1name='scalar') + call ncd_defvar(ncid=ncid_hist(t,f), varname='begtime', xtype=ncd_double, & + long_name="Beginning time", units="time units", & + dim1name='scalar') + + call ncd_defvar(ncid=ncid_hist(t,f), varname='num2d', xtype=ncd_int, & + long_name="Size of second dimension", units="unitless", & + dim1name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='hpindex', xtype=ncd_int, & + long_name="History pointer index", units="unitless", & + dim1name='max_nflds' ) + + call ncd_defvar(ncid=ncid_hist(t,f), varname='avgflag', xtype=ncd_char, & + long_name="Averaging flag", & + units="A=Average, X=Maximum, M=Minimum, I=Instantaneous, SUM=Sum", & + dim1name='avgflag_len', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='name', xtype=ncd_char, & + long_name="Fieldnames", & + dim1name='fname_len', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='long_name', xtype=ncd_char, & + long_name="Long descriptive names for fields", & + dim1name='max_chars', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='units', xtype=ncd_char, & + long_name="Units for each history field output", & + dim1name='max_chars', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='type1d', xtype=ncd_char, & + long_name="1st dimension type", & + dim1name='string_length', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='type1d_out', xtype=ncd_char, & + long_name="1st output dimension type", & + dim1name='string_length', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='type2d', xtype=ncd_char, & + long_name="2nd dimension type", & + dim1name='string_length', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='p2c_scale_type', xtype=ncd_char, & + long_name="PFT to column scale type", & + dim1name='scale_type_string_length', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='c2l_scale_type', xtype=ncd_char, & + long_name="column to landunit scale type", & + dim1name='scale_type_string_length', dim2name='max_nflds' ) + call ncd_defvar(ncid=ncid_hist(t,f), varname='l2g_scale_type', xtype=ncd_char, & + long_name="landunit to gridpoint scale type", & + dim1name='scale_type_string_length', dim2name='max_nflds' ) + + call ncd_enddef(ncid_hist(t,f)) + + end do file_loop1 + end do tape_loop1 RETURN @@ -4648,18 +4759,21 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) !================================================ ! Add history filenames to master restart file - do t = 1,ntapes - call ncd_io('history_tape_in_use', history_tape_in_use(t), 'write', ncid, nt=t) - if (history_tape_in_use(t)) then - my_locfnh = locfnh(t) - my_locfnhr = locfnhr(t) - else - my_locfnh = 'non_existent_file' - my_locfnhr = 'non_existent_file' - end if - call ncd_io('locfnh', my_locfnh, 'write', ncid, nt=t) - call ncd_io('locfnhr', my_locfnhr, 'write', ncid, nt=t) - end do + counter = 0 + tape_loop2: do t = 1, ntapes + file_loop2: do f = 1, max_split_files + counter = counter + 1 + if (.not. history_tape_in_use(t,f)) then + locfnh(t,f) = 'non_existent_file' + locfnhr(t,f) = 'non_existent_file' + end if + my_locfnh = locfnh(t,f) + my_locfnhr = locfnhr(t,f) + call ncd_io('locfnh', my_locfnh, 'write', ncid, nt=counter) + call ncd_io('locfnhr', my_locfnhr, 'write', ncid, nt=counter) + call ncd_io('history_tape_in_use', history_tape_in_use(t,f), 'write', ncid, nt=counter) + end do file_loop2 + end do tape_loop2 fincl(:,1) = hist_fincl1(:) fincl(:,2) = hist_fincl2(:) @@ -4692,66 +4806,68 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! allocate(itemp(max_nflds)) - do t = 1,ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if + tape_loop3: do t = 1, ntapes + file_loop3: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - call ncd_io(varname='fincl', data=fincl(:,t), ncid=ncid_hist(t), flag='write') + call ncd_io(varname='fincl', data=fincl(:,t), ncid=ncid_hist(t,f), flag='write') - call ncd_io(varname='fexcl', data=fexcl(:,t), ncid=ncid_hist(t), flag='write') + call ncd_io(varname='fexcl', data=fexcl(:,t), ncid=ncid_hist(t,f), flag='write') - call ncd_io(varname='is_endhist', data=tape(t)%is_endhist, ncid=ncid_hist(t), flag='write') + call ncd_io(varname='is_endhist', data=tape(t)%is_endhist, ncid=ncid_hist(t,f), flag='write') - call ncd_io(varname='dov2xy', data=tape(t)%dov2xy, ncid=ncid_hist(t), flag='write') + call ncd_io(varname='dov2xy', data=tape(t)%dov2xy, ncid=ncid_hist(t,f), flag='write') - itemp(:) = 0 - do f=1,tape(t)%nflds - itemp(f) = tape(t)%hlist(f)%field%num2d - end do - call ncd_io(varname='num2d', data=itemp(:), ncid=ncid_hist(t), flag='write') + itemp(:) = 0 + do fld = 1, tape(t)%nflds(f) + itemp(fld) = tape(t)%hlist(fld,f)%field%num2d + end do + call ncd_io(varname='num2d', data=itemp(:), ncid=ncid_hist(t,f), flag='write') - itemp(:) = 0 - do f=1,tape(t)%nflds - itemp(f) = tape(t)%hlist(f)%field%hpindex - end do - call ncd_io(varname='hpindex', data=itemp(:), ncid=ncid_hist(t), flag='write') - - call ncd_io('nflds', tape(t)%nflds, 'write', ncid_hist(t) ) - call ncd_io('ntimes', tape(t)%ntimes, 'write', ncid_hist(t) ) - call ncd_io('nhtfrq', tape(t)%nhtfrq, 'write', ncid_hist(t) ) - call ncd_io('mfilt', tape(t)%mfilt, 'write', ncid_hist(t) ) - call ncd_io('ncprec', tape(t)%ncprec, 'write', ncid_hist(t) ) - call ncd_io('begtime', tape(t)%begtime, 'write', ncid_hist(t) ) - allocate(tmpstr(tape(t)%nflds,3 ),tname(tape(t)%nflds), & - tavgflag(tape(t)%nflds),tunits(tape(t)%nflds),tlongname(tape(t)%nflds), & - p2c_scale_type(tape(t)%nflds), c2l_scale_type(tape(t)%nflds), & - l2g_scale_type(tape(t)%nflds)) - do f=1,tape(t)%nflds - tname(f) = tape(t)%hlist(f)%field%name - tunits(f) = tape(t)%hlist(f)%field%units - tlongname(f) = tape(t)%hlist(f)%field%long_name - tmpstr(f,1) = tape(t)%hlist(f)%field%type1d - tmpstr(f,2) = tape(t)%hlist(f)%field%type1d_out - tmpstr(f,3) = tape(t)%hlist(f)%field%type2d - tavgflag(f) = tape(t)%hlist(f)%avgflag - p2c_scale_type(f) = tape(t)%hlist(f)%field%p2c_scale_type - c2l_scale_type(f) = tape(t)%hlist(f)%field%c2l_scale_type - l2g_scale_type(f) = tape(t)%hlist(f)%field%l2g_scale_type - end do - call ncd_io( 'name', tname, 'write',ncid_hist(t)) - call ncd_io('long_name', tlongname, 'write', ncid_hist(t)) - call ncd_io('units', tunits, 'write',ncid_hist(t)) - call ncd_io('type1d', tmpstr(:,1), 'write', ncid_hist(t)) - call ncd_io('type1d_out', tmpstr(:,2), 'write', ncid_hist(t)) - call ncd_io('type2d', tmpstr(:,3), 'write', ncid_hist(t)) - call ncd_io('avgflag',tavgflag , 'write', ncid_hist(t)) - call ncd_io('p2c_scale_type', p2c_scale_type, 'write', ncid_hist(t)) - call ncd_io('c2l_scale_type', c2l_scale_type, 'write', ncid_hist(t)) - call ncd_io('l2g_scale_type', l2g_scale_type, 'write', ncid_hist(t)) - deallocate(tname,tlongname,tunits,tmpstr,tavgflag) - deallocate(p2c_scale_type, c2l_scale_type, l2g_scale_type) - enddo + itemp(:) = 0 + do fld = 1, tape(t)%nflds(f) + itemp(fld) = tape(t)%hlist(fld,f)%field%hpindex + end do + call ncd_io(varname='hpindex', data=itemp(:), ncid=ncid_hist(t,f), flag='write') + + call ncd_io('nflds', tape(t)%nflds(f), 'write', ncid_hist(t,f) ) + call ncd_io('ntimes', tape(t)%ntimes(f), 'write', ncid_hist(t,f) ) + call ncd_io('nhtfrq', tape(t)%nhtfrq, 'write', ncid_hist(t,f) ) + call ncd_io('mfilt', tape(t)%mfilt, 'write', ncid_hist(t,f) ) + call ncd_io('ncprec', tape(t)%ncprec, 'write', ncid_hist(t,f) ) + call ncd_io('begtime', tape(t)%begtime, 'write', ncid_hist(t,f) ) + allocate(tmpstr(tape(t)%nflds(f), 3), tname(tape(t)%nflds(f)), & + tavgflag(tape(t)%nflds(f)), tunits(tape(t)%nflds(f)), tlongname(tape(t)%nflds(f)), & + p2c_scale_type(tape(t)%nflds(f)), c2l_scale_type(tape(t)%nflds(f)), & + l2g_scale_type(tape(t)%nflds(f))) + do fld = 1, tape(t)%nflds(f) + tname(fld) = tape(t)%hlist(fld,f)%field%name + tunits(fld) = tape(t)%hlist(fld,f)%field%units + tlongname(fld) = tape(t)%hlist(fld,f)%field%long_name + tmpstr(fld,1) = tape(t)%hlist(fld,f)%field%type1d + tmpstr(fld,2) = tape(t)%hlist(fld,f)%field%type1d_out + tmpstr(fld,3) = tape(t)%hlist(fld,f)%field%type2d + tavgflag(fld) = tape(t)%hlist(fld,f)%avgflag + p2c_scale_type(fld) = tape(t)%hlist(fld,f)%field%p2c_scale_type + c2l_scale_type(fld) = tape(t)%hlist(fld,f)%field%c2l_scale_type + l2g_scale_type(fld) = tape(t)%hlist(fld,f)%field%l2g_scale_type + end do + call ncd_io( 'name', tname, 'write',ncid_hist(t,f)) + call ncd_io('long_name', tlongname, 'write', ncid_hist(t,f)) + call ncd_io('units', tunits, 'write',ncid_hist(t,f)) + call ncd_io('type1d', tmpstr(:,1), 'write', ncid_hist(t,f)) + call ncd_io('type1d_out', tmpstr(:,2), 'write', ncid_hist(t,f)) + call ncd_io('type2d', tmpstr(:,3), 'write', ncid_hist(t,f)) + call ncd_io('avgflag',tavgflag , 'write', ncid_hist(t,f)) + call ncd_io('p2c_scale_type', p2c_scale_type, 'write', ncid_hist(t,f)) + call ncd_io('c2l_scale_type', c2l_scale_type, 'write', ncid_hist(t,f)) + call ncd_io('l2g_scale_type', l2g_scale_type, 'write', ncid_hist(t,f)) + deallocate(tname,tlongname,tunits,tmpstr,tavgflag) + deallocate(p2c_scale_type, c2l_scale_type, l2g_scale_type) + end do file_loop3 + end do tape_loop3 deallocate(itemp) ! @@ -4762,7 +4878,7 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) !================================================ call ncd_inqdlen(ncid,dimid,ntapes_onfile, name='ntapes') - if (is_restart()) then + if_restart1: if (is_restart()) then if (ntapes_onfile /= ntapes) then write(iulog,*) 'ntapes = ', ntapes, ' ntapes_onfile = ', ntapes_onfile call endrun(msg=' ERROR: number of ntapes differs from restart file. '// & @@ -4770,8 +4886,8 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) additional_msg=errMsg(sourcefile, __LINE__)) end if - if (ntapes > 0) then - allocate(history_tape_in_use_onfile(ntapes)) + ntapes_gt_0: if (ntapes > 0) then + allocate(history_tape_in_use_onfile(max_split_files*ntapes)) call ncd_io('history_tape_in_use', history_tape_in_use_onfile, 'read', ncid, & readvar=readvar) if (.not. readvar) then @@ -4780,205 +4896,216 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! true for all tapes <= ntapes. history_tape_in_use_onfile(:) = .true. end if - do t = 1, ntapes - if (history_tape_in_use_onfile(t) .neqv. history_tape_in_use(t)) then - write(iulog,*) subname//' ERROR: history_tape_in_use on restart file' - write(iulog,*) 'disagrees with current run: For tape ', t - write(iulog,*) 'On restart file: ', history_tape_in_use_onfile(t) - write(iulog,*) 'In current run : ', history_tape_in_use(t) - write(iulog,*) 'This suggests that this tape was empty in one case,' - write(iulog,*) 'but non-empty in the other. (history_tape_in_use .false.' - write(iulog,*) 'means that history tape is empty.)' - call endrun(msg=' ERROR: history_tape_in_use differs from restart file. '// & - 'You can NOT change history options on restart.', & - additional_msg=errMsg(sourcefile, __LINE__)) - end if - end do - - call ncd_io('locfnh', locfnh(1:ntapes), 'read', ncid ) - call ncd_io('locfnhr', locrest(1:ntapes), 'read', ncid ) - do t = 1,ntapes - call strip_null(locrest(t)) - call strip_null(locfnh(t)) - end do - end if - end if + counter = 0 + tape_loop4: do t = 1, ntapes + file_loop4: do f = 1, max_split_files + counter = counter + 1 + if (history_tape_in_use_onfile(counter) .neqv. history_tape_in_use(t,f)) then + write(iulog,*) subname//' ERROR: history_tape_in_use on restart file' + write(iulog,*) 'disagrees with current run: For tape and file ', t, f + write(iulog,*) 'On restart file: ', history_tape_in_use_onfile(counter) + write(iulog,*) 'In current run : ', history_tape_in_use(t,f) + write(iulog,*) 'This suggests that this tape was empty in one case,' + write(iulog,*) 'but non-empty in the other. (history_tape_in_use .false.' + write(iulog,*) 'means that history tape is empty.)' + call endrun(msg=' ERROR: history_tape_in_use differs from restart file. '// & + 'You can NOT change history options on restart.', & + additional_msg=errMsg(sourcefile, __LINE__)) + end if + end do file_loop4 + end do tape_loop4 + call ncd_io('locfnh', locfnh_onfile, 'read', ncid ) + call ncd_io('locfnhr', locrest_onfile, 'read', ncid ) + tape_loop5: do t = 1, ntapes + file_loop5: do f = 1, max_split_files + call strip_null(locrest_onfile(f,t)) + call strip_null(locfnh_onfile(f,t)) + ! These character variables get read with their dimensions backwards + ! so flip them before using them + locrest(t,f) = locrest_onfile(f,t) + locfnh(t,f) = locfnh_onfile(f,t) + end do file_loop5 + end do tape_loop5 + end if ntapes_gt_0 + end if if_restart1 ! Determine necessary indices - the following is needed if model decomposition is different on restart start(1)=1 - if ( is_restart() )then - do t = 1,ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if + if_restart2: if ( is_restart() ) then + tape_loop6: do t = 1, ntapes + file_loop6: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - call getfil( locrest(t), locfnhr(t), 0 ) - call ncd_pio_openfile (ncid_hist(t), trim(locfnhr(t)), ncd_nowrite) + call getfil( locrest(t,f), locfnhr(t,f), 0 ) + call ncd_pio_openfile (ncid_hist(t,f), trim(locfnhr(t,f)), ncd_nowrite) - if ( t == 1 )then + if ( t == 1 .and. f == 1 )then - call ncd_inqdlen(ncid_hist(1),dimid,max_nflds,name='max_nflds') + call ncd_inqdlen(ncid_hist(1,f),dimid,max_nflds,name='max_nflds') - allocate(itemp(max_nflds)) - end if + allocate(itemp(max_nflds)) + end if - call ncd_inqvid(ncid_hist(t), 'name', varid, name_desc) - call ncd_inqvid(ncid_hist(t), 'long_name', varid, longname_desc) - call ncd_inqvid(ncid_hist(t), 'units', varid, units_desc) - call ncd_inqvid(ncid_hist(t), 'type1d', varid, type1d_desc) - call ncd_inqvid(ncid_hist(t), 'type1d_out', varid, type1d_out_desc) - call ncd_inqvid(ncid_hist(t), 'type2d', varid, type2d_desc) - call ncd_inqvid(ncid_hist(t), 'avgflag', varid, avgflag_desc) - call ncd_inqvid(ncid_hist(t), 'p2c_scale_type', varid, p2c_scale_type_desc) - call ncd_inqvid(ncid_hist(t), 'c2l_scale_type', varid, c2l_scale_type_desc) - call ncd_inqvid(ncid_hist(t), 'l2g_scale_type', varid, l2g_scale_type_desc) - - call ncd_io(varname='fincl', data=fincl(:,t), ncid=ncid_hist(t), flag='read') - - call ncd_io(varname='fexcl', data=fexcl(:,t), ncid=ncid_hist(t), flag='read') - - call ncd_io('nflds', nflds_onfile, 'read', ncid_hist(t) ) - if ( nflds_onfile /= tape(t)%nflds )then - write(iulog,*) 'nflds = ', tape(t)%nflds, ' nflds_onfile = ', nflds_onfile - call endrun(msg=' ERROR: number of fields different than on restart file!,'// & - ' you can NOT change history options on restart!' //& + call ncd_inqvid(ncid_hist(t,f), 'name', varid, name_desc) + call ncd_inqvid(ncid_hist(t,f), 'long_name', varid, longname_desc) + call ncd_inqvid(ncid_hist(t,f), 'units', varid, units_desc) + call ncd_inqvid(ncid_hist(t,f), 'type1d', varid, type1d_desc) + call ncd_inqvid(ncid_hist(t,f), 'type1d_out', varid, type1d_out_desc) + call ncd_inqvid(ncid_hist(t,f), 'type2d', varid, type2d_desc) + call ncd_inqvid(ncid_hist(t,f), 'avgflag', varid, avgflag_desc) + call ncd_inqvid(ncid_hist(t,f), 'p2c_scale_type', varid, p2c_scale_type_desc) + call ncd_inqvid(ncid_hist(t,f), 'c2l_scale_type', varid, c2l_scale_type_desc) + call ncd_inqvid(ncid_hist(t,f), 'l2g_scale_type', varid, l2g_scale_type_desc) + + call ncd_io(varname='fincl', data=fincl(:,t), ncid=ncid_hist(t,f), flag='read') + + call ncd_io(varname='fexcl', data=fexcl(:,t), ncid=ncid_hist(t,f), flag='read') + + call ncd_io('nflds', nflds_onfile, 'read', ncid_hist(t,f) ) + if ( nflds_onfile /= tape(t)%nflds(f) ) then + write(iulog,*) 'nflds = ', tape(t)%nflds(f), ' nflds_onfile = ', nflds_onfile + call endrun(msg=' ERROR: number of fields different than on restart file!,'// & + ' you can NOT change history options on restart!' //& errMsg(sourcefile, __LINE__)) - end if - call ncd_io('ntimes', tape(t)%ntimes, 'read', ncid_hist(t) ) - call ncd_io('nhtfrq', tape(t)%nhtfrq, 'read', ncid_hist(t) ) - call ncd_io('mfilt', tape(t)%mfilt, 'read', ncid_hist(t) ) - call ncd_io('ncprec', tape(t)%ncprec, 'read', ncid_hist(t) ) - call ncd_io('begtime', tape(t)%begtime, 'read', ncid_hist(t) ) - - call ncd_io(varname='is_endhist', data=tape(t)%is_endhist, ncid=ncid_hist(t), flag='read') - call ncd_io(varname='dov2xy', data=tape(t)%dov2xy, ncid=ncid_hist(t), flag='read') - call ncd_io(varname='num2d', data=itemp(:), ncid=ncid_hist(t), flag='read') - do f=1,tape(t)%nflds - tape(t)%hlist(f)%field%num2d = itemp(f) - end do + end if + call ncd_io('ntimes', tape(t)%ntimes(f), 'read', ncid_hist(t,f) ) + call ncd_io('nhtfrq', tape(t)%nhtfrq, 'read', ncid_hist(t,f) ) + call ncd_io('mfilt', tape(t)%mfilt, 'read', ncid_hist(t,f) ) + call ncd_io('ncprec', tape(t)%ncprec, 'read', ncid_hist(t,f) ) + call ncd_io('begtime', tape(t)%begtime, 'read', ncid_hist(t,f) ) + + call ncd_io(varname='is_endhist', data=tape(t)%is_endhist, ncid=ncid_hist(t,f), flag='read') + call ncd_io(varname='dov2xy', data=tape(t)%dov2xy, ncid=ncid_hist(t,f), flag='read') + call ncd_io(varname='num2d', data=itemp(:), ncid=ncid_hist(t,f), flag='read') + do fld = 1, tape(t)%nflds(f) + tape(t)%hlist(fld,f)%field%num2d = itemp(fld) + end do + + call ncd_io(varname='hpindex', data=itemp(:), ncid=ncid_hist(t,f), flag='read') + do fld = 1, tape(t)%nflds(f) + tape(t)%hlist(fld,f)%field%hpindex = itemp(fld) + end do + + fld_loop2: do fld = 1, tape(t)%nflds(f) + start(2) = fld + call ncd_io( name_desc, tape(t)%hlist(fld,f)%field%name, & + 'read', ncid_hist(t,f), start ) + call ncd_io( longname_desc, tape(t)%hlist(fld,f)%field%long_name, & + 'read', ncid_hist(t,f), start ) + call ncd_io( units_desc, tape(t)%hlist(fld,f)%field%units, & + 'read', ncid_hist(t,f), start ) + call ncd_io( type1d_desc, tape(t)%hlist(fld,f)%field%type1d, & + 'read', ncid_hist(t,f), start ) + call ncd_io( type1d_out_desc, tape(t)%hlist(fld,f)%field%type1d_out, & + 'read', ncid_hist(t,f), start ) + call ncd_io( type2d_desc, tape(t)%hlist(fld,f)%field%type2d, & + 'read', ncid_hist(t,f), start ) + call ncd_io( avgflag_desc, tape(t)%hlist(fld,f)%avgflag, & + 'read', ncid_hist(t,f), start ) + call ncd_io( p2c_scale_type_desc, tape(t)%hlist(fld,f)%field%p2c_scale_type, & + 'read', ncid_hist(t,f), start ) + call ncd_io( c2l_scale_type_desc, tape(t)%hlist(fld,f)%field%c2l_scale_type, & + 'read', ncid_hist(t,f), start ) + call ncd_io( l2g_scale_type_desc, tape(t)%hlist(fld,f)%field%l2g_scale_type, & + 'read', ncid_hist(t,f), start ) + call strip_null(tape(t)%hlist(fld,f)%field%name) + call strip_null(tape(t)%hlist(fld,f)%field%long_name) + call strip_null(tape(t)%hlist(fld,f)%field%units) + call strip_null(tape(t)%hlist(fld,f)%field%type1d) + call strip_null(tape(t)%hlist(fld,f)%field%type1d_out) + call strip_null(tape(t)%hlist(fld,f)%field%type2d) + call strip_null(tape(t)%hlist(fld,f)%field%p2c_scale_type) + call strip_null(tape(t)%hlist(fld,f)%field%c2l_scale_type) + call strip_null(tape(t)%hlist(fld,f)%field%l2g_scale_type) + call strip_null(tape(t)%hlist(fld,f)%avgflag) + + type1d_out = trim(tape(t)%hlist(fld,f)%field%type1d_out) + select case (trim(type1d_out)) + case (grlnd) + num1d_out = numg + beg1d_out = bounds%begg + end1d_out = bounds%endg + case (nameg) + num1d_out = numg + beg1d_out = bounds%begg + end1d_out = bounds%endg + case (namel) + num1d_out = numl + beg1d_out = bounds%begl + end1d_out = bounds%endl + case (namec) + num1d_out = numc + beg1d_out = bounds%begc + end1d_out = bounds%endc + case (namep) + num1d_out = nump + beg1d_out = bounds%begp + end1d_out = bounds%endp + case default + write(iulog,*) trim(subname),' ERROR: read unknown 1d output type=',trim(type1d_out) + call endrun(msg=errMsg(sourcefile, __LINE__)) + end select - call ncd_io(varname='hpindex', data=itemp(:), ncid=ncid_hist(t), flag='read') - do f=1,tape(t)%nflds - tape(t)%hlist(f)%field%hpindex = itemp(f) - end do + tape(t)%hlist(fld,f)%field%num1d_out = num1d_out + tape(t)%hlist(fld,f)%field%beg1d_out = beg1d_out + tape(t)%hlist(fld,f)%field%end1d_out = end1d_out - do f=1,tape(t)%nflds - start(2) = f - call ncd_io( name_desc, tape(t)%hlist(f)%field%name, & - 'read', ncid_hist(t), start ) - call ncd_io( longname_desc, tape(t)%hlist(f)%field%long_name, & - 'read', ncid_hist(t), start ) - call ncd_io( units_desc, tape(t)%hlist(f)%field%units, & - 'read', ncid_hist(t), start ) - call ncd_io( type1d_desc, tape(t)%hlist(f)%field%type1d, & - 'read', ncid_hist(t), start ) - call ncd_io( type1d_out_desc, tape(t)%hlist(f)%field%type1d_out, & - 'read', ncid_hist(t), start ) - call ncd_io( type2d_desc, tape(t)%hlist(f)%field%type2d, & - 'read', ncid_hist(t), start ) - call ncd_io( avgflag_desc, tape(t)%hlist(f)%avgflag, & - 'read', ncid_hist(t), start ) - call ncd_io( p2c_scale_type_desc, tape(t)%hlist(f)%field%p2c_scale_type, & - 'read', ncid_hist(t), start ) - call ncd_io( c2l_scale_type_desc, tape(t)%hlist(f)%field%c2l_scale_type, & - 'read', ncid_hist(t), start ) - call ncd_io( l2g_scale_type_desc, tape(t)%hlist(f)%field%l2g_scale_type, & - 'read', ncid_hist(t), start ) - call strip_null(tape(t)%hlist(f)%field%name) - call strip_null(tape(t)%hlist(f)%field%long_name) - call strip_null(tape(t)%hlist(f)%field%units) - call strip_null(tape(t)%hlist(f)%field%type1d) - call strip_null(tape(t)%hlist(f)%field%type1d_out) - call strip_null(tape(t)%hlist(f)%field%type2d) - call strip_null(tape(t)%hlist(f)%field%p2c_scale_type) - call strip_null(tape(t)%hlist(f)%field%c2l_scale_type) - call strip_null(tape(t)%hlist(f)%field%l2g_scale_type) - call strip_null(tape(t)%hlist(f)%avgflag) - - type1d_out = trim(tape(t)%hlist(f)%field%type1d_out) - select case (trim(type1d_out)) - case (grlnd) - num1d_out = numg - beg1d_out = bounds%begg - end1d_out = bounds%endg - case (nameg) - num1d_out = numg - beg1d_out = bounds%begg - end1d_out = bounds%endg - case (namel) - num1d_out = numl - beg1d_out = bounds%begl - end1d_out = bounds%endl - case (namec) - num1d_out = numc - beg1d_out = bounds%begc - end1d_out = bounds%endc - case (namep) - num1d_out = nump - beg1d_out = bounds%begp - end1d_out = bounds%endp - case default - write(iulog,*) trim(subname),' ERROR: read unknown 1d output type=',trim(type1d_out) - call endrun(msg=errMsg(sourcefile, __LINE__)) - end select - - tape(t)%hlist(f)%field%num1d_out = num1d_out - tape(t)%hlist(f)%field%beg1d_out = beg1d_out - tape(t)%hlist(f)%field%end1d_out = end1d_out - - num2d = tape(t)%hlist(f)%field%num2d - allocate (tape(t)%hlist(f)%hbuf(beg1d_out:end1d_out,num2d), & - tape(t)%hlist(f)%nacs(beg1d_out:end1d_out,num2d), & - stat=status) - if (status /= 0) then - write(iulog,*) trim(subname),' ERROR: allocation error for hbuf,nacs at t,f=',t,f - call endrun(msg=errMsg(sourcefile, __LINE__)) - endif - tape(t)%hlist(f)%hbuf(:,:) = 0._r8 - tape(t)%hlist(f)%nacs(:,:) = 0 - - type1d = tape(t)%hlist(f)%field%type1d - select case (type1d) - case (grlnd) - num1d = numg - beg1d = bounds%begg - end1d = bounds%endg - case (nameg) - num1d = numg - beg1d = bounds%begg - end1d = bounds%endg - case (namel) - num1d = numl - beg1d = bounds%begl - end1d = bounds%endl - case (namec) - num1d = numc - beg1d = bounds%begc - end1d = bounds%endc - case (namep) - num1d = nump - beg1d = bounds%begp - end1d = bounds%endp - case default - write(iulog,*) trim(subname),' ERROR: read unknown 1d type=',type1d - call endrun(msg=errMsg(sourcefile, __LINE__)) - end select + num2d = tape(t)%hlist(fld,f)%field%num2d + allocate (tape(t)%hlist(fld,f)%hbuf(beg1d_out:end1d_out,num2d), & + tape(t)%hlist(fld,f)%nacs(beg1d_out:end1d_out,num2d), & + stat=status) + if (status /= 0) then + write(iulog,*) trim(subname),' ERROR: allocation error for hbuf,nacs at t,f,fld=',t,f,fld + call endrun(msg=errMsg(sourcefile, __LINE__)) + endif + tape(t)%hlist(fld,f)%hbuf(:,:) = 0._r8 + tape(t)%hlist(fld,f)%nacs(:,:) = 0 + + type1d = tape(t)%hlist(fld,f)%field%type1d + select case (type1d) + case (grlnd) + num1d = numg + beg1d = bounds%begg + end1d = bounds%endg + case (nameg) + num1d = numg + beg1d = bounds%begg + end1d = bounds%endg + case (namel) + num1d = numl + beg1d = bounds%begl + end1d = bounds%endl + case (namec) + num1d = numc + beg1d = bounds%begc + end1d = bounds%endc + case (namep) + num1d = nump + beg1d = bounds%begp + end1d = bounds%endp + case default + write(iulog,*) trim(subname),' ERROR: read unknown 1d type=',type1d + call endrun(msg=errMsg(sourcefile, __LINE__)) + end select - tape(t)%hlist(f)%field%num1d = num1d - tape(t)%hlist(f)%field%beg1d = beg1d - tape(t)%hlist(f)%field%end1d = end1d + tape(t)%hlist(fld,f)%field%num1d = num1d + tape(t)%hlist(fld,f)%field%beg1d = beg1d + tape(t)%hlist(fld,f)%field%end1d = end1d - end do ! end of flds loop + end do fld_loop2 - ! If history file is not full, open it + ! If history file is not full, open it - if (tape(t)%ntimes /= 0) then - call ncd_pio_openfile (nfid(t), trim(locfnh(t)), ncd_write) - end if + if (tape(t)%ntimes(f) /= 0) then + call ncd_pio_openfile (nfid(t,f), trim(locfnh(t,f)), ncd_write) + end if - end do ! end of tapes loop + end do file_loop6 + end do tape_loop6 hist_fincl1(:) = fincl(:,1) hist_fincl2(:) = fincl(:,2) @@ -5002,11 +5129,11 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) hist_fexcl9(:) = fexcl(:,9) hist_fexcl10(:) = fexcl(:,10) - end if + end if if_restart2 if ( allocated(itemp) ) deallocate(itemp) - end if + end if define_read_write !====================================================================== ! Read/write history file restart data. @@ -5015,114 +5142,118 @@ subroutine hist_restart_ncd (bounds, ncid, flag, rdate) ! A new history file is used on a branch run. !====================================================================== - if (flag == 'write') then - - do t = 1,ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if + read_write: if (flag == 'write') then - if (.not. tape(t)%is_endhist) then - - do f = 1,tape(t)%nflds - name = tape(t)%hlist(f)%field%name - name_acc = trim(name) // "_acc" - type1d_out = tape(t)%hlist(f)%field%type1d_out - type2d = tape(t)%hlist(f)%field%type2d - num2d = tape(t)%hlist(f)%field%num2d - beg1d_out = tape(t)%hlist(f)%field%beg1d_out - end1d_out = tape(t)%hlist(f)%field%end1d_out - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf - - if (num2d == 1) then - allocate(hbuf1d(beg1d_out:end1d_out), & - nacs1d(beg1d_out:end1d_out), stat=status) - if (status /= 0) then - write(iulog,*) trim(subname),' ERROR: allocation' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if + tape_loop7: do t = 1, ntapes + file_loop7: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - hbuf1d(beg1d_out:end1d_out) = hbuf(beg1d_out:end1d_out,1) - nacs1d(beg1d_out:end1d_out) = nacs(beg1d_out:end1d_out,1) + if (.not. tape(t)%is_endhist) then - call ncd_io(ncid=ncid_hist(t), flag='write', varname=trim(name), & - dim1name=type1d_out, data=hbuf1d) - call ncd_io(ncid=ncid_hist(t), flag='write', varname=trim(name_acc), & - dim1name=type1d_out, data=nacs1d) + fld_loop3: do fld = 1, tape(t)%nflds(f) + name = tape(t)%hlist(fld,f)%field%name + name_acc = trim(name) // "_acc" + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + type2d = tape(t)%hlist(fld,f)%field%type2d + num2d = tape(t)%hlist(fld,f)%field%num2d + beg1d_out = tape(t)%hlist(fld,f)%field%beg1d_out + end1d_out = tape(t)%hlist(fld,f)%field%end1d_out + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf - deallocate(hbuf1d) - deallocate(nacs1d) - else - call ncd_io(ncid=ncid_hist(t), flag='write', varname=trim(name), & - dim1name=type1d_out, data=hbuf) - call ncd_io(ncid=ncid_hist(t), flag='write', varname=trim(name_acc), & - dim1name=type1d_out, data=nacs) - end if + if (num2d == 1) then + allocate(hbuf1d(beg1d_out:end1d_out), & + nacs1d(beg1d_out:end1d_out), stat=status) + if (status /= 0) then + write(iulog,*) trim(subname),' ERROR: allocation' + call endrun(msg=errMsg(sourcefile, __LINE__)) + end if + + hbuf1d(beg1d_out:end1d_out) = hbuf(beg1d_out:end1d_out,1) + nacs1d(beg1d_out:end1d_out) = nacs(beg1d_out:end1d_out,1) + + call ncd_io(ncid=ncid_hist(t,f), flag='write', varname=trim(name), & + dim1name=type1d_out, data=hbuf1d) + call ncd_io(ncid=ncid_hist(t,f), flag='write', varname=trim(name_acc), & + dim1name=type1d_out, data=nacs1d) + + deallocate(hbuf1d) + deallocate(nacs1d) + else + call ncd_io(ncid=ncid_hist(t,f), flag='write', varname=trim(name), & + dim1name=type1d_out, data=hbuf) + call ncd_io(ncid=ncid_hist(t,f), flag='write', varname=trim(name_acc), & + dim1name=type1d_out, data=nacs) + end if - end do + end do fld_loop3 - end if ! end of is_endhist block + end if ! end of is_endhist block - call ncd_pio_closefile(ncid_hist(t)) + call ncd_pio_closefile(ncid_hist(t,f)) - end do ! end of ntapes loop + end do file_loop7 + end do tape_loop7 else if (flag == 'read') then ! Read history restart information if history files are not full - do t = 1,ntapes - if (.not. history_tape_in_use(t)) then - cycle - end if - - if (.not. tape(t)%is_endhist) then - - do f = 1,tape(t)%nflds - name = tape(t)%hlist(f)%field%name - name_acc = trim(name) // "_acc" - type1d_out = tape(t)%hlist(f)%field%type1d_out - type2d = tape(t)%hlist(f)%field%type2d - num2d = tape(t)%hlist(f)%field%num2d - beg1d_out = tape(t)%hlist(f)%field%beg1d_out - end1d_out = tape(t)%hlist(f)%field%end1d_out - nacs => tape(t)%hlist(f)%nacs - hbuf => tape(t)%hlist(f)%hbuf - - if (num2d == 1) then - allocate(hbuf1d(beg1d_out:end1d_out), & - nacs1d(beg1d_out:end1d_out), stat=status) - if (status /= 0) then - write(iulog,*) trim(subname),' ERROR: allocation' - call endrun(msg=errMsg(sourcefile, __LINE__)) - end if + tape_loop8: do t = 1, ntapes + file_loop8: do f = 1, max_split_files + if (.not. history_tape_in_use(t,f)) then + cycle + end if - call ncd_io(ncid=ncid_hist(t), flag='read', varname=trim(name), & - dim1name=type1d_out, data=hbuf1d) - call ncd_io(ncid=ncid_hist(t), flag='read', varname=trim(name_acc), & - dim1name=type1d_out, data=nacs1d) + if (.not. tape(t)%is_endhist) then - hbuf(beg1d_out:end1d_out,1) = hbuf1d(beg1d_out:end1d_out) - nacs(beg1d_out:end1d_out,1) = nacs1d(beg1d_out:end1d_out) + fld_loop4: do fld = 1, tape(t)%nflds(f) + name = tape(t)%hlist(fld,f)%field%name + name_acc = trim(name) // "_acc" + type1d_out = tape(t)%hlist(fld,f)%field%type1d_out + type2d = tape(t)%hlist(fld,f)%field%type2d + num2d = tape(t)%hlist(fld,f)%field%num2d + beg1d_out = tape(t)%hlist(fld,f)%field%beg1d_out + end1d_out = tape(t)%hlist(fld,f)%field%end1d_out + nacs => tape(t)%hlist(fld,f)%nacs + hbuf => tape(t)%hlist(fld,f)%hbuf - deallocate(hbuf1d) - deallocate(nacs1d) - else - call ncd_io(ncid=ncid_hist(t), flag='read', varname=trim(name), & - dim1name=type1d_out, data=hbuf) - call ncd_io(ncid=ncid_hist(t), flag='read', varname=trim(name_acc), & - dim1name=type1d_out, data=nacs) - end if - end do + if (num2d == 1) then + allocate(hbuf1d(beg1d_out:end1d_out), & + nacs1d(beg1d_out:end1d_out), stat=status) + if (status /= 0) then + write(iulog,*) trim(subname),' ERROR: allocation' + call endrun(msg=errMsg(sourcefile, __LINE__)) + end if + + call ncd_io(ncid=ncid_hist(t,f), flag='read', varname=trim(name), & + dim1name=type1d_out, data=hbuf1d, posNOTonfile=.true.) + call ncd_io(ncid=ncid_hist(t,f), flag='read', varname=trim(name_acc), & + dim1name=type1d_out, data=nacs1d, posNOTonfile=.true.) + + hbuf(beg1d_out:end1d_out,1) = hbuf1d(beg1d_out:end1d_out) + nacs(beg1d_out:end1d_out,1) = nacs1d(beg1d_out:end1d_out) + + deallocate(hbuf1d) + deallocate(nacs1d) + else + call ncd_io(ncid=ncid_hist(t,f), flag='read', varname=trim(name), & + dim1name=type1d_out, data=hbuf, posNOTonfile=.true.) + call ncd_io(ncid=ncid_hist(t,f), flag='read', varname=trim(name_acc), & + dim1name=type1d_out, data=nacs, posNOTonfile=.true.) + end if + end do fld_loop4 - end if + end if - call ncd_pio_closefile(ncid_hist(t)) + call ncd_pio_closefile(ncid_hist(t,f)) - end do + end do file_loop8 + end do tape_loop8 - end if + end if read_write end subroutine hist_restart_ncd @@ -5135,13 +5266,15 @@ integer function max_nFields() ! !ARGUMENTS: ! ! !LOCAL VARIABLES: - integer :: t ! index + integer :: t, f ! indices character(len=*),parameter :: subname = 'max_nFields' !----------------------------------------------------------------------- max_nFields = 0 do t = 1,ntapes - max_nFields = max(max_nFields, tape(t)%nflds) + do f = 1, max_split_files + max_nFields = max(max_nFields, tape(t)%nflds(f)) + end do end do return end function max_nFields @@ -5221,18 +5354,18 @@ subroutine list_index (list, name, index) ! !LOCAL VARIABLES: !EOP character(len=max_namlen) :: listname ! input name with ":" stripped off. - integer f ! field index + integer fld ! field index character(len=*),parameter :: subname = 'list_index' !----------------------------------------------------------------------- ! Only list items index = 0 - do f=1,max_flds - listname = getname (list(f)) + do fld = 1, max_flds + listname = getname (list(fld)) if (listname == ' ') exit if (listname == name) then - index = f + index = fld exit end if end do @@ -5240,7 +5373,7 @@ subroutine list_index (list, name, index) end subroutine list_index !----------------------------------------------------------------------- - character(len=max_length_filename) function set_hist_filename (hist_freq, hist_mfilt, hist_file) + character(len=max_length_filename) function set_hist_filename (hist_freq, hist_mfilt, hist_file, f_index) ! ! !DESCRIPTION: ! Determine history dataset filenames. @@ -5255,11 +5388,13 @@ character(len=max_length_filename) function set_hist_filename (hist_freq, hist_m integer, intent(in) :: hist_freq !history file frequency integer, intent(in) :: hist_mfilt !history file number of time-samples integer, intent(in) :: hist_file !history file index + integer, intent(in) :: f_index ! instantaneous or accumulated_file_index ! ! !LOCAL VARIABLES: !EOP character(len=max_chars) :: cdate !date char string character(len= 1) :: hist_index !p,1 or 2 (currently) + character(len = 1) :: file_index ! instantaneous or accumulated_file_index integer :: day !day (1 -> 31) integer :: mon !month (1 -> 12) integer :: yr !year (0 -> ...) @@ -5276,8 +5411,13 @@ character(len=max_length_filename) function set_hist_filename (hist_freq, hist_m write(cdate,'(i4.4,"-",i2.2,"-",i2.2,"-",i5.5)') yr,mon,day,sec endif write(hist_index,'(i1.1)') hist_file - 1 + if (f_index == instantaneous_file_index) then + file_index = 'i' ! instantaneous file_index + else if (f_index == accumulated_file_index) then + file_index = 'a' ! accumulated file_index + end if set_hist_filename = "./"//trim(caseid)//"."//trim(compname)//trim(inst_suffix)//& - ".h"//hist_index//"."//trim(cdate)//".nc" + ".h"//hist_index//file_index//"."//trim(cdate)//".nc" ! check to see if the concatenated filename exceeded the ! length. Simplest way to do this is ensure that the file diff --git a/src/main/lnd2atmType.F90 b/src/main/lnd2atmType.F90 index 6b414889e3..2b8c723202 100644 --- a/src/main/lnd2atmType.F90 +++ b/src/main/lnd2atmType.F90 @@ -12,7 +12,7 @@ module lnd2atmType use decompMod , only : bounds_type use clm_varpar , only : numrad, ndst, nlevgrnd !ndst = number of dust bins. use clm_varcon , only : spval - use clm_varctl , only : iulog, use_lch4 + use clm_varctl , only : iulog, use_lch4, use_cn, use_fates use shr_megan_mod , only : shr_megan_mechcomps_n use shr_fire_emis_mod,only : shr_fire_emis_mechcomps_n use shr_drydep_mod, only : n_drydep @@ -245,6 +245,7 @@ subroutine InitHistory(this, bounds) ! !LOCAL VARIABLES: integer :: begc, endc integer :: begg, endg + character(len=8) :: default = "inactive" !--------------------------------------------------------------------- begc = bounds%begc; endc = bounds%endc @@ -263,12 +264,15 @@ subroutine InitHistory(this, bounds) long_name='sensible heat flux generated from conversion of ice runoff to liquid', & ptr_col=this%eflx_sh_ice_to_liq_col) + if (use_cn .or. use_fates) then + default = 'active' + endif this%net_carbon_exchange_grc(begg:endg) = spval call hist_addfld1d(fname='FCO2', units='kgCO2/m2/s', & avgflag='A', & long_name='CO2 flux to atmosphere (+ to atm)', & ptr_lnd=this%net_carbon_exchange_grc, & - default='inactive') + default=trim(default)) ! No need to set this to spval (or 0) because it is a gridcell-level field, so should ! have valid values everywhere diff --git a/src/main/ncdio_pio.F90.in b/src/main/ncdio_pio.F90.in index 86f3e0cb43..991823fb67 100644 --- a/src/main/ncdio_pio.F90.in +++ b/src/main/ncdio_pio.F90.in @@ -541,14 +541,6 @@ contains character(len=32) :: subname = 'ncd_inqfdims' ! subroutine name !----------------------------------------------------------------------- - if (single_column) then - ni = 1 - nj = 1 - ns = 1 - isgrid2d = .true. - RETURN - end if - ni = 0 nj = 0 @@ -1353,7 +1345,7 @@ contains start(:) = 0 count(:) = 0 - if (flag == 'read') then + if (flag == 'read' .or. flag == 'read_noscm') then call ncd_inqvid(ncid, varname, varid, vardesc, readvar=varpresent) @@ -1382,7 +1374,7 @@ contains #else if (varpresent) then allocate(idata1d(size(data))) - if (single_column) then + if (single_column .and. flag == 'read') then call scam_field_offsets(ncid,'undefined', vardesc,& start, count, found=found, posNOTonfile=posNOTonfile) if ( found )then @@ -1478,7 +1470,7 @@ contains start(:) = 0 count(:) = 0 - if (flag == 'read') then + if (flag == 'read' .or. flag == 'read_noscm') then call ncd_inqvid(ncid, varname, varid, vardesc, readvar=varpresent) @@ -1499,7 +1491,7 @@ contains end if #else if (varpresent) then - if (single_column) then + if (single_column .and. flag == 'read') then call scam_field_offsets(ncid,'undefined', vardesc,& start, count, found=found, posNOTonfile=posNOTonfile) if ( found )then @@ -1648,7 +1640,7 @@ contains !----------------------------------------------------------------------- !TYPE int,double,logical - subroutine ncd_io_1d_{TYPE}(varname, data, dim1name, flag, ncid, nt, readvar, cnvrtnan2fill) + subroutine ncd_io_1d_{TYPE}(varname, data, dim1name, flag, ncid, nt, readvar, cnvrtnan2fill, posNOTonfile) ! ! !DESCRIPTION: ! netcdf I/O for 1d @@ -1662,6 +1654,7 @@ contains integer , optional, intent(in) :: nt ! time sample index logical , optional, intent(out) :: readvar ! true => variable is on initial dataset (read only) logical , optional, intent(in) :: cnvrtnan2fill ! true => convert any NaN's to _FillValue (spval) + logical , optional, intent(in) :: posNOTonfile ! Position is NOT on this file ! ! Local Variables character(len=8) :: subgrid_level_name ! nameg, namel, etc. @@ -1676,12 +1669,15 @@ contains integer :: start(3) ! netcdf start index integer :: count(3) ! netcdf count index integer :: status ! error code + logical :: found ! if true, found lat/lon dims on file logical :: varpresent ! if true, variable is on tape integer :: xtype ! type of var in file integer , pointer :: idata(:) ! Temporary integer data to send to file type(iodesc_plus_type) , pointer :: iodesc_plus type(var_desc_t) :: vardesc integer :: oldhandle ! previous value of pio_error_handle + integer :: ni,nj,ns ! lat/lon indicies + logical :: isgrid2d ! if true, latlon grid character(len=*),parameter :: subname='ncd_io_1d_{TYPE}' ! subroutine name !----------------------------------------------------------------------- @@ -1703,14 +1699,15 @@ contains end if #endif - if (flag == 'read') then + if (flag == 'read' .or. flag == 'read_noscm') then call ncd_inqvid(ncid, varname, varid, vardesc, readvar=varpresent) if (varpresent) then if (single_column) then start(:) = 1 ; count(:) = 1 - call scam_field_offsets(ncid,subgrid_level_name,vardesc,start,count) - if (trim(subgrid_level_name) == grlnd) then + call scam_field_offsets(ncid,subgrid_level_name,vardesc,start,count,found=found,posNOTonfile=posNOTonfile) + call ncd_inqfdims(ncid, isgrid2d, ni, nj, ns) + if (isgrid2d) then n=2 if (present(nt)) then start(3) = nt ; count(3) = 1 @@ -1823,7 +1820,7 @@ contains !TYPE int,double subroutine ncd_io_2d_{TYPE}(varname, data, dim1name, lowerb2, upperb2, & - flag, ncid, nt, readvar, switchdim, cnvrtnan2fill) + flag, ncid, nt, readvar, switchdim, cnvrtnan2fill, posNOTonfile ) ! ! !DESCRIPTION: ! Netcdf i/o of 2d @@ -1839,7 +1836,7 @@ contains logical, optional, intent(out) :: readvar ! true => variable is on initial dataset (read only) logical, optional, intent(in) :: switchdim ! true=> permute dim1 and dim2 for output logical, optional, intent(in) :: cnvrtnan2fill ! true => convert any NaN's to _FillValue (spval) - ! + logical, optional, intent(in) :: posNOTonfile ! Position is NOT on this file ! ! !LOCAL VARIABLES: #if ({ITYPE}==TYPEINT) integer , pointer :: temp(:,:) @@ -1862,7 +1859,10 @@ contains logical :: varpresent ! if true, variable is on tape integer :: lb1,lb2 integer :: ub1,ub2 + integer :: ni,nj,ns + logical :: isgrid2d ! if true, latlon grid integer :: xtype ! netcdf type of variable on file + logical :: found ! if true, found lat/lon dims on file type(iodesc_plus_type) , pointer :: iodesc_plus type(var_desc_t) :: vardesc @@ -1898,14 +1898,16 @@ contains allocate(temp(lb2:ub2,lb1:ub1)) end if - if (flag == 'read') then + if (flag == 'read' .or. flag == 'read_noscm') then call ncd_inqvid(ncid, varname, varid, vardesc, readvar=varpresent) if (varpresent) then - if (single_column) then + if (single_column .and. flag == 'read') then start(:) = 1 ; count(:) = 1 - call scam_field_offsets(ncid, subgrid_level_name, vardesc, start, count) - if (trim(subgrid_level_name) == grlnd) then + call scam_field_offsets(ncid, subgrid_level_name, vardesc, start, count,found=found,posNOTonfile=posNOTonfile) + call ncd_inqfdims(ncid, isgrid2d, ni, nj, ns) + call ncd_inqvdims(ncid, ndims, vardesc) + if (isgrid2d) then count(3) = size(data,dim=2) n=3 if (present(nt)) then @@ -1913,11 +1915,9 @@ contains n=4 end if else - count(2) = size(data,dim=2) - n=2 + n=ndims if (present(nt)) then - start(3) = nt ; count(3) = 1 - n=3 + start(n) = nt ; count(n) = 1 end if end if if (present(switchdim)) then @@ -2067,7 +2067,7 @@ contains !----------------------------------------------------------------------- !TYPE int,double - subroutine ncd_io_3d_{TYPE}(varname, data, dim1name, flag, ncid, nt, readvar) + subroutine ncd_io_3d_{TYPE}(varname, data, dim1name, flag, ncid, nt, readvar, posNOTonfile) ! ! !DESCRIPTION: ! Netcdf i/o of 3d @@ -2080,7 +2080,7 @@ contains character(len=*) , intent(in) :: dim1name ! dimension 1 name integer, optional, intent(in) :: nt ! time sample index logical, optional, intent(out) :: readvar ! true => variable is on initial dataset (read only) - ! + logical, optional, intent(in) :: posNOTonfile ! Position is NOT on this file ! ! !LOCAL VARIABLES: integer :: ndim1,ndim2 character(len=8) :: subgrid_level_name ! nameg, namel, etc. @@ -2099,6 +2099,9 @@ contains logical :: varpresent ! if true, variable is on tape type(iodesc_plus_type) , pointer :: iodesc_plus type(var_desc_t) :: vardesc + integer :: ni,nj,ns + logical :: isgrid2d ! if true, latlon grid + logical :: found ! if true, found lat/lon dims on file character(len=*),parameter :: subname='ncd_io_3d_{TYPE}' ! subroutine name !----------------------------------------------------------------------- @@ -2108,15 +2111,16 @@ contains write(iulog,*) trim(subname),' ',trim(flag),' ',trim(varname),' ',trim(subgrid_level_name) end if - if (flag == 'read') then + if (flag == 'read' .or. flag == 'read_noscm') then call ncd_inqvid(ncid, varname, varid, vardesc, readvar=varpresent) if (varpresent) then - if (single_column) then + if (single_column .and. flag == 'read') then start(:) = 1 count(:) = 1 - call scam_field_offsets(ncid, subgrid_level_name, vardesc, start, count) - if (trim(subgrid_level_name) == grlnd) then + call scam_field_offsets(ncid, subgrid_level_name, vardesc, start, count,found=found,posNOTonfile=posNOTonfile) + call ncd_inqfdims(ncid, isgrid2d, ni, nj, ns) + if (isgrid2d) then count(3) = size(data,dim=2); count(4) = size(data,dim=3) n=4 @@ -2435,7 +2439,10 @@ contains if ( trim(dimname)=='nj'.or. trim(dimname)=='lat'.or. trim(dimname)=='lsmlat') then start(i)=latidx count(i)=1 - else if ( trim(dimname)=='ni'.or. trim(dimname)=='lon'.or. trim(dimname)=='lsmlon') then + else if ( trim(dimname)=='ni'.or. trim(dimname)=='lon'.or. trim(dimname)=='lsmlon'.or. trim(dimname)=='gridcell') then + start(i)=lonidx + count(i)=1 + else if ( trim(dimname)=='gridcell') then start(i)=lonidx count(i)=1 else if ( trim(dimname)=='column') then diff --git a/src/main/organicFileMod.F90 b/src/main/organicFileMod.F90 index 3adbd5b6f1..5b61a8c0db 100644 --- a/src/main/organicFileMod.F90 +++ b/src/main/organicFileMod.F90 @@ -6,8 +6,8 @@ module organicFileMod ! !MODULE: organicFileMod ! ! !DESCRIPTION: -! Contains methods for reading in organic matter data file which has -! organic matter density for each grid point and soil level +! Contains methods for reading in organic matter data file which has +! organic matter density for each grid point and soil level ! ! !USES use abortutils , only : endrun @@ -30,7 +30,7 @@ module organicFileMod ! !EOP ! -!----------------------------------------------------------------------- +!----------------------------------------------------------------------- contains @@ -42,7 +42,7 @@ module organicFileMod ! !INTERFACE: subroutine organicrd(organic) ! -! !DESCRIPTION: +! !DESCRIPTION: ! Read the organic matter dataset. ! ! !USES: @@ -68,7 +68,7 @@ subroutine organicrd(organic) !EOP character(len=256) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf id - integer :: ni,nj,ns ! dimension sizes + integer :: ni,nj,ns ! dimension sizes logical :: isgrid2d ! true => file is 2d logical :: readvar ! true => variable is on dataset character(len=32) :: subname = 'organicrd' ! subroutine name @@ -77,9 +77,9 @@ subroutine organicrd(organic) ! Initialize data to zero - no organic matter dataset organic(:,:) = 0._r8 - + ! Read data if file was specified in namelist - + if (fsurdat /= ' ') then if (masterproc) then write(iulog,*) 'Attempting to read organic matter data .....' @@ -90,14 +90,14 @@ subroutine organicrd(organic) call ncd_pio_openfile (ncid, locfn, 0) call ncd_inqfdims (ncid, isgrid2d, ni, nj, ns) - if (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj) then + if (.not. single_column .and. (ldomain%ns /= ns .or. ldomain%ni /= ni .or. ldomain%nj /= nj)) then write(iulog,*)trim(subname), 'ldomain and input file do not match dims ' write(iulog,*)trim(subname), 'ldomain%ni,ni,= ',ldomain%ni,ni write(iulog,*)trim(subname), 'ldomain%nj,nj,= ',ldomain%nj,nj write(iulog,*)trim(subname), 'ldomain%ns,ns,= ',ldomain%ns,ns call endrun() end if - + call ncd_io(ncid=ncid, varname='ORGANIC', flag='read', data=organic, & dim1name=grlnd, readvar=readvar) if (.not. readvar) call endrun('organicrd: errror reading ORGANIC') diff --git a/src/main/pftconMod.F90 b/src/main/pftconMod.F90 index b48ef92a43..0ed2028fcb 100644 --- a/src/main/pftconMod.F90 +++ b/src/main/pftconMod.F90 @@ -2,8 +2,9 @@ module pftconMod !----------------------------------------------------------------------- ! !DESCRIPTION: - ! Module containing vegetation constants and method to - ! read and initialize vegetation (PFT) constants. + ! Module containing vegetation constants, methods to + ! read and initialize vegetation (PFT) constants, and methods to query + ! PFT characteristics ! ! !USES: use shr_kind_mod, only : r8 => shr_kind_r8 @@ -33,7 +34,6 @@ module pftconMod integer, public :: nc3_arctic_grass ! value for C3 arctic grass integer, public :: nc3_nonarctic_grass ! value for C3 non-arctic grass integer, public :: nc4_grass ! value for C4 grass - integer, public :: npcropmin ! value for first crop integer, public :: ntmp_corn ! value for temperate corn, rain fed (rf) integer, public :: nirrig_tmp_corn ! value for temperate corn, irrigated (ir) integer, public :: nswheat ! value for spring temperate cereal (rf) @@ -96,16 +96,22 @@ module pftconMod integer, public :: nirrig_trp_corn !value for tropical corn (ir) integer, public :: ntrp_soybean !value for tropical soybean (rf) integer, public :: nirrig_trp_soybean !value for tropical soybean (ir) - integer, public :: npcropmax ! value for last prognostic crop in list integer, public :: nc3crop ! value for generic crop (rf) integer, public :: nc3irrig ! value for irrigated generic crop (ir) + ! First and last prognostic crops + integer :: npcropmin ! value for first crop + integer :: npcropmax ! value for last prognostic crop in list + ! Number of crop functional types actually used in the model. This includes each CFT for ! which is_pft_known_to_model is true. Note that this includes irrigated crops even if ! irrigation is turned off in this run: it just excludes crop types that aren't handled ! at all, as given by the mergetoclmpft list. integer, public :: num_cfts_known_to_model + ! Number of prognostic crop functional types on the parameter file, even if not actually used + integer, public :: num_cfts_possible + ! !PUBLIC TYPES: type, public :: pftcon_type @@ -294,6 +300,7 @@ module pftconMod procedure, private :: InitRead procedure, private :: set_is_pft_known_to_model ! Set is_pft_known_to_model based on mergetoclmpft procedure, private :: set_num_cfts_known_to_model ! Set the module-level variable, num_cfts_known_to_model + procedure, private :: set_num_cfts_possible ! Set the module-level variable, num_cfts_possible end type pftcon_type @@ -315,6 +322,10 @@ module pftconMod character(len=*), parameter, private :: sourcefile = & __FILE__ + + public :: is_prognostic_crop + public :: get_crop_n_from_veg_type + public :: get_veg_type_from_crop_n !----------------------------------------------------------------------- contains @@ -1246,6 +1257,7 @@ subroutine InitRead(this) call this%set_is_pft_known_to_model() call this%set_num_cfts_known_to_model() + call this%set_num_cfts_possible() ! Set vegetation family identifier (tree/shrub/grass) do m = 0,mxpft @@ -1351,19 +1363,19 @@ subroutine InitRead(this) else call endrun(msg=' ERROR: crop has wrong values'//errMsg(sourcefile, __LINE__)) end if - if ( (i /= noveg) .and. (i < npcropmin) .and. & + if ( (i /= noveg) .and. (.not. is_prognostic_crop(i)) .and. & abs(this%pconv(i) + this%pprod10(i) + this%pprod100(i) - 1.0_r8) > 1.e-7_r8 )then call endrun(msg=' ERROR: pconv+pprod10+pprod100 do NOT sum to one.'//errMsg(sourcefile, __LINE__)) end if if ( this%pprodharv10(i) > 1.0_r8 .or. this%pprodharv10(i) < 0.0_r8 )then call endrun(msg=' ERROR: pprodharv10 outside of range.'//errMsg(sourcefile, __LINE__)) end if - if (i < npcropmin .and. this%biofuel_harvfrac(i) /= 0._r8) then + if ((.not. is_prognostic_crop(i)) .and. this%biofuel_harvfrac(i) /= 0._r8) then call endrun(msg=' ERROR: biofuel_harvfrac non-zero for a non-prognostic crop PFT.'//& errMsg(sourcefile, __LINE__)) end if do k = repr_structure_min, repr_structure_max - if (i < npcropmin .and. this%repr_structure_harvfrac(i,k) /= 0._r8) then + if ((.not. is_prognostic_crop(i)) .and. this%repr_structure_harvfrac(i,k) /= 0._r8) then call endrun(msg=' ERROR: repr_structure_harvfrac non-zero for a non-prognostic crop PFT.'//& errMsg(sourcefile, __LINE__)) end if @@ -1438,6 +1450,27 @@ subroutine set_num_cfts_known_to_model(this) end subroutine set_num_cfts_known_to_model + !----------------------------------------------------------------------- + subroutine set_num_cfts_possible(this) + ! + ! !DESCRIPTION: + ! Set the module-level variable, num_cfts_possible + ! + ! !USES: + ! + ! !ARGUMENTS: + class(pftcon_type), intent(in) :: this + ! + ! !LOCAL VARIABLES: + integer :: m + + character(len=*), parameter :: subname = 'set_num_cfts_possible' + !----------------------------------------------------------------------- + + num_cfts_possible = npcropmax - npcropmin + 1 + + end subroutine set_num_cfts_possible + !----------------------------------------------------------------------- subroutine Clean(this) ! @@ -1607,5 +1640,51 @@ subroutine Clean(this) deallocate( this%ndays_on) end subroutine Clean + !----------------------------------------------------------------------- + elemental logical function is_prognostic_crop(veg_type) + ! + ! !DESCRIPTION: + ! Given a vegetation type (pft, integer), return whether it's a prognostic crop. Does not + ! include generic crops (those and natural PFTs will return .false.). + ! + ! NOTE: This isn't a completely robust way to check if this is a prognostic crop patch. At the + ! very least, it should also check if <= npcropmax. Ideally it would use a new prognostic_crop + ! flag on the parameter file iteself. + ! + ! !ARGUMENTS + integer, intent(in) :: veg_type + + is_prognostic_crop = veg_type >= npcropmin + + end function is_prognostic_crop + + !----------------------------------------------------------------------- + elemental integer function get_crop_n_from_veg_type(veg_type) result(crop_n) + ! + ! !DESCRIPTION: + ! Given a vegetation type (pft, integer), return a 1-indexed number indicating where it would + ! be in a list of all simulated crops. + ! + ! !ARGUMENTS + integer, intent(in) :: veg_type + + crop_n = veg_type - npcropmin + 1 + + end function get_crop_n_from_veg_type + + !----------------------------------------------------------------------- + elemental integer function get_veg_type_from_crop_n(crop_n) result(veg_type) + ! + ! !DESCRIPTION: + ! Given a return a 1-indexed number indicating where a PFT would be in a list of all simulated + ! crops, return vegetation type (ivt) + ! + ! !ARGUMENTS + integer, intent(in) :: crop_n + + veg_type = npcropmin + crop_n - 1 + + end function get_veg_type_from_crop_n + end module pftconMod diff --git a/src/main/subgridWeightsMod.F90 b/src/main/subgridWeightsMod.F90 index 45e7d32306..059533f44b 100644 --- a/src/main/subgridWeightsMod.F90 +++ b/src/main/subgridWeightsMod.F90 @@ -193,7 +193,7 @@ subroutine init_subgrid_weights_mod(bounds) avgflag='A', long_name='% of each landunit on grid cell', & ptr_lnd=subgrid_weights_diagnostics%pct_landunit) - if(.not.use_fates.or.use_fates_sp) then + if(.not.use_fates) then call hist_addfld2d (fname='PCT_NAT_PFT', units='%', type2d='natpft', & avgflag='A', long_name='% of each PFT on the natural vegetation (i.e., soil) landunit', & ptr_lnd=subgrid_weights_diagnostics%pct_nat_pft) @@ -770,7 +770,7 @@ subroutine set_subgrid_diagnostic_fields(bounds) ! Note: (SPM, 10-20-15): If this isn't set then debug mode with intel and ! yellowstone will fail when trying to write pct_nat_pft since it contains ! all NaN's. - call set_pct_pft_diagnostics(bounds) + if(.not.use_fates) call set_pct_pft_diagnostics(bounds) call set_pct_glc_mec_diagnostics(bounds) diff --git a/src/main/surfrdMod.F90 b/src/main/surfrdMod.F90 index 4005ec7845..188773dfcd 100644 --- a/src/main/surfrdMod.F90 +++ b/src/main/surfrdMod.F90 @@ -15,6 +15,7 @@ module surfrdMod use clm_varcon , only : grlnd use clm_varctl , only : iulog use clm_varctl , only : use_cndv, use_crop, use_fates + use clm_varctl , only : fname_len use surfrdUtilsMod , only : check_sums_equal_1, apply_convert_ocean_to_land, collapse_crop_types use surfrdUtilsMod , only : collapse_to_dominant, collapse_crop_var, collapse_individual_lunits use ncdio_pio , only : file_desc_t, var_desc_t, ncd_pio_openfile, ncd_pio_closefile @@ -233,7 +234,7 @@ subroutine surfrd_get_data (begg, endg, ldomain, lfsurdat, lhillslope_file, actu character(len=*), intent(in) :: lhillslope_file ! hillslope dataset filename ! ! !LOCAL VARIABLES: - character(len=256):: locfn ! local file name + character(len=fname_len) :: locfn ! local file name integer, parameter :: n_dom_urban = 1 ! # of dominant urban landunits type(file_desc_t) :: ncid ! netcdf id for lfsurdat type(file_desc_t) :: ncid_hillslope ! netcdf id for lhillslope_file @@ -359,12 +360,14 @@ subroutine surfrd_get_num_patches (lfsurdat, actual_maxsoil_patches, actual_nump ! ! !LOCAL VARIABLES: - character(len=256):: locfn ! local file name + character(len=fname_len) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf file id integer :: dimid ! netCDF dimension id logical :: cft_dim_exists ! dimension exists on dataset + logical :: natpft_dim_exists ! dimension exists on dataset integer :: check_numpft ! Surface dataset count of numpft, should ! match maxsoil_patches - actual_numcft + integer :: actual_numnatpft ! natpft value from sfc dataset character(len=32) :: subname = 'surfrd_get_num_patches' ! subroutine name !----------------------------------------------------------------------- @@ -396,9 +399,17 @@ subroutine surfrd_get_num_patches (lfsurdat, actual_maxsoil_patches, actual_nump call ncd_inqdlen(ncid, dimid, actual_maxsoil_patches, 'lsmpft') actual_numpft = actual_maxsoil_patches - actual_numcft - call ncd_inqdlen(ncid, dimid, check_numpft, 'natpft') + ! Read numpft + call ncd_inqdid(ncid, 'natpft', dimid, natpft_dim_exists) + if ( natpft_dim_exists ) then + call ncd_inqdlen(ncid, dimid, actual_numnatpft, 'natpft') + call ncd_inqdlen(ncid, dimid, check_numpft, 'natpft') + else + actual_numnatpft = 0 + end if - if(check_numpft.ne.actual_numpft)then +!jt if(check_numpft.ne.actual_numpft)then + if(actual_numcft+actual_numnatpft.ne.actual_maxsoil_patches)then write(iulog,*)'the sum of the cftdim and the natpft dim should match the lsmpft dim in the surface file' write(iulog,*)'natpft: ',check_numpft write(iulog,*)'lsmpft: ',actual_maxsoil_patches @@ -427,7 +438,7 @@ subroutine surfrd_get_nlevurb (lfsurdat, actual_nlevurb) integer, intent(out) :: actual_nlevurb ! nlevurb from surface dataset ! ! !LOCAL VARIABLES: - character(len=256):: locfn ! local file name + character(len=fname_len) :: locfn ! local file name type(file_desc_t) :: ncid ! netcdf file id integer :: dimid ! netCDF dimension id character(len=32) :: subname = 'surfrd_get_nlevurb' ! subroutine name @@ -1153,7 +1164,6 @@ subroutine surfrd_lakemask(begg, endg) ! !USES: use clm_instur , only : pct_lake_max use dynSubgridControlMod , only : get_flanduse_timeseries - use clm_varctl , only : fname_len use fileutils , only : getfil ! ! !ARGUMENTS: @@ -1162,7 +1172,7 @@ subroutine surfrd_lakemask(begg, endg) ! ! !LOCAL VARIABLES: type(file_desc_t) :: ncid_dynuse ! netcdf id for landuse timeseries file - character(len=256) :: locfn ! local file name + character(len=fname_len) :: locfn ! local file name character(len=fname_len) :: fdynuse ! landuse.timeseries filename logical :: readvar ! @@ -1208,7 +1218,6 @@ subroutine surfrd_urbanmask(begg, endg) ! !USES: use clm_instur , only : pct_urban_max use dynSubgridControlMod , only : get_flanduse_timeseries - use clm_varctl , only : fname_len use fileutils , only : getfil ! ! !ARGUMENTS: @@ -1217,7 +1226,7 @@ subroutine surfrd_urbanmask(begg, endg) ! ! !LOCAL VARIABLES: type(file_desc_t) :: ncid_dynuse ! netcdf id for landuse timeseries file - character(len=256) :: locfn ! local file name + character(len=fname_len) :: locfn ! local file name character(len=fname_len) :: fdynuse ! landuse.timeseries filename logical :: readvar ! diff --git a/src/main/test/CMakeLists.txt b/src/main/test/CMakeLists.txt index 97bbf081cc..bf8c164260 100644 --- a/src/main/test/CMakeLists.txt +++ b/src/main/test/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(filter_test) add_subdirectory(initVertical_test) add_subdirectory(ncdio_utils_test) add_subdirectory(topo_test) +add_subdirectory(abortutils_test) diff --git a/src/main/test/abortutils_test/CMakeLists.txt b/src/main/test/abortutils_test/CMakeLists.txt new file mode 100644 index 0000000000..4b49e744f2 --- /dev/null +++ b/src/main/test/abortutils_test/CMakeLists.txt @@ -0,0 +1,8 @@ +set(pfunit_sources + test_abortutils.pf) + +add_pfunit_ctest(endrun + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf + EXTRA_FINALIZE unittest_finalize_esmf + EXTRA_USE unittestInitializeAndFinalize) diff --git a/src/main/test/abortutils_test/test_abortutils.pf b/src/main/test/abortutils_test/test_abortutils.pf new file mode 100644 index 0000000000..bf1f7babd2 --- /dev/null +++ b/src/main/test/abortutils_test/test_abortutils.pf @@ -0,0 +1,200 @@ +module test_abortutils + + ! Tests of abortutils + + use funit + use abortutils + use unittestUtils, only : endrun_msg + use shr_kind_mod, only : CL => shr_kind_cl + use clm_varctl, only : iulog + + implicit none + + @TestCase + type, extends(TestCase) :: TestAbortUtils + contains + procedure :: setUp + procedure :: tearDown + end type TestAbortUtils + +contains + + ! ======================================================================== + ! Helper routines + ! ======================================================================== + + subroutine setUp(this) + use unittestSimpleSubgridSetupsMod, only : setup_single_veg_patch + use GridcellType , only : grc + class(TestAbortUtils), intent(inout) :: this + + ! NOTE: Setup a single gridcell with one vegetated patch + ! So there's only one: gridcell, landunit, column, patch + ! This isn't needed for some tests, but doesn't hurt to do it + call setup_single_veg_patch(pft_type=1) + ! NOTE: Set lat and lon for this gridcell, so something is printed in the log + grc%londeg(1) = 255.0 + grc%latdeg(1) = 30.0 + end subroutine setUp + + subroutine tearDown(this) + use unittestSubgridMod, only : unittest_subgrid_teardown + class(TestAbortUtils), intent(inout) :: this + + call unittest_subgrid_teardown() + + end subroutine tearDown + + ! ======================================================================== + ! Begin tests + ! ======================================================================== + + @Test + subroutine endrun_plain_vanilla_aborts(this) + ! Test vanilla operation of endrun + class(TestAbortUtils), intent(inout) :: this + + call endrun() + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_plain_vanilla_aborts + + @Test + subroutine endrun_nomsg_file_line_vanilla_aborts(this) + ! Test vanilla operation of endrun with file and line number input + class(TestAbortUtils), intent(inout) :: this + + call endrun(line=1000, file='test_file.F90') + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_nomsg_file_line_vanilla_aborts + + @Test + subroutine endrun_nomsg_onlyfile_vanilla_aborts(this) + ! Test vanilla operation of endrun with only file input + class(TestAbortUtils), intent(inout) :: this + + call endrun(file='test_file.F90') + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_nomsg_onlyfile_vanilla_aborts + + @Test + subroutine endrun_msg_vanilla_aborts(this) + ! Test vanilla operation of endrun with a message sent in + class(TestAbortUtils), intent(inout) :: this + character(len=CL) :: msg = "test_message" + + call endrun( msg = msg) + @assertExceptionRaised(endrun_msg(msg)) + + end subroutine endrun_msg_vanilla_aborts + + @Test + subroutine endrun_addmsg_vanilla_aborts(this) + ! Test vanilla operation of endrun with an additional message sent in + class(TestAbortUtils), intent(inout) :: this + character(len=CL) :: msg = "test_message" + character(len=CL) :: add_msg = "additional_test_message" + + call endrun(msg=msg, additional_msg=add_msg) + @assertExceptionRaised(endrun_msg(msg)) + + end subroutine endrun_addmsg_vanilla_aborts + + @Test + subroutine endrun_addmsg_pt_context_aborts(this) + ! Test pt_context operation of endrun with an additional message sent in + use decompMod, only : subgrid_level_lndgrid, subgrid_level_gridcell + use decompMod, only : subgrid_level_landunit, subgrid_level_column, subgrid_level_patch + use decompMod, only : subgrid_level_cohort + class(TestAbortUtils), intent(inout) :: this + character(len=CL) :: msg = "test_message" + character(len=CL) :: add_msg = "additional_test_message" + integer :: p = 1, l + integer, parameter :: nlevel = 6 + integer :: subgrid_lvl(nlevel) = (/ subgrid_level_lndgrid, subgrid_level_gridcell, & + subgrid_level_landunit, subgrid_level_column, subgrid_level_patch, & + subgrid_level_cohort /) + + ! Loop over all the subgrid level types + ! Skip the first one and the last one which are: lndgrid and cohort + do l = 2, nlevel-1 + call endrun(subgrid_index=p, subgrid_level=subgrid_lvl(l), msg=msg, additional_msg=add_msg) + @assertExceptionRaised(endrun_msg(msg)) + end do + + end subroutine endrun_addmsg_pt_context_aborts + + @Test + subroutine endrun_nomsg_pt_context_bad_pt_aborts(this) + ! Test pt_context operation of endrun with an additional message sent in + use decompMod, only : subgrid_level_lndgrid, subgrid_level_gridcell + use decompMod, only : subgrid_level_landunit, subgrid_level_column, subgrid_level_patch + use decompMod, only : subgrid_level_cohort + class(TestAbortUtils), intent(inout) :: this + integer :: p = 2, l + integer, parameter :: nlevel = 6 + integer :: subgrid_lvl(nlevel) = (/ subgrid_level_lndgrid, subgrid_level_gridcell, & + subgrid_level_landunit, subgrid_level_column, subgrid_level_patch, & + subgrid_level_cohort /) + + ! Loop over all the subgrid level types + ! Skip the first one and the last one which are: lndgrid and cohort + do l = 2, nlevel-1 + call endrun(subgrid_index=p, subgrid_level=subgrid_lvl(l)) + @assertExceptionRaised(endrun_msg('')) + end do + + end subroutine endrun_nomsg_pt_context_bad_pt_aborts + + @Test + subroutine endrun_pt_context_lndgrid_aborts(this) + use decompMod, only : subgrid_level_lndgrid + class(TestAbortUtils), intent(inout) :: this + character(len=CL) :: msg = "test_message" + integer :: p = 1 + + ! NOTE: Also test without an additional msg + call endrun(subgrid_index=p, subgrid_level=subgrid_level_lndgrid, msg=msg) + @assertExceptionRaised(endrun_msg(msg)) + + end subroutine endrun_pt_context_lndgrid_aborts + + @Test + subroutine endrun_nomsg_pt_context_cohort_aborts(this) + use decompMod, only : subgrid_level_cohort + class(TestAbortUtils), intent(inout) :: this + integer :: p = 1 + + ! NOTE: Also test without either msg or additional msg + call endrun(subgrid_index=p, subgrid_level=subgrid_level_cohort) + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_nomsg_pt_context_cohort_aborts + + @Test + subroutine endrun_nomsg_addmsg_pt_context_unspec_aborts(this) + use decompMod, only : subgrid_level_unspecified + class(TestAbortUtils), intent(inout) :: this + integer :: p = 1 + character(len=CL) :: add_msg = "additional_test_message" + + ! NOTE: Don't use msg but do use additional_msg + call endrun(subgrid_index=p, subgrid_level=subgrid_level_unspecified, additional_msg=add_msg) + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_nomsg_addmsg_pt_context_unspec_aborts + + @Test + subroutine endrun_nomsg_pt_context_badlvl_aborts(this) + use decompMod, only : subgrid_level_unspecified + class(TestAbortUtils), intent(inout) :: this + integer :: p = 1 + + call endrun(subgrid_index=p, subgrid_level=-9999) + @assertExceptionRaised(endrun_msg('')) + + end subroutine endrun_nomsg_pt_context_badlvl_aborts + +end module test_abortutils diff --git a/src/self_tests/TestNcdioPio.F90 b/src/self_tests/TestNcdioPio.F90 index 3bac472950..379a7432bb 100644 --- a/src/self_tests/TestNcdioPio.F90 +++ b/src/self_tests/TestNcdioPio.F90 @@ -6,7 +6,7 @@ module TestNcdioPio #include "shr_assert.h" use ncdio_pio - use shr_kind_mod, only : r8 => shr_kind_r8 + use shr_kind_mod, only : r4 => shr_kind_r4, r8 => shr_kind_r8 use Assertions, only : assert_equal use clm_varcon, only : nameg use abortutils, only : endrun @@ -443,7 +443,16 @@ subroutine test_read_vars_change_type() local_int_1d_grc(:) = 0._r8 call ncd_io(varname='data_double_1d_grc_float', data=local_int_1d_grc, & dim1name=nameg, ncid=ncid, flag='read') - call assert_equal(expected=int(data_double_1d_grc), actual=local_int_1d_grc, & + ! Note that, in the following assertion, for the expected value, we need to do a + ! two-step conversion: first convert to float (i.e., r4 real), then convert to int. + ! This is because this two-step conversion is done in the process of + ! writing-then-reading (double converted to float upon write, then float converted to + ! int upon read). If we instead convert directly to int, this is prone to rounding + ! errors that can sometimes lead to mismatches (if the actual value is very close to + ! an integer, the double representation can be slightly greater than the integer while + ! the float representation can be slightly smaller, or vice versa). (See also + ! https://github.com/ESCOMP/CTSM/issues/3316.) + call assert_equal(expected=int(real(data_double_1d_grc, r4)), actual=local_int_1d_grc, & msg='data_double_1d_grc_float to int') call write_to_log(subname//': Reading int into logical') diff --git a/src/soilbiogeochem/CMakeLists.txt b/src/soilbiogeochem/CMakeLists.txt index e2baa2d1b2..ac467c3e5f 100644 --- a/src/soilbiogeochem/CMakeLists.txt +++ b/src/soilbiogeochem/CMakeLists.txt @@ -2,6 +2,7 @@ # source files that are currently used in unit tests list(APPEND clm_sources + SoilBiogeochemCarbonFluxType.F90 SoilBiogeochemStateType.F90 SoilBiogeochemDecompCascadeConType.F90 SoilBiogeochemStateType.F90 diff --git a/src/soilbiogeochem/SoilBiogeochemCarbonFluxType.F90 b/src/soilbiogeochem/SoilBiogeochemCarbonFluxType.F90 index c333a4939c..f997231cbf 100644 --- a/src/soilbiogeochem/SoilBiogeochemCarbonFluxType.F90 +++ b/src/soilbiogeochem/SoilBiogeochemCarbonFluxType.F90 @@ -61,9 +61,6 @@ module SoilBiogeochemCarbonFluxType real(r8), pointer :: soilc_change_col (:) ! (gC/m2/s) FUN used soil C real(r8), pointer :: fates_litter_flux (:) ! (gC/m2/s) A summary of the total litter ! flux passed in from FATES. - ! This is a diagnostic for balance checks only - real(r8), pointer :: fates_product_loss_grc (:) ! (gC/m2/s) product loss flux at gridcell scale to be used with FATES is on - ! track tradiagonal matrix real(r8), pointer :: matrix_decomp_fire_k_col (:,:) ! decomposition rate due to fire (gC*m3)/(gC*m3*step)) real(r8), pointer :: tri_ma_vr (:,:) ! vertical C transfer rate in sparse matrix format (gC*m3)/(gC*m3*step)) @@ -184,7 +181,6 @@ subroutine InitAllocate(this, bounds) allocate(this%soilc_change_col (begc:endc)) ; this%soilc_change_col (:) = nan if(use_fates)then - allocate(this%fates_product_loss_grc(begg:endg)) ; this%fates_product_loss_grc(:) = nan allocate(this%fates_litter_flux(begc:endc)); this%fates_litter_flux(:) = nan else allocate(this%fates_litter_flux(0:0)); this%fates_litter_flux(:) = nan @@ -687,9 +683,6 @@ subroutine InitCold(this, bounds) call this%SetValues (num_column=num_special_col, filter_column=special_col, & value_column=0._r8) - if(use_fates_bgc)then - this%fates_product_loss_grc(bounds%begg:bounds%endg) = 0._r8 - endif end subroutine InitCold diff --git a/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 b/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 index 57bc82984e..041f6ee740 100644 --- a/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 +++ b/src/soilbiogeochem/SoilBiogeochemCompetitionMod.F90 @@ -76,7 +76,7 @@ subroutine readParams ( ncid ) type(file_desc_t),intent(inout) :: ncid ! pio netCDF file id ! ! !LOCAL VARIABLES: - character(len=32) :: subname = 'CNAllocParamsType' + character(len=32) :: subname = 'readParams' character(len=100) :: errCode = '-Error reading in parameters file:' logical :: readv ! has variable been read in or not real(r8) :: tempr ! temporary to read in parameter @@ -130,7 +130,7 @@ subroutine SoilBiogeochemCompetitionInit ( bounds) ! !USES: use clm_varcon , only: secspday use clm_time_manager, only: get_step_size_real - use clm_varctl , only: iulog, cnallocate_carbon_only_set + use clm_varctl , only: iulog, allocate_carbon_only_set use shr_infnan_mod , only: nan => shr_infnan_nan, assignment(=) ! ! !ARGUMENTS: @@ -160,7 +160,7 @@ subroutine SoilBiogeochemCompetitionInit ( bounds) errMsg(sourcefile, __LINE__)) end select - call cnallocate_carbon_only_set(carbon_only) + call allocate_carbon_only_set(carbon_only) end subroutine SoilBiogeochemCompetitionInit @@ -175,7 +175,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu soilbiogeochem_nitrogenflux_inst,canopystate_inst) ! ! !USES: - use clm_varctl , only: cnallocate_carbon_only, iulog + use clm_varctl , only: allocate_carbon_only, iulog use clm_varpar , only: nlevdecomp, ndecomp_cascade_transitions use clm_varpar , only: i_cop_mic, i_oli_mic use clm_varcon , only: nitrif_n2o_loss_frac @@ -338,7 +338,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu fpi_vr(c,j) = 1.0_r8 actual_immob_vr(c,j) = potential_immob_vr(c,j) sminn_to_plant_vr(c,j) = plant_ndemand(c) * nuptake_prof(c,j) - else if ( cnallocate_carbon_only()) then !.or. & + else if ( allocate_carbon_only()) then !.or. & ! this code block controls the addition of N to sminn pool ! to eliminate any N limitation, when Carbon_Only is set. This lets the ! model behave essentially as a carbon-only model, but with the @@ -729,7 +729,7 @@ subroutine SoilBiogeochemCompetition (bounds, num_bgc_soilc, filter_bgc_soilc,nu ! eliminate N limitations, so there is still a diagnostic quantity ! that describes the degree of N limitation at steady-state. - if ( cnallocate_carbon_only()) then !.or. & + if ( allocate_carbon_only()) then !.or. & if ( fpi_no3_vr(c,j) + fpi_nh4_vr(c,j) < 1._r8 ) then fpi_nh4_vr(c,j) = 1.0_r8 - fpi_no3_vr(c,j) supplement_to_sminn_vr(c,j) = (potential_immob_vr(c,j) & diff --git a/src/soilbiogeochem/SoilBiogeochemNLeachingMod.F90 b/src/soilbiogeochem/SoilBiogeochemNLeachingMod.F90 index a646feb1d7..f0a42b379c 100644 --- a/src/soilbiogeochem/SoilBiogeochemNLeachingMod.F90 +++ b/src/soilbiogeochem/SoilBiogeochemNLeachingMod.F90 @@ -10,7 +10,7 @@ module SoilBiogeochemNLeachingMod use abortutils , only : endrun use decompMod , only : bounds_type use clm_varcon , only : dzsoi_decomp, zisoi - use clm_varctl , only : use_nitrif_denitrif + use clm_varctl , only : use_nitrif_denitrif, use_nvmovement use SoilBiogeochemNitrogenStateType , only : soilbiogeochem_nitrogenstate_type use SoilBiogeochemNitrogenFluxType , only : soilbiogeochem_nitrogenflux_type use WaterStateBulkType , only : waterstatebulk_type @@ -212,16 +212,21 @@ subroutine SoilBiogeochemNLeaching(bounds, num_bgc_soilc, filter_bgc_soilc, & if (h2osoi_liq(c,j) > 0._r8) then disn_conc = (sf_no3 * smin_no3_vr(c,j) * col%dz(c,j) )/(h2osoi_liq(c,j) ) end if - ! - ! calculate the N leaching flux as a function of the dissolved - ! concentration and the sub-surface drainage flux - smin_no3_leached_vr(c,j) = disn_conc * drain_tot(c) * h2osoi_liq(c,j) / ( tot_water(c) * col%dz(c,j) ) - ! - ! ensure that leaching rate isn't larger than soil N pool - smin_no3_leached_vr(c,j) = min(smin_no3_leached_vr(c,j), smin_no3_vr(c,j) / dt ) - ! - ! limit the leaching flux to a positive value - smin_no3_leached_vr(c,j) = max(smin_no3_leached_vr(c,j), 0._r8) + ! Evaluating the leaching flux in this module, if use_nvmovement is not true + ! and in SoilNitrogenMovementMod if use_nvmovement is true + if (.not. use_nvmovement) then + ! + ! calculate the N leaching flux as a function of the dissolved + ! concentration and the sub-surface drainage flux + smin_no3_leached_vr(c,j) = disn_conc * drain_tot(c) * h2osoi_liq(c,j) / ( tot_water(c) * col%dz(c,j) ) + ! + ! ensure that leaching rate isn't larger than soil N pool + smin_no3_leached_vr(c,j) = min(smin_no3_leached_vr(c,j), smin_no3_vr(c,j) / dt ) + ! + ! limit the leaching flux to a positive value + smin_no3_leached_vr(c,j) = max(smin_no3_leached_vr(c,j), 0._r8) + end if + ! ! ! calculate the N loss from surface runoff, assuming a shallow mixing of surface waters into soil and removal based on runoff @@ -242,13 +247,17 @@ subroutine SoilBiogeochemNLeaching(bounds, num_bgc_soilc, filter_bgc_soilc, & ! limit the flux to a positive value smin_no3_runoff_vr(c,j) = max(smin_no3_runoff_vr(c,j), 0._r8) - ! limit the flux based on current smin_no3 state - ! only let at most the assumed soluble fraction - ! of smin_no3 be leached on any given timestep - smin_no3_leached_vr(c,j) = min(smin_no3_leached_vr(c,j), (sf_no3 * smin_no3_vr(c,j))/dt) + ! Evaluating the leaching flux in this module, if use_nvmovement is not true + ! and in SoilNitrogenMovementMod if use_nvmovement is true + if (.not. use_nvmovement) then + ! limit the flux based on current smin_no3 state + ! only let at most the assumed soluble fraction + ! of smin_no3 be leached on any given timestep + smin_no3_leached_vr(c,j) = min(smin_no3_leached_vr(c,j), (sf_no3 * smin_no3_vr(c,j))/dt) - ! limit the flux to a positive value - smin_no3_leached_vr(c,j) = max(smin_no3_leached_vr(c,j), 0._r8) + ! limit the flux to a positive value + smin_no3_leached_vr(c,j) = max(smin_no3_leached_vr(c,j), 0._r8) + end if end do end do diff --git a/src/soilbiogeochem/SoilNitrogenMovementMod.F90 b/src/soilbiogeochem/SoilNitrogenMovementMod.F90 new file mode 100644 index 0000000000..cc352fd827 --- /dev/null +++ b/src/soilbiogeochem/SoilNitrogenMovementMod.F90 @@ -0,0 +1,247 @@ +module SoilNitrogenMovementMod + + !------------------------------------------------------------------------ + ! DESCRIPTION + ! implementation of Soil-Water-Atmosphere-Plant (SWAP3.2) + ! and Pantakar 1980 algorithm to Community Land Model + ! SWAP3.2 website: https://edepot.wur.nl/39776 + ! Author: Jinmu Luo, Cornell EAS, April 1 2024 + + use decompMod , only : bounds_type + use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_infnan_mod , only : isnan => shr_infnan_isnan + use shr_infnan_mod , only : isinf => shr_infnan_isinf + use clm_varctl , only : iulog + use spmdMod , only : masterproc + use abortutils , only : endrun + use clm_time_manager , only : get_step_size_real + use clm_time_manager , only : get_curr_date + use SoilBiogeochemNitrogenFluxType , only : soilbiogeochem_nitrogenflux_type + use SoilBiogeochemNitrogenStateType , only : soilbiogeochem_nitrogenstate_type + use WaterStateBulkType , only : waterstatebulk_type + use SoilStatetype , only : soilstate_type + use SoilHydrologyType , only : soilhydrology_type + use ColumnType , only : col + + ! + implicit none + private + ! + ! PUBLIC MEMBER FUNCTIONS + public SoilNitrogenMovement + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + !------------------------------------------------------------------------ + + contains + + !------------------------------------------------------------------------ + subroutine SoilNitrogenMovement(bounds, num_bgc_soilc, filter_bgc_soilc, waterstatebulk_inst, & + soilstate_inst, soilhydrology_inst, soilbiogeochem_nitrogenflux_inst, soilbiogeochem_nitrogenstate_inst) + ! + ! implementation of the advection-diffusion algorithm in Patankar1980 + ! + ! This part of code is only designed for fast aqueous transport and leaching of inorganic nitrate + ! no sources and other sinks are included in this module + ! leaching flux is taken out of soil pool + ! Author: Jinmu Luo, Cornell EAS, Apr 11 2024 + ! + !USES: + use decompMod , only : bounds_type + use clm_varpar , only : nlevdecomp, nlevgrnd + use clm_time_manager , only : get_step_size_real, get_curr_date + use clm_varcon , only : zsoi, zisoi, dzsoi_decomp, mmh2o_to_m3h2o_per_m2 + use ColumnType , only : col + use clm_varctl , only : use_bedrock + use TridiagonalMod , only : Tridiagonal + !ARGUMENTS: + type(bounds_type) , intent(in) :: bounds ! bounds + integer , intent(in) :: num_bgc_soilc ! number of soil columns in filter + integer , intent(in) :: filter_bgc_soilc(:) ! filter for soil columns + type(waterstatebulk_type) , intent(in) :: waterstatebulk_inst + type(soilstate_type) , intent(in) :: soilstate_inst + type(soilhydrology_type) , intent(in) :: soilhydrology_inst + type(soilbiogeochem_nitrogenflux_type) , intent(inout) :: soilbiogeochem_nitrogenflux_inst + type(soilbiogeochem_nitrogenstate_type) , intent(inout) :: soilbiogeochem_nitrogenstate_inst + + !LOCAL VARIABLES: + integer :: c,fc,j ! do loop indices + integer :: year, mon, day, tod + integer :: jtop(bounds%begc:bounds%endc) ! top level at each column + real(r8) :: dtime ! land model time step (sec) + real(r8) :: wafc, wafc2 ! Fraction of water that is liquid by mass + real(r8) :: dispersion_length = 0.1_r8 ! dispersion length (m), Jury et al., 1991 + real(r8) :: theta, thetasat ! soil water and soil water at the saturation level + real(r8) :: no3_diffusivity_in_water = 1.7e-9_r8 ! Molecular diffusivity of NO3- in water, m2/s + real(r8) :: dissolve_frac = 1.0_r8 ! dissolve fraction + real(r8) :: flux_component_gridpoint_ahead ! A function in Patankar 1980, figure 5.6 + real(r8) :: peclet_num ! Peclet number in Patankar 1980, foumula 5.18 + real(r8) :: peclet_num_in, peclet_num_out ! temporary Peclet numbers + real(r8) :: qflx_in, qflx_out ! water fluxes same as qin, qout but in m3 H2O/m2/s + real(r8) :: dz_node(1:nlevdecomp+1) ! difference between nodes + real(r8) :: mass_old(bounds%begc:bounds%endc) ! Temporal column mass, g/m2 + real(r8) :: mass_new(bounds%begc:bounds%endc) ! Temporal column mass, g/m2 + real(r8) :: swliq(bounds%begc:bounds%endc,1:nlevdecomp) ! volumetric liquid soil water [m3/m3], hardwired to 1 for non-transport layers and layers below bedrock + real(r8) :: total_diffusivity(bounds%begc:bounds%endc,1:nlevdecomp+1) ! Total diffusivity + real(r8) :: a_tri(bounds%begc:bounds%endc,0:nlevdecomp+1) ! "a" vector for tridiagonal matrix + real(r8) :: b_tri(bounds%begc:bounds%endc,0:nlevdecomp+1) ! "b" vector for tridiagonal matrix + real(r8) :: c_tri(bounds%begc:bounds%endc,0:nlevdecomp+1) ! "c" vector for tridiagonal matrix + real(r8) :: r_tri(bounds%begc:bounds%endc,0:nlevdecomp+1) ! "r" vector for tridiagonal solution + real(r8) :: conc_trcr(bounds%begc:bounds%endc,0:nlevdecomp+1) ! temporary for concentration, g/m3H2O + + ! set up the A function, table 5.2 in Patankar 1980 has multiple A function. + ! Notes: According to Table 5.2, here we use the "Power Law" version of the function + ! A is a dimensionless coefficient described in equation 5.37 of Patankar (1980) + ! The same identical function appears in CLM's SoilBiogeochemLittVertTranspMod.F90 as aaa(pe) + ! Patankar (1980) is posted here: https://github.com/ESCOMP/CTSM/pull/2992#discussion_r2294809728 + flux_component_gridpoint_ahead(peclet_num) = max(0._r8, ( 1._r8 - 0.1_r8 * abs(peclet_num) )**5 ) + + associate(& + h2osoi_vol => waterstatebulk_inst%h2osoi_vol_col , & ! Input: [real(r8) (:,:) ] volumetric soil water (0<=h2osoi_vol<=watsat) [m3/m3] + h2osoi_liq => waterstatebulk_inst%h2osoi_liq_col , & ! Input: [real(r8) (:,:) ] col liquid water (kg/m2) + h2osoi_ice => waterstatebulk_inst%h2osoi_ice_col , & ! Input: [real(r8) (:,:) ] col ice lens (kg/m2) + watsat => soilstate_inst%watsat_col , & ! Input: [real(r8) (:,:) ] volumetric soil water at saturation (porosity) + qout => soilhydrology_inst%qout_col , & ! Input: [real(r8) (:,:) ] soil water out of the bottom, mm h2o/s + qin => soilhydrology_inst%qin_col , & ! Input: [real(r8) (:,:) ] soil water into the bottom, mm h2o/s + smin_no3_vr => soilbiogeochem_nitrogenstate_inst%smin_no3_vr_col , & ! Inout: [real(r8) (:,:) ] soil nitrate concentration, gN/m3 + smin_no3_leached_vr => soilbiogeochem_nitrogenflux_inst%smin_no3_leached_vr_col & ! Output: [real(r8) (:,:) ] rate of mineral NO3 leaching (gN/m3/s) + ) + + !Get the size of model time step + dtime = get_step_size_real() + call get_curr_date(year, mon, day, tod) + + ! Preparing for the necessary parameters, like Delta z, Jtop, and the total diffusivity coefficients. + do j = 1, nlevdecomp + 1 + if (j < nlevdecomp) then + dz_node(j) = zsoi(j+1) - zsoi(j) + end if + do fc = 1, num_bgc_soilc + c = filter_bgc_soilc(fc) + jtop(c) = 0 + if (j < nlevdecomp) then + wafc = h2osoi_liq(c,j)/(h2osoi_liq(c,j) + h2osoi_ice(c,j)) + wafc2 = h2osoi_liq(c,j+1)/(h2osoi_liq(c,j+1) + h2osoi_ice(c,j+1)) + swliq(c,j) = wafc * h2osoi_vol(c,j) + swliq(c,j+1) = wafc2 * h2osoi_vol(c,j+1) + if (swliq(c,j) == 0._r8 .or. swliq(c,j+1) == 0._r8) then + theta = 0._r8 + thetasat = 1._r8 + else + theta = swliq(c,j) + dzsoi_decomp(j)/2 * (swliq(c,j+1) - swliq(c,j))/dz_node(j) + thetasat = watsat(c,j) + dzsoi_decomp(j)/2 * (watsat(c,j+1) - watsat(c,j))/dz_node(j) + end if + ! here we refer the j as the interface of j + zj/2 + total_diffusivity(c,j) = theta * (no3_diffusivity_in_water * theta**(7/3) * thetasat**(-2)) + total_diffusivity(c,j) = total_diffusivity(c,j) + dispersion_length * mmh2o_to_m3h2o_per_m2 * abs(qout(c,j)) + else + !no gradient for the last layer + total_diffusivity(c,j) = total_diffusivity(c,j-1) + end if + end do ! Loop for columns + end do ! Loop for depths + dz_node(nlevdecomp) = dz_node(nlevdecomp-1) + dz_node(nlevdecomp+1) = dz_node(nlevdecomp) + + ! Calculate the tridiagonal matrix, using Crank-Nicholson soluiton + ! dc/dt = (1-alpha)dF(t+1)/dx + alpha*F(t)/dx, + ! alpha=0 + do j = 0, nlevdecomp + 1 + do fc = 1, num_bgc_soilc + c = filter_bgc_soilc(fc) + if ( j==0 .or. j==nlevdecomp+1) then + !atmosphere and bottom layer, no concentration gradient here + conc_trcr(c,j) = 0._r8 + a_tri(c,j) = 0._r8 + b_tri(c,j) = 1._r8 + c_tri(c,j) = 0._r8 + r_tri(c,j) = 0._r8 + elseif (swliq(c,j) == 0._r8 .or. j > col%nbedrock(c)) then + ! extremely dry condition and layers beneath the bedrock, no aqueous transport of nitrate + conc_trcr(c,j) = dissolve_frac * smin_no3_vr(c,j) + a_tri(c,j) = 0._r8 + b_tri(c,j) = 1._r8 + c_tri(c,j) = 0._r8 + r_tri(c,j) = conc_trcr(c,j) + swliq(c,j) = 1.0_r8 ! change swliq into 1 to be used in the update session below + elseif ( j == 1) then + ! topmost soil layer, flux only interacts with the layer below it + conc_trcr(c,j) = dissolve_frac * smin_no3_vr(c,j)/swliq(c,j) + qflx_out = qout(c,j) * mmh2o_to_m3h2o_per_m2 + peclet_num_out = qflx_out * dz_node(j) / total_diffusivity(c,j) + a_tri(c,j) = 0._r8 + c_tri(c,j) = -total_diffusivity(c,j) / dz_node(j) * flux_component_gridpoint_ahead(peclet_num_out) - max(-qflx_out, 0._r8) + b_tri(c,j) = total_diffusivity(c,j) / dz_node(j) * flux_component_gridpoint_ahead(peclet_num_out) + max(qflx_out, 0._r8) + swliq(c,j) / dtime * dzsoi_decomp(j) + r_tri(c,j) = conc_trcr(c,j)/dtime*swliq(c,j)*dzsoi_decomp(j) + elseif ( j == col%nbedrock(c)) then + ! Assume the bottom layer concentration is always zero + ! This method count the loss at this layer as the leaching flux at the bottom + conc_trcr(c,j) = 0._r8 + a_tri(c,j) = 0._r8 + b_tri(c,j) = 1._r8 + c_tri(c,j) = 0._r8 + r_tri(c,j) = conc_trcr(c,j)/dtime*swliq(c,j)*dzsoi_decomp(j) + else + ! Active layers from second one to bedrock-1, concentration should be in gN/m3Water + conc_trcr(c,j) = dissolve_frac * smin_no3_vr(c,j)/swliq(c,j) + qflx_in = qin(c,j) * mmh2o_to_m3h2o_per_m2 ! mm H2O/s to m3 H2O/m2/s + qflx_out = qout(c,j) * mmh2o_to_m3h2o_per_m2 + peclet_num_in = qflx_in * dz_node(j-1) / total_diffusivity(c,j-1) + peclet_num_out = qflx_out * dz_node(j) / total_diffusivity(c,j) + a_tri(c,j) = -total_diffusivity(c,j-1) / dz_node(j-1) * flux_component_gridpoint_ahead(peclet_num_in) - max(qflx_in, 0._r8) + c_tri(c,j) = -total_diffusivity(c,j) / dz_node(j) * flux_component_gridpoint_ahead(peclet_num_out) - max(-qflx_out, 0._r8) + b_tri(c,j) = total_diffusivity(c,j-1) / dz_node(j-1) * flux_component_gridpoint_ahead(peclet_num_in) + max(-qflx_in, 0._r8) + & + total_diffusivity(c,j) / dz_node(j) * flux_component_gridpoint_ahead(peclet_num_out) + max(qflx_out, 0._r8) + swliq(c,j) / dtime * dzsoi_decomp(j) + r_tri(c,j) = conc_trcr(c,j)/dtime*swliq(c,j)*dzsoi_decomp(j) + end if + end do ! Loop for columns + end do ! Loop for depths + + ! solve the tridiagonal matrix + call Tridiagonal(bounds, 0, nlevdecomp+1, & + jtop(bounds%begc:bounds%endc), & + num_bgc_soilc, filter_bgc_soilc, & + a_tri(bounds%begc:bounds%endc, :), & + b_tri(bounds%begc:bounds%endc, :), & + c_tri(bounds%begc:bounds%endc, :), & + r_tri(bounds%begc:bounds%endc, :), & + conc_trcr(bounds%begc:bounds%endc,0:nlevdecomp+1)) + + ! Calculate the leaching flux + mass_old(bounds%begc:bounds%endc) = 0._r8 + mass_new(bounds%begc:bounds%endc) = 0._r8 + do j = 1, nlevdecomp + do fc = 1, num_bgc_soilc + c = filter_bgc_soilc(fc) + smin_no3_leached_vr(c,j) = 0._r8 + mass_old(c) = mass_old(c) + smin_no3_vr(c,j)*dzsoi_decomp(j) + mass_new(c) = mass_new(c) + (smin_no3_vr(c,j) * (1._r8 - dissolve_frac) + conc_trcr(c,j) * swliq(c,j)) * dzsoi_decomp(j) + end do + end do + + do fc = 1, num_bgc_soilc + c = filter_bgc_soilc(fc) + ! g/m3/sec, leaching mass is at the layer above the bedrock + smin_no3_leached_vr(c, col%nbedrock(c)) = max(0._r8, (mass_old(c) - mass_new(c))/dzsoi_decomp(col%nbedrock(c))/dtime) + end do + + ! Update the pools of interest + do j = 1, nlevdecomp + do fc = 1, num_bgc_soilc + c = filter_bgc_soilc(fc) + smin_no3_vr(c,j) = smin_no3_vr(c,j) - smin_no3_vr(c,j) * dissolve_frac + conc_trcr(c,j) * swliq(c,j) + ! Return this leaching flux back to smin_no3 pool, and update will be finished in CNNStateUpdate3Mod + if( j == col%nbedrock(c) ) then + smin_no3_vr(c,j) = smin_no3_vr(c,j) + smin_no3_leached_vr(c,j)*dtime + end if + end do ! loop for columns + end do !loop for depths + + end associate + + + end subroutine SoilNitrogenMovement + + +end module SoilNitrogenMovementMod diff --git a/src/soilbiogeochem/TillageMod.F90 b/src/soilbiogeochem/TillageMod.F90 index 4a24daf4c2..5f94a44777 100644 --- a/src/soilbiogeochem/TillageMod.F90 +++ b/src/soilbiogeochem/TillageMod.F90 @@ -284,7 +284,7 @@ subroutine get_apply_tillage_multipliers(idop, c, j, decomp_k) ! Written by Sam Rabin, based on original code by Michael Graham. ! ! !USES - use pftconMod , only : npcropmin + use pftconMod , only : is_prognostic_crop use clm_varcon, only : zisoi, dzsoi_decomp use landunit_varcon , only : istcrop use PatchType , only : patch @@ -315,7 +315,7 @@ subroutine get_apply_tillage_multipliers(idop, c, j, decomp_k) sumwt = 0.0_r8 do p = col%patchi(c),col%patchf(c) if (patch%active(p) .and. patch%wtcol(p) /= 0._r8) then - if (patch%itype(p) < npcropmin) then + if (.not. is_prognostic_crop(patch%itype(p))) then ! Do not till generic crops tillage_mults_1patch(:) = 1._r8 else diff --git a/src/unit_test_stubs/main/ncdio_pio_fake.F90.in b/src/unit_test_stubs/main/ncdio_pio_fake.F90.in index e8ef14e457..7f38565e90 100644 --- a/src/unit_test_stubs/main/ncdio_pio_fake.F90.in +++ b/src/unit_test_stubs/main/ncdio_pio_fake.F90.in @@ -48,6 +48,7 @@ module ncdio_pio public :: check_var ! determine if variable is on netcdf file public :: check_dim ! determine if dimension is on netcdf file public :: check_var_or_dim ! determine if variable or dimension is on netcdf file + public :: check_dim_size ! validity check on dimension public :: ncd_io ! do fake i/o (currently only set up to read) public :: ncd_inqvid ! inquire on a variable id public :: ncd_set_var ! set data on "file" for one variable @@ -340,6 +341,25 @@ contains end subroutine check_var_or_dim + !----------------------------------------------------------------------- + subroutine check_dim_size(ncid, dimname, value, msg) + ! + ! !DESCRIPTION: + ! Validity check on dimension + ! + ! !ARGUMENTS: + class(file_desc_t),intent(in) :: ncid ! PIO file handle + character(len=*) , intent(in) :: dimname ! Dimension name + integer, intent(in) :: value ! Expected dimension size + + character(len=*), intent(in), optional :: msg ! Optional additional message printed upon error + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + ! Does nothing assumes the dim size is as expected + + end subroutine check_dim_size !----------------------------------------------------------------------- subroutine ncd_inqdid(ncid, name, dimid, dimexist) diff --git a/src/unit_test_stubs/share_esmf/CMakeLists.txt b/src/unit_test_stubs/share_esmf/CMakeLists.txt index 1d767543ea..c68bb17a98 100644 --- a/src/unit_test_stubs/share_esmf/CMakeLists.txt +++ b/src/unit_test_stubs/share_esmf/CMakeLists.txt @@ -1,5 +1,8 @@ list(APPEND clm_sources ExcessIceStreamType.F90 + FireDataBaseType.F90 + CTSMForce2DStreamBaseType.F90 + laiStreamMod.F90 PrigentRoughnessStreamType.F90 ZenderSoilErodStreamType.F90 ) diff --git a/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 b/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 new file mode 100644 index 0000000000..59e6a4d551 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/CTSMForce2DStreamBaseType.F90 @@ -0,0 +1,120 @@ +module CTSMForce2DStreamBaseType + + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use abortutils , only : endrun + use decompMod , only : bounds_type + use clm_varctl, only : FL => fname_len + + implicit none + private + + type, abstract, public :: ctsm_force_2DStream_base_type + private + character(len=FL) :: stream_filename ! The stream data filename (also in sdat) + character(len=CL) :: stream_name ! The stream name (also in sdat) + contains + + ! PUBLIC METHODS + procedure(Init_interface) , public, deferred :: Init + procedure, public, non_overridable :: InitBase ! Initialize and read data in the streams, , store the g_to_ig index array + procedure(Clean_interface), public, deferred :: Clean ! Clean and deallocate the object class method + procedure, public, non_overridable :: CleanBase ! Clean method for the base type + procedure, public, non_overridable :: Advance ! Advance the streams data to the current model date + procedure, public :: GetPtr1D ! Get pointer to the 1D data array + procedure(Interp_interface), public, deferred :: Interp ! method in extensions to turn stream data into output data + + end type ctsm_force_2DStream_base_type + + abstract interface + + subroutine Init_interface( this, bounds, fldfilename, meshfile, mapalgo, tintalgo, taxmode, & + year_first, year_last, model_year_align ) + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + end subroutine Init_interface + + subroutine Clean_interface(this) + ! Uses: + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + end subroutine Clean_interface + + subroutine Interp_interface(this, bounds) + ! Uses: + use decompMod , only : bounds_type + import :: ctsm_force_2DStream_base_type + ! + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + end subroutine Interp_interface + + end interface + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + + contains + + subroutine InitBase( this, bounds, varnames, fldfilename, meshfile, mapalgo, tintalgo, taxmode, name, & + year_first, year_last, model_year_align ) + ! Uses: + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + type(bounds_type), intent(in) :: bounds + character(*), intent(in) :: varnames(:) ! variable names to read from stream file + character(*), intent(in) :: fldfilename ! stream data filename (full pathname) (single file) + ! NOTE: fldfilename could be expanded to an array if needed, but currently we only have one file + character(*), intent(in) :: meshfile ! full pathname to stream mesh file (none for global data) + character(*), intent(in) :: mapalgo ! stream mesh -> model mesh mapping type + character(*), intent(in) :: tintalgo ! time interpolation algorithm + character(*), intent(in) :: taxMode ! time axis mode + character(*), intent(in) :: name ! name of stream + integer, intent(in) :: year_first ! first year to use + integer, intent(in) :: year_last ! last year to use + integer, intent(in) :: model_year_align ! align yearFirst with this model year + + end subroutine InitBase + + subroutine CleanBase( this ) + class(ctsm_force_2DStream_base_type) , intent(inout) :: this + + call endrun('CTSMForce2DStreamBaseType: CleanBase method not implemented in stub') + + end subroutine CleanBase + + subroutine Advance(this) + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + + call endrun('CTSMForce2DStreamBaseType: Advance method not implemented in stub') + + end subroutine Advance + + subroutine GetPtr1D(this, fldname, dataptr1d) + ! Get the pointer to the 1D data array for the given field name + ! Uses: + ! Arguments: + class(ctsm_force_2DStream_base_type), intent(inout) :: this + character(*), intent(in) :: fldname ! field name to get pointer for + real(r8), pointer :: dataptr1d(:) ! Pointer to the 1D data + + end subroutine GetPtr1D + +end module CTSMForce2DStreamBaseType diff --git a/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 b/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 new file mode 100644 index 0000000000..63046188a3 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 @@ -0,0 +1,123 @@ +module FireDataBaseType + +#include "shr_assert.h" + + !----------------------------------------------------------------------- + ! !DESCRIPTION: + ! module for handling of fire data + ! UNIT-TEST STUB for fire data Streams + ! This just allows the fire code to be tested without + ! reading in the streams data, by faking it and setting it to a + ! constant value. + ! + ! !USES: + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use shr_log_mod , only : errMsg => shr_log_errMsg + use clm_varctl , only : iulog + use spmdMod , only : masterproc, mpicom, iam + use abortutils , only : endrun + use decompMod , only : bounds_type + use FireMethodType , only : fire_method_type + ! + implicit none + private + ! + ! !PUBLIC TYPES: + public :: fire_base_type + ! + type, abstract, extends(fire_method_type) :: fire_base_type + private + ! !PRIVATE MEMBER DATA: + real(r8), public, pointer :: forc_hdm(:) ! Human population density + real(r8), public, pointer :: forc_lnfm(:) ! Lightning frequency + real(r8), public, pointer :: gdp_lf_col(:) ! col global real gdp data (k US$/capita) + real(r8), public, pointer :: peatf_lf_col(:) ! col global peatland fraction data (0-1) + integer , public, pointer :: abm_lf_col(:) ! col global peak month of crop fire emissions + + contains + ! + ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: BaseFireInit ! Initialization of Fire + procedure, public :: FireInit => BaseFireInit ! Initialization of Fire + procedure, public :: FireInterp ! Interpolate fire data + procedure, public :: BaseFireReadNML ! Read in the namelist + procedure, public :: FireReadNML => BaseFireReadNML ! Read in the namelist + procedure(need_lightning_and_popdens_interface), public, deferred :: & + need_lightning_and_popdens ! Returns true if need lightning & popdens + + end type fire_base_type + + abstract interface + !----------------------------------------------------------------------- + function need_lightning_and_popdens_interface(this) result(need_lightning_and_popdens) + ! + ! !DESCRIPTION: + ! Returns true if need lightning and popdens, false otherwise + ! + ! USES + import :: fire_base_type + ! + ! !ARGUMENTS: + class(fire_base_type), intent(in) :: this + logical :: need_lightning_and_popdens ! function result + !----------------------------------------------------------------------- + end function need_lightning_and_popdens_interface + end interface + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + +!============================================================================== +contains +!============================================================================== + + subroutine BaseFireReadNML( this, bounds, NLFilename ) + ! + ! !DESCRIPTION: + ! Read the namelist for Fire + ! + ! !USES: + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + character(len=*), intent(in) :: NLFilename ! Namelist filename + end subroutine BaseFireReadNML + + !================================================================ + subroutine BaseFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize CN Fire module + ! !USES: + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + + end if + + end subroutine BaseFireInit + + !================================================================ + subroutine FireInterp(this,bounds) + ! + ! !DESCRIPTION: + ! Interpolate CN Fire datasets + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + + end if + + end subroutine FireInterp + +end module FireDataBaseType diff --git a/src/unit_test_stubs/share_esmf/laiStreamMod.F90 b/src/unit_test_stubs/share_esmf/laiStreamMod.F90 new file mode 100644 index 0000000000..a39a3eb053 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/laiStreamMod.F90 @@ -0,0 +1,74 @@ +module laiStreamMod + + !----------------------------------------------------------------------- + ! !DESCRIPTION: + ! Read LAI from stream + ! + ! !USES: + use decompMod , only : bounds_type + use abortutils , only : endrun + use clm_varctl , only : iulog + ! + ! !PUBLIC TYPES: + implicit none + private + + ! !PUBLIC MEMBER FUNCTIONS: + public :: lai_init ! position datasets for LAI + public :: lai_advance ! Advance the LAI streams (outside of a Open-MP threading loop) + public :: lai_interp ! interpolates between two years of LAI data (when LAI streams + + character(len=*), parameter :: sourcefile = & + __FILE__ + +!============================================================================== +contains +!============================================================================== + + subroutine lai_init(bounds) + ! + ! Initialize data stream information for LAI. + ! + ! !USES: + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds ! bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_init + + !================================================================ + subroutine lai_advance( bounds ) + ! + ! Advance LAI streams + ! + ! !USES: + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_advance + + !================================================================ + subroutine lai_interp(bounds, canopystate_inst) + ! + ! Interpolate data stream information for Lai. + ! + ! !USES: + use CanopyStateType , only : canopystate_type + ! + ! !ARGUMENTS: + type(bounds_type) , intent(in) :: bounds + type(canopystate_type) , intent(inout) :: canopystate_inst + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_interp + +end module LaiStreamMod diff --git a/src/utils/clmfates_interfaceMod.F90 b/src/utils/clmfates_interfaceMod.F90 index f6aca5ebe0..d084cfdeb7 100644 --- a/src/utils/clmfates_interfaceMod.F90 +++ b/src/utils/clmfates_interfaceMod.F90 @@ -49,6 +49,7 @@ module CLMFatesInterfaceMod use PRTGenericMod , only : prt_cnp_flex_allom_hyp use clm_varctl , only : use_fates use clm_varctl , only : fates_spitfire_mode + use clm_varctl , only : use_fates_managed_fire use clm_varctl , only : use_fates_tree_damage use clm_varctl , only : use_fates_planthydro use clm_varctl , only : use_fates_cohort_age_tracking @@ -79,6 +80,7 @@ module CLMFatesInterfaceMod use clm_varctl , only : use_lch4 use clm_varctl , only : fates_history_dimlevel use clm_varctl , only : nsrest, nsrBranch + use clm_varctl , only : Allocate_Carbon_only use clm_varcon , only : tfrz use clm_varcon , only : spval use clm_varcon , only : denice @@ -440,6 +442,7 @@ subroutine CLMFatesGlobals2() integer :: pass_hydro_solver integer :: pass_radiation_model integer :: pass_electron_transport_model + integer :: pass_managed_fire call t_startf('fates_globals2') @@ -484,29 +487,22 @@ subroutine CLMFatesGlobals2() end if call set_fates_ctrlparms('use_tree_damage',ival=pass_tree_damage) - ! These may be in a non-limiting status (ie when supplements) - ! are added, but they are always allocated and cycled non-the less - ! FATES may want to interact differently with other models - ! that don't even have these arrays allocated. - ! FATES also checks that if NO3 is cycled in ELM, then - ! any plant affinity parameters are checked. - - if(use_nitrif_denitrif) then - call set_fates_ctrlparms('nitrogen_spec',ival=1) - else - call set_fates_ctrlparms('nitrogen_spec',ival=2) - end if - - ! Phosphorus is not tracked in CLM - call set_fates_ctrlparms('phosphorus_spec',ival=0) - - + ! Pass spitfire mode values call set_fates_ctrlparms('spitfire_mode',ival=fates_spitfire_mode) call set_fates_ctrlparms('sf_nofire_def',ival=no_fire) call set_fates_ctrlparms('sf_scalar_lightning_def',ival=scalar_lightning) call set_fates_ctrlparms('sf_successful_ignitions_def',ival=successful_ignitions) call set_fates_ctrlparms('sf_anthro_ignitions_def',ival=anthro_ignitions) + ! Pass managed fire mode value + if (use_fates_managed_fire) then + pass_managed_fire = 1 + else + pass_managed_fire = 0 + end if + call set_fates_ctrlparms('use_managed_fire',ival=pass_managed_fire) + + ! This has no variable on the FATES side yet (RGK) !call set_fates_ctrlparms('sf_anthro_suppression_def',ival=anthro_suppression) @@ -1033,15 +1029,17 @@ subroutine init(this, bounds_proc, flandusepftdat) end if - ! Set patch itypes on natural veg columns to nonsense - ! This will force a crash if the model outside of FATES tries to think - ! of the patch as a PFT. + ! Set patch itypes on natural veg columns to a nonsense value. itype + ! associates a patch with a specific pft, which is fundamentally + ! inconsistent with fates and should never be used on a fates patch. + ! This will force a crash if the model outside tries to associate + ! a fates designated patch as a PFT. do s = 1, this%fates(nc)%nsites c = this%f2hmap(nc)%fcolumn(s) pi = col%patchi(c)+1 pf = col%patchf(c) -! patch%itype(pi:pf) = ispval + patch%itype(pi:pf) = ispval patch%is_fates(pi:pf) = .true. end do @@ -1159,6 +1157,12 @@ subroutine dynamics_driv(this, nc, bounds_clump, & real(r8) :: s_node, smp_node ! local for relative water content and potential logical :: after_start_of_harvest_ts integer :: iharv + logical :: nitr_suppl ! true -> CLM is supplementing Nitrogen + logical, parameter :: phos_dummy_suppl = .true. ! true -> Phosphorus is NOT limited (i.e. supplemented) + ! This argument is needed for FATES + ! to specify if phosphorus is being + ! supplemented, Phosphorous is not limited in CLM + ! so we set it to TRUE !----------------------------------------------------------------------- ! --------------------------------------------------------------------------------- @@ -1340,10 +1344,16 @@ subroutine dynamics_driv(this, nc, bounds_clump, & end do + if(Allocate_Carbon_only())then + nitr_suppl = .true. + else + nitr_suppl = .false. + end if + ! Nutrient uptake fluxes have been accumulating with each short ! timestep, here, we unload them from the boundary condition ! structures into the cohort structures. - call UnPackNutrientAquisitionBCs(this%fates(nc)%sites, this%fates(nc)%bc_in) + call UnPackNutrientAquisitionBCs(this%fates(nc)%sites, this%fates(nc)%bc_in, nitr_suppl, phos_dummy_suppl) ! Distribute any seeds from neighboring gridcells into the current gridcell ! Global seed availability array populated by WrapGlobalSeedDispersal call @@ -2363,11 +2373,11 @@ subroutine wrap_sunfrac(this,nc,atm2lnd_inst,canopystate_inst) call t_startf('fates_wrapsunfrac') - associate( forc_solad => atm2lnd_inst%forc_solad_not_downscaled_grc, & - forc_solai => atm2lnd_inst%forc_solai_grc, & - fsun => canopystate_inst%fsun_patch, & - laisun => canopystate_inst%laisun_patch, & - laisha => canopystate_inst%laisha_patch ) + associate( forc_solad_g => atm2lnd_inst%forc_solad_not_downscaled_grc, & + forc_solai_g => atm2lnd_inst%forc_solai_grc, & + fsun => canopystate_inst%fsun_patch, & + laisun => canopystate_inst%laisun_patch, & + laisha => canopystate_inst%laisha_patch ) ! ------------------------------------------------------------------------------- ! Convert input BC's @@ -2379,8 +2389,8 @@ subroutine wrap_sunfrac(this,nc,atm2lnd_inst,canopystate_inst) g = col%gridcell(c) do ifp = 1, this%fates(nc)%sites(s)%youngest_patch%patchno - this%fates(nc)%bc_in(s)%solad_parb(ifp,:) = forc_solad(g,:) - this%fates(nc)%bc_in(s)%solai_parb(ifp,:) = forc_solai(g,:) + this%fates(nc)%bc_in(s)%solad_parb(ifp,:) = forc_solad_g(g,:) + this%fates(nc)%bc_in(s)%solai_parb(ifp,:) = forc_solai_g(g,:) end do end do @@ -2909,7 +2919,7 @@ end subroutine wrap_WoodProducts ! ============================================================================== subroutine wrap_co2_to_atm(this, bounds_clump, num_soilc, filter_soilc, & - soilbiogeochem_carbonflux_inst, net_carbon_exchange_grc ) + soilbiogeochem_carbonflux_inst, c_products_inst, net_carbon_exchange_grc ) ! USES use subgridAveMod, only : c2g @@ -2920,6 +2930,7 @@ subroutine wrap_co2_to_atm(this, bounds_clump, num_soilc, filter_soilc, & integer , intent(in) :: num_soilc ! size of column filter integer , intent(in) :: filter_soilc(:) ! column filter type(soilbiogeochem_carbonflux_type), intent(in) :: soilbiogeochem_carbonflux_inst + type(cn_products_type) , intent(in) :: c_products_inst real(r8) , intent(inout) :: net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) ! Locals @@ -2930,7 +2941,6 @@ subroutine wrap_co2_to_atm(this, bounds_clump, num_soilc, filter_soilc, & net_carbon_exchange_col(bounds_clump%begc:bounds_clump%endc) = 0.0_r8 net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) = 0.0_r8 ci = bounds_clump%clump_index - ! Loop over columns do fc = 1, num_soilc @@ -2952,9 +2962,8 @@ subroutine wrap_co2_to_atm(this, bounds_clump, num_soilc, filter_soilc, & l2g_scale_type = 'unity') ! change sign, same way it is done in get_net_carbon_exchange - do g = bounds_clump%begg,bounds_clump%endg - net_carbon_exchange_grc(g) = net_carbon_exchange_grc(g) - soilbiogeochem_carbonflux_inst%fates_product_loss_grc(g) - enddo + net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) = net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) & + - c_products_inst%product_loss_grc(bounds_clump%begg:bounds_clump%endg) net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) = - net_carbon_exchange_grc(bounds_clump%begg:bounds_clump%endg) @@ -3357,7 +3366,8 @@ subroutine Init2(this, bounds, NLFilename) call t_startf('fates_init2') - call this%fates_fire_data_method%FireInit(bounds, NLFilename) + call this%fates_fire_data_method%FireInit(bounds) + call this%fates_fire_data_method%FireReadNML(bounds, NLFilename) call t_stopf('fates_init2') diff --git a/src/utils/fileutils.F90 b/src/utils/fileutils.F90 index e7146468e7..f67cf91af0 100644 --- a/src/utils/fileutils.F90 +++ b/src/utils/fileutils.F90 @@ -21,6 +21,9 @@ module fileutils public :: getavu !Get next available Fortran unit number !----------------------------------------------------------------------- + character(len=*), parameter, private :: sourcefile = & + __FILE__ + contains !----------------------------------------------------------------------- @@ -76,7 +79,7 @@ subroutine getfil (fulpath, locfn, iflag) write(iulog,'(a)')'(GETFIL): full pathname is '//trim(fulpath) write(iulog,'(a)')'(GETFIL): local filename has zero length' end if - call shr_sys_abort + call shr_sys_abort('GETFIL: local filename has zero length', file=sourcefile, line=__LINE__) else if (masterproc) then write(iulog,'(a)')'(GETFIL): attempting to find local file ',trim(locfn) @@ -105,7 +108,7 @@ subroutine getfil (fulpath, locfn, iflag) write(iulog,'(a)')'(GETFIL): failed getting file from full path: '//fulpath end if if (iflag==0) then - call shr_sys_abort ('GETFIL: FAILED to get '//trim(fulpath)) + call shr_sys_abort ('GETFIL: FAILED to get '//trim(fulpath), file=sourcefile, line=__LINE__) else RETURN endif @@ -131,7 +134,7 @@ subroutine opnfil (locfn, iun, form) if (len_trim(locfn) == 0) then write(iulog,*)'(OPNFIL): local filename has zero length' - call shr_sys_abort + call shr_sys_abort('OPNFIL: local filename has zero length', file=sourcefile, line=__LINE__) endif if (form=='u' .or. form=='U') then ft = 'unformatted' @@ -142,7 +145,7 @@ subroutine opnfil (locfn, iun, form) if (ioe /= 0) then write(iulog,*)'(OPNFIL): failed to open file ',trim(locfn), & & ' on unit ',iun,' ierr=',ioe - call shr_sys_abort + call shr_sys_abort('OPNFIL: failed to open '//trim(locfn), rc=ioe, file=sourcefile, line=__LINE__) else if ( masterproc )then write(iulog,*)'(OPNFIL): Successfully opened file ',trim(locfn), & & ' on unit= ',iun diff --git a/tools/contrib/README b/tools/contrib/README index 40a208c96b..d56b185b73 100644 --- a/tools/contrib/README +++ b/tools/contrib/README @@ -6,7 +6,7 @@ available before adding it. These scripts may not be as well tested or supported tools. They are also ONLY assumed to work on the NCAR supercomputer. So paths will be hardwired to assume NCAR directory structures. -The python scripts require the following settings before running on cheyenne: +The python scripts require the following settings before running on Derecho: module load conda ../../py_env_create @@ -16,28 +16,28 @@ Brief description of scripts: create_scrip_file.ncl Create a SCRIP grid file needed for running with WRF -run_clm_historical - does all the setup and submission required to do a 1850-2010 CLM - historical simulation in three separate submissions - v1 - Andrew Slater+Dave Lawrence, 8/2015 +run_clm_historical.v11.csh + does all the setup and submission required to do a 1850-2023 CLM + historical simulation in five separate submissions + v11 - Oleson, 07/2025 modify_singlept_site Modify some data on a surface dataset created by site_and_regional/subset_data -SpinupStability_SP_v9.ncl +SpinupStability_SP_v10.ncl This script assesses the equilibrium state of a Satellite Phenology (SP) spinup run, works on either monthly or annual mean history files - Keith - Oleson 12/2021 + Oleson 07/2025 -SpinupStability_BGC_v10.ncl +SpinupStability_BGC_v11.ncl This script assesses the equilibrium state of a Biogeochemistry (BGC) spinup run, works on either monthly or annual mean history files - Keith - Oleson 12/2021 + Oleson 07/2025 -SpinupStability_BGC_v11_SE.ncl +SpinupStability_BGC_v12_SE.ncl This script assesses the equilibrium state of a ne30pg3 (spectral element) Biogeochemistry (BGC) spinup run, works on either monthly or annual mean - history files - Keith Oleson 03/2025 + history files - Oleson 07/2025 run_clmtowers This script will run any number of flux tower sites. diff --git a/tools/contrib/SpinupStability_BGC_v10.ncl b/tools/contrib/SpinupStability_BGC_v11.ncl similarity index 98% rename from tools/contrib/SpinupStability_BGC_v10.ncl rename to tools/contrib/SpinupStability_BGC_v11.ncl index 5ed7516455..d81b38ad9c 100644 --- a/tools/contrib/SpinupStability_BGC_v10.ncl +++ b/tools/contrib/SpinupStability_BGC_v11.ncl @@ -1,14 +1,9 @@ ; NCL script -; SpinupStability_BGC_v10.ncl +; SpinupStability_BGC_v11.ncl ; Script to examine stability of BGC spinup simulation. ; This version operates on either monthly mean or multi-annual mean multi-variable history files ; NOTE: THIS SCRIPT IS ONLY INTENDED FOR USE WITH 2-D LAT/LON GRIDS -; Keith Oleson, Mar 2020 - -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_code.ncl" -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_csm.ncl" -load "~oleson/lnd_diag/run/contributed.ncl" -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/shea_util.ncl" +; Keith Oleson, July 2025 begin @@ -30,17 +25,18 @@ begin ; annual_hist flag to False if your case has monthly mean history files. ; AND set the region (supported options: Global, Arctic, SPT). ; AND set the subper (subsampling period in years, number of years that atm forcing repeats). -; The script assumes that your history files are in /glade/scratch/$username/archive/$caseid/lnd/hist +; The script assumes that your history files are in /glade/derecho/scratch/$username/archive/$caseid/lnd/hist ; You need a run consisting of at least three cycles of atmospheric data ;=======================================================================; ; GLOBAL EXAMPLE - caseid = "clm50_release-clm5.0.15_2deg_GSWP3V1_1850AD" - username = "oleson" + caseid = "ctsm53026_BNF_AD" + username = "slevis" annual_hist = True region = "Global" ; Global, Arctic, or SPT (single point) subper = 20 ; Subsampling period in years paleo = False ; Use paleo map + hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) ; SPT (single point) EXAMPLE ; caseid = "clm50_release-clm5.0.15_SPT_GSWP3V1_1850spin" @@ -49,12 +45,13 @@ begin ; region = "SPT" ; Global, Arctic, or SPT (single point) ; subper = 20 ; Subsampling period in years ; paleo = False ; Use paleo map +; hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) do_plot = True ; do_plot = False ;=======================================================================; - data_dir = "/glade/scratch/"+username+"/archive/"+caseid+"/lnd/hist/" + data_dir = "/glade/derecho/scratch/"+username+"/archive/"+caseid+"/lnd/hist/" if ( systemfunc("test -d "+data_dir+"; echo $?" ) .ne. 0 )then print( "Input directory does not exist or not found: "+data_dir ); print( "Make sure username and caseid and base directory is set correctly" ) @@ -85,9 +82,9 @@ begin end if if (annual_hist) then - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*-*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*-*-*"+".nc") else - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*"+".nc") end if flsdims = dimsizes(fls) diff --git a/tools/contrib/SpinupStability_BGC_v11_SE.ncl b/tools/contrib/SpinupStability_BGC_v12_SE.ncl similarity index 99% rename from tools/contrib/SpinupStability_BGC_v11_SE.ncl rename to tools/contrib/SpinupStability_BGC_v12_SE.ncl index db00c0b484..aa044b9c17 100644 --- a/tools/contrib/SpinupStability_BGC_v11_SE.ncl +++ b/tools/contrib/SpinupStability_BGC_v12_SE.ncl @@ -1,9 +1,9 @@ ; NCL script -; SpinupStability_BGC_v11_SE.ncl +; SpinupStability_BGC_v12_SE.ncl ; Script to examine stability of spinup simulation. ; This version operates on either monthly mean or multi-annual mean multi-variable history files ; and supports ne30 grids only -; Keith Oleson, March 2025 +; Keith Oleson, July 2025 ; ; ARH mods, April 2020 - modified to work for unstructured grids via remapping to FV 1 deg. grid. ; KO mods, March 2025 - regrid history file using ncremap to get area and landfrac instead of @@ -41,6 +41,7 @@ begin region = "Global" ; Global subper = 20 ; Subsampling period in years paleo = False ; Use paleo map ; UNTESTED IN THIS VERSION + hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) ;--- ARH mods --- map_method = "conserve" ;bilinear,conserve or patch @@ -81,9 +82,9 @@ begin totecosysc_thresh = 1.0 ; disequilibrium threshold for individual gridcells (gC/m2/yr) if (annual_hist) then - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*-*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*-*-*"+".nc") else - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*"+".nc") end if flsdims = dimsizes(fls) diff --git a/tools/contrib/SpinupStability_SP_v9.ncl b/tools/contrib/SpinupStability_SP_v10.ncl similarity index 97% rename from tools/contrib/SpinupStability_SP_v9.ncl rename to tools/contrib/SpinupStability_SP_v10.ncl index 58a769a910..6b3d310234 100644 --- a/tools/contrib/SpinupStability_SP_v9.ncl +++ b/tools/contrib/SpinupStability_SP_v10.ncl @@ -1,13 +1,8 @@ ; NCL script -; SpinupStability_SP_v9.ncl +; SpinupStability_SP_v10.ncl ; Script to examine stability of SP spinup simulation. ; This version operates on either monthly mean or multi-annual mean multi-variable history files -; Keith Oleson, Sep 2018 - -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_code.ncl" -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/gsn_csm.ncl" -load "~oleson/lnd_diag/run/contributed.ncl" -load "$NCARG_ROOT/lib/ncarg/nclscripts/csm/shea_util.ncl" +; Keith Oleson, July 2025 begin @@ -27,21 +22,24 @@ begin ; To run this script, just enter in your case name and username below. ; AND set the annual_hist flag to "True" if your case has annual mean history files or set ; annual_hist flag to "False" if your case has monthly mean history files. -; The script ; assumes that your history files are in /glade/scratch/$username/archive/$caseid/lnd/hist +; The script assumes that your history files are in /glade/derecho/scratch/$username/archive/$caseid/lnd/hist ;=======================================================================; - caseid = "cesm20exp10j_1deg_CPLHST_nndep_1850pAD" + caseid = "clm50sp_cesm23a02cPPEn08ctsm51d030_1deg_GSWP3V1_1850spin" username = "oleson" annual_hist = "False" cplot = "Global" subper = 20 h2osoi_layer = 8 ; Desired soil layer (layer 8 is about 1m) tsoi_layer = 10 ; Desired soil layer (layer 10 is about 3m) + hist_ext = "h0" ; Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) do_plot = "True" ;=======================================================================; - data_dir = "/glade/scratch/"+username+"/archive/"+caseid+"/lnd/hist/" + data_dir = "/glade/derecho/scratch/"+username+"/archive/"+caseid+"/lnd/hist/" +; FOR TESTING +; data_dir = "/glade/campaign/cgd/tss/common/Land_Only_Simulations/CTSM51_DEV/CLM50_CTSM51_LAND_ONLY_RELEASE/"+caseid+"/lnd/hist/" ; Thresholds glob_thresh_fsh = 0.02 ; global threshold for FSH equilibrium (delta W m-2 / yr) @@ -54,9 +52,9 @@ begin tws_thresh = 0.001 ; disequilibrium threshold for individual gridcells (m) if (annual_hist .eq. "True") then - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*-*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*-*-*"+".nc") else - fls = systemfunc("ls " + data_dir + caseid+".clm2.h0.*-*"+".nc") + fls = systemfunc("ls " + data_dir + caseid+".clm2."+hist_ext+".*-*"+".nc") end if flsdims = dimsizes(fls) diff --git a/tools/contrib/abm_raw.ncl b/tools/contrib/abm_raw.ncl new file mode 100644 index 0000000000..42b3bbbd08 --- /dev/null +++ b/tools/contrib/abm_raw.ncl @@ -0,0 +1,41 @@ +; Script written by Fang Li and emailed to slevis on 2025/07/24 +; Script for generating the abm (peak crop-fire month) raw dataset +; /glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/rawdata/mksrf_abm_0.5x0.5_simyr2000.c250715.nc +; starting from preexisting raw dataset +; /glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/rawdata/mksrf_abm_0.5x0.5_simyr2000.c240821.nc + +f1=addfile("/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/mksrf_abm_0.5x0.5_simyr2000.c240821.nc","r") +abm1=f1->abm +lon1=f1->lon +lat1=f1->lat + +fils=systemfunc("ls /glade/work/fangli/obs/GFED5/crop05/BA*.nc") +fs=addfiles(fils,"r") +ListSetType(fs,"join") +baf=fs[:]->baf +bafm=dim_avg_n(baf,0) + +f=addfile("fpc_crop05.nc","r") ; MCD12C1 12+14 +fpc_crop=f->fpc_crop + +fw=addfile("fpc_water05.nc","r") +fpc_water=fw->fpc_water + +abm=abm1 +do ilat=0, 359 + do ilon=0, 719 + if(fpc_crop(ilat,ilon).gt.0.005 .and. fpc_water(ilat,ilon).lt.0.5)then + if(sum(bafm(:,ilat,ilon)).gt.0.0)then + abm(ilat,ilon)=maxind(bafm(:,ilat,ilon))+1 + else + abm(ilat,ilon)=13 + end if + else + abm(ilat,ilon)=14 + end if +end do +end do + + fout1=addfile("abm05-raw.nc","c") + fout1->abm=abm + diff --git a/tools/contrib/popden.ncl b/tools/contrib/popden.ncl new file mode 100644 index 0000000000..b9503be020 --- /dev/null +++ b/tools/contrib/popden.ncl @@ -0,0 +1,44 @@ +; Script written by Fang Li and emailed to slevis on 2025/07/24 +; Script for generating the hdm (aka popdens or population density) stream file +; /glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/firedata/clmforc.Li_2025_CMIP7_hdm_0.5x0.5_simyr1850-2100_c250717.nc +; starting from preexisting file +; /glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/firedata/clmforc.Li_2018_SSP3_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc +; and raw data in /glade/work/fangli/hd/TRENDY/pop-dens_input4MIPs_population_CMIP_PIK-CMIP-1-0-0_gr_1850-2025.nc +; Is the latter the same as /glade/campaign/cesm/cesmdata/input4MIPs_raw/input4MIPs/CMIP7/CMIP/PIK/PIK-CMIP-1-0-0 ? + +f=addfile("/glade/campaign/cesm/cesmdata/cseg/inputdata/lnd/clm2/firedata/clmforc.Li_2018_SSP3_CMIP6_hdm_0.5x0.5_AVHRR_simyr1850-2100_c181205.nc","r") +hdm=f->hdm + +f1=addfile("/glade/work/fangli/hd/TRENDY/pop-dens_input4MIPs_population_CMIP_PIK-CMIP-1-0-0_gr_1850-2025.nc","r") +hdmn=f1->pop_dens + +f2=addfile("/glade/work/fangli/hd/TRENDY/ctl05.clm2.h0.1850-01.nc","r") +landfrac=f2->landfrac +landmask=f2->landmask +landf1=landfrac*landmask +landf=landf1 +landf(:,0:359)=landf1(:,360:719) +landf(:,360:719)=landf1(:,0:359) + +do ilat=0,359 + do ilon=0,719 + if(ismissing(landf(ilat,ilon)))then + hdm(0:175,ilat,ilon)=(/hdmn(:,ilat,ilon)/) + else + hdm(0:175,ilat,ilon)=(/hdmn(:,ilat,ilon)/landf(ilat,ilon)/) + end if + end do +end do + +fout=addfile("clmforc.Li_2025_CMIP7_hdm_0.5x0.5_simyr1850-2100_c250717.nc","c") + vNam=getfilevarnames(f) + do i=0,dimsizes(vNam)-1 + if(vNam(dimsizes(vNam)-1-i).ne."hdm")then + x=f->$vNam(dimsizes(vNam)-1-i)$ + fout->$vNam(dimsizes(vNam)-1-i)$=x + delete(x) + else + fout->hdm=hdm + end if + end do + diff --git a/tools/contrib/run_clm_historical b/tools/contrib/run_clm_historical.v11.csh similarity index 70% rename from tools/contrib/run_clm_historical rename to tools/contrib/run_clm_historical.v11.csh index 8dc9269d3b..04e22aa31a 100755 --- a/tools/contrib/run_clm_historical +++ b/tools/contrib/run_clm_historical.v11.csh @@ -2,19 +2,32 @@ ######################################################################################### # -# - Execute this script to do a CLM historical simulation from 1850 - 2014. This +# - Execute this script to do a CLM historical simulation from 1850 - 2023. This # script will complete all the changes required at year 1901 to deal with the # fact that met forcing data does not go back to 1850. # +# - The CASENAME should be the only thing the user needs to change. +# +# - Run this script in the background on a Derecho login node like this: +# ./run_clm_historical.v11.csh >&! run_clm_historical.out & +# CAUTION: Note the hostname (echo $HOST) and the PID for the script and the "sleep" job (identify them using ps -u $USER) +# E.g., +# PID TTY TIME CMD +# 581 ? 00:00:00 sleep +# 30684 ? 00:00:00 run_clm_histori +# If the simulation crashes at any point, you'll need to kill the script and the "sleep" job (kill -9 $PID) +# before restarting the simulation. You will have to run the remainder of the simulation manually. +# # - Unmodified script will do the following. # Part 1: Simulation 1: 1850 - 1870 (21 years) using repeated 1901-1920 forcing # Part 2: Simulation 2: 1871 - 1900 (30 years) using repeated 1901-1920 forcing -# Part 3: Simulation 3+4+5+6: 1901-1988 (four 22 year) simulations using 1901-1988 forcing -# Part 4: Simulation 7: 1989-2004 (one 16 year) branch simulation w/daily output using 1989-2004 forcing -# Part 5: Simulation 8: 2005-2014 (one 10 year) branch simulation w/daily & subdaily output using 2005-2014 forcing +# Part 3: Simulation 3+4+5: 1901-1978 (three 26 year) simulations using 1901-1978 forcing +# Part 4: Simulation 7: 1979-1999 (one 21 year) branch simulation w/daily output using 1979-1999 forcing +# Part 5: Simulation 8: 2000-2023 (one 24 year) branch simulation w/daily & subdaily output using 2000-2023 forcing +# Note that this approach generates year 1979 and 2000 restart files needed for other specific compsets. # # - Script assumes that simulation can run at least 30 years within a 12 hour block on -# Cheyenne. To find the timing in an equivalent sample run, look in the timing +# Derecho. To find the timing in an equivalent sample run, look in the timing # directory and grep as follows > grep 'simulated_years/day' cesm_timing* # # - In the env_batch.xml file for the case.run group ensure the following: @@ -22,22 +35,23 @@ # # - This script assumes that env_mach_pes.xml has been setup and case.setup has already been run # -# - This script makes use of user_nl_datm1901-1920 and user_nl_datm1901-2014 -# # - Before submitting script, make a copy of your modified or unmodified user_nl_clm file # into "original_user_nl_clm". This should only contain namelist items that will not change throughout -# the run. +# the run. You can start with this example: +# /glade/u/home/oleson/run_hist_1850_files/BGC/original_user_nl_clm_ctsm5.3.0 # Create a file called user_nl_clm_histdaily that contains the desired history output namelist items -# for the 1989-2004 simulation +# for the 1979-2023 simulation. You can start with this example: +# /glade/u/home/oleson/run_hist_1850_files/BGC/user_nl_clm_histdaily_ctsm # Create a file called user_nl_clm_histsubdaily that contains the desired history output namelist items -# for the 2005-2014 simulation +# for the 2001-2023 simulation. You can start with this example: +# /glade/u/home/oleson/run_hist_1850_files/BGC/user_nl_clm_histsubdaily_ctsm # # - The atm data files start in 1901, so with : # ALIGN year of 1901, (this is in units of RUN or simulation years) # START year of 1901, (this is in units of FORCE'ing data years) # -# RUN Year : 1850 ... 1860 1861 ... 1870 ... 1880 1881 ... 1890 ... 1900 1901 ... 2014 -# FORCE Year : 1910 ... 1920 1901 ... 1910 ... 1920 1901 ... 1910 ... 1920 1901 ... 2014 +# RUN Year : 1850 ... 1860 1861 ... 1870 ... 1880 1881 ... 1890 ... 1900 1901 ... 2023 +# FORCE Year : 1910 ... 1920 1901 ... 1910 ... 1920 1901 ... 1910 ... 1920 1901 ... 2023 # # - The script could be broken up into several parts if you want to check the initial set of # simulations. @@ -52,6 +66,12 @@ # ./run_clm_historical.v6.csh ! > & run_historical.out & # - Modify history output for CMIP6 - Keith Oleson January, 2019 # ./run_clm_historical.v7.csh ! > & run_historical.out & +# - Modify to run with nuopc - Keith Oleson September, 2022 +# ./run_clm_historical.v9.csh ! > & run_historical.out & +# - Modify to produce restart files for 1979 and 2000 and run with CRUv7 extension to 2023 - Keith Oleson September, 2024 +# ./run_clm_historical.v10.csh ! > & run_clm_historical.out & +# - Modify to look for either h0 or h0a files - Keith Oleson July, 2025 +# ./run_clm_historical.v11.csh ! > & run_clm_historical.out & ######################################################################################### ######################################################################################### @@ -63,7 +83,10 @@ ######################################################################################### # --- CASENAME is your case name -set CASENAME = 'clm50_release-clm5.0.15_2deg_GSWP3V1_hist' +set CASENAME = 'ctsm530_f19_PPE_TESTv11_hist' + +# --- Set to either h0 (ctsm5.3.061 or earlier) or h0a (ctsm5.3.062 or later) +set HIST_EXT = 'h0' # --- Set the user namelist file. cp original_user_nl_clm user_nl_clm @@ -79,9 +102,7 @@ cp original_user_nl_clm user_nl_clm ./xmlchange DATM_YR_ALIGN=1901 ./xmlchange DATM_YR_START=1901 ./xmlchange DATM_YR_END=1920 - -# need to use user_nl_datm files to get years right -cp user_nl_datm1901-1920 user_nl_datm +./xmlchange DATM_SKIP_RESTART_READ=FALSE # --- Check that you end up using the correct env_run.xml file set nenvr = `ls -1 env_run*.xml | wc -l` @@ -92,7 +113,7 @@ if ($nenvr > 1) then endif # --- If you have not already built the code, then do so now -#./case.clean_build +#./case.build --clean-all qcmd -- ./case.build # --- Now submit the job and let it run @@ -114,7 +135,7 @@ qcmd -- ./case.build ######################################################################################### -set WDIR = '/glade/scratch/'$USER'/'$CASENAME'/run/' +set WDIR = '/glade/derecho/scratch/'$USER'/'$CASENAME'/run/' set DONE_RUNA = 0 set DONE_ARCHIVE = 0 set RESTART_FILE = $WDIR$CASENAME'.clm2.r.1871-01-01-00000.nc' @@ -125,7 +146,7 @@ while ($DONE_RUNA == 0) set DONE_RUNA = 1 echo '1850-1870 run is complete' while ($DONE_ARCHIVE == 0) - set nh0 = `ls -l $WDIR/*clm?.h0.* | egrep -c '^-'` + set nh0 = `ls -l $WDIR/*clm2.{$HIST_EXT}.* | egrep -c '^-'` echo $nh0 if ($nh0 == 1) then set DONE_ARCHIVE = 1 @@ -152,12 +173,12 @@ end ######################################################################################### # # This portion checks to see if the 1871-1900 portion of the run is done (or it waits -# 10 minutes before checking again). It then removes (or rather moves and renames) the -# datm files so that the model will use the full array of data from 1901-2014. -# This part runs with forcing data files that actually exist for 1901-2014 +# 10 minutes before checking again). It then sets DATM_SKIP_RESTART_READ to TRUE +# so that the model will use the full array of forcing data from 1901-2023. # -# This will start the run from 1901 (hence the CONTINUE_RUN=TRUE) and do four 22 year -# simulations: 1901 + 4*22 - 1 = 1988 (minus 1 because we do 1901) +# This will start the run from 1901 (hence the CONTINUE_RUN=TRUE) and do three 26 year +# simulations: 1901 + 3*26 - 1 = 1978 (minus 1 because we do 1901) +# This will give us a restart file at the beginning of 1979. # # The new values for env_run.xml are put in place # Then submit the job @@ -165,8 +186,7 @@ end ######################################################################################### -set WDIR = '/glade/scratch/'$USER'/'$CASENAME'/run/' -set DDIR = $WDIR'restart_dump/' +set WDIR = '/glade/derecho/scratch/'$USER'/'$CASENAME'/run/' set DONE_RUNA = 0 set DONE_ARCHIVE = 0 set RESTART_FILE = $WDIR$CASENAME'.clm2.r.1901-01-01-00000.nc' @@ -177,7 +197,7 @@ while ($DONE_RUNA == 0) set DONE_RUNA = 1 echo '1850-1900 run is complete' while ($DONE_ARCHIVE == 0) - set nh0 = `ls -l $WDIR/*clm?.h0.* | egrep -c '^-'` + set nh0 = `ls -l $WDIR/*clm2.{$HIST_EXT}.* | egrep -c '^-'` echo $nh0 if ($nh0 == 1) then set DONE_ARCHIVE = 1 @@ -192,25 +212,16 @@ while ($DONE_RUNA == 0) endif end -# --- If the first two sets of simulations are done, move the datm files and compress them -if (! -d $DDIR) then - mkdir $DDIR -endif -mv -i $WDIR$CASENAME.datm.rs1*.bin $DDIR -gzip $DDIR$CASENAME*.bin - -# Since this particular run won't go for 55 years in 12 hours, do this in four 22 year chunks, thus -# we have to resubmit the job 3 times. +# Since this particular run won't go for 78 years in 12 hours, do this in three 26 year chunks, thus +# we have to resubmit the job 2 times. +./xmlchange DATM_SKIP_RESTART_READ=TRUE ./xmlchange STOP_OPTION=nyears -./xmlchange STOP_N=22 +./xmlchange STOP_N=26 ./xmlchange DATM_YR_ALIGN=1901 ./xmlchange DATM_YR_START=1901 -./xmlchange DATM_YR_END=2014 +./xmlchange DATM_YR_END=2023 ./xmlchange CONTINUE_RUN=TRUE -./xmlchange RESUBMIT=3 - -# need to use user_nl_datm files to get years right -cp user_nl_datm1901-2014 user_nl_datm +./xmlchange RESUBMIT=2 # --- Check that you end up using the correct env_run.xml file set nenvr = `ls -1 env_run*.xml | wc -l` @@ -227,22 +238,23 @@ endif # PART 4 ######################################################################################### # -# This portion checks to see if the 1901-1988 part of the run is complete -# and then runs the model for 1989-2004 as a branch run to get daily output +# This portion checks to see if the 1901-1978 part of the run is complete +# and then runs the model for 1979-1999 as a branch run to get daily output +# and to get a restart file at the beginning of 2000 # ######################################################################################### set DONE_RUNA = 0 set DONE_ARCHIVE = 0 -set RESTART_FILE = $WDIR$CASENAME'.clm2.r.1989-01-01-00000.nc' +set RESTART_FILE = $WDIR$CASENAME'.clm2.r.1979-01-01-00000.nc' # --- Check if the second set of simulations have completed and the data archived (every ten minutes) while ($DONE_RUNA == 0) if (-e $RESTART_FILE) then set DONE_RUNA = 1 - echo '1901-1989 run is complete' + echo '1901-1978 run is complete' while ($DONE_ARCHIVE == 0) - set nh0 = `ls -l $WDIR/*clm?.h0.* | egrep -c '^-'` + set nh0 = `ls -l $WDIR/*clm2.{$HIST_EXT}.* | egrep -c '^-'` echo $nh0 if ($nh0 == 1) then set DONE_ARCHIVE = 1 @@ -260,9 +272,9 @@ end # --- Ensure that the env_run.xml file has the correct content ./xmlchange RUN_TYPE=branch ./xmlchange RUN_REFCASE={$CASENAME} -./xmlchange RUN_REFDATE=1989-01-01 +./xmlchange RUN_REFDATE=1979-01-01 ./xmlchange STOP_OPTION=nyears -./xmlchange STOP_N=16 +./xmlchange STOP_N=21 ./xmlchange CONTINUE_RUN=FALSE ./xmlchange RESUBMIT=0 @@ -280,22 +292,22 @@ cat user_nl_clm_histdaily >> user_nl_clm # PART 5 ######################################################################################### # -# This portion checks to see if the 1989-2004 part of the run is complete -# and then runs the model for 2005-2014 as a branch run to get daily and subdaily output +# This portion checks to see if the 1979-1999 part of the run is complete +# and then runs the model for 2000-2023 as a branch run to get daily and subdaily output # ######################################################################################### set DONE_RUNA = 0 set DONE_ARCHIVE = 0 -set RESTART_FILE = $WDIR$CASENAME'.clm2.r.2005-01-01-00000.nc' +set RESTART_FILE = $WDIR$CASENAME'.clm2.r.2000-01-01-00000.nc' # --- Check if the second set of simulations have completed and the data archived (every ten minutes) while ($DONE_RUNA == 0) if (-e $RESTART_FILE) then set DONE_RUNA = 1 - echo '1989-2004 run is complete' + echo '1979-1999 run is complete' while ($DONE_ARCHIVE == 0) - set nh0 = `ls -l $WDIR/*clm?.h0.* | egrep -c '^-'` + set nh0 = `ls -l $WDIR/*clm2.{$HIST_EXT}.* | egrep -c '^-'` echo $nh0 if ($nh0 == 1) then set DONE_ARCHIVE = 1 @@ -313,9 +325,9 @@ end # --- Ensure that the env_run.xml file has the correct content ./xmlchange RUN_TYPE=branch ./xmlchange RUN_REFCASE={$CASENAME} -./xmlchange RUN_REFDATE=2005-01-01 +./xmlchange RUN_REFDATE=2000-01-01 ./xmlchange STOP_OPTION=nyears -./xmlchange STOP_N=10 +./xmlchange STOP_N=24 ./xmlchange CONTINUE_RUN=FALSE ./xmlchange RESUBMIT=0 @@ -334,22 +346,22 @@ cat user_nl_clm_histsubdaily >> user_nl_clm ######################################################################################### # -# This portion checks to see if the 2005-2014 part of the run is complete +# This portion checks to see if the 2005-2023 part of the run is complete # and ends the script # ######################################################################################### set DONE_RUNA = 0 set DONE_ARCHIVE = 0 -set RESTART_FILE = $WDIR$CASENAME'.clm2.r.2015-01-01-00000.nc' +set RESTART_FILE = $WDIR$CASENAME'.clm2.r.2024-01-01-00000.nc' # --- Check if the second set of simulations have completed and the data archived (every ten minutes) while ($DONE_RUNA == 0) if (-e $RESTART_FILE) then set DONE_RUNA = 1 - echo '2005-2014 run is complete' + echo '2005-2023 run is complete' while ($DONE_ARCHIVE == 0) - set nh0 = `ls -l $WDIR/*clm?.h0.* | egrep -c '^-'` + set nh0 = `ls -l $WDIR/*clm2.{$HIST_EXT}.* | egrep -c '^-'` echo $nh0 if ($nh0 == 1) then set DONE_ARCHIVE = 1 diff --git a/tools/mksurfdata_esmf/Makefile b/tools/mksurfdata_esmf/Makefile index 936fb7da78..835f732a02 100644 --- a/tools/mksurfdata_esmf/Makefile +++ b/tools/mksurfdata_esmf/Makefile @@ -1,5 +1,7 @@ # -*- mode:Makefile -*- # +# Running with this Makefile requires that you build the code first. +# # Before running "make urban-alpha" or any target that includes it, # execute "module load nco" first. # @@ -28,14 +30,20 @@ # Set up special characters null := +# Path to the executable +MKSURFDATA_EXE = tool_bld/mksurfdata_esmf + # Set a few things needed for batch handling -PROJECT = $(shell cat $(HOME)/.cesm_proj) +PROJECT := ${PROJECT} +ifeq ($(PROJECT),$(null)) + PROJECT = $(shell cat $(HOME)/.cesm_proj) +endif LOGOUT = $@.stdout.txt PWD = $(shell pwd) BATCHJOBS_ch = qsub ifeq ($(PROJECT),$(null)) - $(error Can NOT find PROJECT number from ~/.cesm_proj file create it and try again) + $(error Can't get project number from either PROJECT environment variable or ~/.cesm_proj file. Try calling make like "PROJECT=P12345678 make ..." or put your project number in ~/.cesm_proj) endif BATCHJOBS = $(BATCHJOBS_ch) @@ -104,6 +112,10 @@ CROP = \ crop-global-1850-ne30 \ crop-global-1850-mpasa480 \ +# Build the executable if it doesn't exist and any target depends on it +$(MKSURFDATA_EXE): + ./gen_mksurfdata_build + # Start all with all-subset because user is bound to forget to first run # module load nco # Usually, include global-present-ultra-hi-res temporarily while diff --git a/tools/mksurfdata_esmf/gen_mksurfdata_namelist.xml b/tools/mksurfdata_esmf/gen_mksurfdata_namelist.xml index d617fe1c49..a2266bf0a0 100644 --- a/tools/mksurfdata_esmf/gen_mksurfdata_namelist.xml +++ b/tools/mksurfdata_esmf/gen_mksurfdata_namelist.xml @@ -10,7 +10,7 @@ - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histMKSRFDeg025_240709/mksrf_landuse_ctsm53_pftlai_CLIM.c240709.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histMKSRFDeg025_240709/mksrf_landuse_ctsm53_pftlai_CLIM.c240709.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc @@ -21,7 +21,7 @@ - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histMKSRFDeg025_240709/mksrf_landuse_ctsm53_soilcolor_CLIM.c240709.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histMKSRFDeg025_240709/mksrf_landuse_ctsm53_soilcolor_CLIM.c240709.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc @@ -213,28 +213,28 @@ version of the raw dataset will probably go away. - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_1700.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_1700.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_1850.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/historical/urban_properties_GaoOneil_05deg_ThreeClass_1850_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_1850.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_1850.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_1850.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/historical/urban_properties_GaoOneil_05deg_ThreeClass_1850_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_2000.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_2000.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_2000.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/historical/urban_properties_GaoOneil_05deg_ThreeClass_2000_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_2005.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_2005.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_2005.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/historical/urban_properties_GaoOneil_05deg_ThreeClass_2005_cdf5_c20220910.nc @@ -253,7 +253,7 @@ version of the raw dataset will probably go away. - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53histTRENDY2024Deg025_240728/mksrf_landuse_ctsm53_histTRENDY2024_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/historical/urban_properties_GaoOneil_05deg_ThreeClass_%y_cdf5_c20220910.nc @@ -268,7 +268,7 @@ version of the raw dataset will probably go away. - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP126Deg025_240728/mksrf_landuse_ctsm53_TRSSP126_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP126Deg025_240728/mksrf_landuse_ctsm53_TRSSP126_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp1/urban_properties_GaoOneil_05deg_ThreeClass_ssp1_%y_cdf5_c20220910.nc @@ -281,21 +281,21 @@ version of the raw dataset will probably go away. - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP119Deg025_240728/mksrf_landuse_ctsm53_TRSSP119_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP119Deg025_240728/mksrf_landuse_ctsm53_TRSSP119_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp1/urban_properties_GaoOneil_05deg_ThreeClass_ssp1_%y_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP245Deg025_240728/mksrf_landuse_ctsm53_TRSSP245_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP245Deg025_240728/mksrf_landuse_ctsm53_TRSSP245_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp2/urban_properties_GaoOneil_05deg_ThreeClass_ssp2_%y_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP370Deg025_240728/mksrf_landuse_ctsm53_TRSSP370_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP370Deg025_240728/mksrf_landuse_ctsm53_TRSSP370_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp3/urban_properties_GaoOneil_05deg_ThreeClass_ssp3_%y_cdf5_c20220910.nc @@ -309,14 +309,14 @@ version of the raw dataset will probably go away. - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP460Deg025_240728/mksrf_landuse_ctsm53_TRSSP460_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP460Deg025_240728/mksrf_landuse_ctsm53_TRSSP460_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp4/urban_properties_GaoOneil_05deg_ThreeClass_ssp4_%y_cdf5_c20220910.nc - /glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP585Deg025_240728/mksrf_landuse_ctsm53_TRSSP585_%y.c240728.nc + lnd/clm2/rawdata/CTSM53RawData/globalctsm53TRSSP585Deg025_240728/mksrf_landuse_ctsm53_TRSSP585_%y.c240728.nc lnd/clm2/mappingdata/grids/UNSTRUCTgrid_0.25x0.25_nomask_cdf5_c200129.nc lnd/clm2/rawdata/lake_area/mksurf_lake_0.05x0.05_hist_clm5_hydrolakes_%y.cdf5.c20220325.nc lnd/clm2/rawdata/gao_oneill_urban/ssp5/urban_properties_GaoOneil_05deg_ThreeClass_ssp5_%y_cdf5_c20220910.nc diff --git a/tools/param_utils/query_paramfile b/tools/param_utils/query_paramfile new file mode 100755 index 0000000000..6bde5f09ec --- /dev/null +++ b/tools/param_utils/query_paramfile @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +""" +For description and instructions, please see README. +""" + +import os +import sys + +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), + os.pardir, + os.pardir, + 'python') +sys.path.insert(1, _CTSM_PYTHON) + +from ctsm.param_utils.query_paramfile import main + +if __name__ == "__main__": + main() + diff --git a/tools/param_utils/set_paramfile b/tools/param_utils/set_paramfile new file mode 100755 index 0000000000..cebdb81d30 --- /dev/null +++ b/tools/param_utils/set_paramfile @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +""" +For description and instructions, please see README. +""" + +import os +import sys + +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), + os.pardir, + os.pardir, + 'python') +sys.path.insert(1, _CTSM_PYTHON) + +from ctsm.param_utils.set_paramfile import main + +if __name__ == "__main__": + main() + diff --git a/tools/site_and_regional/PLUMBER2_sites.csv b/tools/site_and_regional/PLUMBER2_sites.csv index f252fa1d61..1097568051 100644 --- a/tools/site_and_regional/PLUMBER2_sites.csv +++ b/tools/site_and_regional/PLUMBER2_sites.csv @@ -2,6 +2,7 @@ #start_year and end_year will be used to define DATM_YR_ALIGH, DATM_YR_START and DATM_YR_END, and STOP_N in units of nyears. #RUN_STARTDATE and START_TOD are specified because we are starting at GMT corresponding to local midnight. #ATM_NCPL is specified so that the time step of the model matches the time interval specified by the atm forcing data. +#longitudes must be in the range [-180,180] ,Site,Lat,Lon,pft1,pft1-%,pft1-cth,pft1-cbh,pft2,pft2-%,pft2-cth,pft2-cbh,start_year,end_year,RUN_STARTDATE,START_TOD,ATM_NCPL 1,AR-SLu,-33.464802,-66.459808,5,50.00, 4.50, 0.13,7,50.00, 4.50, 2.59,2010,2010,2010-01-01,10800,48 2,AT-Neu,47.116669,11.317500,13,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2002,2012,2001-12-31,82800,48 @@ -73,7 +74,7 @@ 68,DK-Sor,55.485870,11.644640,7,100.00,25.00,14.37,-999,-999.00,-999.00,-999.00,1997,2014,1996-12-31,82800,48 69,DK-ZaH,74.473282,-20.550293,12,100.00, 0.47, 0.01,-999,-999.00,-999.00,-999.00,2000,2013,2000-01-01,0,48 70,ES-ES1,39.345970,-0.318817,1,100.00, 7.50, 3.75,-999,-999.00,-999.00,-999.00,1999,2006,1998-12-31,82800,48 -71,ES-ES2,39.275558,-0.315277,-999,-999.00,-999.00,-999.00,16,100.00, 0.50, 0.01,2005,2006,2004-12-31,82800,48 +71,ES-ES2,39.275558,-0.315277,16,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2005,2006,2004-12-31,82800,48 72,ES-LgS,37.097935,-2.965820,10,30.00, 0.20, 0.04,13,70.00, 0.50, 0.01,2007,2007,2006-12-31,82800,48 73,ES-LMa,39.941502,-5.773346,7,30.00, 8.00, 4.60,14,70.00, 0.50, 0.01,2004,2006,2003-12-31,82800,48 74,ES-VDA,42.152180, 1.448500,7,30.00, 0.50, 0.29,13,70.00, 0.50, 0.01,2004,2004,2003-12-31,82800,48 @@ -94,7 +95,7 @@ 89,IE-Ca1,52.858791,-6.918152,15,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2004,2006,2004-01-01,0,48 90,IE-Dri,51.986691,-8.751801,13,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2003,2005,2003-01-01,0,48 91,IT-Amp,41.904099,13.605160,13,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2003,2006,2002-12-31,82800,48 -92,IT-BCi,40.523800,14.957440,-999,-999.00,-999.00,-999.00,16,100.00, 0.50, 0.01,2005,2010,2004-12-31,82800,48 +92,IT-BCi,40.523800,14.957440,16,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2005,2010,2004-12-31,82800,48 93,IT-CA1,42.380409,12.026560,7,100.00, 5.50, 3.16,-999,-999.00,-999.00,-999.00,2012,2013,2011-12-31,82800,48 94,IT-CA2,42.377220,12.026040,15,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2012,2013,2011-12-31,82800,48 95,IT-CA3,42.380001,12.022200,7,100.00, 3.50, 2.01,-999,-999.00,-999.00,-999.00,2012,2013,2011-12-31,82800,48 @@ -151,8 +152,8 @@ 146,US-MMS,39.323200,-86.413086,7,100.00,27.00,15.52,-999,-999.00,-999.00,-999.00,1999,2014,1999-01-01,18000,24 147,US-MOz,38.744110,-92.200012,7,100.00,24.00,13.80,-999,-999.00,-999.00,-999.00,2005,2006,2005-01-01,21600,48 148,US-Myb,38.049801,-121.765106,13,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2011,2014,2011-01-01,28800,48 -149,US-Ne1,41.165100,-96.476593,-999,-999.00,-999.00,-999.00,16,100.00, 0.50, 0.01,2002,2012,2002-01-01,21600,24 -150,US-Ne2,41.164902,-96.470093,-999,-999.00,-999.00,-999.00,16,100.00, 0.50, 0.01,2002,2012,2002-01-01,21600,24 +149,US-Ne1,41.165100,-96.476593,16,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2002,2012,2002-01-01,21600,24 +150,US-Ne2,41.164902,-96.470093,16,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2002,2012,2002-01-01,21600,24 151,US-Ne3,41.179699,-96.439697,15,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2002,2012,2002-01-01,21600,24 152,US-NR1,40.032902,-105.546402,1,100.00,12.00, 6.00,-999,-999.00,-999.00,-999.00,1999,2014,1999-01-01,25200,48 153,US-PFa,45.945900,-90.272308,1, 8.18,30.00,15.00,7,91.82,30.00,17.25,1995,2014,1995-01-01,21600,24 @@ -165,7 +166,7 @@ 160,US-Syv,46.242001,-89.347717,1, 4.91,27.00,13.50,7,95.09,27.00,15.53,2002,2008,2002-01-01,21600,48 161,US-Ton,38.431599,-120.966003,7,70.00, 7.10, 4.08,14,30.00, 0.50, 0.01,2001,2014,2001-01-01,28800,48 162,US-Tw4,38.103001,-121.641403,13,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2014,2014,2014-01-01,28800,48 -163,US-Twt,38.108700,-121.653107,-999,-999.00,-999.00,-999.00,16,100.00, 0.50, 0.01,2010,2014,2010-01-01,28800,48 +163,US-Twt,38.108700,-121.653107,16,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2010,2014,2010-01-01,28800,48 164,US-UMB,45.559799,-84.713806,7,100.00,20.00,11.50,-999,-999.00,-999.00,-999.00,2000,2014,2000-01-01,18000,24 165,US-Var,38.413300,-120.950729,14,100.00, 0.50, 0.01,-999,-999.00,-999.00,-999.00,2001,2014,2001-01-01,28800,48 166,US-WCr,45.805901,-90.079895,7,100.00,24.00,13.80,-999,-999.00,-999.00,-999.00,1999,2006,1999-01-01,21600,48 diff --git a/tools/site_and_regional/default_data_1850.cfg b/tools/site_and_regional/default_data_1850.cfg index 3c9f28c0a2..ce68b1debf 100644 --- a/tools/site_and_regional/default_data_1850.cfg +++ b/tools/site_and_regional/default_data_1850.cfg @@ -1,7 +1,7 @@ [main] clmforcingindir = /glade/campaign/cesm/cesmdata/inputdata -[datm_crujra] +[datm] dir = atm/datm7/atm_forcing.datm7.CRUJRA.0.5d.c20241231/three_stream domain = domain.crujra_v2.3_0.5x0.5.c220801.nc solardir = . @@ -14,19 +14,6 @@ solarname = CLMCRUJRA2024.Solar precname = CLMCRUJRA2024.Precip tpqwname = CLMCRUJRA2024.TPQW -[datm_gswp3] -dir = atm/datm7/atm_forcing.datm7.GSWP3.0.5d.v1.c170516 -domain = domain.lnd.360x720_gswp3.0v1.c170606.nc -solardir = Solar -precdir = Precip -tpqwdir = TPHWL -solartag = clmforc.GSWP3.c2011.0.5x0.5.Solr. -prectag = clmforc.GSWP3.c2011.0.5x0.5.Prec. -tpqwtag = clmforc.GSWP3.c2011.0.5x0.5.TPQWL. -solarname = CLMGSWP3v1.Solar -precname = CLMGSWP3v1.Precip -tpqwname = CLMGSWP3v1.TPQW - [surfdat] dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 surfdat_78pft = surfdata_0.9x1.25_hist_1850_78pfts_c240908.nc diff --git a/tools/site_and_regional/default_data_2000.cfg b/tools/site_and_regional/default_data_2000.cfg index a832d810cc..60c012561c 100644 --- a/tools/site_and_regional/default_data_2000.cfg +++ b/tools/site_and_regional/default_data_2000.cfg @@ -1,7 +1,7 @@ [main] clmforcingindir = /glade/campaign/cesm/cesmdata/cseg/inputdata -[datm_crujra] +[datm] dir = atm/datm7/atm_forcing.datm7.CRUJRA.0.5d.c20241231/three_stream domain = domain.crujra_v2.3_0.5x0.5.c220801.nc solardir = . @@ -14,19 +14,6 @@ solarname = CLMCRUJRA2024.Solar precname = CLMCRUJRA2024.Precip tpqwname = CLMCRUJRA2024.TPQW -[datm_gswp3] -dir = atm/datm7/atm_forcing.datm7.GSWP3.0.5d.v1.c170516 -domain = domain.lnd.360x720_gswp3.0v1.c170606.nc -solardir = Solar -precdir = Precip -tpqwdir = TPHWL -solartag = clmforc.GSWP3.c2011.0.5x0.5.Solr. -prectag = clmforc.GSWP3.c2011.0.5x0.5.Prec. -tpqwtag = clmforc.GSWP3.c2011.0.5x0.5.TPQWL. -solarname = CLMGSWP3v1.Solar -precname = CLMGSWP3v1.Precip -tpqwname = CLMGSWP3v1.TPQW - [surfdat] dir = lnd/clm2/surfdata_esmf/ctsm5.3.0 surfdat_16pft = surfdata_0.9x1.25_hist_2000_16pfts_c240908.nc diff --git a/tools/site_and_regional/mesh_plotter b/tools/site_and_regional/mesh_plotter index 2f195a2b31..e1358dfc75 100755 --- a/tools/site_and_regional/mesh_plotter +++ b/tools/site_and_regional/mesh_plotter @@ -11,8 +11,8 @@ please check the python/ctsm/mesh_plotter.py file. ---------------------------------------------------------------- Instructions for running using conda python environments: -../../py_env_create --dask -conda activate ctsm_py_wdask +../../py_env_create +conda activate ctsm_pylib |------------------------------------------------------------------| |--------------------- Instructions -----------------------------| |------------------------------------------------------------------| diff --git a/tools/site_and_regional/neon_gcs_upload b/tools/site_and_regional/neon_gcs_upload index 1c931e3b8d..5c673a1963 100755 --- a/tools/site_and_regional/neon_gcs_upload +++ b/tools/site_and_regional/neon_gcs_upload @@ -154,7 +154,7 @@ def main(description): continue with Case(case_path) as case: archive_dir = os.path.join(case.get_value("DOUT_S_ROOT"),"lnd","hist") - for histfile in glob.iglob(archive_dir + "/*.h1.*"): + for histfile in glob.iglob(archive_dir + "/*.h1a.*"): newfile = os.path.basename(histfile) upload_blob("neon-ncar-artifacts", histfile, os.path.join("NEON","archive",site,"lnd","hist",newfile))