From ac43f5bde1a282766ca61d9be1d5f2dec726c32a Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 28 Jul 2021 17:01:06 -0600 Subject: [PATCH 01/12] add cime capabilities --- tools/contrib/neon/neon_runner.py | 351 ++++++++++++++---------------- 1 file changed, 164 insertions(+), 187 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 08bbd9dcd9..0d5e4a55dc 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#! /usr/bin/env python3 """ |------------------------------------------------------------------| @@ -55,7 +55,6 @@ #Import libraries -from __future__ import print_function import os import sys @@ -66,14 +65,18 @@ import argparse import subprocess import pandas as pd - +import glob from datetime import date from getpass import getuser #-- get the environment variable -cesmroot = os.environ.get('CESM_ROOT') -cesmroot = '/home/negins/ctsm_cime/' +cesmroot = os.environ.get('CTSM_ROOT') +if not cesmroot: + cesmroot = os.environ.get('CESM_ROOT') + +if not cesmroot: + raise SystemExit("ERROR: CTSM_ROOT or CESM_ROOT must be defined in environment") _LIBDIR = os.path.join(cesmroot,"cime","scripts","Tools") sys.path.append(_LIBDIR) @@ -87,70 +90,50 @@ from argparse import RawTextHelpFormatter from CIME.locked_files import lock_file, unlock_file +logger = logging.getLogger(__name__) -myname = getuser() - -#-- valid neon site options -valid_neon_sites = ['ABBY','BARR','BART','BLAN', - 'BONA','CLBJ','CPER','DCFS', - 'DEJU','DELA','DSNY','GRSM', - 'GUAN','HARV','HEAL','JERC', - 'JORN','KONA','KONZ','LAJA', - 'LENO','MLBS','MOAB','NIWO', - 'NOGP','OAES','ONAQ','ORNL', - 'OSBS','PUUM','RMNP','SCBI', - 'SERC','SJER','SOAP','SRER', - 'STEI','STER','TALL','TEAK', - 'TOOL','TREE','UKFS','UNDE', - 'WOOD','WREF','YELL' - ] - -def get_parser(): +def get_parser(args, description, valid_neon_sites): """ Get parser object for this script. """ - parser = argparse.ArgumentParser(description=__doc__, + parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter) - + + CIME.utils.setup_standard_logging_options(parser) + parser.print_usage = parser.print_help - - parser.add_argument('--neon_site', + + parser.add_argument('--neon-sites', help='4-letter neon site code.', action="store", - dest="site_name", required=False, - choices=valid_neon_sites, + choices=valid_neon_sites + ['all'], + dest="neon_sites", default=["OSBS"], - #action='append', nargs='+') - parser.add_argument('--surf_dir', - help=''' - Directory of single point surface dataset. - [default: %(default)s] - ''', - action="store", - dest="surf_dir", - type =str, - required=False, - default="/glade/scratch/"+myname+"/single_point/") - parser.add_argument('--out_dir', + +# not used +# parser.add_argument('--surf-dir', +# help=''' +# Directory of single point surface dataset. +# [default: %(default)s] +# ''', +# action="store", +# dest="surf_dir", +# type =str, +# required=False, +# default="/glade/scratch/"+myname+"/single_point/") + + parser.add_argument('--case-root', help=''' - Directory to write updated single point surface dataset. + Root Directory of cases [default: %(default)s] ''', action="store", - dest="out_dir", + dest="case_root", type =str, required=False, - default="/glade/scratch/"+myname+"/neon_sims/") - parser.add_argument('-a','--all', - help=''' - Flag for running all neon sites, instead of selected few sites. - [default: %(default)s] - ''', - action="store_true", - dest="all_flag", - default = False) + default="CIME_OUTPUT_ROOT as defined in cime") subparsers = parser.add_subparsers ( dest='run_type', @@ -168,7 +151,7 @@ def get_parser(): sasu_parser = subparsers.add_parser ('sasu', help=''' Sasu spin-up options --not in CTSM yet''') - ad_parser.add_argument ('--ad_length', + ad_parser.add_argument ('--ad-length', help=''' How many years to run AD spin-up [default: %(default)s] @@ -177,7 +160,7 @@ def get_parser(): type = int, default = 200) - pad_parser.add_argument ('--postad_length', + pad_parser.add_argument ('--postad-length', help=''' How many years to run in post-AD mode [default: %(default)s] @@ -196,7 +179,7 @@ def get_parser(): required = True, type = str) - tr_parser.add_argument('--start_year', + tr_parser.add_argument('--start-year', help=''' Start year for running CTSM simulation. [default: %(default)s] @@ -207,7 +190,7 @@ def get_parser(): type = int, default = 2018) - tr_parser.add_argument('--end_year', + tr_parser.add_argument('--end-year', help=''' End year for running CTSM simulation. [default: %(default)s] @@ -228,6 +211,16 @@ def get_parser(): required = True, type = str) + parser.add_argument('--overwrite', + help=''' + overwrite existing case directories + [default: %(default)s] + ''', + action="store_true", + dest="overwrite", + required = False, + default = False) + #parser.add_argument('--spinup', # help=''' # AD spin-up @@ -265,13 +258,21 @@ def get_parser(): # dest="sasu_flag", # required = False, # default = False) - parser.add_argument('-d','--debug', - help='Debug mode will print more information. ', - action="store_true", - dest="debug", - default=False) - - return parser + + args = CIME.utils.parse_args_and_handle_standard_logging_options(args, parser) + + if 'all' in args.neon_sites: + neon_sites = valid_neon_sites + else: + neon_sites = args.neon_sites + for site in neon_sites: + if site not in valid_neon_sites: + raise ValueError("Invalid site name {}".format(site)) + + if "CIME_OUTPUT_ROOT" in args.case_root: + args.case_root = None + + return neon_sites, args.case_root, args.run_type, args.overwrite class NeonSite : """ @@ -291,13 +292,76 @@ def __init__(self, name, start_year, end_year, start_month, end_month): self.end_year = end_year self.start_month = start_month self.end_month = end_month - + def __str__(self): return str(self.__class__) + '\n' + '\n'.join((str(item) + ' = ' for item in (self.__dict__))) - -def check_neon_listing(): + def build_base_case(self, case_root, res, compset, overwrite): + """ + 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 + """ + logger.info("---- building a base case -------") + user_mods_dirs = [os.path.join(cesmroot,"cime_config","usermods_dirs","NEON",self.name)] + if case_root: + case_path = os.path.join(case_root,self.name) + logger.info ('case_root : {}'.format(case_root)) + else: + case_path = self.name + + logger.info ('base_case_name : {}'.format(self.name)) + logger.info ('user_mods_dir : {}'.format(user_mods_dirs[0])) + + if overwrite and os.path.isdir(case_path): + logger.info ("Removing the existing case at: ", case_path) + shutil.rmtree(case_path) + + with Case(case_path, read_only=False) as case: + if not os.path.isdir(case_path): + logger.info("---- creating a base case -------") + + case.create(case_path, cesmroot, compset, res, mpilib="mpi-serial", + run_unsupported=True, answer="r",walltime="04:00:00", + user_mods_dirs = user_mods_dirs, driver="nuopc") + + logger.info("---- base case created ------") + + #--change any config for base_case: + #case.set_value("RUN_TYPE","startup") + + + logger.info("---- base case setup ------") + case.case_setup() + else: + case.case_setup(reset=True) + + logger.info("---- base case build ------") + # always walk through the build process to make sure it's up to date. + t0 = time.time() + build.case_build(case_path, case=case) + t1 = time.time() + total = t1-t0 + logger.info ("Time required to building the base case: {} s.".format(total)) + # update case_path to be the full path to the base case + case_path = case.get_value("CASEROOT") + return case_path + + +def check_neon_listing(valid_neon_sites): """ A function to download and parse neon listing file. """ @@ -305,10 +369,10 @@ def check_neon_listing(): url = 'https://neon-ncar.s3.data.neonscience.org/listing.csv' download_file(url, listing_file) - available_list= parse_neon_listing(listing_file) + available_list= parse_neon_listing(listing_file, valid_neon_sites) return available_list -def parse_neon_listing(listing_file): +def parse_neon_listing(listing_file, valid_neon_sites): """ A function to parse neon listing file and find neon sites with the dates @@ -344,7 +408,7 @@ def parse_neon_listing(listing_file): #-- check if it is a valid neon site if any(key in x for x in valid_neon_sites): site_name = key - logging.debug ("Valid neon site " + site_name+" found!") + logger.info ("Valid neon site " + site_name+" found!") tmp_df = grouped_df.get_group(key) @@ -364,13 +428,13 @@ def parse_neon_listing(listing_file): start_month = tmp_df2[1].iloc[0] end_month = tmp_df2[1].iloc[-1] - logging.debug ('start_year='+start_year) - logging.debug ('start_year=',end_year) - logging.debug ('start_month=',start_month) - logging.debug ('end_month=',end_month) + logger.debug ('start_year='+start_year) + logger.debug ('start_year=',end_year) + logger.debug ('start_month=',start_month) + logger.debug ('end_month=',end_month) neon_site = NeonSite(site_name, start_year, end_year, start_month, end_month) - logging.debug (neon_site) + logger.debug (neon_site) available_list.append(neon_site) return available_list @@ -398,67 +462,6 @@ def download_file(url, fname): print('File '+fname+'was not available on the neon server:'+ url) -def build_base_case(base_root, base_case_name, res, compset, overwrite, - user_mods_dir): - """ - 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: - base_root (str): - root of the base_case CIME - base_case_name (str): - Name of the base case - res (str): - base_case resolution or gridname - compset (str): - base case compset - overwrite (bool) : - Flag to overwrite the case if exists - user_mods_dir (list): - list of user_mods_dir - Note, cime does not accept str only - and it should be a list of strs. - """ - print("---- building a base case -------") - - case_root = os.path.join(base_root,base_case_name) - print ('base_root : ', base_root) - print ('base_case_name : ', base_case_name) - print ('user_mods_dir : ', user_mods_dir) - print ('case_root : ',case_root) - - if overwrite and os.path.isdir(case_root): - shutil.rmtree(case_root) - print ("Removing the existing case at: ", case_root) - - print("---- creating a base case -------") - - with Case(case_root, read_only=False) as case: - if not os.path.isdir(case_root): - case.create(os.path.basename(case_root), cesmroot, compset, res, - run_unsupported=True, answer="r",walltime="04:00:00", - user_mods_dirs = ["NEON/HARV"], 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() - - print("---- base case build ------") - - t0 = time.time() - build.case_build(case_root, case=case) - t1 = time.time() - total = t1-t0 - print ("Time took for building this case: ", total, "s.") - - return case_root #----------------------------------- # NS thinks these should be inside NeonSite object. @@ -492,11 +495,11 @@ def run_spinup_ad(orig_root, case_root, user_mods_dir, overwrite, ad_length, neo if overwrite and os.path.isdir(case_root): shutil.rmtree(case_root) - print("---- removing the existing case -------") + logger.info("---- removing the existing case -------") if not os.path.isdir(case_root): with Case(orig_root, read_only=False) as clone: - print("---- cloning the base case -------") + logger.info("---- cloning the base case -------") clone.create_clone(case_root, keepexe=True) with Case(case_root, read_only=False) as case: @@ -563,11 +566,11 @@ def run_postad(orig_root, case_root, user_mods_dir, overwrite, postad_length, ne if overwrite and os.path.isdir(case_root): shutil.rmtree(case_root) - print("---- removing the existing case -------") + logger.info("---- removing the existing case -------") if not os.path.isdir(case_root): with Case(orig_root, read_only=False) as clone: - print("---- cloning the base case -------") + logger.info("---- cloning the base case -------") clone.create_clone(case_root, keepexe=True) with Case(case_root, read_only=False) as case: @@ -643,11 +646,11 @@ def run_transient(orig_root, case_root, user_mods_dir, overwrite, start_year, en if overwrite and os.path.isdir(case_root): shutil.rmtree(case_root) - print("---- removing the existing case -------") + logger.info("---- removing the existing case -------") if not os.path.isdir(case_root): with Case(orig_root, read_only=False) as clone: - print("---- cloning the base case -------") + logger.info("---- cloning the base case -------") clone.create_clone(case_root, keepexe=True) with Case(case_root, read_only=False) as case: @@ -690,68 +693,42 @@ def run_transient(orig_root, case_root, user_mods_dir, overwrite, start_year, en case.submit() -def main(): - - - args = get_parser().parse_args() +def main(description): - if args.debug: - logging.basicConfig(level=logging.DEBUG) + # Get the list of supported neon sites from usermods + valid_neon_sites = glob.glob(os.path.join(cesmroot,"cime_config","usermods_dirs","NEON","[!d]*")) + valid_neon_sites = [v.split('/')[-1] for v in valid_neon_sites] - #-- specify site/sites to run ctsm - site_list=args.site_name + site_list, case_root, run_type, overwrite = get_parser(sys.argv, description, valid_neon_sites) - #-- if -a or --all run all neon sites: - all_flag = args.all_flag - if all_flag: - site_list = valid_neon_sites + logger.debug ("case_root : "+ case_root) - out_dir = args.out_dir - out_dir = "/home/negins/" - logging.debug ("out_dir : "+ out_dir) - - #if (not os.path.isdir(out_dir)): - # os.mkdir(out_dir) + if not os.path.exists(case_root): + os.makedirs(case_root) #-- check neon listing file for available data: - available_list = check_neon_listing() + available_list = check_neon_listing(valid_neon_sites) #================================= #-- all neon sites can be cloned from one generic case #-- so no need to define a base_case for every site. - #base_case_name = "base_case_"+neon_site - base_case_name = "base_case_neon" - - base_root = os.path.join(out_dir,"neon_sims",base_case_name) - - print ("base_root:", base_root) - res = "CLM_USRDAT" compset = "I1PtClm51Bgc" - # base_case can be any neon case - base_neon_site = "HARV" - user_mods_dir = [os.path.join(cesmroot,"cime_config","usermods_dirs","NEON",base_neon_site)] - - overwrite=True - - #orig_root = build_base_case(base_root, base_case_name, res, - # compset, overwrite, user_mods_dir) - orig_root = "/home/negins/neon_0725/base_case_ABBY/base_case_ABBY" - - #-- Looping over neon sites - + orig_root = None for neon_site in available_list: if neon_site.name in site_list: - - print ("-----------------------------------") - print ("Running CTSM for neon site : ", neon_site.name) + if not orig_root: + + orig_root = neon_site.build_base_case(case_root, res, + compset, overwrite) + logger.info ("-----------------------------------") + logger.info ("Running CTSM for neon site : {}".format(neon_site.name)) - #if args.ad_flag: - if (args.run_type=="ad"): + if (run_type=="ad"): ad_length = args.ad_length print ("Running Accelerated Decomposition Spinup for: ", args.ad_length) print ("-----------------------------------") @@ -763,7 +740,7 @@ def main(): run_spinup_ad(orig_root, ad_case_root, user_mods_dir, overwrite, ad_length, neon_site) #if args.postad_flag: - if (args.run_type=="postad"): + if (run_type=="postad"): postad_length = args.postad_length print ("Running in post-AD mode for: ", args.ad_length) print ("-----------------------------------") @@ -774,7 +751,7 @@ def main(): run_postad(orig_root, postad_case_root, user_mods_dir, overwrite, postad_length, neon_site) #if args.transient_flag: - if (args.run_type=="transient"): + if (run_type=="transient"): start_year = args.start_year end_year = args.start_year print ("Running in transient mode for: ", start_year, "to", end_year) @@ -788,6 +765,6 @@ def main(): if __name__ == "__main__": - main() + main(__doc__) From f4182982fc4a3142e6786ab70b3c295a961d9dc4 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 29 Jul 2021 07:46:46 -0600 Subject: [PATCH 02/12] use ctsm util scripts for cime path --- python/ctsm/path_utils.py | 3 +++ tools/contrib/neon/neon_runner.py | 26 +++++++++++--------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/python/ctsm/path_utils.py b/python/ctsm/path_utils.py index aaf79c2e66..7972fcd297 100644 --- a/python/ctsm/path_utils.py +++ b/python/ctsm/path_utils.py @@ -83,6 +83,9 @@ def add_cime_lib_to_path(standalone_only=False): cime_lib_path = os.path.join(cime_path, 'scripts', 'lib') prepend_to_python_path(cime_lib_path) + cime_lib_path = os.path.join(cime_path, + 'scripts', 'Tools') + prepend_to_python_path(cime_lib_path) return cime_path # ======================================================================== diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 0d5e4a55dc..819f2c23ba 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -55,7 +55,7 @@ #Import libraries - + import os import sys import time @@ -70,18 +70,13 @@ from getpass import getuser #-- get the environment variable +# Then something like this: +_CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..","..","..",'python')) +print("{}".format(_CTSM_PYTHON)) +sys.path.insert(1, _CTSM_PYTHON) -cesmroot = os.environ.get('CTSM_ROOT') -if not cesmroot: - cesmroot = os.environ.get('CESM_ROOT') - -if not cesmroot: - raise SystemExit("ERROR: CTSM_ROOT or CESM_ROOT must be defined in environment") - -_LIBDIR = os.path.join(cesmroot,"cime","scripts","Tools") -sys.path.append(_LIBDIR) -_LIBDIR = os.path.join(cesmroot,"cime","scripts","lib") -sys.path.append(_LIBDIR) +from ctsm import add_cime_to_path +from ctsm.path_utils import path_to_ctsm_root import CIME.build as build from standard_script_setup import * @@ -297,7 +292,7 @@ def __str__(self): return str(self.__class__) + '\n' + '\n'.join((str(item) + ' = ' for item in (self.__dict__))) - def build_base_case(self, case_root, res, compset, overwrite): + def build_base_case(self, cesmroot, case_root, res, compset, overwrite): """ Function for building a base_case to clone. To spend less time on building ctsm for the neon cases, @@ -316,6 +311,7 @@ def build_base_case(self, case_root, res, compset, overwrite): Flag to overwrite the case if exists """ logger.info("---- building a base case -------") + user_mods_dirs = [os.path.join(cesmroot,"cime_config","usermods_dirs","NEON",self.name)] if case_root: case_path = os.path.join(case_root,self.name) @@ -694,7 +690,7 @@ def run_transient(orig_root, case_root, user_mods_dir, overwrite, start_year, en def main(description): - + cesmroot = path_to_ctsm_root() # Get the list of supported neon sites from usermods valid_neon_sites = glob.glob(os.path.join(cesmroot,"cime_config","usermods_dirs","NEON","[!d]*")) valid_neon_sites = [v.split('/')[-1] for v in valid_neon_sites] @@ -722,7 +718,7 @@ def main(description): if neon_site.name in site_list: if not orig_root: - orig_root = neon_site.build_base_case(case_root, res, + orig_root = neon_site.build_base_case(cesmroot, case_root, res, compset, overwrite) logger.info ("-----------------------------------") logger.info ("Running CTSM for neon site : {}".format(neon_site.name)) From e5054ec9796523ed4e319d5e77e727a18eab8a08 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 29 Jul 2021 07:52:11 -0600 Subject: [PATCH 03/12] update comment --- tools/contrib/neon/neon_runner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 819f2c23ba..1b4bc645dd 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -69,8 +69,7 @@ from datetime import date from getpass import getuser -#-- get the environment variable -# Then something like this: +# Get the ctsm util tools and then the cime tools. _CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..","..","..",'python')) print("{}".format(_CTSM_PYTHON)) sys.path.insert(1, _CTSM_PYTHON) From dcfd3bbdae5831df956a3df09f3c492e55793e42 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 29 Jul 2021 11:08:34 -0600 Subject: [PATCH 04/12] combine run sections --- tools/contrib/neon/neon_runner.py | 396 ++++++++---------------------- 1 file changed, 104 insertions(+), 292 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 1b4bc645dd..cecd509e76 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -80,7 +80,7 @@ import CIME.build as build from standard_script_setup import * from CIME.case import Case -from CIME.utils import safe_copy +from CIME.utils import safe_copy, expect from argparse import RawTextHelpFormatter from CIME.locked_files import lock_file, unlock_file @@ -129,6 +129,16 @@ def get_parser(args, description, valid_neon_sites): required=False, default="CIME_OUTPUT_ROOT as defined in cime") + parser.add_argument('--overwrite', + help=''' + overwrite existing case directories + [default: %(default)s] + ''', + action="store_true", + dest="overwrite", + required = False, + default = False) + subparsers = parser.add_subparsers ( dest='run_type', help='Four different ways to run this script.') @@ -205,16 +215,6 @@ def get_parser(args, description, valid_neon_sites): required = True, type = str) - parser.add_argument('--overwrite', - help=''' - overwrite existing case directories - [default: %(default)s] - ''', - action="store_true", - dest="overwrite", - required = False, - default = False) - #parser.add_argument('--spinup', # help=''' # AD spin-up @@ -265,8 +265,19 @@ def get_parser(args, description, valid_neon_sites): if "CIME_OUTPUT_ROOT" in args.case_root: args.case_root = None - - return neon_sites, args.case_root, args.run_type, args.overwrite + if args.run_type: + run_type = args.run_type + else: + run_type = "ad" + + if run_type == "ad": + run_length = int(args.ad_length) + elif run_type == "postad": + run_length = int(args.postad_length) + else: + run_length = 0 + exit + return neon_sites, args.case_root, run_type, args.overwrite, run_length class NeonSite : """ @@ -286,11 +297,15 @@ def __init__(self, name, start_year, end_year, start_month, end_month): self.end_year = end_year self.start_month = start_month self.end_month = end_month + self.cesmroot = None def __str__(self): return str(self.__class__) + '\n' + '\n'.join((str(item) + ' = ' for item in (self.__dict__))) + def set_cesm_root(self, cesmroot): + self.cesmroot = cesmroot + def build_base_case(self, cesmroot, case_root, res, compset, overwrite): """ Function for building a base_case to clone. @@ -310,7 +325,7 @@ def build_base_case(self, cesmroot, case_root, res, compset, overwrite): Flag to overwrite the case if exists """ logger.info("---- building a base case -------") - + self.base_case_root = case_root user_mods_dirs = [os.path.join(cesmroot,"cime_config","usermods_dirs","NEON",self.name)] if case_root: case_path = os.path.join(case_root,self.name) @@ -322,7 +337,7 @@ def build_base_case(self, cesmroot, case_root, res, compset, overwrite): logger.info ('user_mods_dir : {}'.format(user_mods_dirs[0])) if overwrite and os.path.isdir(case_path): - logger.info ("Removing the existing case at: ", case_path) + logger.info ("Removing the existing case at: {}".format(case_path)) shutil.rmtree(case_path) with Case(case_path, read_only=False) as case: @@ -355,6 +370,68 @@ def build_base_case(self, cesmroot, case_root, res, compset, overwrite): case_path = case.get_value("CASEROOT") return case_path + def run_case(self, base_case_root, run_type, run_length, overwrite=False): + 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)) + case_root = os.path.abspath(os.path.join(base_case_root,"..", self.name+"."+run_type)) + if os.path.isdir(case_root) and overwrite: + logger.info("---- removing the existing case -------") + shutil.rmtree(case_root) + 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: + logger.info("---- cloning the base case in {}".format(case_root)) + basecase.create_clone(case_root, keepexe=True) + + with Case(case_root, read_only=False) as case: + case.set_value("DATM_YR_ALIGN",self.start_year) + case.set_value("DATM_YR_START",self.start_year) + case.set_value("DATM_YR_END",self.end_year) + case.set_value("STOP_OPTION", "nyears") + case.set_value("STOP_N", run_length) + case.set_value("REST_N", 100) + case.set_value("CONTINUE_RUN", False) + + if run_type == "ad": + case.set_value("CLM_FORCE_COLDSTART","on") + case.set_value("CLM_ACCELERATED_SPINUP","on") + case.set_value("RESUBMIT", 1) + case.set_value("RUN_REFDATE", "0018-01-01") + case.set_value("RUN_STARTDATE", "0018-01-01") + else: + case.set_value("CLM_FORCE_COLDSTART","off") + case.set_value("CLM_ACCELERATED_SPINUP","off") + case.set_value("RESUBMIT", 0) + if run_type == "post_ad": + case.set_value("RUN_REFDATE", "0218-01-01") + case.set_value("RUN_STARTDATE", "0218-01-01") + + if run_type == "transient": + case.set_value("REST_N", "12") + case.set_value("RUN_REFDATE", "2018-01-01") + case.set_value("RUN_STARTDATE", "2018-01-01") + else: + self.modify_user_nl(case_root, run_type) + + case.create_namelists() + case.submit() + + + def modify_user_nl(self, case_root, run_type): + user_nl_fname = os.path.join(case_root, "user_nl_clm") + + if run_type != "transient": + user_nl_lines = [ + "hist_mfilt = 20", + "hist_nhtfrq = -8760", + "hist_empty_htapes = .true.", + "hist_fincl1 = 'TOTECOSYSC', 'TOTECOSYSN', 'TOTSOMC', 'TOTSOMN', 'TOTVEGC', 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO'"] + + with open(user_nl_fname, "a") as fd: + for line in user_nl_lines: + fd.write("{}\n".format(line)) + + def check_neon_listing(valid_neon_sites): """ @@ -390,9 +467,12 @@ def parse_neon_listing(listing_file, valid_neon_sites): #-- TODO: do we want to check for v2 in future? - #-- filter lines with atm/cdep/v1 - df = df[df['object'].str.contains("atm/cdeps/v1")] - + #-- filter lines with atm/cdep + df = df[df['object'].str.contains("atm/cdeps/")] + + print("df is {}".format(df)) + + #-- split the object str to extract site name df=df['object'].str.split("/", expand=True) @@ -456,245 +536,13 @@ def download_file(url, fname): elif response.status_code == 404: print('File '+fname+'was not available on the neon server:'+ url) - - -#----------------------------------- -# NS thinks these should be inside NeonSite object. -#----------------------------------- -def run_spinup_ad(orig_root, case_root, user_mods_dir, overwrite, ad_length, neon_site): - """ - Function to run accelerated decomposition over specific length. - Accelerated Decomposition is denoted as AD in the code. - For creating the ad case we clone the base case - - Args: - orig_root (str): - root of the base_case - that we want to clone from - case_root (str): - Name of the AD case root - user_mods_dir (list): - list of user_mods_dir - Note, cime does not accept str only - and it should be a list of strs. - overwrite (bool) : - Flag to overwrite the case if exists - ad_length (int) : - Length of the AD spinup in years - neon_site (NeonSite): - NeonSite object that we are running. - """ - - if not os.path.isdir(orig_root): - sys.exit('Base case does not exist in', orig_root) - - if overwrite and os.path.isdir(case_root): - shutil.rmtree(case_root) - logger.info("---- removing the existing case -------") - - if not os.path.isdir(case_root): - with Case(orig_root, read_only=False) as clone: - logger.info("---- cloning the base case -------") - clone.create_clone(case_root, keepexe=True) - - with Case(case_root, read_only=False) as case: - case.set_value("DATM_YR_ALIGN",neon_site.start_year) - case.set_value("DATM_YR_START",neon_site.start_year) - case.set_value("DATM_YR_END",neon_site.end_year) - case.set_value("STOP_OPTION", "nyears") - - case.set_value("CLM_FORCE_COLDSTART","on") - case.set_value("CLM_ACCELERATED_SPINUP","on") - - case.set_value("STOP_N", ad_length.__str__()) - case.set_value("REST_N", "100") - - case.set_value("RUN_REFDATE", "0018-01-01") - case.set_value("RUN_STARTDATE", "0018-01-01") - - case.set_value("CONTINUE_RUN", "FALSE") - case.set_value("RESUBMIT", "1") - - user_nl_fname = os.path.join(case_root, "user_nl_clm") - - user_nl_file = open(user_nl_fname, "a") - user_nl_lines = [ - "hist_mfilt = 20", - "hist_nhtfrq = -8760", - "hist_empty_htapes = .true.", - "hist_fincl1 = 'TOTECOSYSC', 'TOTECOSYSN', 'TOTSOMC', 'TOTSOMN', 'TOTVEGC', 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO'"] - - for line in user_nl_lines: - user_nl_file.write("%s\n" % line) - - user_nl_file.close() - - case.create_namelists() - case.submit() - - -def run_postad(orig_root, case_root, user_mods_dir, overwrite, postad_length, neon_site): - """ - Function to run post-ad simultion for a proper spin-up over specific length. - For creating the ad case we clone the base case - - Args: - orig_root (str): - root of the base_case - that we want to clone from - case_root (str): - Name of the AD case root - user_mods_dir (list): - list of user_mods_dir - Note, cime does not accept str only - and it should be a list of strs. - overwrite (bool) : - Flag to overwrite the case if exists - postad_length (int) : - Length of the AD spinup in years - neon_site (NeonSite): - NeonSite object that we are running. - """ - - if not os.path.isdir(orig_root): - sys.exit('Base case does not exist in', orig_root) - - if overwrite and os.path.isdir(case_root): - shutil.rmtree(case_root) - logger.info("---- removing the existing case -------") - - if not os.path.isdir(case_root): - with Case(orig_root, read_only=False) as clone: - logger.info("---- cloning the base case -------") - clone.create_clone(case_root, keepexe=True) - - with Case(case_root, read_only=False) as case: - case.set_value("DATM_YR_ALIGN",neon_site.start_year) - case.set_value("DATM_YR_START",neon_site.start_year) - case.set_value("DATM_YR_END",neon_site.end_year) - case.set_value("STOP_OPTION", "nyears") - - case.set_value("CLM_FORCE_COLDSTART","off") - case.set_value("CLM_ACCELERATED_SPINUP","off") - - case.set_value("STOP_N", postad_length.__str__()) - case.set_value("REST_N", "100") - - #TODO: change this instead of hardcoding it to 218 - case.set_value("RUN_REFDATE", "0218-01-01") - case.set_value("RUN_STARTDATE", "0218-01-01") - - case.set_value("CONTINUE_RUN", "FALSE") - case.set_value("RESUBMIT","0") - - user_nl_fname = os.path.join(case_root, "user_nl_clm") - - user_nl_file = open(user_nl_fname, "a") - user_nl_lines = [ - "hist_mfilt = 20", - "hist_nhtfrq = -8760", - "hist_empty_htapes = .true.", - "hist_fincl1 = 'TOTECOSYSC', 'TOTECOSYSN', 'TOTSOMC', 'TOTSOMN', 'TOTVEGC', 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO'"] - - #-- TODO: If we add - #-- point to the correct finidat - fini_line = ("finidat="+post_finidat) - - for line in user_nl_lines: - user_nl_file.write("%s\n" % line) - - user_nl_file.write("%s\n" % fini_line) - - user_nl_file.close() - - case.create_namelists() - case.submit() - -def run_transient(orig_root, case_root, user_mods_dir, overwrite, start_year, end_year): - """ - Function to run transient simultion for a single case. - For creating the transient case we clone the base case - - Args: - orig_root (str): - root of the base_case - that we want to clone from - case_root (str): - Name of the AD case root - user_mods_dir (list): - list of user_mods_dir - Note, cime does not accept str only - and it should be a list of strs. - overwrite (bool) : - Flag to overwrite the case if exists - postad_length (int) : - Length of the AD spinup in years - neon_site (NeonSite): - NeonSite object that we are running. - start_year (int): - start year that the user specifies - end_year (int): - end year that the user specifies. - """ - if not os.path.isdir(orig_root): - sys.exit('Base case does not exist in', orig_root) - - if overwrite and os.path.isdir(case_root): - shutil.rmtree(case_root) - logger.info("---- removing the existing case -------") - - if not os.path.isdir(case_root): - with Case(orig_root, read_only=False) as clone: - logger.info("---- cloning the base case -------") - clone.create_clone(case_root, keepexe=True) - - with Case(case_root, read_only=False) as case: - case.set_value("DATM_YR_ALIGN",start_year) - case.set_value("DATM_YR_START",start_year) - case.set_value("DATM_YR_END",end_year) - case.set_value("STOP_OPTION", "nyears") - - case.set_value("CLM_FORCE_COLDSTART","off") - case.set_value("CLM_ACCELERATED_SPINUP","off") - - case.set_value("STOP_N", "12") - case.set_value("REST_N", "12") - - case.set_value("RUN_REFDATE", "2018-01-01") - case.set_value("RUN_STARTDATE", "2018-01-01") - - case.set_value("CONTINUE_RUN", "FALSE") - case.set_value("RESUBMIT","0") - - user_nl_fname = os.path.join(case_root, "user_nl_clm") - - # No need to make any changes to this ? - #Confirm this - - user_nl_file = open(user_nl_fname, "a") - #user_nl_lines = [ - # "hist_mfilt = 48", - # "hist_nhtfrq = -1", - # "hist_empty_htapes = .true.", - # "hist_fincl1 = 'TOTECOSYSC', 'TOTECOSYSN', 'TOTSOMC', 'TOTSOMN', 'TOTVEGC', 'TOTVEGN', 'TLAI', 'GPP', 'CPOOL', 'NPP', 'TWS', 'H2OSNO'"] - # point to the correct finidat - #for line in user_nl_lines: - # user_nl_file.write("%s\n" % line) - fini_line = ("finidat="+post_finidat) - - user_nl_file.close() - - case.create_namelists() - case.submit() - - def main(description): cesmroot = path_to_ctsm_root() # Get the list of supported neon sites from usermods valid_neon_sites = glob.glob(os.path.join(cesmroot,"cime_config","usermods_dirs","NEON","[!d]*")) valid_neon_sites = [v.split('/')[-1] for v in valid_neon_sites] - site_list, case_root, run_type, overwrite = get_parser(sys.argv, description, valid_neon_sites) + site_list, case_root, run_type, overwrite, run_length = get_parser(sys.argv, description, valid_neon_sites) logger.debug ("case_root : "+ case_root) @@ -712,52 +560,16 @@ def main(description): compset = "I1PtClm51Bgc" #-- Looping over neon sites - orig_root = None + base_case_root = None for neon_site in available_list: + neon_site.set_cesm_root(cesmroot) if neon_site.name in site_list: - if not orig_root: - - orig_root = neon_site.build_base_case(cesmroot, case_root, res, + if not base_case_root: + base_case_root = neon_site.build_base_case(cesmroot, case_root, res, compset, overwrite) logger.info ("-----------------------------------") logger.info ("Running CTSM for neon site : {}".format(neon_site.name)) - - - if (run_type=="ad"): - ad_length = args.ad_length - print ("Running Accelerated Decomposition Spinup for: ", args.ad_length) - print ("-----------------------------------") - - ad_case_name = "spinup_AD_"+neon_site.name - ad_case_root = os.path.join(out_dir,"neon_sims",ad_case_name) - user_mods_dir=["NEON/"+neon_site.name] - overwrite = True - run_spinup_ad(orig_root, ad_case_root, user_mods_dir, overwrite, ad_length, neon_site) - - #if args.postad_flag: - if (run_type=="postad"): - postad_length = args.postad_length - print ("Running in post-AD mode for: ", args.ad_length) - print ("-----------------------------------") - postad_case_name = "spinup_postAD_"+neon_site.name - postad_case_root = os.path.join(out_dir,"neon_sims",postad_case_name) - user_mods_dir=["NEON/"+neon_site.name] - - run_postad(orig_root, postad_case_root, user_mods_dir, overwrite, postad_length, neon_site) - - #if args.transient_flag: - if (run_type=="transient"): - start_year = args.start_year - end_year = args.start_year - print ("Running in transient mode for: ", start_year, "to", end_year) - print ("-----------------------------------") - transient_case_name = "transient_"+neon_site.name - transient_case_root = os.path.join(out_dir,"neon_sims",transient_case_name) - user_mods_dir=["NEON/"+neon_site.name] - - run_transient(orig_root, transient_case_root, user_mods_dir, overwrite, start_year, end_year) - - + neon_site.run_case(base_case_root, run_type, run_length, overwrite) if __name__ == "__main__": main(__doc__) From 6eefee312a575f64166fabdc4c5dd6c2a6f8fc74 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 29 Jul 2021 12:06:32 -0600 Subject: [PATCH 05/12] add ref case and add user_mods to clone step --- tools/contrib/neon/neon_runner.py | 42 ++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index cecd509e76..d115fe54cf 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -118,6 +118,17 @@ def get_parser(args, description, valid_neon_sites): # required=False, # default="/glade/scratch/"+myname+"/single_point/") + parser.add_argument('--base-case', + help=''' + Root Directory of base case build + [default: %(default)s] + ''', + action="store", + dest="base_case_root", + type =str, + required=False, + default=None) + parser.add_argument('--case-root', help=''' Root Directory of cases @@ -277,7 +288,7 @@ def get_parser(args, description, valid_neon_sites): else: run_length = 0 exit - return neon_sites, args.case_root, run_type, args.overwrite, run_length + return neon_sites, args.case_root, run_type, args.overwrite, run_length, args.base_case_root class NeonSite : """ @@ -297,15 +308,12 @@ def __init__(self, name, start_year, end_year, start_month, end_month): self.end_year = end_year self.start_month = start_month self.end_month = end_month - self.cesmroot = None + self.cesmroot = path_to_ctsm_root() def __str__(self): return str(self.__class__) + '\n' + '\n'.join((str(item) + ' = ' for item in (self.__dict__))) - def set_cesm_root(self, cesmroot): - self.cesmroot = cesmroot - def build_base_case(self, cesmroot, case_root, res, compset, overwrite): """ Function for building a base_case to clone. @@ -381,7 +389,7 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): # read_only = False should not be required here with Case(base_case_root, read_only=False) as basecase: logger.info("---- cloning the base case in {}".format(case_root)) - basecase.create_clone(case_root, keepexe=True) + basecase.create_clone(case_root, keepexe=True, user_mods_dirs=user_mods_dirs) with Case(case_root, read_only=False) as case: case.set_value("DATM_YR_ALIGN",self.start_year) @@ -402,14 +410,30 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): case.set_value("CLM_FORCE_COLDSTART","off") case.set_value("CLM_ACCELERATED_SPINUP","off") case.set_value("RESUBMIT", 0) + case.set_value("RUN_TYPE", "hybrid") + if run_type == "post_ad": case.set_value("RUN_REFDATE", "0218-01-01") case.set_value("RUN_STARTDATE", "0218-01-01") + ad_case_root = case_root.replace(".postad",".ad") + expect(os.path.isdir(ad_case_root), "ERROR: ad spinup must be completed first") + with Case(ad_case_root) as adcase: + adrundir = adcase.get_value("RUNDIR") + + case.set_value("RUN_REFDIR", adrundir) + case.set_value("RUN_REFCASE", os.path.basename(ad_case_root)) if run_type == "transient": case.set_value("REST_N", "12") case.set_value("RUN_REFDATE", "2018-01-01") case.set_value("RUN_STARTDATE", "2018-01-01") + postad_case_root = case_root.replace(".transient",".postad") + expect(os.path.isdir(postad_case_root), "ERROR: postad spinup must be completed first") + with Case(postad_case_root) as postadcase: + postadrundir = postadcase.get_value("RUNDIR") + + case.set_value("RUN_REFDIR", postadrundir) + case.set_value("RUN_REFCASE", os.path.basename(postad_case_root)) else: self.modify_user_nl(case_root, run_type) @@ -422,6 +446,7 @@ def modify_user_nl(self, case_root, run_type): if run_type != "transient": user_nl_lines = [ + "hist_fincl2 = ''", "hist_mfilt = 20", "hist_nhtfrq = -8760", "hist_empty_htapes = .true.", @@ -542,7 +567,7 @@ def main(description): valid_neon_sites = glob.glob(os.path.join(cesmroot,"cime_config","usermods_dirs","NEON","[!d]*")) valid_neon_sites = [v.split('/')[-1] for v in valid_neon_sites] - site_list, case_root, run_type, overwrite, run_length = get_parser(sys.argv, description, valid_neon_sites) + site_list, case_root, run_type, overwrite, run_length, base_case_root = get_parser(sys.argv, description, valid_neon_sites) logger.debug ("case_root : "+ case_root) @@ -560,9 +585,8 @@ def main(description): compset = "I1PtClm51Bgc" #-- Looping over neon sites - base_case_root = None + for neon_site in available_list: - neon_site.set_cesm_root(cesmroot) if neon_site.name in site_list: if not base_case_root: base_case_root = neon_site.build_base_case(cesmroot, case_root, res, From 738b9db96f6b64892961398ecbb11f98a2ebab56 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 29 Jul 2021 15:59:27 -0600 Subject: [PATCH 06/12] for spinup start and end on year boundaries --- tools/contrib/neon/neon_runner.py | 43 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index d115fe54cf..2b860f10ff 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -71,7 +71,6 @@ # Get the ctsm util tools and then the cime tools. _CTSM_PYTHON = os.path.abspath(os.path.join(os.path.dirname(__file__), "..","..","..",'python')) -print("{}".format(_CTSM_PYTHON)) sys.path.insert(1, _CTSM_PYTHON) from ctsm import add_cime_to_path @@ -304,10 +303,10 @@ class NeonSite : """ def __init__(self, name, start_year, end_year, start_month, end_month): self.name = name - self.start_year= start_year - self.end_year = end_year - self.start_month = start_month - self.end_month = end_month + 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() def __str__(self): @@ -392,9 +391,7 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): basecase.create_clone(case_root, keepexe=True, user_mods_dirs=user_mods_dirs) with Case(case_root, read_only=False) as case: - case.set_value("DATM_YR_ALIGN",self.start_year) - case.set_value("DATM_YR_START",self.start_year) - case.set_value("DATM_YR_END",self.end_year) + case.set_value("STOP_OPTION", "nyears") case.set_value("STOP_N", run_length) case.set_value("REST_N", 100) @@ -434,7 +431,24 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): case.set_value("RUN_REFDIR", postadrundir) case.set_value("RUN_REFCASE", os.path.basename(postad_case_root)) + + case.set_value("DATM_YR_ALIGN",self.start_year) + case.set_value("DATM_YR_START",self.start_year) + case.set_value("DATM_YR_END",self.end_year) else: + # for the spinup we want the start and end on year boundaries + if self.start_month == 1: + case.set_value("DATM_YR_ALIGN",self.start_year) + case.set_value("DATM_YR_START",self.start_year) + elif self.start_year + 1 <= self.end_year: + case.set_value("DATM_YR_ALIGN",self.start_year+1) + case.set_value("DATM_YR_START",self.start_year+1) + if self.end_month == 12: + case.set_value("DATM_YR_END",self.end_year) + else: + case.set_value("DATM_YR_END",self.end_year-1) + + self.modify_user_nl(case_root, run_type) case.create_namelists() @@ -495,15 +509,12 @@ def parse_neon_listing(listing_file, valid_neon_sites): #-- filter lines with atm/cdep df = df[df['object'].str.contains("atm/cdeps/")] - print("df is {}".format(df)) - - #-- split the object str to extract site name df=df['object'].str.split("/", expand=True) #-- groupby site name grouped_df = df.groupby(7) - + for key, item in grouped_df: #-- check if it is a valid neon site if any(key in x for x in valid_neon_sites): @@ -528,10 +539,10 @@ def parse_neon_listing(listing_file, valid_neon_sites): start_month = tmp_df2[1].iloc[0] end_month = tmp_df2[1].iloc[-1] - logger.debug ('start_year='+start_year) - logger.debug ('start_year=',end_year) - logger.debug ('start_month=',start_month) - logger.debug ('end_month=',end_month) + logger.debug ('start_year={}'.format(start_year)) + logger.debug ('end_year={}'.format(end_year)) + logger.debug ('start_month={}'.format(start_month)) + logger.debug ('end_month={}'.format(end_month)) neon_site = NeonSite(site_name, start_year, end_year, start_month, end_month) logger.debug (neon_site) From 59b1b7d3b5fb2e67f8bb932327a598e3daecedad Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 30 Jul 2021 08:18:54 -0600 Subject: [PATCH 07/12] match lastest file version and handle planned file name change --- tools/contrib/neon/neon_runner.py | 34 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 2b860f10ff..5853f70672 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -352,7 +352,7 @@ def build_base_case(self, cesmroot, case_root, res, compset, overwrite): logger.info("---- creating a base case -------") case.create(case_path, cesmroot, compset, res, mpilib="mpi-serial", - run_unsupported=True, answer="r",walltime="04:00:00", + run_unsupported=True, answer="r",walltime="08:00:00", user_mods_dirs = user_mods_dirs, driver="nuopc") logger.info("---- base case created ------") @@ -479,7 +479,7 @@ def check_neon_listing(valid_neon_sites): listing_file = 'listing.csv' url = 'https://neon-ncar.s3.data.neonscience.org/listing.csv' - download_file(url, listing_file) +# download_file(url, listing_file) available_list= parse_neon_listing(listing_file, valid_neon_sites) return available_list @@ -514,35 +514,37 @@ def parse_neon_listing(listing_file, valid_neon_sites): #-- groupby site name grouped_df = df.groupby(7) - for key, item in grouped_df: #-- check if it is a valid neon site if any(key in x for x in valid_neon_sites): site_name = key - logger.info ("Valid neon site " + site_name+" found!") tmp_df = grouped_df.get_group(key) - #-- filter files only ending with .nc - tmp_df = tmp_df[tmp_df[8].str.contains('.nc')] - + #-- filter files only ending with YYYY-MM.nc + tmp_df = tmp_df[tmp_df[8].str.contains('\d\d\d\d-\d\d.nc')] + latest_version = tmp_df[6].iloc[-1] + tmp_df = tmp_df[tmp_df[6].str.contains(latest_version)] #-- remove .nc from the file names tmp_df[8] = tmp_df[8].str.replace('.nc','') tmp_df2 = tmp_df[8].str.split("-", expand=True) - + # ignore any prefix in file name and just get year + tmp_df2[0] = tmp_df2[0].str.slice(-4) #-- figure out start_year and end_year - start_year = tmp_df2[0].iloc[0] - end_year = tmp_df2[0].iloc[-1] + start_year = int(tmp_df2[0].iloc[0]) + end_year = int(tmp_df2[0].iloc[-1]) #-- figure out start_month and end_month - start_month = tmp_df2[1].iloc[0] - end_month = tmp_df2[1].iloc[-1] + start_month = int(tmp_df2[1].iloc[0]) + end_month = int(tmp_df2[1].iloc[-1]) - logger.debug ('start_year={}'.format(start_year)) - logger.debug ('end_year={}'.format(end_year)) - logger.debug ('start_month={}'.format(start_month)) - logger.debug ('end_month={}'.format(end_month)) + logger.info ("Valid neon site " + site_name+" found!") + logger.info ("File version {}".format(latest_version)) + logger.info ('start_year={}'.format(start_year)) + logger.info ('end_year={}'.format(end_year)) + logger.info ('start_month={}'.format(start_month)) + logger.info ('end_month={}'.format(end_month)) neon_site = NeonSite(site_name, start_year, end_year, start_month, end_month) logger.debug (neon_site) From 56985690ed97f26dbfccd7c38bc37320e9768457 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 30 Jul 2021 08:19:41 -0600 Subject: [PATCH 08/12] remove debug comment --- tools/contrib/neon/neon_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 5853f70672..75fc97f5c5 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -479,7 +479,7 @@ def check_neon_listing(valid_neon_sites): listing_file = 'listing.csv' url = 'https://neon-ncar.s3.data.neonscience.org/listing.csv' -# download_file(url, listing_file) + download_file(url, listing_file) available_list= parse_neon_listing(listing_file, valid_neon_sites) return available_list From 99a00e9e11f61ae9f129a74359bb659118feefba Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 2 Aug 2021 16:20:19 -0600 Subject: [PATCH 09/12] more neon_runner work --- tools/contrib/neon/neon_runner.py | 92 +++++++++++++------------------ 1 file changed, 39 insertions(+), 53 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 75fc97f5c5..e58a3fd093 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -63,6 +63,7 @@ import logging import requests import argparse +import re import subprocess import pandas as pd import glob @@ -79,7 +80,7 @@ import CIME.build as build from standard_script_setup import * from CIME.case import Case -from CIME.utils import safe_copy, expect +from CIME.utils import safe_copy, expect, symlink_force from argparse import RawTextHelpFormatter from CIME.locked_files import lock_file, unlock_file @@ -183,16 +184,6 @@ def get_parser(args, description, valid_neon_sites): type = int, default = 100) - pad_parser.add_argument('--finidat', - help=''' - finidat file location from spinup step to start from. - [default: %(default)s] - ''', - action="store", - dest="finidat_postad", - required = True, - type = str) - tr_parser.add_argument('--start-year', help=''' Start year for running CTSM simulation. @@ -215,16 +206,6 @@ def get_parser(args, description, valid_neon_sites): type = int, default = 2020) - tr_parser.add_argument('--finidat', - help=''' - finidat file location from spinup step to start from. - [default: %(default)s] - ''', - action="store", - dest="finidat_transient", - required = True, - type = str) - #parser.add_argument('--spinup', # help=''' # AD spin-up @@ -279,14 +260,13 @@ def get_parser(args, description, valid_neon_sites): run_type = args.run_type else: run_type = "ad" - if run_type == "ad": run_length = int(args.ad_length) elif run_type == "postad": run_length = int(args.postad_length) else: run_length = 0 - exit + return neon_sites, args.case_root, run_type, args.overwrite, run_length, args.base_case_root class NeonSite : @@ -390,8 +370,8 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): logger.info("---- cloning the base case in {}".format(case_root)) basecase.create_clone(case_root, keepexe=True, user_mods_dirs=user_mods_dirs) - with Case(case_root, read_only=False) as case: + with Case(case_root, read_only=False) as case: case.set_value("STOP_OPTION", "nyears") case.set_value("STOP_N", run_length) case.set_value("REST_N", 100) @@ -400,38 +380,19 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): if run_type == "ad": case.set_value("CLM_FORCE_COLDSTART","on") case.set_value("CLM_ACCELERATED_SPINUP","on") - case.set_value("RESUBMIT", 1) case.set_value("RUN_REFDATE", "0018-01-01") case.set_value("RUN_STARTDATE", "0018-01-01") else: case.set_value("CLM_FORCE_COLDSTART","off") case.set_value("CLM_ACCELERATED_SPINUP","off") - case.set_value("RESUBMIT", 0) case.set_value("RUN_TYPE", "hybrid") - if run_type == "post_ad": - case.set_value("RUN_REFDATE", "0218-01-01") - case.set_value("RUN_STARTDATE", "0218-01-01") - ad_case_root = case_root.replace(".postad",".ad") - expect(os.path.isdir(ad_case_root), "ERROR: ad spinup must be completed first") - with Case(ad_case_root) as adcase: - adrundir = adcase.get_value("RUNDIR") - - case.set_value("RUN_REFDIR", adrundir) - case.set_value("RUN_REFCASE", os.path.basename(ad_case_root)) + if run_type == "postad": + self.set_ref_case(case) if run_type == "transient": + self.set_ref_case(case) case.set_value("REST_N", "12") - case.set_value("RUN_REFDATE", "2018-01-01") - case.set_value("RUN_STARTDATE", "2018-01-01") - postad_case_root = case_root.replace(".transient",".postad") - expect(os.path.isdir(postad_case_root), "ERROR: postad spinup must be completed first") - with Case(postad_case_root) as postadcase: - postadrundir = postadcase.get_value("RUNDIR") - - case.set_value("RUN_REFDIR", postadrundir) - case.set_value("RUN_REFCASE", os.path.basename(postad_case_root)) - case.set_value("DATM_YR_ALIGN",self.start_year) case.set_value("DATM_YR_START",self.start_year) case.set_value("DATM_YR_END",self.end_year) @@ -454,7 +415,32 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): case.create_namelists() case.submit() - + def set_ref_case(self, case): + rundir = case.get_value("RUNDIR") + case_root = case.get_value("CASEROOT") + if case_root.endswith(".postad"): + ref_case_root = case_root.replace(".postad",".ad") + else: + ref_case_root = case_root.replace(".transient",".postad") + + expect(os.path.isdir(ref_case_root), "ERROR: spinup must be completed first, could not find directory {}".format(ref_case_root)) + 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)) + for reffile in glob.iglob(refrundir + "/{}.*.nc".format(self.name)): + m = re.search("(\d\d\d\d-\d\d-\d\d)-\d\d\d\d\d.nc", reffile) + if m: + refdate = m.group(1) + symlink_force(reffile, os.path.join(rundir,os.path.basename(reffile))) + 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) + case.set_value("RUN_STARTDATE", refdate) + + def modify_user_nl(self, case_root, run_type): user_nl_fname = os.path.join(case_root, "user_nl_clm") @@ -539,12 +525,12 @@ def parse_neon_listing(listing_file, valid_neon_sites): start_month = int(tmp_df2[1].iloc[0]) end_month = int(tmp_df2[1].iloc[-1]) - logger.info ("Valid neon site " + site_name+" found!") - logger.info ("File version {}".format(latest_version)) - logger.info ('start_year={}'.format(start_year)) - logger.info ('end_year={}'.format(end_year)) - logger.info ('start_month={}'.format(start_month)) - logger.info ('end_month={}'.format(end_month)) + logger.debug ("Valid neon site " + site_name+" found!") + logger.debug ("File version {}".format(latest_version)) + logger.debug ('start_year={}'.format(start_year)) + logger.debug ('end_year={}'.format(end_year)) + logger.debug ('start_month={}'.format(start_month)) + logger.debug ('end_month={}'.format(end_month)) neon_site = NeonSite(site_name, start_year, end_year, start_month, end_month) logger.debug (neon_site) From 26f76c704603157907ed686af6725f75bd88dfe5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 4 Aug 2021 12:10:29 -0600 Subject: [PATCH 10/12] updates for transient runs --- tools/contrib/neon/neon_runner.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index e58a3fd093..7e056d158c 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -67,7 +67,7 @@ import subprocess import pandas as pd import glob -from datetime import date +from datetime import datetime from getpass import getuser # Get the ctsm util tools and then the cime tools. @@ -357,6 +357,13 @@ def build_base_case(self, cesmroot, case_root, res, compset, overwrite): case_path = case.get_value("CASEROOT") return case_path + def diff_month(self): + d1 = datetime(self.end_year,self.end_month, 1) + d2 = datetime(self.start_year, self.start_month, 1) + return (d1.year - d2.year) * 12 + d1.month - d2.month + + + def run_case(self, base_case_root, run_type, run_length, overwrite=False): 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)) @@ -392,6 +399,8 @@ def run_case(self, base_case_root, run_type, run_length, overwrite=False): if run_type == "transient": self.set_ref_case(case) + case.set_value("STOP_OPTION","nmonths") + case.set_value("STOP_N", self.diff_month()) case.set_value("REST_N", "12") case.set_value("DATM_YR_ALIGN",self.start_year) case.set_value("DATM_YR_START",self.start_year) @@ -420,15 +429,16 @@ def set_ref_case(self, case): 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" expect(os.path.isdir(ref_case_root), "ERROR: spinup must be completed first, could not find directory {}".format(ref_case_root)) 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)) - for reffile in glob.iglob(refrundir + "/{}.*.nc".format(self.name)): + for reffile in glob.iglob(refrundir + "/{}{}.*.nc".format(self.name, root)): m = re.search("(\d\d\d\d-\d\d-\d\d)-\d\d\d\d\d.nc", reffile) if m: refdate = m.group(1) From e3200a8dcee77085792bf3107208b763a105ab2d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 4 Aug 2021 12:21:13 -0600 Subject: [PATCH 11/12] add new cdeps tag --- Externals.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals.cfg b/Externals.cfg index 93dd607730..ae4061c048 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -48,7 +48,7 @@ local_path = components/cmeps required = True [cdeps] -tag = cdeps0.12.14 +tag = cdeps0.12.18 protocol = git repo_url = https://github.com/ESCOMP/CDEPS.git local_path = components/cdeps From fae52e9dd07229a138eb88533c9ac062b9178a4c Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 5 Aug 2021 13:50:42 -0600 Subject: [PATCH 12/12] add support for restarts at end of run --- src/cpl/nuopc/lnd_comp_nuopc.F90 | 60 ++++++++++++++++--------------- tools/contrib/neon/neon_runner.py | 5 ++- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/cpl/nuopc/lnd_comp_nuopc.F90 b/src/cpl/nuopc/lnd_comp_nuopc.F90 index 77c666b835..a37fd16576 100644 --- a/src/cpl/nuopc/lnd_comp_nuopc.F90 +++ b/src/cpl/nuopc/lnd_comp_nuopc.F90 @@ -88,6 +88,8 @@ module lnd_comp_nuopc character(len=*) , parameter :: continue_run = 'continue' character(len=*) , parameter :: branch_run = 'branch' + logical :: write_restart_at_endofrun = .false. + character(len=*) , parameter :: u_FILE_u = & __FILE__ @@ -356,7 +358,6 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) integer :: nsrest ! ctsm restart type integer :: lbnum ! input to memory diagnostic integer :: shrlogunit ! original log unit - type(bounds_type) :: bounds ! bounds integer :: n, ni, nj ! Indices character(len=CL) :: cvalue ! config data character(len=CL) :: meshfile_mask ! filename of mesh file with land mask @@ -369,12 +370,14 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) integer :: scol_mask ! single-column mask real(r8) :: scol_spval ! single-column special value to indicate it isn't set character(len=CL) :: single_column_lnd_domainfile ! domain filename to use for single-column mode (i.e. SCAM) + type(bounds_type) :: bounds ! bounds type(ESMF_Field) :: lfield ! Land field read in character(CL) ,pointer :: lfieldnamelist(:) => null() ! Land field namelist item sent with land field integer :: fieldCount ! Number of fields on export state integer :: rank ! Rank of field (1D or 2D) real(r8), pointer :: fldptr1d(:) ! 1D field pointer real(r8), pointer :: fldptr2d(:,:) ! 2D field pointer + logical :: isPresent, isSet character(len=CL) :: model_version ! Model version character(len=CL) :: hostname ! hostname of machine running on character(len=CL) :: username ! user running the model @@ -563,6 +566,12 @@ subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) ! Set model clock in lnd_comp_shr model_clock = clock + call NUOPC_CompAttributeGet(gcomp, name="write_restart_at_endofrun", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + if (isPresent .and. isSet) then + if (trim(cvalue) .eq. '.true.') write_restart_at_endofrun = .true. + end if + print *,__FILE__,__LINE__,write_restart_at_endofrun ! --------------------- ! Initialize first phase of ctsm ! --------------------- @@ -814,22 +823,6 @@ subroutine ModelAdvance(gcomp, rc) end if call update_rad_dtime(doalb) - !-------------------------------- - ! Determine if time to write restart - !-------------------------------- - - call ESMF_ClockGetAlarm(clock, alarmname='alarm_restart', alarm=alarm, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - if (ESMF_AlarmIsRinging(alarm, rc=rc)) then - if (ChkErr(rc,__LINE__,u_FILE_u)) return - rstwr = .true. - call ESMF_AlarmRingerOff( alarm, rc=rc ) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - rstwr = .false. - endif - !-------------------------------- ! Determine if time to stop !-------------------------------- @@ -846,6 +839,25 @@ subroutine ModelAdvance(gcomp, rc) nlend = .false. endif + !-------------------------------- + ! Determine if time to write restart + !-------------------------------- + rstwr = .false. + if (nlend .and. write_restart_at_endofrun) then + rstwr = .true. + else + call ESMF_ClockGetAlarm(clock, alarmname='alarm_restart', alarm=alarm, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + if (ESMF_AlarmIsCreated(alarm, rc=rc)) then + if (ESMF_AlarmIsRinging(alarm, rc=rc)) then + if (ChkErr(rc,__LINE__,u_FILE_u)) return + rstwr = .true. + call ESMF_AlarmRingerOff( alarm, rc=rc ) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + endif + endif + end if + !-------------------------------- ! Run CTSM !-------------------------------- @@ -966,7 +978,6 @@ subroutine ModelSetRunClock(gcomp, rc) character(len=256) :: stop_option ! Stop option units integer :: stop_n ! Number until stop interval integer :: stop_ymd ! Stop date (YYYYMMDD) - integer :: stop_tod ! Stop time of day (seconds) type(ESMF_ALARM) :: stop_alarm character(len=128) :: name integer :: alarmcount @@ -1005,7 +1016,7 @@ subroutine ModelSetRunClock(gcomp, rc) call ESMF_GridCompGet(gcomp, name=name, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return - call ESMF_LogWrite(subname//'setting alarms for ' // trim(name), ESMF_LOGMSG_INFO) + call ESMF_LogWrite(subname//'setting alarms for' // trim(name), ESMF_LOGMSG_INFO) !---------------- ! Restart alarm @@ -1045,17 +1056,10 @@ subroutine ModelSetRunClock(gcomp, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return read(cvalue,*) stop_ymd - call NUOPC_CompAttributeGet(gcomp, name="stop_tod", value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - read(cvalue,*) stop_tod - - call alarmInit(mclock, & - alarm = stop_alarm, & - option = stop_option, & + call alarmInit(mclock, stop_alarm, stop_option, & opt_n = stop_n, & opt_ymd = stop_ymd, & - opt_tod = stop_tod, & - RefTime = mcurrTime, & + RefTime = mcurrTime, & alarmname = 'alarm_stop', rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return diff --git a/tools/contrib/neon/neon_runner.py b/tools/contrib/neon/neon_runner.py index 7e056d158c..d407fd7899 100755 --- a/tools/contrib/neon/neon_runner.py +++ b/tools/contrib/neon/neon_runner.py @@ -448,7 +448,10 @@ def set_ref_case(self, case): 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) - case.set_value("RUN_STARTDATE", refdate) + if case_root.endswith(".postad"): + case.set_value("RUN_STARTDATE", refdate) + else: + case.set_value("RUN_STARTDATE", "{yr:04d}-{mo:02d}-01".format(yr=self.start_year, mo=self.start_month)) def modify_user_nl(self, case_root, run_type):