diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 58a1869f9d..4798c37853 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -31,3 +31,4 @@ d866510188d26d51bcd6d37239283db690af7e82
183fc26a6691bbdf87f515dc47924a64be3ced9b
6fccf682eaf718615407d9bacdd3903b8786a03d
2500534eb0a83cc3aff94b30fb62e915054030bf
+78d05967c2b027dc9776a884716597db6ef7f57c
diff --git a/.gitignore b/.gitignore
index ca701132a7..ec2a3b17f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,6 +111,7 @@ unit_test_build
/tools/site_and_regional/????.ad/
/tools/site_and_regional/????.postad/
/tools/site_and_regional/????.transient/
+/tools/site_and_regional/archive/
# build output
*.o
diff --git a/cime_config/buildnml b/cime_config/buildnml
index 84e1581406..0521830616 100755
--- a/cime_config/buildnml
+++ b/cime_config/buildnml
@@ -144,8 +144,8 @@ def buildnml(case, caseroot, compname):
or clm_usrdat_name is "NEON.PRISM"
):
logger.warning(
- "WARNING: Do you have approriprate initial conditions for this simulation?"
- + " Check that the finidat file used in the lnd_in namelist is apprporiately spunup for your case"
+ "WARNING: Do you have appropriate initial conditions for this simulation?"
+ + " Check that the finidat file used in the lnd_in namelist is appropriately spunup for your case"
)
if comp_atm != "datm":
diff --git a/cime_config/config_pes.xml b/cime_config/config_pes.xml
index 34fd593425..d39ba06e49 100644
--- a/cime_config/config_pes.xml
+++ b/cime_config/config_pes.xml
@@ -119,13 +119,13 @@
none
-1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
+ -4
+ -4
+ -4
+ -4
+ -4
+ -4
+ -4
1
@@ -631,6 +631,43 @@
+
+
+
+ none
+
+ -1
+ -21
+ -21
+ -21
+ -21
+ -21
+ -21
+ -21
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
@@ -742,19 +779,56 @@
+
+
+
+ none
+
+ -1
+ -8
+ -8
+ -8
+ -8
+ -8
+ -8
+ -8
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
none
-1
- -5
- -5
- -5
- -5
- -5
- -5
- -5
+ -12
+ -12
+ -12
+ -12
+ -12
+ -12
+ -12
1
@@ -1560,6 +1634,158 @@
+
+
+
+ none
+
+ -1
+ -4
+ -4
+ -4
+ -4
+ -4
+ -4
+ -4
+ -4
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
+
+
+
+ none
+
+ -1
+ -8
+ -8
+ -8
+ -8
+ -8
+ -8
+ -8
+ -8
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
+
+
+
+ none
+
+ -1
+ -12
+ -12
+ -12
+ -12
+ -12
+ -12
+ -12
+ -12
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
+
+
+
+ none
+
+ -1
+ -36
+ -36
+ -36
+ -36
+ -36
+ -36
+ -36
+ -36
+
+
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+
+
+ 0
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+ -1
+
+
+
+
diff --git a/doc/ChangeLog b/doc/ChangeLog
index fbaa87f2dd..f822c5a72e 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,4 +1,65 @@
===============================================================
+Tag name: ctsm5.1.dev175
+Originator(s): slevis (Samuel Levis,UCAR/TSS,303-665-1310)
+Date: Thu 21 Mar 2024 05:49:04 PM MDT
+One-line Summary: merge-b4bdev-20240321
+
+Purpose and description of changes
+----------------------------------
+
+Merge master 20240313 #2421 (Update of externals to what's expected in cesm2_3_beta17)
+Fix for cray compiler format issue #2391
+Remove LILAC references to mct #2374
+Refactoring of neon_site into tower_site and neon_site #2363
+Fix misplaced stopf in CNDriverMod.F90 #2358
+Update some PE layouts on Derecho #2429
+
+Significant changes to scientifically-supported configurations
+--------------------------------------------------------------
+
+Does this tag change answers significantly for any of the following physics configurations?
+(Details of any changes will be given in the "Answer changes" section below.)
+
+ [Put an [X] in the box for any configuration with significant answer changes.]
+
+[ ] clm5_1
+
+[ ] clm5_0
+
+[ ] ctsm5_0-nwp
+
+[ ] clm4_5
+
+
+Bugs fixed
+----------
+CTSM issues fixed (include CTSM Issue #):
+ Listed in Purpose and Description above
+
+Testing summary:
+----------------
+
+ [PASS means all tests PASS; OK means tests PASS other than expected fails.]
+
+ regular tests (aux_clm: https://github.com/ESCOMP/CTSM/wiki/System-Testing-Guide#pre-merge-system-testing):
+
+ derecho ----- OK
+ izumi ------- OK
+
+Answer changes
+--------------
+Changes answers relative to baseline: NO
+
+Other details
+-------------
+List any externals directories updated (cime, rtm, mosart, cism, fates, etc.):
+ See #2421 for update of externals to what's expected in cesm2_3_beta17
+
+Pull Requests that document the changes (include PR ids):
+ https://github.com/ESCOMP/ctsm/pull/2431
+
+===============================================================
+===============================================================
Tag name: ctsm5.1.dev174
Originator(s): olyson (Keith Oleson,UCAR/TSS)
Date: Thu 14 Mar 2024 04:56:37 PM MDT
diff --git a/doc/ChangeSum b/doc/ChangeSum
index 49afdeb03f..e8c32d90a8 100644
--- a/doc/ChangeSum
+++ b/doc/ChangeSum
@@ -1,5 +1,6 @@
Tag Who Date Summary
============================================================================================================================
+ ctsm5.1.dev175 slevis 03/21/2024 merge-b4bdev-20240321
ctsm5.1.dev174 olyson 03/14/2024 Improve vegetation health at high latitudes
ctsm5.1.dev173 rgknox 03/13/2024 New FATES namelist variable: fates_history_dimlevel
ctsm5.1.dev172 erik 03/12/2024 Merge b4b-dev
diff --git a/lilac/src/lilac_mod.F90 b/lilac/src/lilac_mod.F90
index 12dd4f74a6..af5e2edd22 100644
--- a/lilac/src/lilac_mod.F90
+++ b/lilac/src/lilac_mod.F90
@@ -8,7 +8,6 @@ module lilac_mod
! External libraries
use ESMF
- use mct_mod , only : mct_world_init
! shr code routines
use shr_sys_mod , only : shr_sys_abort
@@ -146,10 +145,7 @@ subroutine lilac_init2(mpicom, atm_global_index, atm_lons, atm_lats, &
integer, parameter :: debug = 1 !-- internal debug level
character(len=*), parameter :: subname=trim(modname)//': [lilac_init] '
- ! initialization of mct and pio
- integer :: ncomps = 1 ! for mct
- integer, pointer :: mycomms(:) ! for mct
- integer, pointer :: myids(:) ! for mct
+ ! initialization of pio
integer :: compids(1) = (/1/) ! for pio_init2 - array with component ids
character(len=32) :: compLabels(1) = (/'LND'/) ! for pio_init2
character(len=64) :: comp_name(1) = (/'LND'/) ! for pio_init2
@@ -220,14 +216,6 @@ subroutine lilac_init2(mpicom, atm_global_index, atm_lons, atm_lats, &
call ESMF_VMGet(vm, localPet=mytask, rc=rc)
if (chkerr(rc,__LINE__,u_FILE_u)) return
- !-------------------------------------------------------------------------
- ! Initialize MCT (this is needed for data model functionality)
- !-------------------------------------------
- allocate(mycomms(1), myids(1))
- mycomms = (/mpicom/) ; myids = (/1/)
- call mct_world_init(ncomps, mpicom, mycomms, myids)
- call ESMF_LogWrite(subname//"initialized mct ... ", ESMF_LOGMSG_INFO)
-
!-------------------------------------------------------------------------
! Initialize PIO with second initialization
!-------------------------------------------------------------------------
diff --git a/python/Makefile b/python/Makefile
index b43e1c5e53..9645242111 100644
--- a/python/Makefile
+++ b/python/Makefile
@@ -28,6 +28,10 @@ PYLINT_SRC = \
# ../cime_config/buildnml
all: test black lint
+# ----------------------------------------------------------------
+# See the stest target about this issue
+ @echo "Run './run_ctsm_py_tests --sys' by hand afterwards"
+# ----------------------------------------------------------------
@echo
@echo
@echo "Successfully ran all standard tests"
@@ -40,7 +44,14 @@ utest: FORCE
.PHONY: stest
stest: FORCE
- $(PYTHON) ./run_ctsm_py_tests $(TEST_ARGS) --sys
+# ----------------------------------------------------------------
+# EBK 2024-03-19: Comment out running here because of this issue:
+# https://github.com/ESCOMP/CTSM/pull/2363#issuecomment-1967884908
+#$(PYTHON) ./run_ctsm_py_tests $(TEST_ARGS) --sys
+# Instead run by hand which seems to be working for now...
+# ----------------------------------------------------------------
+ @echo "System tests currently don't run under Make so..."
+ @echo "Run './run_ctsm_py_tests --sys' by hand afterwards"
.PHONY: lint
lint: FORCE
diff --git a/python/ctsm/site_and_regional/neon_site.py b/python/ctsm/site_and_regional/neon_site.py
index 9ad690becb..4af8e66fdd 100755
--- a/python/ctsm/site_and_regional/neon_site.py
+++ b/python/ctsm/site_and_regional/neon_site.py
@@ -3,18 +3,18 @@
"""
# Import libraries
-import glob
import logging
import os
-import re
-import shutil
import sys
-import time
# Get the ctsm util tools and then the cime tools.
_CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "python"))
sys.path.insert(1, _CTSM_PYTHON)
+# -- import local classes for this script
+# pylint: disable=wrong-import-position
+from ctsm.site_and_regional.tower_site import TowerSite
+
# pylint: disable=wrong-import-position, import-error, unused-import, wrong-import-order
from ctsm import add_cime_to_path
from ctsm.path_utils import path_to_ctsm_root
@@ -28,130 +28,31 @@
# pylint: disable=too-many-instance-attributes
-class NeonSite:
+class NeonSite(TowerSite):
"""
A class for encapsulating neon sites.
"""
- def __init__(self, name, start_year, end_year, start_month, end_month, finidat):
- self.name = name
- self.start_year = int(start_year)
- self.end_year = int(end_year)
- self.start_month = int(start_month)
- self.end_month = int(end_month)
- self.cesmroot = path_to_ctsm_root()
- self.finidat = finidat
-
def build_base_case(
- self, cesmroot, output_root, res, compset, overwrite=False, setup_only=False
+ self,
+ cesmroot,
+ output_root,
+ res,
+ compset,
+ user_mods_dirs=None,
+ overwrite=False,
+ setup_only=False,
):
- """
- Function for building a base_case to clone.
- To spend less time on building ctsm for the neon cases,
- all the other cases are cloned from this case
-
- Args:
- self:
- The NeonSite object
- base_root (str):
- root of the base_case CIME
- res (str):
- base_case resolution or gridname
- compset (str):
- base case compset
- overwrite (bool) :
- Flag to overwrite the case if exists
- """
- print("---- building a base case -------")
- # pylint: disable=attribute-defined-outside-init
- self.base_case_root = output_root
- # pylint: enable=attribute-defined-outside-init
- user_mods_dirs = [os.path.join(cesmroot, "cime_config", "usermods_dirs", "NEON", self.name)]
- if not output_root:
- output_root = os.getcwd()
- case_path = os.path.join(output_root, self.name)
-
- logger.info("base_case_name : %s", self.name)
- logger.info("user_mods_dir : %s", user_mods_dirs[0])
-
- if overwrite and os.path.isdir(case_path):
- print("Removing the existing case at: {}".format(case_path))
- if os.getcwd() == case_path:
- abort("Trying to remove the directory tree that we are in")
-
- shutil.rmtree(case_path)
-
- with Case(case_path, read_only=False) as case:
- if not os.path.isdir(case_path):
- print("---- creating a base case -------")
-
- case.create(
- case_path,
- cesmroot,
- compset,
- res,
- run_unsupported=True,
- answer="r",
- output_root=output_root,
- user_mods_dirs=user_mods_dirs,
- driver="nuopc",
- )
-
- print("---- base case created ------")
-
- # --change any config for base_case:
- # case.set_value("RUN_TYPE","startup")
- print("---- base case setup ------")
- case.case_setup()
- else:
- # For existing case check that the compset name is correct
- existingcompname = case.get_value("COMPSET")
- match = re.search("^HIST", existingcompname, flags=re.IGNORECASE)
- if re.search("^HIST", compset, flags=re.IGNORECASE) is None:
- expect(
- match is None,
- """Existing base case is a historical type and should not be
- --rerun with the --overwrite option""",
- )
- else:
- expect(
- match is not None,
- """Existing base case should be a historical type and is not
- --rerun with the --overwrite option""",
- )
- # reset the case
- case.case_setup(reset=True)
- case_path = case.get_value("CASEROOT")
-
- if setup_only:
- return case_path
+ if user_mods_dirs is None:
+ user_mods_dirs = [
+ os.path.join(self.cesmroot, "cime_config", "usermods_dirs", "NEON", self.name)
+ ]
+ print("in neonsite adding usermodsdirs")
+ print("usermodsdirs: {}".format(user_mods_dirs))
+ case_path = super().build_base_case(cesmroot, output_root, res, compset, user_mods_dirs)
- print("---- base case build ------")
- print("--- This may take a while and you may see WARNING messages ---")
- # always walk through the build process to make sure it's up to date.
- initial_time = time.time()
- build.case_build(case_path, case=case)
- end_time = time.time()
- total = end_time - initial_time
- print("Time required to building the base case: {} s.".format(total))
- # update case_path to be the full path to the base case
return case_path
- # pylint: disable=no-self-use
- def get_batch_query(self, case):
- """
- Function for querying the batch queue query command for a case, depending on the
- user's batch system.
-
- Args:
- case:
- case object
- """
-
- if case.get_value("BATCH_SYSTEM") == "none":
- return "none"
- return case.get_value("batch_query")
-
# pylint: disable=too-many-statements
def run_case(
self,
@@ -160,6 +61,8 @@ def run_case(
prism,
run_length,
user_version,
+ tower_type=None,
+ user_mods_dirs=None,
overwrite=False,
setup_only=False,
no_batch=False,
@@ -195,206 +98,16 @@ def run_case(
user_mods_dirs = [
os.path.join(self.cesmroot, "cime_config", "usermods_dirs", "NEON", self.name)
]
- expect(
- os.path.isdir(base_case_root),
- "Error base case does not exist in {}".format(base_case_root),
+ tower_type = "NEON"
+ super().run_case(
+ base_case_root, run_type, prism, run_length, user_version, tower_type, user_mods_dirs
)
- # -- if user gives a version:
- if user_version:
- version = user_version
- else:
- version = "latest"
-
- print("using this version:", version)
-
- if experiment is not None:
- self.name = self.name + "." + experiment
- case_root = os.path.abspath(os.path.join(base_case_root, "..", self.name + "." + run_type))
-
- rundir = None
- if os.path.isdir(case_root):
- if overwrite:
- print("---- removing the existing case -------")
- if os.getcwd() == case_root:
- abort("Trying to remove the directory tree that we are in")
- shutil.rmtree(case_root)
- elif rerun:
- with Case(case_root, read_only=False) as case:
- rundir = case.get_value("RUNDIR")
- # For existing case check that the compset name is correct
- existingcompname = case.get_value("COMPSET")
- match = re.search("^HIST", existingcompname, flags=re.IGNORECASE)
- # pylint: disable=undefined-variable
- if re.search("^HIST", compset, flags=re.IGNORECASE) is None:
- expect(
- match is None,
- """Existing base case is a historical type and should not be
- --rerun with the --overwrite option""",
- )
- # pylint: enable=undefined-variable
- else:
- expect(
- match is not None,
- """Existing base case should be a historical type and is not
- --rerun with the --overwrite option""",
- )
- if os.path.isfile(os.path.join(rundir, "ESMF_Profile.summary")):
- print("Case {} appears to be complete, not rerunning.".format(case_root))
- elif not setup_only:
- print("Resubmitting case {}".format(case_root))
- case.submit(no_batch=no_batch)
- print("-----------------------------------")
- print("Successfully submitted case!")
- batch_query = self.get_batch_query(case)
- if batch_query != "none":
- print(f"Use {batch_query} to check its run status")
- return
- else:
- logger.warning("Case already exists in %s, not overwritting", case_root)
- return
-
- if run_type == "postad":
- adcase_root = case_root.replace(".postad", ".ad")
- if not os.path.isdir(adcase_root):
- logger.warning("postad requested but no ad case found in %s", adcase_root)
- return
-
- if not os.path.isdir(case_root):
- # read_only = False should not be required here
- with Case(base_case_root, read_only=False) as basecase:
- print("---- cloning the base case in {}".format(case_root))
- #
- # EBK: 11/05/2022 -- Note keeping the user_mods_dirs argument is important. Although
- # it causes some of the user_nl_* files to have duplicated inputs. It also ensures
- # that the shell_commands file is copied, as well as taking care of the DATM inputs.
- # See https://github.com/ESCOMP/CTSM/pull/1872#pullrequestreview-1169407493
- #
- basecase.create_clone(case_root, keepexe=True, user_mods_dirs=user_mods_dirs)
-
- with Case(case_root, read_only=False) as case:
- if run_type != "transient":
- # in order to avoid the complication of leap years,
- # we always set the run_length in units of days.
- case.set_value("STOP_OPTION", "ndays")
- case.set_value("REST_OPTION", "end")
- case.set_value("CONTINUE_RUN", False)
- case.set_value("NEONVERSION", version)
- if prism:
- case.set_value("CLM_USRDAT_NAME", "NEON.PRISM")
-
- if run_type == "ad":
- case.set_value("CLM_FORCE_COLDSTART", "on")
- case.set_value("CLM_ACCELERATED_SPINUP", "on")
- case.set_value("RUN_REFDATE", "0018-01-01")
- case.set_value("RUN_STARTDATE", "0018-01-01")
- case.set_value("RESUBMIT", 1)
- case.set_value("STOP_N", run_length)
-
- else:
- case.set_value("CLM_FORCE_COLDSTART", "off")
- case.set_value("CLM_ACCELERATED_SPINUP", "off")
- case.set_value("RUN_TYPE", "hybrid")
-
- if run_type == "postad":
- self.set_ref_case(case)
- case.set_value("STOP_N", run_length)
-
- # For transient cases STOP will be set in the user_mod_directory
- if run_type == "transient":
- if self.finidat:
- case.set_value("RUN_TYPE", "startup")
- else:
- if not self.set_ref_case(case):
- return
- case.set_value("CALENDAR", "GREGORIAN")
- case.set_value("RESUBMIT", 0)
- case.set_value("STOP_OPTION", "nmonths")
-
- if not rundir:
- rundir = case.get_value("RUNDIR")
- self.modify_user_nl(case_root, run_type, rundir)
-
- case.create_namelists()
- # explicitly run check_input_data
- case.check_all_input_data()
- if not setup_only:
- case.submit(no_batch=no_batch)
- print("-----------------------------------")
- print("Successfully submitted case!")
- batch_query = self.get_batch_query(case)
- if batch_query != "none":
- print(f"Use {batch_query} to check its run status")
-
- def set_ref_case(self, case):
- """
- Set an existing case as the reference case, eg for use with spinup.
- """
- rundir = case.get_value("RUNDIR")
- case_root = case.get_value("CASEROOT")
- if case_root.endswith(".postad"):
- ref_case_root = case_root.replace(".postad", ".ad")
- root = ".ad"
- else:
- ref_case_root = case_root.replace(".transient", ".postad")
- root = ".postad"
- if not os.path.isdir(ref_case_root):
- logger.warning(
- "ERROR: spinup must be completed first, could not find directory %s", ref_case_root
- )
- return False
-
- with Case(ref_case_root) as refcase:
- refrundir = refcase.get_value("RUNDIR")
- case.set_value("RUN_REFDIR", refrundir)
- case.set_value("RUN_REFCASE", os.path.basename(ref_case_root))
- refdate = None
- for reffile in glob.iglob(refrundir + "/{}{}.clm2.r.*.nc".format(self.name, root)):
- m_searched = re.search(r"(\d\d\d\d-\d\d-\d\d)-\d\d\d\d\d.nc", reffile)
- if m_searched:
- refdate = m_searched.group(1)
- symlink_force(reffile, os.path.join(rundir, os.path.basename(reffile)))
- logger.info("Found refdate of %s", refdate)
- if not refdate:
- logger.warning("Could not find refcase for %s", case_root)
- return False
-
- for rpfile in glob.iglob(refrundir + "/rpointer*"):
- safe_copy(rpfile, rundir)
- if not os.path.isdir(os.path.join(rundir, "inputdata")) and os.path.isdir(
- os.path.join(refrundir, "inputdata")
- ):
- symlink_force(os.path.join(refrundir, "inputdata"), os.path.join(rundir, "inputdata"))
-
- case.set_value("RUN_REFDATE", refdate)
- if case_root.endswith(".postad"):
- case.set_value("RUN_STARTDATE", refdate)
- # NOTE: if start options are set, RUN_STARTDATE should be modified here
- return True
-
- def modify_user_nl(self, case_root, run_type, rundir):
- """
- Modify user namelist. If transient, include finidat in user_nl;
- Otherwise, adjust user_nl to include different mfilt, nhtfrq, and variables in hist_fincl1.
- """
- user_nl_fname = os.path.join(case_root, "user_nl_clm")
- user_nl_lines = None
- if run_type == "transient":
- if self.finidat:
- user_nl_lines = [
- "finidat = '{}/inputdata/lnd/ctsm/initdata/{}'".format(rundir, self.finidat)
- ]
- else:
- user_nl_lines = [
- "hist_fincl2 = ''",
- "hist_mfilt = 20",
- "hist_nhtfrq = -8760",
- "hist_empty_htapes = .true.",
+ def modify_user_nl(self, case_root, run_type, rundir, site_lines=None):
+ # TODO: include neon-specific user namelist lines, using this as just an example currently
+ if site_lines is None:
+ site_lines = [
"""hist_fincl1 = 'TOTECOSYSC', 'TOTECOSYSN', 'TOTSOMC', 'TOTSOMN', 'TOTVEGC',
- 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO'""",
+ 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO',"""
]
-
- if user_nl_lines:
- with open(user_nl_fname, "a") as nl_file:
- for line in user_nl_lines:
- nl_file.write("{}\n".format(line))
+ super().modify_user_nl(case_root, run_type, rundir, site_lines)
diff --git a/python/ctsm/site_and_regional/run_neon.py b/python/ctsm/site_and_regional/run_neon.py
index 72bf3fdfb4..d831f8dba2 100755
--- a/python/ctsm/site_and_regional/run_neon.py
+++ b/python/ctsm/site_and_regional/run_neon.py
@@ -174,8 +174,10 @@ def main(description):
"""
cesmroot = path_to_ctsm_root()
# Get the list of supported neon sites from usermods
+ # The [!Fd]* portion means that we won't retrieve cases that start with:
+ # F (FATES) or d (default). We should be aware of adding cases that start with these.
valid_neon_sites = glob.glob(
- os.path.join(cesmroot, "cime_config", "usermods_dirs", "NEON", "[!d]*")
+ os.path.join(cesmroot, "cime_config", "usermods_dirs", "NEON", "[!Fd]*")
)
valid_neon_sites = sorted([v.split("/")[-1] for v in valid_neon_sites])
@@ -220,8 +222,9 @@ def main(description):
if run_from_postad:
neon_site.finidat = None
if not base_case_root:
+ user_mods_dirs = None
base_case_root = neon_site.build_base_case(
- cesmroot, output_root, res, compset, overwrite, setup_only
+ cesmroot, output_root, res, compset, user_mods_dirs, overwrite, setup_only
)
logger.info("-----------------------------------")
logger.info("Running CTSM for neon site : %s", neon_site.name)
diff --git a/python/ctsm/site_and_regional/tower_site.py b/python/ctsm/site_and_regional/tower_site.py
new file mode 100644
index 0000000000..1679df83e9
--- /dev/null
+++ b/python/ctsm/site_and_regional/tower_site.py
@@ -0,0 +1,421 @@
+"""
+This module includes the definition for the TowerSite class,
+which has NeonSite and Plumber2Site child classes. This class defines common
+functionalities that are in both NeonSite and Plumber2Site classes.
+"""
+# -- Import libraries
+
+# -- standard libraries
+import os.path
+import glob
+import logging
+import re
+import shutil
+import sys
+import time
+
+# Get the ctsm util tools and then the cime tools.
+_CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "python"))
+sys.path.insert(1, _CTSM_PYTHON)
+
+# pylint: disable=wrong-import-position, import-error, unused-import, wrong-import-order
+from ctsm import add_cime_to_path
+from ctsm.path_utils import path_to_ctsm_root
+from ctsm.utils import abort
+
+from CIME import build
+from CIME.case import Case
+from CIME.utils import safe_copy, expect, symlink_force
+
+logger = logging.getLogger(__name__)
+
+
+# pylint: disable=too-many-instance-attributes
+class TowerSite:
+ """
+ Parent class to NeonSite and Plumber2Site classes.
+ ...
+ Attributes
+ ----------
+ Methods
+ -------
+ """
+
+ def __init__(self, name, start_year, end_year, start_month, end_month, finidat):
+ """
+ Initializes TowerSite with the given arguments.
+ Parameters
+ ----------
+ """
+ self.name = name
+ self.start_year = int(start_year)
+ self.end_year = int(end_year)
+ self.start_month = int(start_month)
+ self.end_month = int(end_month)
+ self.cesmroot = path_to_ctsm_root()
+ self.finidat = finidat
+
+ def __str__(self):
+ """
+ Converts ingredients of the TowerSite to string for printing.
+ """
+ return "{}\n{}".format(
+ str(self.__class__),
+ "\n".join(
+ (
+ "{} = {}".format(str(key), str(self.__dict__[key]))
+ for key in sorted(self.__dict__)
+ )
+ ),
+ )
+
+ def build_base_case(
+ self, cesmroot, output_root, res, compset, user_mods_dirs, overwrite=False, setup_only=False
+ ):
+ """
+ Function for building a base_case to clone.
+ To spend less time on building ctsm for the neon cases,
+ all the other cases are cloned from this case
+ Args:
+ self:
+ The NeonSite object
+ base_root (str):
+ root of the base_case CIME
+ res (str):
+ base_case resolution or gridname
+ compset (str):
+ base case compset
+ overwrite (bool) :
+ Flag to overwrite the case if exists
+ """
+ print("---- building a base case -------")
+ # pylint: disable=attribute-defined-outside-init
+ self.base_case_root = output_root
+ # pylint: enable=attribute-defined-outside-init
+ if not output_root:
+ output_root = os.getcwd()
+ case_path = os.path.join(output_root, self.name)
+
+ logger.info("base_case_name : %s", self.name)
+ logger.info("user_mods_dir : %s", user_mods_dirs[0])
+
+ if overwrite and os.path.isdir(case_path):
+ print("Removing the existing case at: {}".format(case_path))
+ if os.getcwd() == case_path:
+ abort("Trying to remove the directory tree that we are in")
+
+ shutil.rmtree(case_path)
+
+ with Case(case_path, read_only=False) as case:
+ if not os.path.isdir(case_path):
+ print("---- creating a base case -------")
+ case.create(
+ case_path,
+ cesmroot,
+ compset,
+ res,
+ run_unsupported=True,
+ answer="r",
+ output_root=output_root,
+ user_mods_dirs=user_mods_dirs,
+ driver="nuopc",
+ )
+
+ print("---- base case created ------")
+
+ # --change any config for base_case:
+ # case.set_value("RUN_TYPE","startup")
+ print("---- base case setup ------")
+ case.case_setup()
+ else:
+ # For existing case check that the compset name is correct
+ existingcompname = case.get_value("COMPSET")
+ match = re.search("^HIST", existingcompname, flags=re.IGNORECASE)
+ if re.search("^HIST", compset, flags=re.IGNORECASE) is None:
+ expect(
+ match is None,
+ """Existing base case is a historical type and should not be
+ --rerun with the --overwrite option""",
+ )
+ else:
+ expect(
+ match is not None,
+ """Existing base case should be a historical type and is not
+ --rerun with the --overwrite option""",
+ )
+ # reset the case
+ case.case_setup(reset=True)
+ case_path = case.get_value("CASEROOT")
+
+ if setup_only:
+ return case_path
+
+ print("---- base case build ------")
+ print("--- This may take a while and you may see WARNING messages ---")
+ # always walk through the build process to make sure it's up to date.
+ initial_time = time.time()
+ build.case_build(case_path, case=case)
+ end_time = time.time()
+ total = end_time - initial_time
+ print("Time required to building the base case: {} s.".format(total))
+ # update case_path to be the full path to the base case
+ return case_path
+
+ # pylint: disable=no-self-use
+ def get_batch_query(self, case):
+ """
+ Function for querying the batch queue query command for a case, depending on the
+ user's batch system.
+ Args:
+ case:
+ case object
+ """
+
+ if case.get_value("BATCH_SYSTEM") == "none":
+ return "none"
+ return case.get_value("batch_query")
+
+ def modify_user_nl(self, case_root, run_type, rundir, site_lines=None):
+ """
+ Modify user namelist. If transient, include finidat in user_nl;
+ Otherwise, adjust user_nl to include different mfilt, nhtfrq, and variables in hist_fincl1.
+ """
+ user_nl_fname = os.path.join(case_root, "user_nl_clm")
+ user_nl_lines = None
+ if run_type == "transient":
+ if self.finidat:
+ user_nl_lines = [
+ "finidat = '{}/inputdata/lnd/ctsm/initdata/{}'".format(rundir, self.finidat)
+ ]
+ else:
+ user_nl_lines = [
+ "hist_fincl2 = ''",
+ "hist_mfilt = 20",
+ "hist_nhtfrq = -8760",
+ "hist_empty_htapes = .true.",
+ ] + site_lines
+
+ if user_nl_lines:
+ with open(user_nl_fname, "a") as nl_file:
+ for line in user_nl_lines:
+ nl_file.write("{}\n".format(line))
+
+ def set_ref_case(self, case):
+ """
+ Set an existing case as the reference case, eg for use with spinup.
+ """
+ rundir = case.get_value("RUNDIR")
+ case_root = case.get_value("CASEROOT")
+ if case_root.endswith(".postad"):
+ ref_case_root = case_root.replace(".postad", ".ad")
+ root = ".ad"
+ else:
+ ref_case_root = case_root.replace(".transient", ".postad")
+ root = ".postad"
+ if not os.path.isdir(ref_case_root):
+ logger.warning(
+ "ERROR: spinup must be completed first, could not find directory %s", ref_case_root
+ )
+ return False
+
+ with Case(ref_case_root) as refcase:
+ refrundir = refcase.get_value("RUNDIR")
+ case.set_value("RUN_REFDIR", refrundir)
+ case.set_value("RUN_REFCASE", os.path.basename(ref_case_root))
+ refdate = None
+ for reffile in glob.iglob(refrundir + "/{}{}.clm2.r.*.nc".format(self.name, root)):
+ m_searched = re.search(r"(\d\d\d\d-\d\d-\d\d)-\d\d\d\d\d.nc", reffile)
+ if m_searched:
+ refdate = m_searched.group(1)
+ symlink_force(reffile, os.path.join(rundir, os.path.basename(reffile)))
+ logger.info("Found refdate of %s", refdate)
+ if not refdate:
+ logger.warning("Could not find refcase for %s", case_root)
+ return False
+
+ for rpfile in glob.iglob(refrundir + "/rpointer*"):
+ safe_copy(rpfile, rundir)
+ if not os.path.isdir(os.path.join(rundir, "inputdata")) and os.path.isdir(
+ os.path.join(refrundir, "inputdata")
+ ):
+ symlink_force(os.path.join(refrundir, "inputdata"), os.path.join(rundir, "inputdata"))
+
+ case.set_value("RUN_REFDATE", refdate)
+ if case_root.endswith(".postad"):
+ case.set_value("RUN_STARTDATE", refdate)
+ # NOTE: if start options are set, RUN_STARTDATE should be modified here
+ return True
+
+ # pylint: disable=too-many-statements
+ # TODO: This code should be broken up into smaller pieces
+ def run_case(
+ self,
+ base_case_root,
+ run_type,
+ prism,
+ run_length,
+ user_version,
+ tower_type,
+ user_mods_dirs,
+ overwrite=False,
+ setup_only=False,
+ no_batch=False,
+ rerun=False,
+ experiment=False,
+ ):
+ """
+ Run case.
+
+ Args:
+ self
+ base_case_root: str, opt
+ file path of base case
+ run_type: str, opt
+ transient, post_ad, or ad case, default transient
+ prism: bool, opt
+ if True, use PRISM precipitation, default False
+ run_length: str, opt
+ length of run, default '4Y'
+ user_version: str, opt
+ default 'latest'
+ overwrite: bool, opt
+ default False
+ setup_only: bool, opt
+ default False; if True, set up but do not run case
+ no_batch: bool, opt
+ default False
+ rerun: bool, opt
+ default False
+ experiment: str, opt
+ name of experiment, default False
+ """
+ expect(
+ os.path.isdir(base_case_root),
+ "Error base case does not exist in {}".format(base_case_root),
+ )
+ # -- if user gives a version:
+ if user_version:
+ version = user_version
+ else:
+ version = "latest"
+
+ print("using this version:", version)
+
+ if experiment is not False:
+ self.name = self.name + "." + experiment
+ case_root = os.path.abspath(os.path.join(base_case_root, "..", self.name + "." + run_type))
+
+ rundir = None
+ if os.path.isdir(case_root):
+ if overwrite:
+ print("---- removing the existing case -------")
+ if os.getcwd() == case_root:
+ abort("Trying to remove the directory tree that we are in")
+
+ shutil.rmtree(case_root)
+ elif rerun:
+ with Case(case_root, read_only=False) as case:
+ rundir = case.get_value("RUNDIR")
+ # For existing case check that the compset name is correct
+ existingcompname = case.get_value("COMPSET")
+ match = re.search("^HIST", existingcompname, flags=re.IGNORECASE)
+ # pylint: disable=undefined-variable
+ if re.search("^HIST", compset, flags=re.IGNORECASE) is None:
+ expect(
+ match is None,
+ """Existing base case is a historical type and should not be
+ --rerun with the --overwrite option""",
+ )
+ # pylint: enable=undefined-variable
+ else:
+ expect(
+ match is not None,
+ """Existing base case should be a historical type and is not
+ --rerun with the --overwrite option""",
+ )
+ if os.path.isfile(os.path.join(rundir, "ESMF_Profile.summary")):
+ print("Case {} appears to be complete, not rerunning.".format(case_root))
+ elif not setup_only:
+ print("Resubmitting case {}".format(case_root))
+ case.submit(no_batch=no_batch)
+ print("-----------------------------------")
+ print("Successfully submitted case!")
+ batch_query = self.get_batch_query(case)
+ if batch_query != "none":
+ print(f"Use {batch_query} to check its run status")
+ return
+ else:
+ logger.warning("Case already exists in %s, not overwritting", case_root)
+ return
+ if run_type == "postad":
+ adcase_root = case_root.replace(".postad", ".ad")
+ if not os.path.isdir(adcase_root):
+ logger.warning("postad requested but no ad case found in %s", adcase_root)
+ return
+
+ if not os.path.isdir(case_root):
+ # read_only = False should not be required here
+ with Case(base_case_root, read_only=False) as basecase:
+ print("---- cloning the base case in {}".format(case_root))
+ #
+ # EBK: 11/05/2022 -- Note keeping the user_mods_dirs argument is important. Although
+ # it causes some of the user_nl_* files to have duplicated inputs. It also ensures
+ # that the shell_commands file is copied, as well as taking care of the DATM inputs.
+ # See https://github.com/ESCOMP/CTSM/pull/1872#pullrequestreview-1169407493
+ #
+ basecase.create_clone(case_root, keepexe=True, user_mods_dirs=user_mods_dirs)
+
+ with Case(case_root, read_only=False) as case:
+ if run_type != "transient":
+ # in order to avoid the complication of leap years,
+ # we always set the run_length in units of days.
+ case.set_value("STOP_OPTION", "ndays")
+ case.set_value("REST_OPTION", "end")
+ case.set_value("CONTINUE_RUN", False)
+ if tower_type == "NEON":
+ case.set_value("NEONVERSION", version)
+ if prism:
+ case.set_value("CLM_USRDAT_NAME", "NEON.PRISM")
+
+ if run_type == "ad":
+ case.set_value("CLM_FORCE_COLDSTART", "on")
+ case.set_value("CLM_ACCELERATED_SPINUP", "on")
+ case.set_value("RUN_REFDATE", "0018-01-01")
+ case.set_value("RUN_STARTDATE", "0018-01-01")
+ case.set_value("RESUBMIT", 1)
+ case.set_value("STOP_N", run_length)
+
+ else:
+ case.set_value("CLM_FORCE_COLDSTART", "off")
+ case.set_value("CLM_ACCELERATED_SPINUP", "off")
+ case.set_value("RUN_TYPE", "hybrid")
+
+ if run_type == "postad":
+ self.set_ref_case(case)
+ case.set_value("STOP_N", run_length)
+
+ # For transient cases STOP will be set in the user_mod_directory
+ if run_type == "transient":
+ if self.finidat:
+ case.set_value("RUN_TYPE", "startup")
+ else:
+ if not self.set_ref_case(case):
+ return
+ case.set_value("CALENDAR", "GREGORIAN")
+ case.set_value("RESUBMIT", 0)
+ case.set_value("STOP_OPTION", "nmonths")
+ if not rundir:
+ rundir = case.get_value("RUNDIR")
+
+ self.modify_user_nl(case_root, run_type, rundir)
+
+ case.create_namelists()
+ # explicitly run check_input_data
+ case.check_all_input_data()
+ if not setup_only:
+ case.submit(no_batch=no_batch)
+ print("-----------------------------------")
+ print("Successfully submitted case!")
+ batch_query = self.get_batch_query(case)
+ if batch_query != "none":
+ print(f"Use {batch_query} to check its run status")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 27d85d464f..568b53cd15 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -19,13 +19,10 @@ add_definitions(-DHIDE_MPI)
add_subdirectory(${CLM_ROOT}/share/src csm_share)
add_subdirectory(${CLM_ROOT}/share/unit_test_stubs/util csm_share_stubs)
add_subdirectory(${CLM_ROOT}/share/src/esmf_wrf_timemgr esmf_wrf_timemgr)
-add_subdirectory(${CLM_ROOT}/components/cpl7/driver/shr drv_share)
-# Extract just the files we need from drv_share
-set (drv_sources_needed_base
- glc_elevclass_mod.F90
- )
-extract_sources("${drv_sources_needed_base}" "${drv_sources}" drv_sources_needed)
+# Add the single file we need from CMEPS
+set (drv_sources_needed
+ ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90)
# Add CLM source directories
add_subdirectory(${CLM_ROOT}/src/utils clm_utils)
diff --git a/src/biogeochem/CNDriverMod.F90 b/src/biogeochem/CNDriverMod.F90
index bee506c8cb..b23019eb23 100644
--- a/src/biogeochem/CNDriverMod.F90
+++ b/src/biogeochem/CNDriverMod.F90
@@ -285,9 +285,9 @@ subroutine CNDriverNoLeaching(bounds,
nvegnpool, &
num_bgc_vegp, filter_bgc_vegp, 0._r8, &
num_bgc_soilc, filter_bgc_soilc, 0._r8)
+ call t_stopf('CNZero-vegbgc-nflux')
end if
- call t_stopf('CNZero-vegbgc-nflux')
call t_startf('CNZero-soilbgc-nflux')
call soilbiogeochem_nitrogenflux_inst%SetValues( &
num_bgc_soilc, filter_bgc_soilc, 0._r8)
diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90
index a96e9e939f..713e9fe5d9 100644
--- a/src/biogeochem/CropType.F90
+++ b/src/biogeochem/CropType.F90
@@ -352,7 +352,9 @@ subroutine InitHistory(this, bounds)
ptr_patch=this%sowing_reason_perharv_patch, default='inactive')
this%harvest_reason_thisyr_patch(begp:endp,:) = spval
- call hist_addfld2d (fname='HARVEST_REASON_PERHARV', units='1 = mature; 2 = max season length; 3 = incorrect Dec. 31 sowing; 4 = sowing today; 5 = sowing tomorrow; 6 = tomorrow == idop; 7 = killed by cold temperature during vernalization', type2d='mxharvests', &
+ call hist_addfld2d (fname='HARVEST_REASON_PERHARV', units='1 = mature; 2 = max season length; 3 = incorrect Dec. 31 '// &
+ 'sowing; 4 = sowing today; 5 = sowing tomorrow; 6 = tomorrow == idop; 7 = killed by cold temperature during vernalization', &
+ type2d='mxharvests', &
avgflag='I', long_name='Reason for each crop harvest; should only be output annually', &
ptr_patch=this%harvest_reason_thisyr_patch, default='inactive')
diff --git a/src/unit_test_stubs/csm_share/CMakeLists.txt b/src/unit_test_stubs/csm_share/CMakeLists.txt
index f1c6f12ded..33ddbfb342 100644
--- a/src/unit_test_stubs/csm_share/CMakeLists.txt
+++ b/src/unit_test_stubs/csm_share/CMakeLists.txt
@@ -1,6 +1,4 @@
list(APPEND share_sources
- mct_mod_stub.F90
- seq_comm_mct.F90
shr_mpi_mod_stub.F90
)
diff --git a/src/unit_test_stubs/csm_share/mct_mod_stub.F90 b/src/unit_test_stubs/csm_share/mct_mod_stub.F90
deleted file mode 100644
index 832b8847d7..0000000000
--- a/src/unit_test_stubs/csm_share/mct_mod_stub.F90
+++ /dev/null
@@ -1,30 +0,0 @@
-module mct_mod
-
- ! This is a stub of mct_mod, which only includes the bare minimum needed to build CLM
- ! unit tests
-
- implicit none
-
- public :: mct_gsMap
- public :: mct_gsMap_orderedPoints
-
- type mct_gsMap
- ! Empty, dummy type
- end type mct_gsMap
-
-contains
-
- subroutine mct_gsMap_orderedPoints(GSMap, PEno, Points)
- ! Stub routine that simply matches the signature of mct_gsMap_orderedPoints
- ! this routine allocates the Points array, to match the documented behavior of the
- ! real routine. This is needed so that a later deallocate will succeed. But note that
- ! it is just allocated to be of size 1, so it cannot be used for any real
- ! calculations.
- type(mct_gsMap), intent(in) :: GSMap
- integer, intent(in) :: PEno
- integer,dimension(:),pointer :: Points
-
- allocate(Points(1))
- end subroutine mct_gsMap_orderedPoints
-
-end module mct_mod
diff --git a/src/unit_test_stubs/csm_share/seq_comm_mct.F90 b/src/unit_test_stubs/csm_share/seq_comm_mct.F90
deleted file mode 100644
index f8201284ba..0000000000
--- a/src/unit_test_stubs/csm_share/seq_comm_mct.F90
+++ /dev/null
@@ -1,10 +0,0 @@
-module seq_comm_mct
- ! Stub of seq_comm_mct, containing just what's needed for CLM modules.
- !
- ! Note that the true seq_comm_mct is in cime/scr/drivers/mct/shr
-
- implicit none
- save
-
- integer, public :: logunit = 6
-end module seq_comm_mct