From dd73ec29897ae5c3c950981621f800e52161ca4c Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 10:18:33 -0600 Subject: [PATCH 01/27] add git-fleximod ci test --- .github/workflows/fleximod_test.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/fleximod_test.yaml diff --git a/.github/workflows/fleximod_test.yaml b/.github/workflows/fleximod_test.yaml new file mode 100644 index 00000000..24bcac95 --- /dev/null +++ b/.github/workflows/fleximod_test.yaml @@ -0,0 +1,24 @@ +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + fleximod-test: + runs-on: ubuntu-latest + strategy: + matrix: + # oldest supported and latest supported + python-version: ["3.7", "3.x"] + steps: + - id: checkout-CESM + uses: actions/checkout@v4 + - id: run-fleximod + run: | + $GITHUB_WORKSPACE/bin/git-fleximod update + $GITHUB_WORKSPACE/bin/git-fleximod test +# - name: Setup tmate session +# if: ${{ failure() }} +# uses: mxschmitt/action-tmate@v3 + + From 68a9ba8b520b0d61dd54d6c67625122a7a1bb624 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 10:22:29 -0600 Subject: [PATCH 02/27] point to correct branch --- .github/workflows/fleximod_test.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/fleximod_test.yaml b/.github/workflows/fleximod_test.yaml index 24bcac95..dde87d52 100644 --- a/.github/workflows/fleximod_test.yaml +++ b/.github/workflows/fleximod_test.yaml @@ -1,8 +1,6 @@ on: - push: - branches: [ master ] pull_request: - branches: [ master ] + branches: [ development ] jobs: fleximod-test: runs-on: ubuntu-latest From ba7746fd573066fbb0ee736a23f03c48a2654fa3 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 10:36:41 -0600 Subject: [PATCH 03/27] update to git-fleximod v8.3 --- .lib/git-fleximod/git_fleximod/cli.py | 2 +- .../git-fleximod/git_fleximod/git_fleximod.py | 15 +++++----- .lib/git-fleximod/git_fleximod/submodule.py | 29 +++++++++++++------ .lib/git-fleximod/pyproject.toml | 2 +- .lib/git-fleximod/tbump.toml | 2 +- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.lib/git-fleximod/git_fleximod/cli.py b/.lib/git-fleximod/git_fleximod/cli.py index fdcf102a..b7bc8078 100644 --- a/.lib/git-fleximod/git_fleximod/cli.py +++ b/.lib/git-fleximod/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.8.2" +__version__ = "0.8.3" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/.lib/git-fleximod/git_fleximod/git_fleximod.py b/.lib/git-fleximod/git_fleximod/git_fleximod.py index 4595cd2a..50e0ef83 100755 --- a/.lib/git-fleximod/git_fleximod/git_fleximod.py +++ b/.lib/git-fleximod/git_fleximod/git_fleximod.py @@ -195,18 +195,19 @@ def submodules_status(gitmodules, root_dir, toplevel=False, depth=0): submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) result,n,l,t = submod.status() - testfails += t - localmods += l - needsupdate += n if toplevel or not submod.toplevel(): print(wrapper.fill(result)) - subdir = os.path.join(root_dir, submod.path) - if os.path.exists(os.path.join(subdir, ".gitmodules")): - submod = GitModules(logger, confpath=subdir) - t,l,n = submodules_status(submod, subdir, depth=depth+1) testfails += t localmods += l needsupdate += n + subdir = os.path.join(root_dir, submod.path) + if os.path.exists(os.path.join(subdir, ".gitmodules")): + gsubmod = GitModules(logger, confpath=subdir) + t,l,n = submodules_status(gsubmod, subdir, depth=depth+1) + if toplevel or not submod.toplevel(): + testfails += t + localmods += l + needsupdate += n return testfails, localmods, needsupdate diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index 48657272..52633c7c 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -92,7 +92,7 @@ def status(self): needsupdate = True else: result = f"e {self.name:>20} has no fxtag defined in .gitmodules{optional}" - testfails = True + testfails = False else: with utils.pushd(smpath): git = GitInterface(smpath, self.logger) @@ -106,10 +106,16 @@ def status(self): line = git.git_operation("log", "--pretty=format:\"%h %d").partition('\n')[0] parts = line.split() ahash = parts[0][1:] + atag = None if len(parts) > 3: - atag = parts[3][:-1] - else: - atag = None + idx = 0 + while idx < len(parts)-1: + idx = idx+1 + if parts[idx] == 'tag:': + atag = parts[idx+1][:-1] + if atag == self.fxtag: + break + #print(f"line is {line} ahash is {ahash} atag is {atag}") # atag = git.git_operation("describe", "--tags", "--always").rstrip() @@ -122,9 +128,11 @@ def status(self): if self.fxtag and atag == self.fxtag: result = f" {self.name:>20} at tag {self.fxtag}" recurse = True + testfails = False elif self.fxtag and ahash[: len(self.fxtag)] == self.fxtag: result = f" {self.name:>20} at hash {ahash}" recurse = True + testfails = False elif atag == ahash: result = f" {self.name:>20} at hash {ahash}" recurse = True @@ -133,14 +141,17 @@ def status(self): testfails = True needsupdate = True else: - result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" - testfails = True - + if atag: + result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" + else: + result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {ahash}" + testfails = False + status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: localmods = True - result = "M" + textwrap.indent(status, " ") - + result = "M" + textwrap.indent(status, " " +# print(f"result {result} needsupdate {needsupdate} localmods {localmods} testfails {testfails}") return result, needsupdate, localmods, testfails diff --git a/.lib/git-fleximod/pyproject.toml b/.lib/git-fleximod/pyproject.toml index 9cff1423..7b0354bd 100644 --- a/.lib/git-fleximod/pyproject.toml +++ b/.lib/git-fleximod/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.8.2" +version = "0.8.3" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/.lib/git-fleximod/tbump.toml b/.lib/git-fleximod/tbump.toml index b4eed7d4..3b6813a2 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.8.2" +current = "0.8.3" # Example of a semver regexp. # Make sure this matches current_version before From 7fd861ce1fb5961c642656b3a282273e03c4070f Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 10:37:55 -0600 Subject: [PATCH 04/27] fix indent --- .lib/git-fleximod/git_fleximod/submodule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index 52633c7c..5d92f6a9 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -141,7 +141,7 @@ def status(self): testfails = True needsupdate = True else: - if atag: + if atag: result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" else: result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {ahash}" From d17ecdd0b583f77fa96312bd0d2f7f0d7376e753 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 10:41:51 -0600 Subject: [PATCH 05/27] fix unclosed parenthesis --- .lib/git-fleximod/git_fleximod/submodule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index 5d92f6a9..b5918de8 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -150,7 +150,7 @@ def status(self): status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: localmods = True - result = "M" + textwrap.indent(status, " " + result = "M" + textwrap.indent(status, " ") # print(f"result {result} needsupdate {needsupdate} localmods {localmods} testfails {testfails}") return result, needsupdate, localmods, testfails From 5194016a4d62d93f064fb0d69daaa51aeb43324b Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 12:05:35 -0600 Subject: [PATCH 06/27] fix from jim --- .lib/git-fleximod/git_fleximod/submodule.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.lib/git-fleximod/git_fleximod/submodule.py b/.lib/git-fleximod/git_fleximod/submodule.py index b5918de8..70a3018a 100644 --- a/.lib/git-fleximod/git_fleximod/submodule.py +++ b/.lib/git-fleximod/git_fleximod/submodule.py @@ -103,7 +103,7 @@ def status(self): needsupdate = True return result, needsupdate, localmods, testfails rurl = git.git_operation("ls-remote","--get-url").rstrip() - line = git.git_operation("log", "--pretty=format:\"%h %d").partition('\n')[0] + line = git.git_operation("log", "--pretty=format:\"%h %d\"").partition('\n')[0] parts = line.split() ahash = parts[0][1:] atag = None @@ -112,12 +112,14 @@ def status(self): while idx < len(parts)-1: idx = idx+1 if parts[idx] == 'tag:': - atag = parts[idx+1][:-1] + atag = parts[idx+1] + while atag.endswith(')') or atag.endswith(',') or atag.endswith("\""): + atag = atag[:-1] if atag == self.fxtag: break - #print(f"line is {line} ahash is {ahash} atag is {atag}") + #print(f"line is {line} ahash is {ahash} atag is {atag} {parts}") # atag = git.git_operation("describe", "--tags", "--always").rstrip() # ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] @@ -129,7 +131,7 @@ def status(self): result = f" {self.name:>20} at tag {self.fxtag}" recurse = True testfails = False - elif self.fxtag and ahash[: len(self.fxtag)] == self.fxtag: + 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 @@ -146,7 +148,7 @@ def status(self): else: result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {ahash}" testfails = False - + status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: localmods = True From 2c50bb0742d03f2fc929fc9030be5e7598ee6d5a Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 25 Jul 2024 13:13:58 -0600 Subject: [PATCH 07/27] update to v8.4 --- .lib/git-fleximod/git_fleximod/cli.py | 2 +- .lib/git-fleximod/pyproject.toml | 2 +- .lib/git-fleximod/tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.lib/git-fleximod/git_fleximod/cli.py b/.lib/git-fleximod/git_fleximod/cli.py index b7bc8078..b6f728f8 100644 --- a/.lib/git-fleximod/git_fleximod/cli.py +++ b/.lib/git-fleximod/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.8.3" +__version__ = "0.8.4" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/.lib/git-fleximod/pyproject.toml b/.lib/git-fleximod/pyproject.toml index 7b0354bd..850e57d5 100644 --- a/.lib/git-fleximod/pyproject.toml +++ b/.lib/git-fleximod/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.8.3" +version = "0.8.4" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/.lib/git-fleximod/tbump.toml b/.lib/git-fleximod/tbump.toml index 3b6813a2..bd82c557 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.8.3" +current = "0.8.4" # Example of a semver regexp. # Make sure this matches current_version before From 9fd05284f6b52f9954fd4a551977572ac719d0b4 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 12 Aug 2024 09:53:10 -0600 Subject: [PATCH 08/27] update workflow, use python 3.8 as oldest version --- .github/workflows/fleximod_test.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/fleximod_test.yaml b/.github/workflows/fleximod_test.yaml index dde87d52..36e68b19 100644 --- a/.github/workflows/fleximod_test.yaml +++ b/.github/workflows/fleximod_test.yaml @@ -1,13 +1,12 @@ on: pull_request: - branches: [ development ] jobs: fleximod-test: runs-on: ubuntu-latest strategy: matrix: # oldest supported and latest supported - python-version: ["3.7", "3.x"] + python-version: ["3.8", "3.x"] steps: - id: checkout-CESM uses: actions/checkout@v4 @@ -15,8 +14,5 @@ jobs: run: | $GITHUB_WORKSPACE/bin/git-fleximod update $GITHUB_WORKSPACE/bin/git-fleximod test -# - name: Setup tmate session -# if: ${{ failure() }} -# uses: mxschmitt/action-tmate@v3 From d68c35ec290a8d2e9620fecc60e30f76f8a1f25e Mon Sep 17 00:00:00 2001 From: Kuan-Chih Wang Date: Wed, 31 Jul 2024 20:01:40 -0600 Subject: [PATCH 09/27] Upgrade MPAS to version 8.2.1 --- .gitmodules | 2 +- src/dynamics/mpas/dycore | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 787b14c9..15bf89f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,7 +8,7 @@ path = src/dynamics/mpas/dycore url = https://github.com/MPAS-Dev/MPAS-Model.git fxsparse = ../.mpas_sparse_checkout - fxtag = ed6f8e39ec0a811b6d079ca0fc6f9fb6e30bad23 + fxtag = v8.2.1 fxrequired = AlwaysRequired fxDONOTUSEurl = https://github.com/MPAS-Dev/MPAS-Model.git [submodule "ncar-physics"] diff --git a/src/dynamics/mpas/dycore b/src/dynamics/mpas/dycore index ed6f8e39..b566fc8a 160000 --- a/src/dynamics/mpas/dycore +++ b/src/dynamics/mpas/dycore @@ -1 +1 @@ -Subproject commit ed6f8e39ec0a811b6d079ca0fc6f9fb6e30bad23 +Subproject commit b566fc8a959390d838aba08fd03c81edae986f6a From eebbb4b29ad6cc07218a81b4b9383e4920faf583 Mon Sep 17 00:00:00 2001 From: Kuan-Chih Wang Date: Fri, 7 Jun 2024 15:14:02 -0600 Subject: [PATCH 10/27] Support MPAS version 8.2.1 in subdriver Additionally, MPI Fortran 2008 interface is now supported through the `mpi_f08` module. It can be enabled by defining the `MPAS_USE_MPI_F08` macro in `CPPFLAGS`. --- .../mpas/driver/dyn_mpas_subdriver.F90 | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 index b21cdcfe..5c9be0f4 100644 --- a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 +++ b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 @@ -10,7 +10,12 @@ module dyn_mpas_subdriver use, intrinsic :: iso_fortran_env, only: output_unit ! Modules from external libraries. +#ifdef MPAS_USE_MPI_F08 + use mpi_f08, only: mpi_comm_null, mpi_comm_rank, mpi_success, & + mpi_comm_type => mpi_comm, operator(==), operator(/=) +#else use mpi, only: mpi_comm_null, mpi_comm_rank, mpi_success +#endif use pio, only: pio_char, pio_int, pio_real, pio_double, & file_desc_t, iosystem_desc_t, pio_file_is_open, pio_iosystem_is_active, & pio_inq_varid, pio_inq_varndims, pio_inq_vartype, pio_noerr @@ -47,6 +52,7 @@ module dyn_mpas_subdriver mpas_pool_add_dimension, mpas_pool_get_dimension, & mpas_pool_get_field, mpas_pool_get_field_info, & mpas_pool_initialize_time_levels + use mpas_stream_inquiry, only: mpas_stream_inquiry_new_streaminfo use mpas_stream_manager, only: postread_reindex, prewrite_reindex, postwrite_reindex use mpas_string_utils, only: mpas_string_replace use mpas_timekeeping, only: mpas_get_clock_time, mpas_get_time @@ -76,7 +82,11 @@ end subroutine model_error_if ! Initialized by `dyn_mpas_init_phase1`. integer :: log_unit = output_unit +#ifdef MPAS_USE_MPI_F08 + type(mpi_comm_type) :: mpi_comm = mpi_comm_null +#else integer :: mpi_comm = mpi_comm_null +#endif integer :: mpi_rank = 0 logical :: mpi_rank_root = .false. @@ -447,7 +457,11 @@ end function stringify !------------------------------------------------------------------------------- subroutine dyn_mpas_init_phase1(self, mpi_comm, model_error_impl, log_unit, mpas_log_unit) class(mpas_dynamical_core_type), intent(inout) :: self +#ifdef MPAS_USE_MPI_F08 + type(mpi_comm_type), intent(in) :: mpi_comm +#else integer, intent(in) :: mpi_comm +#endif procedure(model_error_if) :: model_error_impl integer, intent(in) :: log_unit integer, intent(in) :: mpas_log_unit(2) @@ -503,7 +517,7 @@ subroutine dyn_mpas_init_phase1(self, mpi_comm, model_error_impl, log_unit, mpas call self % debug_print('Calling mpas_framework_init_phase1') ! Initialize MPAS framework with supplied MPI communicator group. - call mpas_framework_init_phase1(self % domain_ptr % dminfo, mpi_comm=self % mpi_comm) + call mpas_framework_init_phase1(self % domain_ptr % dminfo, external_comm=self % mpi_comm) call self % debug_print('Setting up core') @@ -706,6 +720,15 @@ subroutine dyn_mpas_init_phase2(self, pio_iosystem) ! Initialize MPAS framework with supplied PIO system descriptor. call mpas_framework_init_phase2(self % domain_ptr, io_system=pio_iosystem) + ! Instantiate `streaminfo` but do not actually initialize it. Any queries made to it will always return `.false.`. + ! This is the intended behavior because MPAS as a dynamical core is not responsible for managing IO. + self % domain_ptr % streaminfo => mpas_stream_inquiry_new_streaminfo() + + if (.not. associated(self % domain_ptr % streaminfo)) then + call self % model_error('Stream info instantiation failed for core ' // trim(self % domain_ptr % core % corename), & + subname, __LINE__) + end if + ierr = self % domain_ptr % core % define_packages(self % domain_ptr % packages) if (ierr /= 0) then @@ -714,7 +737,8 @@ subroutine dyn_mpas_init_phase2(self, pio_iosystem) end if ierr = self % domain_ptr % core % setup_packages( & - self % domain_ptr % configs, self % domain_ptr % packages, self % domain_ptr % iocontext) + self % domain_ptr % configs, self % domain_ptr % streaminfo, & + self % domain_ptr % packages, self % domain_ptr % iocontext) if (ierr /= 0) then call self % model_error('Package setup failed for core ' // trim(self % domain_ptr % core % corename), & From 35ca3b03c3725d9414fb8ac6aa3cf6f6732d1ed9 Mon Sep 17 00:00:00 2001 From: Kuan-Chih Wang Date: Mon, 15 Apr 2024 15:18:36 -0600 Subject: [PATCH 11/27] Set the git version string of MPAS to a more descriptive value Before, it would always show "N/A" in the log, which is not very helpful. Now, if the git submodule of MPAS is aligned at a tag, it will just show the tag name (e.g., "v8.2.1"). Otherwise, if the git submodule of MPAS is not aligned at a tag, it will show the nearest tag name, the number of commits after that tag and the abbreviated commit hash (e.g., "v8.0.1-54-ged6f8e39e"). --- src/dynamics/mpas/Makefile.in.CESM | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/dynamics/mpas/Makefile.in.CESM b/src/dynamics/mpas/Makefile.in.CESM index f4016f0e..1b6cf71c 100644 --- a/src/dynamics/mpas/Makefile.in.CESM +++ b/src/dynamics/mpas/Makefile.in.CESM @@ -4,6 +4,15 @@ ifeq ($(strip $(LIBROOT)),) LIBROOT = .. endif +ifeq ($(strip $(SRCROOT)),) + $(warning `SRCROOT` should not be empty. Defaulting to `..`) + + SRCROOT = .. + MPAS_SRC_ROOT = $(SRCROOT) +else + MPAS_SRC_ROOT = $(SRCROOT)/src/dynamics/mpas/dycore +endif + # # Define and export variables used by MPAS build infrastructure. # @@ -19,7 +28,7 @@ export BUILD_TARGET = N/A export CORE = atmosphere export EXE_NAME = atmosphere_model export GEN_F90 = false -export GIT_VERSION = N/A +export GIT_VERSION = $(shell git -C "$(MPAS_SRC_ROOT)" describe --always --dirty --tags || echo "N/A") export NAMELIST_SUFFIX = atmosphere # Customize variables (e.g., build options) for use with CESM. @@ -57,9 +66,9 @@ all: @echo 'Users are responsible to provide all necessary build options via environment variables or command line arguments.' @echo '' @echo 'Usage hints:' - @echo ' `make libmpas-prepare ESM="CESM" LIBROOT="..."`' - @echo ' `make libmpas-build ESM="CESM" LIBROOT="..."`' - @echo ' `make libmpas-clean ESM="CESM" LIBROOT="..."`' + @echo ' `make libmpas-prepare ESM="CESM" LIBROOT="..." SRCROOT="..."`' + @echo ' `make libmpas-build ESM="CESM" LIBROOT="..." SRCROOT="..."`' + @echo ' `make libmpas-clean ESM="CESM" LIBROOT="..." SRCROOT="..."`' .PHONY: libmpas-prepare libmpas-prepare: libmpas-archiver-script.txt libmpas-no-physics libmpas-prefix-namelist-groups libmpas-preview From de20b9704d5008789455ccdd9ac66d35b8d26649 Mon Sep 17 00:00:00 2001 From: Kuan-Chih Wang Date: Sat, 13 Jul 2024 04:00:37 -0600 Subject: [PATCH 12/27] Only expose necessary Fortran modules to CAM-SIMA --- src/dynamics/mpas/Makefile.in.CESM | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dynamics/mpas/Makefile.in.CESM b/src/dynamics/mpas/Makefile.in.CESM index 1b6cf71c..8e4ba049 100644 --- a/src/dynamics/mpas/Makefile.in.CESM +++ b/src/dynamics/mpas/Makefile.in.CESM @@ -125,7 +125,6 @@ $(LIBROOT)/libmpas.a: libmpas.a libmpas.a: $(AUTOCLEAN_DEPS) dycore externals frame ops subdrv $(AR) $(ARFLAGS) < libmpas-archiver-script.txt - @find -P . -name "*.mod" -type f -exec $(LN) "{}" . ";" .PHONY: libmpas-clean libmpas-clean: clean @@ -140,4 +139,5 @@ subdrv: driver/dyn_mpas_subdriver.o %.o: %.F90 dycore frame ops ( cd $( Date: Mon, 19 Aug 2024 15:05:55 -0600 Subject: [PATCH 13/27] Modify issue-closing script to work without projects or standard merge messages. --- .github/scripts/branch_pr_issue_closer.py | 461 ++++++--------------- .github/workflows/branch_push_workflow.yml | 8 +- 2 files changed, 130 insertions(+), 339 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 1ad48ebe..fc4a7087 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python """ Script name: branch_PR_issue_closer.py @@ -21,52 +21,15 @@ import re import sys -import subprocess -import shlex import argparse from github import Github +from github import Auth ################# #HELPER FUNCTIONS ################# -#+++++++++++++++++++++++++++++++++++++++++ -#Curl command needed to move project cards -#+++++++++++++++++++++++++++++++++++++++++ - -def project_card_move(oa_token, column_id, card_id): - - """ - Currently pyGithub doesn't contain the methods required - to move project cards from one column to another, so - the unix curl command must be called directly, which is - what this function does. - - The specific command-line call made is: - - curl -H "Authorization: token OA_token" -H \ - "Accept: application/vnd.github.inertia-preview+json" \ - -X POST -d '{"position":"top", "column_id":}' \ - https://api.github.com/projects/columns/cards//moves - - """ - - #create required argument strings from inputs: - github_oa_header = f''' "Authorization: token {oa_token}" ''' - github_url_str = f'''https://api.github.com/projects/columns/cards/{card_id}/moves''' - json_post_inputs = f''' '{{"position":"top", "column_id":{column_id}}}' ''' - - #Create curl command line string: - curl_cmdline = '''curl -H '''+github_oa_header+''' -H "Accept: application/vnd.github.inertia-preview+json" -X POST -d '''+\ - json_post_inputs+''' '''+github_url_str - - #Split command line string into argument list: - curl_arg_list = shlex.split(curl_cmdline) - - #Run command using subprocess: - subprocess.run(curl_arg_list, check=True) - #++++++++++++++++++++++++++++++ #Input Argument parser function #++++++++++++++++++++++++++++++ @@ -135,176 +98,162 @@ def _main_prog(): #Log-in to github API using token #++++++++++++++++++++++++++++++++ - ghub = Github(token) + auth = Auth.Token(token) + ghub = Github(auth=auth) - #+++++++++++++++++++++ + #+++++++++++++++++++++++++ #Open ESCOMP/CAM-SIMA repo - #+++++++++++++++++++++ + #+++++++++++++++++++++++++ cam_repo = ghub.get_repo("ESCOMP/CAM-SIMA") - #+++++++++++++++++++++++++++++ - #Get triggering commit message - #+++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++ + #Get PRs associated with commit + #++++++++++++++++++++++++++++++ github_commit = cam_repo.get_commit(trigger_sha) - commit_message = github_commit.commit.message + commit_prs = github_commit.get_pulls() - #+++++++++++++++++++++++++++++++ - #Search for github PR merge text - #+++++++++++++++++++++++++++++++ + pr_nums = [pr.number for pr in commit_prs] - #Compile Pull Request merge text expression: - pr_merge_pattern = re.compile(r'Merge pull request ') - #Search for merge text, starting at beginning of message: - commit_msg_match = pr_merge_pattern.match(commit_message) + #If list is empty, then no PRs are associated + #with this commit, so go ahead and close: + if not pr_nums: + endmsg = f"No PRs associated with commit:\n{trigger_sha}\n" + endmsg += " so issue-closing script is stopping here." + end_script(endmsg) - #Check if match exists: - if commit_msg_match is not None: - #If it does then pull out text immediately after message: - post_msg_text = commit_message[commit_msg_match.end():] + #++++++++++++++++++++++++++++ + #Loop over all associated PRs + #++++++++++++++++++++++++++++ - #Split text into individual words: - post_msg_word_list = post_msg_text.split() + for pr_num in pr_nums: - #Extract first word: - first_word = post_msg_word_list[0] + #+++++++++++++++++++++++++++++++++++++ + #Check that PR has in fact been merged + #+++++++++++++++++++++++++++++++++++++ - #Print merged pr number to screen: - print(f"Merged PR: {first_word}") + #Extract pull request info: + merged_pull = cam_repo.get_pull(pr_num) - try: - #Try assuming the word is just a number: - pr_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If the conversion fails, then this is likely not a real PR merge, so end the script: - endmsg = "No Pull Request number was found in the commit message, so there is nothing for the script to do." + #If pull request has not been merged, then exit script: + if not merged_pull.merged: + endmsg = f"Pull request associated with commit:\n{trigger_sha}\n" + endmsg += "was not actually merged, so the script will not close anything." end_script(endmsg) - else: - endmsg = "This push commit does not appear to be a merged pull request, so the script will do nothing." - end_script(endmsg) - - #+++++++++++++++++++++++++++++++++++++ - #Check that PR has in fact been merged - #+++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++++ + #Check that PR was not for default branch + #++++++++++++++++++++++++++++++++++++++++ - #Extract pull request info: - merged_pull = cam_repo.get_pull(pr_num) + #Determine default branch on repo: + default_branch = cam_repo.default_branch - #If pull request has not been merged, then exit script: - if not merged_pull.merged: - endmsg = "Pull request in commit message was not actually merged, so the script will not close anything." - end_script(endmsg) - - #++++++++++++++++++++++++++++++++++++++++ - #Check that PR was not for default branch - #++++++++++++++++++++++++++++++++++++++++ - - #Determine default branch on repo: - default_branch = cam_repo.default_branch - - #Extract merged branch from latest Pull request: - merged_branch = merged_pull.base.ref + #Extract merged branch from latest Pull request: + merged_branch = merged_pull.base.ref - #If PR was to default branch, then exit script (as github will handle it automatically): - if merged_branch == default_branch: - endmsg = "Pull request ws merged into default repo branch. Thus issue is closed automatically" - end_script(endmsg) + #If PR was to default branch, then exit script (as github will handle it automatically): + if merged_branch == default_branch: + endmsg = "Pull request was merged into default repo branch. " + endmsg += "Thus issue is closed automatically" + end_script(endmsg) - #++++++++++++++++++++++++++++++++++++++ - #Create integer list of all open issues: - #++++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++ + #Create integer list of all open issues: + #++++++++++++++++++++++++++++++++++++++ - #Extract list of open issues from repo: - open_repo_issues = cam_repo.get_issues(state='open') + #Extract list of open issues from repo: + open_repo_issues = cam_repo.get_issues(state='open') - #Collect all open repo issues: - open_issues = [issue.number for issue in open_repo_issues] + #Collect all open repo issues: + open_issues = [issue.number for issue in open_repo_issues] - #+++++++++++++++++++++++++++++++++++++++++++++ - #Create integer list of all open pull requests - #+++++++++++++++++++++++++++++++++++++++++++++ + #+++++++++++++++++++++++++++++++++++++++++++++ + #Create integer list of all open pull requests + #+++++++++++++++++++++++++++++++++++++++++++++ - #Extract list of open PRs from repo: - open_repo_pulls = cam_repo.get_pulls(state='open') + #Extract list of open PRs from repo: + open_repo_pulls = cam_repo.get_pulls(state='open') - #Collect all open pull requests: - open_pulls = [pr.number for pr in open_repo_pulls] + #Collect all open pull requests: + open_pulls = [pr.number for pr in open_repo_pulls] - #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Check if one of the keywords exists in PR message - #+++++++++++++++++++++++++++++++++++++++++++++++++ + #+++++++++++++++++++++++++++++++++++++++++++++++++ + #Check if one of the keywords exists in PR message + #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Keywords are: - #close, closes, closed - #fix, fixes, fixed - #resolve, resolves, resolved + #Keywords are: + #close, closes, closed + #fix, fixes, fixed + #resolve, resolves, resolved - #Create regex pattern to find keywords: - keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') + #Create regex pattern to find keywords: + keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') - #Extract (lower case) Pull Request message: - pr_msg_lower = merged_pull.body.lower() + #Extract (lower case) Pull Request message: + pr_msg_lower = merged_pull.body.lower() - #search for at least one keyword: - if keyword_pattern.search(pr_msg_lower) is not None: - #If at least one keyword is found, then determine location of every keyword instance: - word_matches = keyword_pattern.finditer(pr_msg_lower) - else: - endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." - end_script(endmsg) + #search for at least one keyword: + word_matches = [] + if keyword_pattern.search(pr_msg_lower) is not None: + #If at least one keyword is found, then determine location of every keyword instance: + word_matches = keyword_pattern.finditer(pr_msg_lower) + else: + endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." + end_script(endmsg) - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Extract issue and PR numbers associated with found keywords in merged PR message - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #Extract issue and PR numbers associated with found keywords in merged PR message + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #create issue pattern ("the number symbol {#} + a number"), - #which ends with either a space, a comma, a period, or - #the end of the string itself: - issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') + #create issue pattern ("the number symbol {#} + a number"), + #which ends with either a space, a comma, a period, or + #the end of the string itself: + issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') - #Create new "close" issues list: - close_issues = [] + #Create new "close" issues list: + close_issues = [] - #Create new "closed" PR list: - close_pulls = [] + #Create new "closed" PR list: + close_pulls = [] - #Search text right after keywords for possible issue numbers: - for match in word_matches: + #Search text right after keywords for possible issue numbers: + for match in word_matches: - #create temporary string starting at end of match: - tmp_msg_str = pr_msg_lower[match.end():] + #create temporary string starting at end of match: + tmp_msg_str = pr_msg_lower[match.end():] - #Check if first word matches issue pattern: - if issue_pattern.match(tmp_msg_str) is not None: + #Check if first word matches issue pattern: + if issue_pattern.match(tmp_msg_str) is not None: - #If so, then look for an issue number immediately following - first_word = tmp_msg_str.split()[0] + #If so, then look for an issue number immediately following + first_word = tmp_msg_str.split()[0] - #Extract issue number from first word: - try: - #First try assuming the string is just a number - issue_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If not, then ignore last letter: + #Extract issue number from first word: try: - issue_num = int(first_word[1:-1]) + #First try assuming the string is just a number + issue_num = int(first_word[1:]) #ignore "#" symbol except ValueError: - #If ignoring the first and last letter doesn't work, - #then the match was likely a false positive, - #so set the issue number to one that will never be found: - issue_num = -9999 - - #Check if number is actually for a PR (as opposed to an issue): - if issue_num in open_pulls: - #Add PR number to "close pulls" list: - close_pulls.append(issue_num) - elif issue_num in open_issues: - #If in fact an issue, then add to "close issues" list: - close_issues.append(issue_num) + #If not, then ignore last letter: + try: + issue_num = int(first_word[1:-1]) + except ValueError: + #If ignoring the first and last letter doesn't work, + #then the match was likely a false positive, + #so set the issue number to one that will never be found: + issue_num = -9999 + + #Check if number is actually for a PR (as opposed to an issue): + if issue_num in open_pulls: + #Add PR number to "close pulls" list: + close_pulls.append(issue_num) + elif issue_num in open_issues: + #If in fact an issue, then add to "close issues" list: + close_issues.append(issue_num) + + #+++END REFERENCED PR LOOP+++ #If no issue numbers are present after any of the keywords, then exit script: if not close_issues and not close_pulls: @@ -321,177 +270,19 @@ def _main_prog(): print("PRs referenced by the merged PR: "+", ".join(\ str(pull) for pull in close_pulls)) - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Determine name of project associated with merged Pull Request - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - #Pull-out all projects from repo: - projects = cam_repo.get_projects() - - #Initalize modified project name: - proj_mod_name = None - - #Loop over all repo projects: - for project in projects: + #++++++++++++++++++++++++++++++++++++++++++++++ + #Attempt to close all referenced issues and PRs + #++++++++++++++++++++++++++++++++++++++++++++++ - #Pull-out columns from each project: - proj_columns = project.get_columns() - - #Loop over columns: - for column in proj_columns: - - #check if column name is "Completed tags" - if column.name == "Completed tags": - #If so, then extract cards: - cards = column.get_cards() - - #Loop over cards: - for card in cards: - #Extract card content: - card_content = card.get_content() - - #Next, check if card number exists and matches merged PR number: - if card_content is not None and card_content.number == pr_num: - #If so, and if Project name is None, then set string: - if proj_mod_name is None: - proj_mod_name = project.name - #Break out of card loop: - break - - #If already set, then somehow merged PR is in two different projects, - #which is not what this script is expecting, so just exit: - endmsg = "Merged Pull Request found in two different projects, so script will do nothing." - end_script(endmsg) - - #Print project name associated with merged PR: - print(f"merged PR project name: {proj_mod_name}") - - #++++++++++++++++++++++++++++++++++++++++ - #Extract repo project "To do" card issues - #++++++++++++++++++++++++++++++++++++++++ - - #Initalize issue counting dictionary: - proj_issues_count = {} - - #Initalize issue id to project card id dictionary: - proj_issue_card_ids = {} - - #Initialize list for issues that have already been closed: - already_closed_issues = [] - - #Loop over all repo projects: - for project in projects: - - #Next, pull-out columns from each project: - proj_columns = project.get_columns() - - #Loop over columns: - for column in proj_columns: - #Check if column name is "To do" - if column.name == "To do": - #If so, then extract cards: - cards = column.get_cards() - - #Loop over cards: - for card in cards: - #Extract card content: - card_content = card.get_content() - - #Next, check if card issue number matches any of the "close" issue numbers from the PR: - if card_content is not None and card_content.number in close_issues: - - #If so, then check if issue number is already in proj_issues_count: - if card_content.number in proj_issues_count: - #Add one to project issue counter: - proj_issues_count[card_content.number] += 1 - - #Also add issue id and card id to id dictionary used for card move, if in relevant project: - if project.name == proj_mod_name: - proj_issue_card_ids[card_content.number] = card.id - - else: - #If not, then append to project issues count dictionary: - proj_issues_count[card_content.number] = 1 - - #Also add issue id and card id to id dictionary used for card move, if in relevant project: - if project.name == proj_mod_name: - proj_issue_card_ids[card_content.number] = card.id - - #Otherwise, check if column name matches "closed issues" column: - elif column.name == "closed issues" and project.name == proj_mod_name: - #Save column id: - column_target_id = column.id - - #Extract cards: - closed_cards = column.get_cards() - - #Loop over cards: - for closed_card in closed_cards: - #Extract card content: - closed_card_content = closed_card.get_content() - - #Check if card issue number matches any of the "close" issue numbers from the PR: - if closed_card_content is not None and closed_card_content.number in close_issues: - #If issue number matches, then it likely means the same - #commit message or issue number reference was used in multiple - #pushes to the same repo (e.g., for a PR and then a tag). Thus - #the issue should be marked as "already closed": - already_closed_issues.append(closed_card_content.number) - - #Remove all issues from issue dictionary that are "already closed": - for already_closed_issue_num in already_closed_issues: - if already_closed_issue_num in proj_issues_count: - proj_issues_count.pop(already_closed_issue_num) - - #If no project cards are found that match the issue, then exit script: - if not proj_issues_count: - endmsg = "No project cards match the issue being closed, so the script will do nothing." - end_script(endmsg) - - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Check if the number of "To-do" project cards matches the total number - #of merged PRs for each 'close' issue. - # - #Then, close all issues for which project cards equals merged PRs - # - #If not, then simply move the project card to the relevant project's - #"closed issues" column. - #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - #Loop over project issues and counts that have been "closed" by merged PR: - for issue_num, issue_count in proj_issues_count.items(): - - #If issue count is just one, then close issue: - if issue_count == 1: - #Extract github issue object: - cam_issue = cam_repo.get_issue(number=issue_num) - #Close issue: - cam_issue.edit(state='closed') - print(f"Issue #{issue_num} has been closed.") - else: - #Extract card id from id dictionary: - if issue_num in proj_issue_card_ids: - card_id = proj_issue_card_ids[issue_num] - else: - #If issue isn't in dictionary, then it means the issue - #number was never found in the "To do" column, which - #likely means the user either referenced the wrong - #issue number, or the issue was never assigned to the - #project. Warn user and then exit with a non-zero - #error so that the Action fails: - endmsg = 'Issue #{} was not found in the "To Do" Column of the "{}" project.\n' \ - 'Either the wrong issue number was referenced, or the issue was never ' \ - 'attached to the project.'.format(issue_num, proj_mod_name) - print(endmsg) - sys.exit(1) - - #Then move the card on the relevant project page to the "closed issues" column: - project_card_move(token.strip(), column_target_id, card_id) - - #++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #Finally, close all Pull Requests in "close_pulls" list: - #++++++++++++++++++++++++++++++++++++++++++++++++++++++ + #Loop over referenced issues: + for issue_num in close_issues: + #Extract github issue object: + cam_issue = cam_repo.get_issue(number=issue_num) + #Close issue: + cam_issue.edit(state='closed') + print(f"Issue #{issue_num} has been closed.") + #Loop over referenced PRs: for pull_num in close_pulls: #Extract Pull request object: cam_pull = cam_repo.get_pull(number=pull_num) diff --git a/.github/workflows/branch_push_workflow.yml b/.github/workflows/branch_push_workflow.yml index 94f4414a..d06af346 100644 --- a/.github/workflows/branch_push_workflow.yml +++ b/.github/workflows/branch_push_workflow.yml @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest steps: # Acquire github action routines - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Acquire specific version of python - - name: Set up Python 3.10 - uses: actions/setup-python@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 with: - python-version: '3.10' # Semantic version range syntax or exact version of a Python version + python-version: '3.11' # Semantic version range syntax or exact version of a Python version # Install required python packages - name: Install dependencies run: | From 822321b60da2af8eaccc24a5c5b67fc1ce57544a Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Tue, 20 Aug 2024 10:16:12 -0700 Subject: [PATCH 14/27] Includes CCPP scheme dependencies in the build (#282) Uses updated CCPP-generated data to include scheme dependencies in the CAM-SIMA build. In draft until https://github.com/ESCOMP/atmospheric_physics/pull/102 is merged. --------- Co-authored-by: Jesse Nusbaumer --- .dockerignore | 3 +++ .gitmodules | 4 ++-- ccpp_framework | 2 +- cime_config/cam_autogen.py | 5 +++++ docker/Dockerfile | 13 +++++++++---- docker/Dockerfile.musica | 40 ++++++++++++++------------------------ src/control/cam_comp.F90 | 1 - src/physics/ncar_ccpp | 2 +- 8 files changed, 36 insertions(+), 34 deletions(-) diff --git a/.dockerignore b/.dockerignore index 267cdb79..12b85fe8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,6 @@ !test/ !.config_files.xml !docker +!bin/ +!.lib/ +!.gitmodules diff --git a/.gitmodules b/.gitmodules index 787b14c9..3c9be99d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "ccpp-framework"] path = ccpp_framework url = https://github.com/NCAR/ccpp-framework - fxtag = 2024-07-11-dev + fxtag = 2024-07-19-dev fxrequired = AlwaysRequired fxDONOTUSEurl = https://github.com/NCAR/ccpp-framework [submodule "mpas"] @@ -14,7 +14,7 @@ [submodule "ncar-physics"] path = src/physics/ncar_ccpp url = https://github.com/ESCOMP/atmospheric_physics - fxtag = atmos_phys0_03_000 + fxtag = 098585940ad763be58ebab849bb8eaf325fda42a fxrequired = AlwaysRequired fxDONOTUSEurl = https://github.com/ESCOMP/atmospheric_physics [submodule "ccs_config"] diff --git a/ccpp_framework b/ccpp_framework index 0f823272..5f338ddf 160000 --- a/ccpp_framework +++ b/ccpp_framework @@ -1 +1 @@ -Subproject commit 0f8232724975c13289cad390c9a71fa2c6a9bff4 +Subproject commit 5f338ddf02638c06548e54e0a218d154b34faff9 diff --git a/cime_config/cam_autogen.py b/cime_config/cam_autogen.py index 0520d602..eeb31229 100644 --- a/cime_config/cam_autogen.py +++ b/cime_config/cam_autogen.py @@ -615,6 +615,11 @@ def generate_physics_suites(build_cache, preproc_defs, host_name, ufiles_str = datatable_report(cap_output_file, request, ";") utility_files = ufiles_str.split(';') _update_genccpp_dir(utility_files, genccpp_dir) + request = DatatableReport("dependencies") + dep_str = datatable_report(cap_output_file, request, ";") + if len(dep_str) > 0: + dependency_files = dep_str.split(';') + _update_genccpp_dir(dependency_files, genccpp_dir) ##XXgoldyXX: ^ Temporary fix: # End if diff --git a/docker/Dockerfile b/docker/Dockerfile index 1d6acc6c..098db37d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # parts of CAM require x86 architecture (gptl, which relies on the rdtsc x86 assembly instruction) # esmf is am image you are expected to have built. Read the README file for instructions -FROM esmf:latest +FROM --platform=linux/amd64 esmf:latest ################################################### ## Install necessary packages @@ -11,13 +11,18 @@ RUN dnf -y update \ git \ hostname \ m4 \ - python \ + python39 \ + pip \ sudo \ svn \ tree \ vim \ && dnf clean all +RUN ln -s $(which python3) /usr/bin/python && \ + pip install --upgrade pip && \ + pip install --upgrade setuptools + ################################################### ## Make sure the mpi compilers can be found ################################################### @@ -50,7 +55,7 @@ USER cam_sima_user WORKDIR /home/cam_sima_user/CAM-SIMA # pull the dependencies -RUN ./manage_externals/checkout_externals +RUN ./bin/git-fleximod update # Copy in the machine information for the container RUN cp /home/cam_sima_user/CAM-SIMA/docker/config_machines.xml /home/cam_sima_user/CAM-SIMA/ccs_config/machines/ @@ -79,7 +84,7 @@ RUN ./xmlchange STOP_N=5 RUN chmod +x /home/cam_sima_user/CAM-SIMA/docker/ftp_download.sh RUN /home/cam_sima_user/CAM-SIMA/docker/ftp_download.sh -# # add the snapshot file +# add the snapshot file RUN echo "ncdata='/home/cam_sima_user/run_heldsuarez_cam6_nt2_bigg_try005.cam.h5.0001-01-01-00000.nc'" >> user_nl_cam RUN ./case.build \ No newline at end of file diff --git a/docker/Dockerfile.musica b/docker/Dockerfile.musica index b02cf7b6..0f59f21d 100644 --- a/docker/Dockerfile.musica +++ b/docker/Dockerfile.musica @@ -1,6 +1,6 @@ # parts of CAM require x86 architecture (gptl, which relies on the rdtsc x86 assembly instruction) # esmf is am image you are expected to have built. Read the README file for instructions -FROM esmf:latest +FROM --platform=linux/amd64 esmf:latest ################################################### ## Install necessary packages @@ -11,13 +11,18 @@ RUN dnf -y update \ git \ hostname \ m4 \ - python \ + python39 \ + pip \ sudo \ svn \ tree \ vim \ && dnf clean all +RUN ln -s $(which python3) /usr/bin/python && \ + pip install --upgrade pip && \ + pip install --upgrade setuptools + ################################################### ## Make sure the mpi compilers can be found ################################################### @@ -36,37 +41,22 @@ RUN cd pnetcdf-1.12.3 && \ ENV FC=gfortran -################################################### -## Build and install json-fortran -################################################### -RUN curl -LO https://github.com/jacobwilliams/json-fortran/archive/8.2.0.tar.gz \ - && tar -zxvf 8.2.0.tar.gz \ - && cd json-fortran-8.2.0 \ - && mkdir build \ - && cd build \ - && cmake -D SKIP_DOC_GEN:BOOL=TRUE .. \ - && make install -j 8 - -# add a symlink -RUN ln -s /usr/local/jsonfortran-gnu-8.2.0/lib/libjsonfortran.a /usr/local/lib/libjsonfortran.a - ################################################### ## Build and install MUSICA ################################################### -RUN git clone https://github.com/NCAR/musica.git +RUN git clone https://github.com/NCAR/musica.git \ + && cd musica \ + && git checkout 2a5eeaac982a3eb80b96d1e2087b91b301d1e748 + RUN mkdir /musica/build \ && cd /musica/build \ - && export JSON_FORTRAN_HOME="/usr/local/jsonfortran-gnu-8.2.0" \ && cmake \ -D ENABLE_TESTS=OFF \ - -D ENABLE_TUVX=OFF \ - .. \ + -D MUSICA_BUILD_FORTRAN_INTERFACE=ON \ + .. \ && make install -j 8 -# add a symlink -RUN ln -s /usr/local/musica-0.3.0/lib64/libmusica.a /usr/local/lib/libmusica.a - ################################################### ## Build CAM-SIMA ################################################### @@ -83,7 +73,7 @@ USER cam_sima_user WORKDIR /home/cam_sima_user/CAM-SIMA # pull the dependencies -RUN ./manage_externals/checkout_externals +RUN ./bin/git-fleximod update # Copy in the machine information for the container RUN cp /home/cam_sima_user/CAM-SIMA/docker/config_machines.xml /home/cam_sima_user/CAM-SIMA/ccs_config/machines/ @@ -104,7 +94,7 @@ WORKDIR $CASE_NAME RUN ./case.setup RUN ./xmlchange CAM_CONFIG_OPTS="--dyn none --physics-suites musica" -RUN ./xmlchange CAM_LINKED_LIBS="-lmusica -ljsonfortran" +RUN ./xmlchange CAM_LINKED_LIBS="-lmusica-fortran -lmusica -lyaml-cpp" RUN ./xmlchange ROF_NCPL=48 RUN ./xmlchange STOP_OPTION=nsteps RUN ./xmlchange STOP_N=5 diff --git a/src/control/cam_comp.F90 b/src/control/cam_comp.F90 index db0e943e..b2fba9a2 100644 --- a/src/control/cam_comp.F90 +++ b/src/control/cam_comp.F90 @@ -561,7 +561,6 @@ subroutine cam_register_constituents(cam_runtime_opts) integer :: errflg character(len=512) :: errmsg type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) - type(ccpp_constituent_properties_t), allocatable, target :: dynamic_constituents(:) character(len=*), parameter :: subname = 'cam_register_constituents: ' ! Initalize error flag and message: diff --git a/src/physics/ncar_ccpp b/src/physics/ncar_ccpp index f4c09618..09858594 160000 --- a/src/physics/ncar_ccpp +++ b/src/physics/ncar_ccpp @@ -1 +1 @@ -Subproject commit f4c09618eaaa19eaf3382f0473a531e20aa9f808 +Subproject commit 098585940ad763be58ebab849bb8eaf325fda42a From ba0a9661aa8d7fcfb6f0a68e53ca2eab1b687a46 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 20 Aug 2024 13:40:02 -0600 Subject: [PATCH 15/27] Address reviewer comments. --- .github/scripts/branch_pr_issue_closer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index fc4a7087..10a0211f 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -140,7 +140,7 @@ def _main_prog(): #If pull request has not been merged, then exit script: if not merged_pull.merged: - endmsg = f"Pull request associated with commit:\n{trigger_sha}\n" + endmsg = f"Pull request #{pr_num} associated with commit:\n{trigger_sha}\n" endmsg += "was not actually merged, so the script will not close anything." end_script(endmsg) @@ -156,7 +156,7 @@ def _main_prog(): #If PR was to default branch, then exit script (as github will handle it automatically): if merged_branch == default_branch: - endmsg = "Pull request was merged into default repo branch. " + endmsg = f"Pull request #{pr_num} was merged into default repo branch. " endmsg += "Thus issue is closed automatically" end_script(endmsg) @@ -201,7 +201,8 @@ def _main_prog(): #If at least one keyword is found, then determine location of every keyword instance: word_matches = keyword_pattern.finditer(pr_msg_lower) else: - endmsg = "Pull request was merged without using any of the keywords. Thus there are no issues to close." + endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " + endmsg += "Thus there are no issues to close." end_script(endmsg) #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From 669dd987ca48f5fbbc442fa1cb7e977e6241f168 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 20 Aug 2024 15:08:35 -0600 Subject: [PATCH 16/27] Add comment explaining try/except purpose. --- .github/scripts/branch_pr_issue_closer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 10a0211f..0f17bb38 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -237,7 +237,9 @@ def _main_prog(): #First try assuming the string is just a number issue_num = int(first_word[1:]) #ignore "#" symbol except ValueError: - #If not, then ignore last letter: + #If not, then ignore the last character, in case the user + #included punctutation (i.e. a space, comma, or period) + #after the PR number: try: issue_num = int(first_word[1:-1]) except ValueError: From eb4e376749e50cad650ad90ecc37a01555a0baaa Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:16:33 -0600 Subject: [PATCH 17/27] Implement new regex patterns suggested by Michael W. plus code clean-up. --- .github/scripts/branch_pr_issue_closer.py | 86 ++++++++++------------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 0f17bb38..a2a45911 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -26,6 +26,25 @@ from github import Github from github import Auth +############### +#REGEX PATTERNS +############### + +#Issue-closing Keywords are: +#close, closes, closed +#fix, fixes, fixed +#resolve, resolves, resolved + +#Create relevant regex patterns: +_CLOSE_KEY = r'close[sd]?' +_FIX_KEY = r'fix(e[sd])?' +_RESOLVE_KEY = r'resolve[sd]?' +_KEYWORDS = rf'{_CLOSE_KEY}|{_FIX_KEY}|{_RESOLVE_KEY}' +_KEYWORDS_CAPTURE_GROUP = rf'(?P{_KEYWORDS})' +_ID_NUMBER = r'\d+' +_ID_CAPTURE_GROUP = rf'(?P{_ID_NUMBER})' +_LINKED_ISSUE_PATTERN = rf'{_KEYWORDS_CAPTURE_GROUP}\s*#{_ID_CAPTURE_GROUP}' + ################# #HELPER FUNCTIONS ################# @@ -184,36 +203,25 @@ def _main_prog(): #Check if one of the keywords exists in PR message #+++++++++++++++++++++++++++++++++++++++++++++++++ - #Keywords are: - #close, closes, closed - #fix, fixes, fixed - #resolve, resolves, resolved - - #Create regex pattern to find keywords: - keyword_pattern = re.compile(r'(^|\s)close(\s|s\s|d\s)|(^|\s)fix(\s|es\s|ed\s)|(^|\s)resolve(\s|s\s|d\s)') + #Compile regex patterns into object: + keyword_pattern = re.compile(_LINKED_ISSUE_PATTERN) #Extract (lower case) Pull Request message: pr_msg_lower = merged_pull.body.lower() - #search for at least one keyword: - word_matches = [] - if keyword_pattern.search(pr_msg_lower) is not None: - #If at least one keyword is found, then determine location of every keyword instance: - word_matches = keyword_pattern.finditer(pr_msg_lower) - else: + #End script if no keywords found: + if keyword_pattern.search(pr_msg_lower) is None: endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " endmsg += "Thus there are no issues to close." end_script(endmsg) + #search for at least one keyword in PR message: + word_matches = keyword_pattern.finditer(pr_msg_lower, re.IGNORECASE) + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #Extract issue and PR numbers associated with found keywords in merged PR message #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - #create issue pattern ("the number symbol {#} + a number"), - #which ends with either a space, a comma, a period, or - #the end of the string itself: - issue_pattern = re.compile(r'#[0-9]+(\s|,|$)|.') - #Create new "close" issues list: close_issues = [] @@ -223,38 +231,16 @@ def _main_prog(): #Search text right after keywords for possible issue numbers: for match in word_matches: - #create temporary string starting at end of match: - tmp_msg_str = pr_msg_lower[match.end():] - - #Check if first word matches issue pattern: - if issue_pattern.match(tmp_msg_str) is not None: - - #If so, then look for an issue number immediately following - first_word = tmp_msg_str.split()[0] - - #Extract issue number from first word: - try: - #First try assuming the string is just a number - issue_num = int(first_word[1:]) #ignore "#" symbol - except ValueError: - #If not, then ignore the last character, in case the user - #included punctutation (i.e. a space, comma, or period) - #after the PR number: - try: - issue_num = int(first_word[1:-1]) - except ValueError: - #If ignoring the first and last letter doesn't work, - #then the match was likely a false positive, - #so set the issue number to one that will never be found: - issue_num = -9999 - - #Check if number is actually for a PR (as opposed to an issue): - if issue_num in open_pulls: - #Add PR number to "close pulls" list: - close_pulls.append(issue_num) - elif issue_num in open_issues: - #If in fact an issue, then add to "close issues" list: - close_issues.append(issue_num) + issue_dict = match.groupdict() + issue_num = int(issue_dict['id'].lstrip('0')) + + #Check if number is actually for a PR (as opposed to an issue): + if issue_num in open_pulls: + #Add PR number to "close pulls" list: + close_pulls.append(issue_num) + elif issue_num in open_issues: + #If in fact an issue, then add to "close issues" list: + close_issues.append(issue_num) #+++END REFERENCED PR LOOP+++ From 81e7d562e019c343974ed72780b0302dcc868a5c Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:29:53 -0600 Subject: [PATCH 18/27] Update comment about keyword match loop. --- .github/scripts/branch_pr_issue_closer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index a2a45911..53f0352d 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -228,7 +228,7 @@ def _main_prog(): #Create new "closed" PR list: close_pulls = [] - #Search text right after keywords for possible issue numbers: + #Go through all matches to pull out PR and issue numbers: for match in word_matches: issue_dict = match.groupdict() From 31f6846af943783aea7cacd2ab4118aeea6830dc Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 22 Aug 2024 13:42:37 -0600 Subject: [PATCH 19/27] Add comment and URL about Github keywords. --- .github/scripts/branch_pr_issue_closer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 53f0352d..43da6831 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -35,6 +35,12 @@ #fix, fixes, fixed #resolve, resolves, resolved +#The keywords are designed to match +#the keywords that exist in Github +#already for default branches, which +#can be found here: +#https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue + #Create relevant regex patterns: _CLOSE_KEY = r'close[sd]?' _FIX_KEY = r'fix(e[sd])?' From 46635897e630260eb698adeb994cfc545b626612 Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:36:24 -0400 Subject: [PATCH 20/27] Simplifying description scanning phase. (#291) Reducing description scanning to one step and leveraging set intersections to create lists of pull requests and issues to close. closes #279 --- .github/scripts/branch_pr_issue_closer.py | 31 ++++++++++------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/scripts/branch_pr_issue_closer.py b/.github/scripts/branch_pr_issue_closer.py index 43da6831..71bb5235 100755 --- a/.github/scripts/branch_pr_issue_closer.py +++ b/.github/scripts/branch_pr_issue_closer.py @@ -215,15 +215,6 @@ def _main_prog(): #Extract (lower case) Pull Request message: pr_msg_lower = merged_pull.body.lower() - #End script if no keywords found: - if keyword_pattern.search(pr_msg_lower) is None: - endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " - endmsg += "Thus there are no issues to close." - end_script(endmsg) - - #search for at least one keyword in PR message: - word_matches = keyword_pattern.finditer(pr_msg_lower, re.IGNORECASE) - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #Extract issue and PR numbers associated with found keywords in merged PR message #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -234,19 +225,25 @@ def _main_prog(): #Create new "closed" PR list: close_pulls = [] + #Create iterator of all keyword/id pairs: + word_matches = keyword_pattern.finditer(pr_msg_lower, re.IGNORECASE) + #Go through all matches to pull out PR and issue numbers: + found_ids = set() for match in word_matches: - issue_dict = match.groupdict() issue_num = int(issue_dict['id'].lstrip('0')) + found_ids.add(issue_num) + + #End script if no keyword/id pairs were found: + if not found_ids: + endmsg = f"Pull request #{pr_num} was merged without using any of the keywords. " + endmsg += "Thus there are no issues to close." + end_script(endmsg) + + close_pulls = list(found_ids.intersection(open_pulls)) + close_issues = list(found_ids.intersection(open_issues)) - #Check if number is actually for a PR (as opposed to an issue): - if issue_num in open_pulls: - #Add PR number to "close pulls" list: - close_pulls.append(issue_num) - elif issue_num in open_issues: - #If in fact an issue, then add to "close issues" list: - close_issues.append(issue_num) #+++END REFERENCED PR LOOP+++ From 672a56a08931ce3daee2b4b7408f89d83bdce789 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 26 Aug 2024 11:29:12 -0400 Subject: [PATCH 21/27] Fix user_defined physical constants not updated outside of masterproc --- src/data/physconst.F90 | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/data/physconst.F90 b/src/data/physconst.F90 index 1c424e0d..8a528342 100644 --- a/src/data/physconst.F90 +++ b/src/data/physconst.F90 @@ -207,6 +207,18 @@ subroutine physconst_readnl(nlfile) if (newg .or. newsday .or. newmwh2o .or. newcpwv .or. newmwdry .or. & newrearth .or. newtmelt .or. newomega) then + + ! Populate the new constants into module after mpi_bcast + if(newg) gravit = user_defined_gravit + if(newsday) sday = user_defined_sday + if(newmwh2o) mwh2o = user_defined_mwh2o + if(newcpwv) cpwv = user_defined_cpwv + if(newmwdry) mwdry = user_defined_mwdry + if(newcpair) cpair = user_defined_cpair + if(newrearth) rearth = user_defined_rearth + if(newtmelt) tmelt = user_defined_tmelt + if(newomega) omega = user_defined_omega + if (masterproc) then write(iulog, *) banner write(iulog, *) '*** New Physical Constant Values set ', & @@ -214,52 +226,44 @@ subroutine physconst_readnl(nlfile) write(iulog, *) bline write(iulog, *) '*** Physical Constant Old Value New Value ***' if (newg) then - gravit = user_defined_gravit field = 'GRAVIT' write(iulog, 2000) field, shr_const_g, gravit end if if (newsday) then - sday = user_defined_sday field = 'SDAY' write(iulog, 2000) field, shr_const_sday, sday end if if (newmwh2o) then - mwh2o = user_defined_mwh2o field = 'MWH20' write(iulog, 2000) field, shr_const_mwwv, mwh2o end if if (newcpwv) then - cpwv = user_defined_cpwv field = 'CPWV' write(iulog, 2000) field, shr_const_cpwv, cpwv end if if (newmwdry) then - mwdry = user_defined_mwdry field = 'MWDRY' write(iulog, 2000) field, shr_const_mwdair, mwdry end if if (newcpair) then - cpair = user_defined_cpair field = 'CPAIR' write(iulog, 2000) field, shr_const_cpdair, cpair end if if (newrearth) then - rearth = user_defined_rearth field = 'REARTH' write(iulog, 2000) field, shr_const_rearth, rearth end if if (newtmelt) then - tmelt = user_defined_tmelt field = 'TMELT' write(iulog, 2000) field, shr_const_tkfrz, tmelt end if if (newomega) then - omega = user_defined_omega field = 'OMEGA' write(iulog, 2000) field, shr_const_omega, omega end if write(iulog,*) banner end if + rga = 1._kind_phys / gravit rearth_recip = 1._kind_phys / rearth if (.not. newomega) then From d6f830aa1e90921e00448a82605450b8dfaf52ab Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 6 Sep 2024 11:36:47 -0400 Subject: [PATCH 22/27] Read total energy using dycore formula from fixed CAM snapshots - read ncdata variables te_ini_phys, te_ini_dyn, te_cur_phys, te_cur_dyn - update to standard names per discussion in https://github.com/ESCOMP/CAM/issues/1141 --- src/data/registry.xml | 28 ++++++++++++++++----- tools/stdnames_to_inputnames_dictionary.xml | 24 +++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/data/registry.xml b/src/data/registry.xml index 57a9ae3b..0b607f09 100644 --- a/src/data/registry.xml +++ b/src/data/registry.xml @@ -202,21 +202,37 @@ horizontal_dimension vertical_interface_dimension zi state_zi - horizontal_dimension - te_ini state_te_ini + te_ini_phys state_te_ini_phys - horizontal_dimension - te_cur state_te_cur + te_cur_phys state_te_cur_phys + + + horizontal_dimension + te_ini_dyn state_te_ini_dyn + + + horizontal_dimension + te_cur_dyn state_te_cur_dyn state_zi - + - te_ini - state_te_ini + te_ini_phys + state_te_ini_phys - + - te_cur - state_te_cur + te_cur_phys + state_te_cur_phys + + + + + te_ini_dyn + state_te_ini_dyn + + + + + te_cur_dyn + state_te_cur_dyn From 5e4dafff6812c1ee7687e070a4932a7c9a3c0931 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Fri, 6 Sep 2024 11:39:03 -0400 Subject: [PATCH 23/27] Update tw_ini, tw_cur standard names --- src/data/registry.xml | 4 ++-- tools/stdnames_to_inputnames_dictionary.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data/registry.xml b/src/data/registry.xml index 0b607f09..590ea2a2 100644 --- a/src/data/registry.xml +++ b/src/data/registry.xml @@ -235,7 +235,7 @@ te_cur_dyn state_te_cur_dyn @@ -243,7 +243,7 @@ tw_ini state_tw_ini diff --git a/tools/stdnames_to_inputnames_dictionary.xml b/tools/stdnames_to_inputnames_dictionary.xml index 0c71e351..77e81cd2 100644 --- a/tools/stdnames_to_inputnames_dictionary.xml +++ b/tools/stdnames_to_inputnames_dictionary.xml @@ -184,13 +184,13 @@ state_te_cur_dyn - + tw_ini state_tw_ini - + tw_cur state_tw_cur From 7e059d1f16b498035095a519f1dd244c19032049 Mon Sep 17 00:00:00 2001 From: Kuan-Chih Wang Date: Mon, 9 Sep 2024 11:35:02 -0600 Subject: [PATCH 24/27] Remove unused operator --- src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 index 5c9be0f4..f6bb502e 100644 --- a/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 +++ b/src/dynamics/mpas/driver/dyn_mpas_subdriver.F90 @@ -12,7 +12,7 @@ module dyn_mpas_subdriver ! Modules from external libraries. #ifdef MPAS_USE_MPI_F08 use mpi_f08, only: mpi_comm_null, mpi_comm_rank, mpi_success, & - mpi_comm_type => mpi_comm, operator(==), operator(/=) + mpi_comm_type => mpi_comm, operator(==) #else use mpi, only: mpi_comm_null, mpi_comm_rank, mpi_success #endif From c99c034afdf0bddb46f4e38cc5ef86af3ae1bc32 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Fri, 13 Sep 2024 15:35:24 -0600 Subject: [PATCH 25/27] Implement constituents infrastructure in analytic IC routines (#299) Originator(s): nusbaume, adamrher Summary: Adds in the subroutine/function hooks needed for the analytic ICs to properly interact with the CCPP constituents object. This PR also contains some bug fixes for the air composition and SE dycore dynamics-physics coupling routines that were found via testing with the FKESSLER and FHS94 compsets. Finally, this PR also includes some slight cleanup of the use of the water vapor constituent standard name within the core CAM-SIMA host code. Fixes #287 cod reviewed by: adamrher, peverwhee Describe any changes made to build system: N/A Describe any changes made to the namelist: M src/dynamics/tests/namelist_definition_analy_ic.xml - Added the US standard atmosphere option to the analytic IC types. List any changes to the defaults for the input datasets (e.g. boundary datasets): N/A List all files eliminated and why: N/A List all files added and what they do: N/A List all existing files that have been modified, and describe the changes: M src/control/cam_comp.F90 M src/control/runtime_obj.F90 - Added new "wv_stdname" parameter to cleanup water vapor standard name usage. M src/data/air_composition.F90 - Fix cpair bug when no dry air species are listed, and use new "wv_stdname" parameter. M src/dynamics/se/dp_coupling.F90 - Implement qneg and remaining constituents infrastructure, and add missing wet-to-dry conversion step. M src/dynamics/se/dycore/prim_state_mod.F90 - Whitespace cleanup. M src/dynamics/se/dyn_comp.F90 M src/dynamics/tests/initial_conditions/ic_baro_dry_jw06.F90 M src/dynamics/tests/initial_conditions/ic_baroclinic.F90 M src/dynamics/tests/initial_conditions/ic_held_suarez.F90 M src/dynamics/tests/initial_conditions/ic_us_standard_atm.F90 - Implement new constituents infrastructure in analytic IC routines. If there are new failures (compare to the existing-test-failures.txt file), have them OK'd by the gatekeeper, note them here, and add them to the file. If there are baseline differences, include the test and the reason for the diff. What is the nature of the change? Roundoff? derecho/intel/aux_sima: All Pass derecho/gnu/aux_sima: All Pass CAM-SIMA date used for the baseline comparison tests if different than latest: --- src/control/cam_comp.F90 | 10 +- src/control/runtime_obj.F90 | 3 + src/data/air_composition.F90 | 16 ++- src/dynamics/se/dp_coupling.F90 | 96 ++++++++++---- src/dynamics/se/dycore/prim_state_mod.F90 | 12 +- src/dynamics/se/dyn_comp.F90 | 11 +- .../initial_conditions/ic_baro_dry_jw06.F90 | 107 ++++++++++----- .../initial_conditions/ic_baroclinic.F90 | 124 +++++++++++------- .../initial_conditions/ic_held_suarez.F90 | 84 ++++++++---- .../initial_conditions/ic_us_standard_atm.F90 | 104 ++++++++++----- .../tests/namelist_definition_analy_ic.xml | 4 +- 11 files changed, 389 insertions(+), 182 deletions(-) diff --git a/src/control/cam_comp.F90 b/src/control/cam_comp.F90 index b2fba9a2..0782738a 100644 --- a/src/control/cam_comp.F90 +++ b/src/control/cam_comp.F90 @@ -542,6 +542,7 @@ subroutine cam_register_constituents(cam_runtime_opts) ! physics suite being invoked during this run. use cam_abortutils, only: endrun, check_allocate use runtime_obj, only: runtime_options + use runtime_obj, only: wv_stdname use phys_comp, only: phys_suite_name use cam_constituents, only: cam_constituents_init use cam_constituents, only: const_set_qmin, const_get_index @@ -569,9 +570,7 @@ subroutine cam_register_constituents(cam_runtime_opts) ! Check if water vapor is already marked as a constituent by the ! physics: - call cam_ccpp_is_scheme_constituent( & - "water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water", & - is_constituent, errflg, errmsg) + call cam_ccpp_is_scheme_constituent(wv_stdname, is_constituent, errflg, errmsg) if (errflg /= 0) then call endrun(subname//trim(errmsg), file=__FILE__, line=__LINE__) @@ -589,7 +588,7 @@ subroutine cam_register_constituents(cam_runtime_opts) ! Register the constituents so they can be advected: call host_constituents(1)%instantiate( & - std_name="water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water", & + std_name=wv_stdname, & long_name="water vapor mixing ratio w.r.t moist air and condensed_water", & units="kg kg-1", & default_value=0._kind_phys, & @@ -639,8 +638,7 @@ subroutine cam_register_constituents(cam_runtime_opts) if (phys_suite_name /= 'held_suarez_1994') then !Held-Suarez is "dry" physics ! Get constituent index for water vapor: - call const_get_index("water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water", & - const_idx) + call const_get_index(wv_stdname, const_idx) ! Set new minimum value: call const_set_qmin(const_idx, 1.E-12_kind_phys) diff --git a/src/control/runtime_obj.F90 b/src/control/runtime_obj.F90 index 0c84c3f5..157545a8 100644 --- a/src/control/runtime_obj.F90 +++ b/src/control/runtime_obj.F90 @@ -9,6 +9,9 @@ module runtime_obj integer, public, parameter :: unset_int = huge(1) real(r8), public, parameter :: unset_real = huge(1.0_r8) + ! Water vapor constituent standard name + character(len=*), public, parameter :: wv_stdname = 'water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water' + ! Public interfaces and data !> \section arg_table_runtime_options Argument Table diff --git a/src/data/air_composition.F90 b/src/data/air_composition.F90 index ea653020..f202ae97 100644 --- a/src/data/air_composition.F90 +++ b/src/data/air_composition.F90 @@ -130,8 +130,10 @@ subroutine air_composition_init() use cam_logfile, only: iulog use physconst, only: r_universal, cpwv use physconst, only: rh2o, cpliq, cpice + use physconst, only: cpair, rair use physics_grid, only: pcols => columns_on_task use vert_coord, only: pver + use runtime_obj, only: wv_stdname use cam_constituents, only: const_name, num_advected use cam_constituents, only: const_set_thermo_active use cam_constituents, only: const_set_water_species @@ -305,9 +307,8 @@ subroutine air_composition_init() ! ! Q ! - case('water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water') - call air_species_info('water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water', & - ix, mw) + case(wv_stdname) !water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water + call air_species_info(wv_stdname, ix, mw) thermodynamic_active_species_idx(icnst) = ix thermodynamic_active_species_cp (icnst) = cpwv thermodynamic_active_species_cv (icnst) = cv3 / mw @@ -455,6 +456,15 @@ subroutine air_composition_init() has_ice = .false. end do + !Set dry air thermodynamic properities if no dry air species provided: + if (dry_species_num == 0) then + !Note: The zeroeth index is used to represent all of dry + ! air instead of just N2 in this configuration + thermodynamic_active_species_cp(0) = cpair + thermodynamic_active_species_cv(0) = cpair - rair + thermodynamic_active_species_R(0) = rair + end if + water_species_in_air_num = water_species_num dry_air_species_num = dry_species_num thermodynamic_active_species_num = water_species_num + dry_species_num diff --git a/src/dynamics/se/dp_coupling.F90 b/src/dynamics/se/dp_coupling.F90 index 290b97a7..8b56e9d9 100644 --- a/src/dynamics/se/dp_coupling.F90 +++ b/src/dynamics/se/dp_coupling.F90 @@ -40,6 +40,8 @@ module dp_coupling real(r8), allocatable :: q_prev(:,:,:) ! Previous Q for computing tendencies +real(kind_phys), allocatable :: qmin_vals(:) !Consitutent minimum values array + !========================================================================================= CONTAINS !========================================================================================= @@ -320,6 +322,8 @@ subroutine p_d_coupling(cam_runtime_opts, phys_state, phys_tend, dyn_in, tl_f, t use test_fvm_mapping, only: test_mapping_overwrite_tendencies use test_fvm_mapping, only: test_mapping_output_mapped_tendencies use cam_ccpp_cap, only: cam_constituents_array + use cam_constituents, only: num_advected + use cam_constituents, only: const_is_water_species ! SE dycore: use bndry_mod, only: bndry_exchange @@ -387,9 +391,29 @@ subroutine p_d_coupling(cam_runtime_opts, phys_state, phys_tend, dyn_in, tl_f, t uv_tmp = 0.0_r8 dq_tmp = 0.0_r8 - ! Grab pointer to constituent array + !Grab pointer to constituent array const_data_ptr => cam_constituents_array() + !Convert wet mixing ratios to dry, which for CAM + !configurations is only the water species: + !$omp parallel do num_threads(max_num_threads) private (k, i, m) + do ilyr = 1, nlev + do icol=1, pcols + !Determine wet to dry adjustment factor: + factor = phys_state%pdel(icol,ilyr)/phys_state%pdeldry(icol,ilyr) + + !This should ideally check if a constituent is a wet + !mixing ratio or not, but until that is working properly + !in the CCPP framework just check for the water species status + !instead, which is all that CAM configurations require: + do m=1, num_advected + if (const_is_water_species(m)) then + const_data_ptr(icol,ilyr,m) = factor*const_data_ptr(icol,ilyr,m) + end if + end do + end do + end do + if (.not. allocated(q_prev)) then call endrun('p_d_coupling: q_prev not allocated') end if @@ -549,11 +573,14 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) ! mixing ratios are converted to a wet basis. Initialize geopotential heights. ! Finally compute energy and water column integrals of the physics input state. -! use constituents, only: qmin use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t use cam_ccpp_cap, only: cam_constituents_array use cam_ccpp_cap, only: cam_model_const_properties + use cam_constituents, only: num_advected + use cam_constituents, only: const_is_water_species use cam_constituents, only: const_get_index + use cam_constituents, only: const_qmin + use runtime_obj, only: wv_stdname use physics_types, only: lagrangian_vertical use physconst, only: cpair, gravit, zvir, cappa use cam_thermo, only: cam_thermo_update @@ -561,7 +588,7 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) use physics_grid, only: columns_on_task use geopotential_temp, only: geopotential_temp_run use static_energy, only: update_dry_static_energy_run -! use qneg, only: qneg_run + use qneg, only: qneg_run ! use check_energy, only: check_energy_timestep_init use hycoef, only: hyai, ps0 use shr_vmath_mod, only: shr_vmath_log @@ -581,12 +608,14 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) type(ccpp_constituent_prop_ptr_t), pointer :: const_prop_ptr(:) integer :: m, i, k - integer :: ix_q, ix_cld_liq, ix_rain + integer :: ix_q !Needed for "geopotential_temp" CCPP scheme integer :: errflg character(len=shr_kind_cx) :: errmsg + character(len=*), parameter :: subname = 'derived_phys_dry' + !-------------------------------------------- ! Variables needed for WACCM-X !-------------------------------------------- @@ -600,16 +629,26 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) nullify(const_prop_ptr) ! Set constituent indices - call const_get_index('water_vapor_mixing_ratio_wrt_moist_air_and_condensed_water', ix_q) - call const_get_index('cloud_liquid_water_mixing_ratio_wrt_moist_air_and_condensed_water', & - ix_cld_liq, abort=.false.) - call const_get_index('rain_mixing_ratio_wrt_moist_air_and_condensed_water', & - ix_rain, abort=.false.) + call const_get_index(wv_stdname, ix_q) ! Grab pointer to constituent and properties arrays const_data_ptr => cam_constituents_array() const_prop_ptr => cam_model_const_properties() + ! Create qmin array (if not already generated): + if (.not.allocated(qmin_vals)) then + allocate(qmin_vals(size(const_prop_ptr)), stat=errflg) + call check_allocate(errflg, subname, & + 'qmin_vals(size(cam_model_const_properties))', & + file=__FILE__, line=__LINE__) + + + ! Set relevant minimum values for each constituent: + do m = 1, size(qmin_vals) + qmin_vals(m) = const_qmin(m) + end do + end if + ! Evaluate derived quantities ! dry pressure variables @@ -649,7 +688,7 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) do k=1,nlev do i=1, pcols ! to be consistent with total energy formula in physic's check_energy module only - ! include water vapor in in moist dp + ! include water vapor in moist dp factor_array(i,k) = 1._kind_phys+const_data_ptr(i,k,ix_q) phys_state%pdel(i,k) = phys_state%pdeldry(i,k)*factor_array(i,k) end do @@ -690,16 +729,18 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) ! physics expect water variables moist factor_array(:,1:nlev) = 1._kind_phys/factor_array(:,1:nlev) - !$omp parallel do num_threads(horz_num_threads) private (k, i) - do k = 1, nlev - do i=1, pcols - const_data_ptr(i,k,ix_q) = factor_array(i,k)*const_data_ptr(i,k,ix_q) - if (ix_cld_liq /= -1) then - const_data_ptr(i,k,ix_cld_liq) = factor_array(i,k)*const_data_ptr(i,k,ix_cld_liq) - end if - if (ix_rain /= -1) then - const_data_ptr(i,k,ix_rain) = factor_array(i,k)*const_data_ptr(i,k,ix_rain) - end if + !$omp parallel do num_threads(horz_num_threads) private (m, k, i) + do m=1, num_advected + do k = 1, nlev + do i=1, pcols + !This should ideally check if a constituent is a wet + !mixing ratio or not, but until that is working properly + !in the CCPP framework just check for the water species status + !instead, which is all that CAM physics requires: + if (const_is_water_species(m)) then + const_data_ptr(i,k,m) = factor_array(i,k)*const_data_ptr(i,k,m) + end if + end do end do end do @@ -736,6 +777,11 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) end do endif + ! Ensure tracers are all greater than or equal to their + ! minimum-allowed value: + call qneg_run('D_P_COUPLING', columns_on_task, pver, & + qmin_vals, const_data_ptr, errflg, errmsg) + !----------------------------------------------------------------------------- ! Call cam_thermo_update. If cam_runtime_opts%update_thermodynamic_variables() ! returns .true., cam_thermo_update will compute cpairv, rairv, mbarv, and cappav as @@ -743,6 +789,7 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) ! Compute molecular viscosity(kmvis) and conductivity(kmcnd). ! Update zvirv registry variable; calculated for WACCM-X. !----------------------------------------------------------------------------- + call cam_thermo_update(const_data_ptr, phys_state%t, pcols, & cam_runtime_opts%update_thermodynamic_variables()) @@ -760,15 +807,6 @@ subroutine derived_phys_dry(cam_runtime_opts, phys_state, phys_tend) phys_state%phis, phys_state%dse, cpairv, & errflg, errmsg) - ! Ensure tracers are all positive - ! Please note this cannot be used until the 'qmin' - ! array is publicly provided by the CCPP constituent object. -JN -#if 0 - call qneg_run('D_P_COUPLING', columns_on_task, pver, & - qmin, const_data_ptr, & - errflg, errmsg) -#endif - !Remove once check_energy scheme exists in CAMDEN: #if 0 ! Compute energy and water integrals of input state diff --git a/src/dynamics/se/dycore/prim_state_mod.F90 b/src/dynamics/se/dycore/prim_state_mod.F90 index acd746d9..6395c169 100644 --- a/src/dynamics/se/dycore/prim_state_mod.F90 +++ b/src/dynamics/se/dycore/prim_state_mod.F90 @@ -418,11 +418,11 @@ subroutine adjust_nsplit(elem, tl,hybrid,nets,nete, fvm, omega_cn) use cam_abortutils, only: endrun use control_mod, only: nu_top ! - type (element_t), intent(inout) :: elem(:) + type (element_t), intent(inout) :: elem(:) type (TimeLevel_t), target, intent(in) :: tl type (hybrid_t), intent(in) :: hybrid integer, intent(in) :: nets,nete - type(fvm_struct), intent(inout) :: fvm(:) + type(fvm_struct), intent(inout) :: fvm(:) real (kind=r8), intent(in) :: omega_cn(2,nets:nete) ! Local variables... integer :: k,ie @@ -457,7 +457,7 @@ subroutine adjust_nsplit(elem, tl,hybrid,nets,nete, fvm, omega_cn) nsplit=2*nsplit_baseline fvm_supercycling = rsplit fvm_supercycling_jet = rsplit - nu_top=2.0_r8*nu_top + nu_top=2.0_r8*nu_top ! ! write diagnostics to log file ! @@ -470,7 +470,7 @@ subroutine adjust_nsplit(elem, tl,hybrid,nets,nete, fvm, omega_cn) end if dtime = get_step_size() tstep = dtime / real(nsplit*qsplit*rsplit, r8) - + else if (nsplit.ne.nsplit_baseline.and.max_o(1)<0.4_r8*threshold) then ! ! should nsplit be reduced again? @@ -480,9 +480,9 @@ subroutine adjust_nsplit(elem, tl,hybrid,nets,nete, fvm, omega_cn) fvm_supercycling = rsplit fvm_supercycling_jet = rsplit nu_top=nu_top/2.0_r8 - + ! nu_div_scale_top(:) = 1.0_r8 - + dtime = get_step_size() tstep = dtime / real(nsplit*qsplit*rsplit, r8) if(hybrid%masterthread) then diff --git a/src/dynamics/se/dyn_comp.F90 b/src/dynamics/se/dyn_comp.F90 index f04a3b26..8d74b66e 100644 --- a/src/dynamics/se/dyn_comp.F90 +++ b/src/dynamics/se/dyn_comp.F90 @@ -1307,7 +1307,7 @@ subroutine read_inidat(dyn_in) file=__FILE__, line=__LINE__) do m_cnst = 1, qsize - m_ind(m_cnst) = m_cnst + m_ind(m_cnst) = thermodynamic_active_species_idx(m_cnst) end do ! Init tracers on the GLL grid. Note that analytic_ic_set_ic makes @@ -1318,9 +1318,11 @@ subroutine read_inidat(dyn_in) V=dbuf4(:,:,:,(qsize+3)), T=dbuf4(:,:,:,(qsize+4)), & Q=dbuf4(:,:,:,1:qsize), m_cnst=m_ind, mask=pmask(:), & PHIS_IN=PHIS_tmp) - deallocate(m_ind) + + ! Deallocate variables that are no longer used: deallocate(glob_ind) deallocate(phis_tmp) + do ie = 1, nelemd indx = 1 do j = 1, np @@ -1348,13 +1350,16 @@ subroutine read_inidat(dyn_in) do i = 1, np ! Set qtmp at the unique columns only if (pmask(((ie - 1) * npsq) + indx)) then - qtmp(i,j,:,ie,m_cnst) = dbuf4(indx, :, ie, m_cnst) + qtmp(i,j,:,ie,m_ind(m_cnst)) = dbuf4(indx, :, ie, m_cnst) end if indx = indx + 1 end do end do end do end do + + ! Deallocate variables that are not longer used: + deallocate(m_ind) deallocate(dbuf4) else diff --git a/src/dynamics/tests/initial_conditions/ic_baro_dry_jw06.F90 b/src/dynamics/tests/initial_conditions/ic_baro_dry_jw06.F90 index bab2239b..87bdf868 100644 --- a/src/dynamics/tests/initial_conditions/ic_baro_dry_jw06.F90 +++ b/src/dynamics/tests/initial_conditions/ic_baro_dry_jw06.F90 @@ -50,10 +50,13 @@ module ic_baro_dry_jw06 subroutine bc_dry_jw06_set_ic(vcoord, latvals, lonvals, U, V, T, PS, PHIS, & Q, m_cnst, mask, verbose) - use dyn_tests_utils, only: vc_moist_pressure, vc_dry_pressure, vc_height - use cam_constituents, only: const_get_index - !use constituents, only: cnst_name - !use const_init, only: cnst_init_default + use shr_kind_mod, only: cx => shr_kind_cx + use dyn_tests_utils, only: vc_moist_pressure, vc_dry_pressure, vc_height + use runtime_obj, only: wv_stdname + use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t + use cam_ccpp_cap, only: cam_model_const_properties + use ccpp_kinds, only: kind_phys + use cam_constituents, only: const_get_index, const_qmin !----------------------------------------------------------------------- ! @@ -79,13 +82,15 @@ subroutine bc_dry_jw06_set_ic(vcoord, latvals, lonvals, U, V, T, PS, PHIS, & logical, allocatable :: mask_use(:) logical :: verbose_use logical :: lu,lv,lt,lq,l3d_vars + logical :: const_has_default integer :: i, k, m integer :: ncol integer :: nlev integer :: ncnst integer :: iret - integer :: ix_rain, ix_cld_liq + integer :: ix_q, m_cnst_ix_q character(len=*), parameter :: subname = 'BC_DRY_JW06_SET_IC' + character(len=cx) :: errmsg !CCPP error message real(r8) :: tmp real(r8) :: r(size(latvals)) real(r8) :: eta @@ -93,9 +98,15 @@ subroutine bc_dry_jw06_set_ic(vcoord, latvals, lonvals, U, V, T, PS, PHIS, & real(r8) :: perturb_lon, perturb_lat real(r8) :: phi_vertical real(r8) :: u_wind(size(latvals)) + real(kind_phys) :: const_default_value !Constituent default value + real(kind_phys) :: const_qmin_value !Constituent minimum value - a_omega = rearth*omega - exponent = rair*gamma/gravit + !Private array of constituent properties (for property interface functions) + type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) + + !Set local constants: + a_omega = rearth*omega + exponent = rair*gamma/gravit allocate(mask_use(size(latvals)), stat=iret) call check_allocate(iret, subname, 'mask_use(size(latvals))', & @@ -116,10 +127,6 @@ subroutine bc_dry_jw06_set_ic(vcoord, latvals, lonvals, U, V, T, PS, PHIS, & verbose_use = .true. end if - !set constituent indices - call const_get_index('cloud_liquid_water_mixing_ratio_wrt_moist_air_and_condensed_water', ix_cld_liq) - call const_get_index('rain_mixing_ratio_wrt_moist_air_and_condensed_water', ix_rain) - ncol = size(latvals, 1) nlev = -1 @@ -236,40 +243,72 @@ subroutine bc_dry_jw06_set_ic(vcoord, latvals, lonvals, U, V, T, PS, PHIS, & end if end if if (lq) then + !Get water vapor constituent index: + call const_get_index(wv_stdname, ix_q) + + !Determine which "Q" variable index matches water vapor: + m_cnst_ix_q = findloc(m_cnst, ix_q, dim=1) + do k = 1, nlev where(mask_use) - Q(:,k,1) = 0.0_r8 + Q(:,k,m_cnst_ix_q) = 0.0_r8 end where end do -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 if(masterproc.and. verbose_use) then - write(iulog,*) ' ', trim(cnst_name(m_cnst(1))), ' initialized by "',subname,'"' + write(iulog,*) ' ', wv_stdname, ' initialized by "',subname,'"' end if -#endif end if end if -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 - if (lq) then - ncnst = size(m_cnst, 1) - if ((vcoord == vc_moist_pressure) .or. (vcoord == vc_dry_pressure)) then - do m = 2, ncnst - call cnst_init_default(m_cnst(m), latvals, lonvals, Q(:,:,m_cnst(m)),& - mask=mask_use, verbose=verbose_use, notfound=.false.) - end do - end if - end if -#else if (lq) then + !Determine total number of constituents: + ncnst = size(m_cnst) + + !Extract constituent properties from CCPP constituents object: + const_props => cam_model_const_properties() + if ((vcoord == vc_moist_pressure) .or. (vcoord == vc_dry_pressure)) then - !Initialize cloud liquid and rain until constituent routines are enabled: - Q(:,:,ix_cld_liq) = 0.0_r8 - Q(:,:,ix_rain) = 0.0_r8 - end if - end if -#endif + do m = 1, ncnst + + !Skip water vapor, as it was aleady set above: + if (m == m_cnst_ix_q) cycle + + !Extract constituent minimum value: + const_qmin_value = const_qmin(m_cnst(m)) + + !Initialize constituent to its minimum value: + Q(:,:,m) = real(const_qmin_value, r8) + + !Check for default value in constituent properties object: + call const_props(m_cnst(m))%has_default(const_has_default, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + if (const_has_default) then + + !If default value exists, then extract default value + !from constituent properties object: + call const_props(m_cnst(m))%default_value(const_default_value, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + !Set constituent to default value in masked region: + do k=1,nlev + where(mask_use) + Q(:,k,m) = real(const_default_value, r8) + end where + end do + + end if !has_default + end do !m_cnst + end if !vcoord + end if !lq deallocate(mask_use) diff --git a/src/dynamics/tests/initial_conditions/ic_baroclinic.F90 b/src/dynamics/tests/initial_conditions/ic_baroclinic.F90 index 082aeca1..e2f76c06 100644 --- a/src/dynamics/tests/initial_conditions/ic_baroclinic.F90 +++ b/src/dynamics/tests/initial_conditions/ic_baroclinic.F90 @@ -11,9 +11,8 @@ module ic_baroclinic use cam_abortutils, only: endrun use spmd_utils, only: masterproc - use physconst, only : rair, gravit, rearth, pi, omega, epsilo - use hycoef, only : hyai, hybi, hyam, hybm, ps0 - use cam_constituents, only: const_get_index + use physconst, only: rair, gravit, rearth, pi, omega, epsilo + use hycoef, only: hyai, hybi, hyam, hybm, ps0 implicit none private @@ -76,11 +75,15 @@ module ic_baroclinic subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & Q, m_cnst, mask, verbose) - use dyn_tests_utils, only: vc_moist_pressure, vc_dry_pressure, vc_height - !use constituents, only: cnst_name - !use const_init, only: cnst_init_default - use inic_analytic_utils, only: analytic_ic_is_moist - use cam_abortutils, only: check_allocate + use shr_kind_mod, only: cx => shr_kind_cx + use dyn_tests_utils, only: vc_moist_pressure, vc_dry_pressure, vc_height + use runtime_obj, only: wv_stdname + use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t + use cam_ccpp_cap, only: cam_model_const_properties + use ccpp_kinds, only: kind_phys + use cam_constituents, only: const_get_index, const_qmin + use inic_analytic_utils, only: analytic_ic_is_moist + use cam_abortutils, only: check_allocate !----------------------------------------------------------------------- ! @@ -107,22 +110,27 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & ! Local variables logical, allocatable :: mask_use(:) logical :: verbose_use - integer :: ix_cld_liq, ix_rain + integer :: ix_q, m_cnst_ix_q integer :: i, k, m integer :: ncol integer :: nlev integer :: ncnst integer :: iret character(len=*), parameter :: subname = 'BC_WAV_SET_IC' + character(len=cx) :: errmsg !CCPP error message real(r8) :: ztop,ptop real(r8) :: uk,vk,Tvk,qk,pk !mid-level state real(r8) :: psurface real(r8) :: wvp,qdry - logical :: lU, lV, lT, lQ, l3d_vars - logical :: cnst1_is_moisture + real(kind_phys) :: const_default_value !Constituent default value + real(kind_phys) :: const_qmin_value !Constituent minimum value + logical :: lU, lV, lT, lQ, l3d_vars, const_has_default real(r8), allocatable :: pdry_half(:), pwet_half(:),zdry_half(:),zk(:) real(r8), allocatable :: zmid(:,:) ! layer midpoint heights for test tracer initialization + !Private array of constituent properties (for property interface functions) + type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) + if ((vcoord == vc_moist_pressure) .or. (vcoord == vc_dry_pressure)) then ! ! pressure-based vertical coordinate @@ -159,9 +167,6 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & mask_use = .true. end if - call const_get_index('cloud_liquid_water_mixing_ratio_wrt_moist_air_and_condensed_water', ix_cld_liq) - call const_get_index('rain_mixing_ratio_wrt_moist_air_and_condensed_water', ix_rain) - if (present(verbose)) then verbose_use = verbose else @@ -235,13 +240,17 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & if (lt) nlev = size(T, 2) if (lq) then + !Get water vapor constituent index: + call const_get_index(wv_stdname, ix_q) + + !Determine which "Q" variable index matches water vapor: + m_cnst_ix_q = findloc(m_cnst, ix_q, dim=1) + + !Determine vertical levels for constituents: nlev = size(Q, 2) - ! check whether first constituent in Q is water vapor. - cnst1_is_moisture = m_cnst(1) == 1 allocate(zmid(size(Q, 1),nlev), stat=iret) call check_allocate(iret, subname, 'zmid(size(Q, 1),nlev)', & file=__FILE__, line=__LINE__) - end if allocate(zk(nlev), stat=iret) @@ -306,12 +315,14 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & pk = moist_pressure_given_z(zk(k),latvals(i)) qk = qv_given_moist_pressure(pk,latvals(i)) else - qk = 0.d0 + qk = 0._r8 + end if + if (lq) then + Q(i,k,m_cnst_ix_q) = qk end if - if (lq .and. cnst1_is_moisture) Q(i,k,1) = qk if (lt) then tvk = Tv_given_z(zk(k),latvals(i)) - T(i,k) = tvk / (1.d0 + Mvap * qk) + T(i,k) = tvk / (1._r8 + Mvap * qk) end if end if end do @@ -339,8 +350,8 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & else qdry = 0.0_r8 end if - if (lq .and. cnst1_is_moisture) then - Q(i,k,1) = qdry + if (lq) then + Q(i,k,m_cnst_ix_q) = qdry end if if (lt) then ! @@ -356,35 +367,58 @@ subroutine bc_wav_set_ic(vcoord,latvals, lonvals, zint, U, V, T, PS, PHIS, & if(lu .and. masterproc.and. verbose_use) write(iulog,*) ' U initialized by "',subname,'"' if(lv .and. masterproc.and. verbose_use) write(iulog,*) ' V initialized by "',subname,'"' if(lt .and. masterproc.and. verbose_use) write(iulog,*) ' T initialized by "',subname,'"' -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 - if(lq .and. cnst1_is_moisture .and. masterproc.and. verbose_use) write(iulog,*) & - ' ', trim(cnst_name(m_cnst(1))), ' initialized by "',subname,'"' -#endif - end if + if(lq .and. masterproc.and. verbose_use) then + write(iulog,*) ' ', wv_stdname, ' initialized by "',subname,'"' + end if + + end if !l3d_vars -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 if (lq) then - ncnst = size(m_cnst, 1) - do m = 1, ncnst - ! water vapor already done above - if (m_cnst(m) == 1) cycle + !Get constituent properties object: + const_props => cam_model_const_properties() - call cnst_init_default(m_cnst(m), latvals, lonvals, Q(:,:,m),& - mask=mask_use, verbose=verbose_use, notfound=.false.,& - z=zmid) + do m = 1, size(m_cnst) - end do + !Skip water vapor, as it was aleady set above: + if (m == m_cnst_ix_q) cycle + + !Extract constituent minimum value: + const_qmin_value = const_qmin(m_cnst(m)) + + !Initialize constituent to its minimum value: + Q(:,:,m) = real(const_qmin_value, r8) + + !Check for default value in constituent properties object: + call const_props(m_cnst(m))%has_default(const_has_default, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + if (const_has_default) then + + !If default value exists, then extract default value + !from constituent properties object: + call const_props(m_cnst(m))%default_value(const_default_value, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + !Set constituent to default value in masked region: + do k=1,nlev + where(mask_use) + Q(:,k,m) = real(const_default_value, r8) + end where + end do + + end if !has_default + + end do !m_cnst end if ! lq -#else - if (lq) then - !Initialize cloud liquid and rain until constituent routines are enabled: - Q(:,:,ix_cld_liq) = 0.0_r8 - Q(:,:,ix_rain) = 0.0_r8 - end if -#endif deallocate(mask_use) if (l3d_vars) then diff --git a/src/dynamics/tests/initial_conditions/ic_held_suarez.F90 b/src/dynamics/tests/initial_conditions/ic_held_suarez.F90 index 18a5d06d..75dd0c1a 100644 --- a/src/dynamics/tests/initial_conditions/ic_held_suarez.F90 +++ b/src/dynamics/tests/initial_conditions/ic_held_suarez.F90 @@ -24,8 +24,12 @@ module ic_held_suarez subroutine hs94_set_ic(latvals, lonvals, U, V, T, PS, PHIS, & Q, m_cnst, mask, verbose) - !use const_init, only: cnst_init_default - use cam_constituents, only: const_get_index + use shr_kind_mod, only: cx => shr_kind_cx + use ccpp_kinds, only: kind_phys + use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t + use cam_ccpp_cap, only: cam_model_const_properties + use cam_constituents, only: const_get_index, const_qmin + use runtime_obj, only: wv_stdname !----------------------------------------------------------------------- ! @@ -49,14 +53,21 @@ subroutine hs94_set_ic(latvals, lonvals, U, V, T, PS, PHIS, & ! Local variables logical, allocatable :: mask_use(:) logical :: verbose_use + logical :: const_has_default integer :: i, k, m - integer :: ix_cld_liq, ix_rain + integer :: ix_q integer :: ncol integer :: nlev integer :: ncnst integer :: iret + real(kind_phys) :: const_default_value !Constituent default value + real(kind_phys) :: const_qmin_value !Constituent minimum value + character(len=cx) :: errmsg !CCPP error message character(len=*), parameter :: subname = 'HS94_SET_IC' + !Private array of constituent properties (for property interface functions) + type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) + allocate(mask_use(size(latvals)), stat=iret) call check_allocate(iret, subname, 'mask_use(size(latvals))', & file=__FILE__, line=__LINE__) @@ -76,12 +87,6 @@ subroutine hs94_set_ic(latvals, lonvals, U, V, T, PS, PHIS, & verbose_use = .true. end if - !set constituent indices - call const_get_index('cloud_liquid_water_mixing_ratio_wrt_moist_air_and_condensed_water', & - ix_cld_liq, abort=.false.) - call const_get_index('rain_mixing_ratio_wrt_moist_air_and_condensed_water', & - ix_rain, abort=.false.) - ncol = size(latvals, 1) nlev = -1 if (present(U)) then @@ -137,32 +142,65 @@ subroutine hs94_set_ic(latvals, lonvals, U, V, T, PS, PHIS, & end if if (present(Q)) then + !Get water vapor constituent index: + call const_get_index(wv_stdname, ix_q) + + !Get constituent properties object: + const_props => cam_model_const_properties() + + !Determine array sizes: nlev = size(Q, 2) ncnst = size(m_cnst, 1) + + !Loop over all constituents: do m = 1, ncnst - if (m_cnst(m) == 1) then + if (m_cnst(m) == ix_q) then ! No water vapor in Held-Suarez do k = 1, nlev where(mask_use) - Q(:,k,m_cnst(m)) = 0.0_r8 + Q(:,k,m) = 0.0_r8 end where end do -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 + if(masterproc .and. verbose_use) then - write(iulog,*) ' ', trim(cnst_name(m_cnst(m))), ' initialized by "',subname,'"' + write(iulog,*) ' ', wv_stdname, ' initialized by "',subname,'"' end if else - call cnst_init_default(m_cnst(m), latvals, lonvals, Q(:,:,m_cnst(m)),& - mask=mask_use, verbose=verbose_use, notfound=.false.) -#else - else - !Initialize cloud liquid and rain until constituent routines are enabled: - Q(:,:,ix_cld_liq) = 0.0_r8 - Q(:,:,ix_rain) = 0.0_r8 -#endif - end if + !Extract constituent minimum value: + const_qmin_value = const_qmin(m_cnst(m)) + + !Initialize constituent to its minimum value: + Q(:,:,m) = real(const_qmin_value, r8) + + !Check for default value in constituent properties object: + call const_props(m_cnst(m))%has_default(const_has_default, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + if (const_has_default) then + + !If default value exists, then extract default value + !from constituent properties object: + call const_props(m_cnst(m))%default_value(const_default_value, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + !Set constituent to default value in masked region: + do k=1,nlev + where(mask_use) + Q(:,k,m) = real(const_default_value, r8) + end where + end do + + end if !has_default + end if !water vapor end do end if diff --git a/src/dynamics/tests/initial_conditions/ic_us_standard_atm.F90 b/src/dynamics/tests/initial_conditions/ic_us_standard_atm.F90 index 0f91f131..41a13a21 100644 --- a/src/dynamics/tests/initial_conditions/ic_us_standard_atm.F90 +++ b/src/dynamics/tests/initial_conditions/ic_us_standard_atm.F90 @@ -7,19 +7,6 @@ module ic_us_standard_atmosphere ! !------------------------------------------------------------------------------- -use shr_kind_mod, only: r8 => shr_kind_r8 -use spmd_utils, only: masterproc - -use hycoef, only: ps0, hyam, hybm -use physconst, only: gravit -!use constituents, only: cnst_name -!use const_init, only: cnst_init_default - -use std_atm_profile, only: std_atm_pres, std_atm_height, std_atm_temp - -use cam_logfile, only: iulog -use cam_abortutils, only: endrun, check_allocate - implicit none private save @@ -33,6 +20,19 @@ module ic_us_standard_atmosphere subroutine us_std_atm_set_ic(latvals, lonvals, zint, U, V, T, PS, PHIS_IN, & PHIS_OUT, Q, m_cnst, mask, verbose) + use shr_kind_mod, only: r8 => shr_kind_r8, cx => shr_kind_cx + use ccpp_kinds, only: kind_phys + use spmd_utils, only: masterproc + use hycoef, only: ps0, hyam, hybm + use physconst, only: gravit + use std_atm_profile, only: std_atm_pres, std_atm_height, std_atm_temp + use cam_logfile, only: iulog + use cam_abortutils, only: endrun, check_allocate + use runtime_obj, only: wv_stdname + use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t + use cam_ccpp_cap, only: cam_model_const_properties + use cam_constituents, only: const_get_index, const_qmin + !---------------------------------------------------------------------------- ! ! Set initial values for static atmosphere with vertical profile from US @@ -58,14 +58,22 @@ subroutine us_std_atm_set_ic(latvals, lonvals, zint, U, V, T, PS, PHIS_IN, & ! Local variables logical, allocatable :: mask_use(:) logical :: verbose_use + logical :: const_has_default integer :: i, k, m integer :: ncol integer :: nlev, nlevp integer :: ncnst integer :: iret + integer :: ix_q, m_cnst_ix_q character(len=*), parameter :: subname = 'us_std_atm_set_ic' + character(len=cx) :: errmsg !CCPP error message real(r8) :: psurf(1) real(r8), allocatable :: pmid(:), zmid(:), zmid2d(:,:) + real(kind_phys) :: const_default_value !Constituent default value + real(kind_phys) :: const_qmin_value !Constituent minimum value + + !Private array of constituent properties (for property interface functions) + type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) !---------------------------------------------------------------------------- ! check input consistency @@ -208,35 +216,67 @@ subroutine us_std_atm_set_ic(latvals, lonvals, zint, U, V, T, PS, PHIS_IN, & zmid2d = 0.5_r8*(zint(:,1:nlev) + zint(:,2:nlev+1)) end if - ncnst = size(m_cnst, 1) + !Get water vapor constituent index: + call const_get_index(wv_stdname, ix_q) + + !Determine which "Q" variable index matches water vapor: + m_cnst_ix_q = findloc(m_cnst, ix_q, dim=1) + + !Determine total number of constituents: + ncnst = size(m_cnst) + + !Extract constituent properties from CCPP constituents object: + const_props => cam_model_const_properties() + do m = 1, ncnst - if (m_cnst(m) == 1) then + if (m_cnst(m) == m_cnst_ix_q) then ! No water vapor in profile do k = 1, nlev where(mask_use) Q(:,k,m_cnst(m)) = 0.0_r8 end where end do -!Un-comment once constituents are working in CAMDEN -JN: -#if 0 if(masterproc .and. verbose_use) then - write(iulog,*) ' ', trim(cnst_name(m_cnst(m))), ' initialized by '//subname + write(iulog,*) ' ', wv_stdname, ' initialized by '//subname end if else - if (present(zint)) then - call cnst_init_default(m_cnst(m), latvals, lonvals, Q(:,:,m_cnst(m)),& - mask=mask_use, verbose=verbose_use, notfound=.false., z=zmid2d) - else - call cnst_init_default(m_cnst(m), latvals, lonvals, Q(:,:,m_cnst(m)),& - mask=mask_use, verbose=verbose_use, notfound=.false.) - end if -#else - else - !Initialize cloud liquid and rain until constituent routines are enabled: - Q(:,:,m_cnst(m)) = 0.0_r8 -#endif - end if - end do + + !Extract constituent minimum value: + const_qmin_value = const_qmin(m_cnst(m)) + + !Initialize constituent to its minimum value: + Q(:,:,m) = real(const_qmin_value, r8) + + !Check for default value in constituent properties object: + call const_props(m_cnst(m))%has_default(const_has_default, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + if (const_has_default) then + + !If default value exists, then extract default value + !from constituent properties object: + call const_props(m_cnst(m))%default_value(const_default_value, & + iret, & + errmsg) + if (iret /= 0) then + call endrun(errmsg, file=__FILE__, line=__LINE__) + end if + + !Set constituent to default value in masked region: + do k=1,nlev + where(mask_use) + Q(:,k,m) = real(const_default_value, r8) + end where + end do + + end if !has_default + + end if !water vapor + end do !ncnst if (allocated(zmid2d)) deallocate(zmid2d) diff --git a/src/dynamics/tests/namelist_definition_analy_ic.xml b/src/dynamics/tests/namelist_definition_analy_ic.xml index 0c17db2f..f71490bc 100644 --- a/src/dynamics/tests/namelist_definition_analy_ic.xml +++ b/src/dynamics/tests/namelist_definition_analy_ic.xml @@ -8,7 +8,9 @@ char*80 dyn_test analytic_ic_nl - none,held_suarez_1994,moist_baroclinic_wave_dcmip2016,dry_baroclinic_wave_dcmip2016,dry_baroclinic_wave_jw2006 + + none,held_suarez_1994,moist_baroclinic_wave_dcmip2016,dry_baroclinic_wave_dcmip2016,dry_baroclinic_wave_jw2006,us_standard_atmosphere + Specify the type of analytic initial conditions for an initial run. held_suarez_1994: Initial conditions specified in Held and Suarez (1994) From 3112ab620ab36c0755bd1e3dc8d682d1b81ed730 Mon Sep 17 00:00:00 2001 From: Haipeng Lin Date: Mon, 16 Sep 2024 18:37:37 -0400 Subject: [PATCH 26/27] Update standard names for tw_ini, tw_cur; add to physics_state ddt --- src/data/registry.xml | 10 ++++++++-- tools/stdnames_to_inputnames_dictionary.xml | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/data/registry.xml b/src/data/registry.xml index 590ea2a2..9bca0f62 100644 --- a/src/data/registry.xml +++ b/src/data/registry.xml @@ -235,7 +235,7 @@ te_cur_dyn state_te_cur_dyn @@ -243,7 +243,7 @@ tw_ini state_tw_ini @@ -313,6 +313,12 @@ inverse_exner_function_wrt_surface_pressure frontogenesis_function frontogenesis_angle + vertically_integrated_total_energy_of_initial_state_using_physics_energy_formula + vertically_integrated_total_energy_of_current_state_using_physics_energy_formula + vertically_integrated_total_energy_of_initial_state_using_dycore_energy_formula + vertically_integrated_total_energy_of_current_state_using_dycore_energy_formula + vertically_integrated_water_vapor_and_condensed_water_of_initial_state + vertically_integrated_water_vapor_and_condensed_water_of_current_state tendency_of_air_temperature_due_to_model_physics diff --git a/tools/stdnames_to_inputnames_dictionary.xml b/tools/stdnames_to_inputnames_dictionary.xml index 77e81cd2..0ac72a21 100644 --- a/tools/stdnames_to_inputnames_dictionary.xml +++ b/tools/stdnames_to_inputnames_dictionary.xml @@ -184,13 +184,13 @@ state_te_cur_dyn - + tw_ini state_tw_ini - + tw_cur state_tw_cur From f8698e08c7a28acb08b5cadb7f378d99bead43f4 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 24 Sep 2024 13:44:27 -0600 Subject: [PATCH 27/27] Create LICENSE Add Apache 2.0 license to CAM-SIMA. --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.