From d65e3286f885e4ee54a36b02d11a17dd37b646ad Mon Sep 17 00:00:00 2001 From: Azamat Mametjanov Date: Tue, 21 Oct 2025 13:49:20 -0700 Subject: [PATCH 001/153] Add EXCL_STRIDE to XML PE configs --- CIME/Tools/pelayout | 20 +++++++++++-------- CIME/XML/pes.py | 20 ++++++++++++++++++- CIME/case/case.py | 5 +++++ CIME/data/config/config_headers.xml | 1 + .../ccs/model-configuration/variables/pes.rst | 2 ++ 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/CIME/Tools/pelayout b/CIME/Tools/pelayout index 48a13a4cc91..d948f4f6f14 100755 --- a/CIME/Tools/pelayout +++ b/CIME/Tools/pelayout @@ -70,13 +70,13 @@ def parse_command_line(args, description): parser.add_argument( "--format", - default="%4C: %6T/%6H; %6R %6P", + default="%4C: %6T/%6H; %6R %6P %6X", help="Format the PE layout items for each component (see below)", ) parser.add_argument( "--header", - default="Comp NTASKS NTHRDS ROOTPE PSTRIDE", + default="Comp NTASKS NTHRDS ROOTPE PSTRIDE XSTRIDE", help="Custom header for PE layout display", ) @@ -116,18 +116,19 @@ def get_value_as_string(case, var, attribute=None, resolved=False, subgroup=None ############################################################################### -def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, arg_format): +def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, xstride, arg_format): ############################################################################### """ Format the PE layout information for each component, using a default format, or using the arg_format input, if it exists. """ - subs = {"C": comp, "T": ntasks, "H": nthreads, "R": rootpe, "P": pstride} + subs = {"C": comp, "T": ntasks, "H": nthreads, "R": rootpe, "P": pstride, "X": xstride} layout_str = re.sub(r"%([0-9]*)C", r"{C:\1}", arg_format) layout_str = re.sub(r"%([-+0-9]*)T", r"{T:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)H", r"{H:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)R", r"{R:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)P", r"{P:\1}", layout_str) + layout_str = re.sub(r"%([-+0-9]*)X", r"{X:\1}", layout_str) layout_str = layout_str.format(**subs) return layout_str @@ -135,7 +136,7 @@ def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, arg_format): # End def format_pelayout ############################################################################### -def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, arg_format, header): +def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, header): ############################################################################### """ Print the PE layout information for each component, using the format, @@ -155,6 +156,7 @@ def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, arg_format, header): nthreads[comp], rootpes[comp], pstrid[comp], + xstrid[comp], arg_format, ) ) @@ -185,6 +187,7 @@ def gather_pelayout(case): nthreads = {} rootpes = {} pstride = {} + xstride = {} comp_classes = case.get_values("COMP_CLASSES") for comp in comp_classes: @@ -192,8 +195,9 @@ def gather_pelayout(case): nthreads[comp] = int(case.get_value("NTHRDS_" + comp)) rootpes[comp] = int(case.get_value("ROOTPE_" + comp)) pstride[comp] = int(case.get_value("PSTRID_" + comp)) + xstride[comp] = int(case.get_value("EXCL_STRIDE_" + comp)) # End for - return ntasks, nthreads, rootpes, pstride + return ntasks, nthreads, rootpes, pstride, xstride # End def gather_pelayout @@ -274,8 +278,8 @@ def _main_func(description): if set_ntasks is not None: modify_ntasks(case, int(set_ntasks)) # End if - ntasks, nthreads, rootpes, pstrid = gather_pelayout(case) - print_pelayout(case, ntasks, nthreads, rootpes, pstrid, arg_format, header) + ntasks, nthreads, rootpes, pstrid, xstrid = gather_pelayout(case) + print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, header) # End with diff --git a/CIME/XML/pes.py b/CIME/XML/pes.py index 3254751c794..4d1d035699c 100644 --- a/CIME/XML/pes.py +++ b/CIME/XML/pes.py @@ -25,6 +25,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): opes_nthrds = {} opes_rootpe = {} opes_pstrid = {} + opes_excl_stride= {} oother_settings = {} other_settings = {} append = {} @@ -41,6 +42,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): opes_nthrds, opes_rootpe, opes_pstrid, + opes_excl_stride, oother_settings, append, ocomments, @@ -60,6 +62,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): pes_nthrds, pes_rootpe, pes_pstrid, + pes_excl_stride, other_settings, os_append, comments, @@ -68,6 +71,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): pes_nthrds.update(opes_nthrds) pes_rootpe.update(opes_rootpe) pes_pstrid.update(opes_pstrid) + pes_excl_stride.update(opes_excl_stride) other_settings.update(oother_settings) os_append.update(append) if ocomments is not None: @@ -80,6 +84,8 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): pes_rootpe[i] = 0 for i in iter(pes_pstrid): pes_pstrid[i] = 0 + for i in iter(pes_excl_stride): + pes_pstrid[i] = 0 logger.info("Pes setting: grid is {} ".format(grid)) logger.info("Pes setting: compset is {} ".format(compset)) @@ -87,6 +93,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): logger.info("Pes setting: threads is {} ".format(pes_nthrds)) logger.info("Pes setting: rootpe is {} ".format(pes_rootpe)) logger.info("Pes setting: pstrid is {} ".format(pes_pstrid)) + logger.info("Pes setting: excl_stride is {} ".format(pes_excl_stride)) logger.info("Pes other settings: {}".format(other_settings)) logger.info("Pes other settings append: {}".format(os_append)) if comments is not None: @@ -97,6 +104,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): pes_nthrds, pes_rootpe, pes_pstrid, + pes_excl_stride, other_settings, os_append, comments, @@ -110,7 +118,8 @@ def _find_matches( compset_choice = None pesize_choice = None max_points = -1 - pes_ntasks, pes_nthrds, pes_rootpe, pes_pstrid, other_settings, append = ( + pes_ntasks, pes_nthrds, pes_rootpe, pes_pstrid, pes_excl_stride, other_settings, append = ( + {}, {}, {}, {}, @@ -173,6 +182,11 @@ def _find_matches( pes_pstrid[ self.name(child).upper() ] = int(self.text(child)) + elif "excl_stride" in vid: + for child in self.get_children(root=node): + pes_excl_stride[ + self.name(child).upper() + ] = int(self.text(child)) # if the value is already upper case its something else we are trying to set else: other_settings[vid] = self.text(node) @@ -231,6 +245,9 @@ def _find_matches( elif "pstrid" in vid: for child in self.get_children(root=node): pes_pstrid[self.name(child).upper()] = int(self.text(child)) + elif "excl_stride" in vid: + for child in self.get_children(root=node): + pes_excl_stride[self.name(child).upper()] = int(self.text(child)) # if the value is already upper case its something else we are trying to set elif vid == self.name(node): text = self.text(node).strip() @@ -250,6 +267,7 @@ def _find_matches( pes_nthrds, pes_rootpe, pes_pstrid, + pes_excl_stride, other_settings, append, comment, diff --git a/CIME/case/case.py b/CIME/case/case.py index ae7fae9b94e..465bf75b1fa 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -1204,6 +1204,7 @@ def _setup_mach_pes(self, pecount, multi_driver, ninst, machine_name, mpilib): pes_nthrds = {} pes_rootpe = {} pes_pstrid = {} + pes_excl_stride = {} other = {} append = {} comment = None @@ -1233,6 +1234,7 @@ def _setup_mach_pes(self, pecount, multi_driver, ninst, machine_name, mpilib): pes_nthrds, pes_rootpe, pes_pstrid, + pes_excl_stride, other, append, comment, @@ -1287,17 +1289,20 @@ def _setup_mach_pes(self, pecount, multi_driver, ninst, machine_name, mpilib): nthrds_str = "NTHRDS_{}".format(comp_class) rootpe_str = "ROOTPE_{}".format(comp_class) pstrid_str = "PSTRID_{}".format(comp_class) + excl_stride_str = "EXCL_STRIDE_{}".format(comp_class) ntasks = pes_ntasks[ntasks_str] if ntasks_str in pes_ntasks else 1 nthrds = pes_nthrds[nthrds_str] if nthrds_str in pes_nthrds else 1 rootpe = pes_rootpe[rootpe_str] if rootpe_str in pes_rootpe else 0 pstrid = pes_pstrid[pstrid_str] if pstrid_str in pes_pstrid else 1 + excl_stride = pes_excl_stride[excl_stride_str] if excl_stride_str in pes_excl_stride else 0 totaltasks.append((ntasks + rootpe) * nthrds) mach_pes_obj.set_value(ntasks_str, ntasks) mach_pes_obj.set_value(nthrds_str, nthrds) mach_pes_obj.set_value(rootpe_str, rootpe) mach_pes_obj.set_value(pstrid_str, pstrid) + mach_pes_obj.set_value(excl_stride_str, excl_stride) # Make sure that every component has been accounted for # set, nthrds and ntasks to 1 otherwise. Also set the ninst values here. diff --git a/CIME/data/config/config_headers.xml b/CIME/data/config/config_headers.xml index 10f59f2c770..748520cc93b 100644 --- a/CIME/data/config/config_headers.xml +++ b/CIME/data/config/config_headers.xml @@ -69,6 +69,7 @@ ROOTPE: the global mpi task of the component root task, if negative, indicates nodes rather than tasks. PSTRID: the stride of MPI tasks across the global set of pes (for now set to 1) NINST : the number of component instances (will be spread evenly across NTASKS) + EXCL_STRIDE: the stride of MPI tasks owned exclusively by a component. If 0, exclusivity is disabled. for example, for NTASKS = 8, NTHRDS = 2, ROOTPE = 32, NINST = 2 the MPI tasks would be placed starting on global pe 32 and each pe would be threaded 2-ways diff --git a/doc/source/ccs/model-configuration/variables/pes.rst b/doc/source/ccs/model-configuration/variables/pes.rst index 4d158c89571..e8be806b939 100644 --- a/doc/source/ccs/model-configuration/variables/pes.rst +++ b/doc/source/ccs/model-configuration/variables/pes.rst @@ -326,6 +326,8 @@ following meanings: - The global MPI task of the component root task; if negative, indicates nodes rather than tasks. The root processor for each component is set relative to the MPI global communicator. * - PSTRID - The stride of MPI tasks across the global set of pes (for now set to 1). This variable is currently not used and is a placeholder for future development. + * - EXCL_STRIDE + - Stride of MPI tasks owned exclusively by a component. If 0, exclusivity is disabled. * - NINST - The number of component instances, which are spread evenly across NTASKS. * - COST_PER_NODE From c06e4fb6f8b0ed064b82379edb6b4bb904f3133d Mon Sep 17 00:00:00 2001 From: Azamat Mametjanov Date: Tue, 21 Oct 2025 18:52:25 -0700 Subject: [PATCH 002/153] Format with black --- CIME/Tools/pelayout | 19 +++++++++++++++++-- CIME/XML/pes.py | 41 ++++++++++++++++++++++++++--------------- CIME/case/case.py | 12 ++++++++---- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/CIME/Tools/pelayout b/CIME/Tools/pelayout index d948f4f6f14..494db7254f7 100755 --- a/CIME/Tools/pelayout +++ b/CIME/Tools/pelayout @@ -47,6 +47,7 @@ import re logger = logging.getLogger(__name__) + ############################################################################### def parse_command_line(args, description): ############################################################################### @@ -122,7 +123,14 @@ def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, xstride, arg_format Format the PE layout information for each component, using a default format, or using the arg_format input, if it exists. """ - subs = {"C": comp, "T": ntasks, "H": nthreads, "R": rootpe, "P": pstride, "X": xstride} + subs = { + "C": comp, + "T": ntasks, + "H": nthreads, + "R": rootpe, + "P": pstride, + "X": xstride, + } layout_str = re.sub(r"%([0-9]*)C", r"{C:\1}", arg_format) layout_str = re.sub(r"%([-+0-9]*)T", r"{T:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)H", r"{H:\1}", layout_str) @@ -135,6 +143,7 @@ def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, xstride, arg_format # End def format_pelayout + ############################################################################### def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, header): ############################################################################### @@ -177,6 +186,7 @@ def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, # End def print_pelayout + ############################################################################### def gather_pelayout(case): ############################################################################### @@ -202,6 +212,7 @@ def gather_pelayout(case): # End def gather_pelayout + ############################################################################### def set_nthreads(case, nthreads): ############################################################################### @@ -214,6 +225,7 @@ def set_nthreads(case, nthreads): # End def set_nthreads + ############################################################################### def modify_ntasks(case, new_tot_tasks): ############################################################################### @@ -262,6 +274,7 @@ def modify_ntasks(case, new_tot_tasks): # End def modify_ntasks + ############################################################################### def _main_func(description): ############################################################################### @@ -279,7 +292,9 @@ def _main_func(description): modify_ntasks(case, int(set_ntasks)) # End if ntasks, nthreads, rootpes, pstrid, xstrid = gather_pelayout(case) - print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, header) + print_pelayout( + case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, header + ) # End with diff --git a/CIME/XML/pes.py b/CIME/XML/pes.py index 4d1d035699c..855c428fbc1 100644 --- a/CIME/XML/pes.py +++ b/CIME/XML/pes.py @@ -1,6 +1,7 @@ """ Interface to the config_pes.xml file. This class inherits from GenericXML.py """ + from CIME.XML.standard_module_setup import * from CIME.XML.generic_xml import GenericXML from CIME.XML.files import Files @@ -25,7 +26,7 @@ def find_pes_layout(self, grid, compset, machine, pesize_opts="M", mpilib=None): opes_nthrds = {} opes_rootpe = {} opes_pstrid = {} - opes_excl_stride= {} + opes_excl_stride = {} oother_settings = {} other_settings = {} append = {} @@ -118,7 +119,15 @@ def _find_matches( compset_choice = None pesize_choice = None max_points = -1 - pes_ntasks, pes_nthrds, pes_rootpe, pes_pstrid, pes_excl_stride, other_settings, append = ( + ( + pes_ntasks, + pes_nthrds, + pes_rootpe, + pes_pstrid, + pes_excl_stride, + other_settings, + append, + ) = ( {}, {}, {}, @@ -164,24 +173,24 @@ def _find_matches( comment = self.text(node) elif "ntasks" in vid: for child in self.get_children(root=node): - pes_ntasks[ - self.name(child).upper() - ] = int(self.text(child)) + pes_ntasks[self.name(child).upper()] = ( + int(self.text(child)) + ) elif "nthrds" in vid: for child in self.get_children(root=node): - pes_nthrds[ - self.name(child).upper() - ] = int(self.text(child)) + pes_nthrds[self.name(child).upper()] = ( + int(self.text(child)) + ) elif "rootpe" in vid: for child in self.get_children(root=node): - pes_rootpe[ - self.name(child).upper() - ] = int(self.text(child)) + pes_rootpe[self.name(child).upper()] = ( + int(self.text(child)) + ) elif "pstrid" in vid: for child in self.get_children(root=node): - pes_pstrid[ - self.name(child).upper() - ] = int(self.text(child)) + pes_pstrid[self.name(child).upper()] = ( + int(self.text(child)) + ) elif "excl_stride" in vid: for child in self.get_children(root=node): pes_excl_stride[ @@ -247,7 +256,9 @@ def _find_matches( pes_pstrid[self.name(child).upper()] = int(self.text(child)) elif "excl_stride" in vid: for child in self.get_children(root=node): - pes_excl_stride[self.name(child).upper()] = int(self.text(child)) + pes_excl_stride[self.name(child).upper()] = int( + self.text(child) + ) # if the value is already upper case its something else we are trying to set elif vid == self.name(node): text = self.text(node).strip() diff --git a/CIME/case/case.py b/CIME/case/case.py index 465bf75b1fa..46e4ac8edc4 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -1094,9 +1094,9 @@ def _get_component_config_data(self, files): ) drv_comp_model_specific = Component(drv_config_file_model_specific, "CPL") - self._component_description[ - "forcing" - ] = drv_comp_model_specific.get_forcing_description(self._compsetname) + self._component_description["forcing"] = ( + drv_comp_model_specific.get_forcing_description(self._compsetname) + ) logger.info( "Compset forcing is {}".format(self._component_description["forcing"]) ) @@ -1295,7 +1295,11 @@ def _setup_mach_pes(self, pecount, multi_driver, ninst, machine_name, mpilib): nthrds = pes_nthrds[nthrds_str] if nthrds_str in pes_nthrds else 1 rootpe = pes_rootpe[rootpe_str] if rootpe_str in pes_rootpe else 0 pstrid = pes_pstrid[pstrid_str] if pstrid_str in pes_pstrid else 1 - excl_stride = pes_excl_stride[excl_stride_str] if excl_stride_str in pes_excl_stride else 0 + excl_stride = ( + pes_excl_stride[excl_stride_str] + if excl_stride_str in pes_excl_stride + else 0 + ) totaltasks.append((ntasks + rootpe) * nthrds) mach_pes_obj.set_value(ntasks_str, ntasks) From 036752248098e877eb1e4381e7d582519a87ec97 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 6 Nov 2025 17:51:52 -0700 Subject: [PATCH 003/153] Allow the allactive "component" to define system tests --- CIME/data/config/cesm/config_files.xml | 2 ++ CIME/utils.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index 3a728b7af84..ea9dd6f0857 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -112,6 +112,7 @@ $CIMEROOT/CIME/data/config/config_tests.xml + $SRCROOT/cime_config/config_tests.xml $COMP_ROOT_DIR_LND/cime_config/config_tests.xml $COMP_ROOT_DIR_LND/cime_config/config_tests.xml $COMP_ROOT_DIR_ATM/cime_config/config_tests.xml @@ -371,6 +372,7 @@ char $CIMEROOT/CIME/SystemTests + $SRCROOT/cime_config/SystemTests $COMP_ROOT_DIR_LND/cime_config/SystemTests $COMP_ROOT_DIR_ATM/cime_config/SystemTests $COMP_ROOT_DIR_OCN/cime_config/SystemTests diff --git a/CIME/utils.py b/CIME/utils.py index 2b48980b703..fad2c79bd13 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2267,7 +2267,7 @@ def find_system_test(testname, case): if testname.startswith("TEST"): system_test_path = "CIME.SystemTests.system_tests_common.{}".format(testname) else: - components = ["any"] + components = ["any", "allactive"] components.extend(case.get_compset_components()) fdir = [] for component in components: From 466f7bcddc38591787f63922b6fda1266456d133 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 6 Nov 2025 17:52:49 -0700 Subject: [PATCH 004/153] Allow extensions of FUNIT test to add arguments to run_tests.py This is needed by the CESM_share extension to FUNIT --- CIME/SystemTests/funit.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CIME/SystemTests/funit.py b/CIME/SystemTests/funit.py index a7c21a06944..7681a6200d6 100644 --- a/CIME/SystemTests/funit.py +++ b/CIME/SystemTests/funit.py @@ -35,6 +35,13 @@ def get_test_spec_dir(self): """ return get_cime_root() + def get_extra_run_tests_args(self): + """ + Override this to return a string containing extra command-line arguments to + run_tests.py + """ + return "" + def run_phase(self): rundir = self._case.get_value("RUNDIR") @@ -51,8 +58,9 @@ def run_phase(self): get_cime_root(), "scripts", "fortran_unit_testing", "run_tests.py" ) ) - args = "--build-dir {} --test-spec-dir {} --machine {}".format( - exeroot, test_spec_dir, mach + extra_args = self.get_extra_run_tests_args() + args = "--build-dir {} --test-spec-dir {} --machine {} {}".format( + exeroot, test_spec_dir, mach, extra_args ) stat = run_cmd( From a5ec807bf1a2b6edf01e7ae0f35a3c11ffd21329 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 7 Nov 2025 17:02:29 -0700 Subject: [PATCH 005/153] Fix test_unit_xml_tests for new case.get_value call The previous commit introduced an extra call to case.get_value to get SYSTEM_TESTS_DIR for component=allactive. This broke the mock get_value returns. To make these returns more robust, I have changed the way they're done to be based on a dictionary lookup rather than an ordered list. This kind of replacement should probably be done for other case.get_value mocks. Note that I identified the argument values that needed to be handled with this code: for i, call in enumerate(case.get_value.call_args_list): print(f" Call {i}: {call}") --- CIME/tests/test_unit_xml_tests.py | 64 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/CIME/tests/test_unit_xml_tests.py b/CIME/tests/test_unit_xml_tests.py index a79bb3b9c0a..898e00bb0b8 100644 --- a/CIME/tests/test_unit_xml_tests.py +++ b/CIME/tests/test_unit_xml_tests.py @@ -32,16 +32,27 @@ def test_support_single_exe(self, _setup_cases_if_not_yet_done): case.get_compset_components.return_value = () - case.get_value.side_effect = ( - "SMS", - tdir, - f"{caseroot}", - "SMS.f19_g16.S", - "cpl", - "SMS.f19_g16.S", - f"{caseroot}", - "SMS.f19_g16.S", - ) + def fake_get_value(item, attribute=None): + simple_lookup = { + "TESTCASE": "SMS", + "CASEROOT": f"{caseroot}", + "CASEBASEID": "SMS.f19_g16.S", + "COMP_INTERFACE": "cpl", + "DRV_RESTART_POINTER": None, + } + if item in simple_lookup: + return simple_lookup[item] + elif item == "SYSTEM_TESTS_DIR": + if attribute["component"] == "any": + return tdir + else: + return None + + raise KeyError( + f"Unmocked call: case.get_value({item}, attribute={attribute})" + ) + + case.get_value.side_effect = fake_get_value tests = Tests() @@ -65,17 +76,28 @@ def test_support_single_exe_error(self, _setup_cases_if_not_yet_done): case.get_compset_components.return_value = () - case.get_value.side_effect = ( - "ERP", - tdir, - f"{caseroot}", - "ERP.f19_g16.S", - "cpl", - None, - "ERP.f19_g16.S", - f"{caseroot}", - "ERP.f19_g16.S", - ) + def fake_get_value(item, attribute=None): + simple_lookup = { + "TESTCASE": "ERP", + "CASEROOT": f"{caseroot}", + "CASEBASEID": "ERP.f19_g16.S", + "CASE": "ERP.f19_g16.S", + "COMP_INTERFACE": "cpl", + "DRV_RESTART_POINTER": None, + } + if item in simple_lookup: + return simple_lookup[item] + elif item == "SYSTEM_TESTS_DIR": + if attribute["component"] == "any": + return tdir + else: + return None + + raise KeyError( + f"Unmocked call: case.get_value({item}, attribute={attribute})" + ) + + case.get_value.side_effect = fake_get_value tests = Tests() From 2d1472364753d7c703dda4165462bf6379fe87d6 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 8 Nov 2025 11:35:07 -0700 Subject: [PATCH 006/153] Don't error if multiple CONFIG_TESTS_FILEs resolve to the same path Or if multiple SYSTEM_TESTS_DIRs resolve to the same path.... This happened when I introduced an entry for allactive: in a standalone checkout of CTSM, COMP_ROOT_DIR_LND is the same as SRCROOT, so the allactive and clm CONFIG_TESTS_FILEs and SYSTEM_TESTS_DIRs pointed to the same path, causing an error. --- CIME/XML/tests.py | 11 +++++++++-- CIME/utils.py | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CIME/XML/tests.py b/CIME/XML/tests.py index 4a9eefc0fc4..2064bfab26f 100644 --- a/CIME/XML/tests.py +++ b/CIME/XML/tests.py @@ -22,13 +22,20 @@ def __init__(self, infile=None, files=None): files = Files() infile = files.get_value("CONFIG_TESTS_FILE") GenericXML.__init__(self, infile) - # append any component specific config_tests.xml files + + # Append any component-specific config_tests.xml files. We take care to only add a + # given file once, since adding a given file multiple times creates a "multiple + # matches" error. (This can happen if multiple CONFIG_TESTS_FILEs resolve to the + # same path.) + files_added = set() for comp in files.get_components("CONFIG_TESTS_FILE"): if comp is None: continue infile = files.get_value("CONFIG_TESTS_FILE", attribute={"component": comp}) - if os.path.isfile(infile): + infile_abspath = os.path.abspath(infile) + if os.path.isfile(infile) and infile_abspath not in files_added: self.read(infile) + files_added.add(infile_abspath) def support_single_exe(self, case): """Checks if case supports --single-exe. diff --git a/CIME/utils.py b/CIME/utils.py index fad2c79bd13..e05d5ad9e4a 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2276,6 +2276,11 @@ def find_system_test(testname, case): ) if tdir is not None: tdir = os.path.abspath(tdir) + if tdir in fdir: + # This can happen if multiple SYSTEM_TESTS_DIRs resolve to the same + # path; in this case, we just want to handle the first occurrence and + # skip the rest. + continue system_test_file = os.path.join(tdir, "{}.py".format(testname.lower())) if os.path.isfile(system_test_file): fdir.append(tdir) From a89f6808d33aedc979d49198973ccde177ebfd6d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 13 Nov 2025 12:44:52 -0700 Subject: [PATCH 007/153] git ignore all files of form *.o\d+ (all pbs output) --- CIME/data/templates/gitignore.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/data/templates/gitignore.template b/CIME/data/templates/gitignore.template index ce731b6d0c3..b821c6c4cae 100644 --- a/CIME/data/templates/gitignore.template +++ b/CIME/data/templates/gitignore.template @@ -5,8 +5,8 @@ run *.log # queueing system output files -run.*.o\d+ -st_archive.*.o\d+ +*.o\d+ +*.o\d+ #python files __pycache__/ From 14f3b158cd74e03b62e709dc7d2c9d33e011e5b0 Mon Sep 17 00:00:00 2001 From: kdraeder Date: Tue, 18 Nov 2025 10:52:14 -0700 Subject: [PATCH 008/153] Made ERI F1850C test work with gregorian calendar. Several changes are needed in cam, which will have a commit with the same title. This was tested using ERI_D_C3_CG_Ld8.ne30pg3_g17.F1850C_LTso.derecho_intel.cam-outfrq9s_leapday CIME/SystemTests/eri.py Changed the year increment from 2 to 4; all runs will be in leap years, which have Feb 29, which is the start date for outfrq9s_leapday. CIME/test_scheduler.py Distinguish carefully between the _C# and _CG test modifiers so that _CG does not confuse it. --- CIME/SystemTests/eri.py | 4 +++- CIME/test_scheduler.py | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CIME/SystemTests/eri.py b/CIME/SystemTests/eri.py index 6d80a8fd808..cc3f2667b3b 100644 --- a/CIME/SystemTests/eri.py +++ b/CIME/SystemTests/eri.py @@ -90,7 +90,9 @@ def run_phase(self): start_1_year, start_1_month, start_1_day = [ int(item) for item in start_1.split("-") ] - start_2_year = start_1_year + 2 + # KDR The default start date is a leap day, so 2-29 2 years later doesn't exist; run failure. + # start_2_year = start_1_year + 2 + start_2_year = start_1_year + 4 start_2 = "{:04d}-{:02d}-{:02d}".format( start_2_year, start_1_month, start_1_day ) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index 2f05d946e03..e5cd6f7f017 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -674,12 +674,12 @@ def _create_newcase_phase(self, test): create_newcase_cmd += " --mpilib {}".format(mpilib) logger.debug(" MPILIB set to {}".format(mpilib)) elif case_opt.startswith("N"): - expect(ncpl == 1, "Cannot combine _C and _N options") + expect(ncpl == 1, "Cannot combine _C# and _N options") ninst = case_opt[1:] create_newcase_cmd += " --ninst {}".format(ninst) logger.debug(" NINST set to {}".format(ninst)) - elif case_opt.startswith("C"): - expect(ninst == 1, "Cannot combine _C and _N options") + elif case_opt.startswith("C") and not case_opt.startswith("CG"): + expect(ninst == 1, "Cannot combine _C# and _N options") ncpl = case_opt[1:] create_newcase_cmd += " --ninst {} --multi-driver".format(ncpl) logger.debug(" NCPL set to {}".format(ncpl)) @@ -698,7 +698,7 @@ def _create_newcase_phase(self, test): if "--driver nuopc" in create_newcase_cmd or ( "--driver" not in create_newcase_cmd and driver == "nuopc" ): - expect(False, "_N option not supported by nuopc driver, use _C instead") + expect(False, "_N option not supported by nuopc driver, use _C# instead") if test_mods is not None: create_newcase_cmd += " --user-mods-dir " From 82240cfdeeae4838c7bbe4daa2ac34ddc2f6b66b Mon Sep 17 00:00:00 2001 From: kdraeder Date: Wed, 19 Nov 2025 08:43:38 -0700 Subject: [PATCH 009/153] Update eri.py --- CIME/SystemTests/eri.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CIME/SystemTests/eri.py b/CIME/SystemTests/eri.py index cc3f2667b3b..fdd02455987 100644 --- a/CIME/SystemTests/eri.py +++ b/CIME/SystemTests/eri.py @@ -90,8 +90,10 @@ def run_phase(self): start_1_year, start_1_month, start_1_day = [ int(item) for item in start_1.split("-") ] - # KDR The default start date is a leap day, so 2-29 2 years later doesn't exist; run failure. - # start_2_year = start_1_year + 2 + # Change the year for the hybrid case to make sure the system can handle this change in year. + # Note: When using a Gregorian calendar, it is important for the two years to be leap years + # because a testmod which tests the gregorian calendar starts the runs on leap day, + # which must exist in all of the start years. start_2_year = start_1_year + 4 start_2 = "{:04d}-{:02d}-{:02d}".format( start_2_year, start_1_month, start_1_day From 8b13390c143c03e0e261040e623018de4b925af3 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 19 Nov 2025 09:01:37 -0700 Subject: [PATCH 010/153] fix black formatting --- CIME/test_scheduler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index e5cd6f7f017..947b405cec6 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -63,6 +63,7 @@ RUN_PHASE, ] # Order matters + ############################################################################### def _translate_test_names_for_new_pecount(test_names, force_procs, force_threads): ############################################################################### @@ -130,6 +131,8 @@ def _translate_test_names_for_new_pecount(test_names, force_procs, force_threads _TIME_CACHE = {} + + ############################################################################### def _get_time_est(test, baseline_root, as_int=False, use_cache=False, raw=False): ############################################################################### @@ -698,7 +701,9 @@ def _create_newcase_phase(self, test): if "--driver nuopc" in create_newcase_cmd or ( "--driver" not in create_newcase_cmd and driver == "nuopc" ): - expect(False, "_N option not supported by nuopc driver, use _C# instead") + expect( + False, "_N option not supported by nuopc driver, use _C# instead" + ) if test_mods is not None: create_newcase_cmd += " --user-mods-dir " From 0afcb5fb2795d49bf84e8b37f1cc340295d5741d Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sun, 23 Nov 2025 08:30:33 -0700 Subject: [PATCH 011/153] Use _cG instead of _CG for Gregorian calendar This removes the ambiguous use of _C for two different purposes. See https://github.com/ESMCI/cime/pull/4899 for some discussion around this change. --- CIME/test_scheduler.py | 12 +++++------- doc/source/system_testing.rst | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index 947b405cec6..a58fcc30f8b 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -677,12 +677,12 @@ def _create_newcase_phase(self, test): create_newcase_cmd += " --mpilib {}".format(mpilib) logger.debug(" MPILIB set to {}".format(mpilib)) elif case_opt.startswith("N"): - expect(ncpl == 1, "Cannot combine _C# and _N options") + expect(ncpl == 1, "Cannot combine _C and _N options") ninst = case_opt[1:] create_newcase_cmd += " --ninst {}".format(ninst) logger.debug(" NINST set to {}".format(ninst)) - elif case_opt.startswith("C") and not case_opt.startswith("CG"): - expect(ninst == 1, "Cannot combine _C# and _N options") + elif case_opt.startswith("C"): + expect(ninst == 1, "Cannot combine _C and _N options") ncpl = case_opt[1:] create_newcase_cmd += " --ninst {} --multi-driver".format(ncpl) logger.debug(" NCPL set to {}".format(ncpl)) @@ -701,9 +701,7 @@ def _create_newcase_phase(self, test): if "--driver nuopc" in create_newcase_cmd or ( "--driver" not in create_newcase_cmd and driver == "nuopc" ): - expect( - False, "_N option not supported by nuopc driver, use _C# instead" - ) + expect(False, "_N option not supported by nuopc driver, use _C instead") if test_mods is not None: create_newcase_cmd += " --user-mods-dir " @@ -886,7 +884,7 @@ def _xml_phase(self, test): envtest.set_test_parameter("USE_ESMF_LIB", "TRUE") logger.debug(" USE_ESMF_LIB set to TRUE") - elif opt == "CG": + elif opt == "cG": envtest.set_test_parameter("CALENDAR", "GREGORIAN") logger.debug(" CALENDAR set to {}".format(opt)) diff --git a/doc/source/system_testing.rst b/doc/source/system_testing.rst index 9b539fb9605..8b315405cb2 100644 --- a/doc/source/system_testing.rst +++ b/doc/source/system_testing.rst @@ -314,7 +314,7 @@ MODIFIERS Description ============ ===================================================================================== _C# Set number of instances to # and use the multi driver (can't use with _N). - _CG CALENDAR set to "GREGORIAN". + _cG CALENDAR set to "GREGORIAN". _D XML variable DEBUG set to "TRUE". From 5078565c1c91fd1053078de52279cbe19475aadd Mon Sep 17 00:00:00 2001 From: kdraeder Date: Tue, 2 Dec 2025 20:33:51 -0700 Subject: [PATCH 012/153] Made ERI outfrq9s_leapday test work for HIST compsets CIME/test_scheduler.py Uses _CG -> _cG change from Sacks Checks for _cG incompatibility with the compset (spinup not allowed) Also needs changes to cam: outfrq9s_leapday/user_nl_clm (from Sacks and Eaton) Prevented CLM failure due to year check outfrq9s_leapday/user_nl_{cice,mosart} Added to make history output daily, which makes the ERI output comparisons work. outfrq9s_leapday/shell_scripts Kept the change of start year from 2012 to 2008 The ERI_D_C3_cG_Ld8.ne3_ne3_mg37.FHISTC_LTso.derecho_intel.cam-outfrq9s_leapday test passes. It has not been tested for standard grids andor B compsets yet. Related changes to system_testing.rst will be suggested in a later PR. --- CIME/test_scheduler.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index a58fcc30f8b..3e360580e46 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -691,6 +691,15 @@ def _create_newcase_phase(self, test): create_newcase_cmd += " --pecount {}".format(pesize) elif case_opt.startswith("V"): driver = case_opt[1:] + elif case_opt.startswith("cG"): + # Need to examine the long name. + # spinup cases are 1850, 2000, 2010 + # not HIST, SSP### + spinup_po = re.compile('([\d]{1,4})') + spinup = spinup_po.match(compset) + expect(spinup == None, + "Gregorian calendar (_cG) cannot be used with spinup compset \n {}".format(compset) + ) create_newcase_cmd += " --driver {}".format(driver) From 68d2197c022cccf6fcee695be874883c989ba3d0 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 06:59:42 -0500 Subject: [PATCH 013/153] Update test_scheduler.py --- CIME/test_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index 3e360580e46..9a01c5069cc 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -695,7 +695,7 @@ def _create_newcase_phase(self, test): # Need to examine the long name. # spinup cases are 1850, 2000, 2010 # not HIST, SSP### - spinup_po = re.compile('([\d]{1,4})') + spinup_po = re.compile(r'([\d]{1,4})') spinup = spinup_po.match(compset) expect(spinup == None, "Gregorian calendar (_cG) cannot be used with spinup compset \n {}".format(compset) From cc3d9c417bd302236697ccff6b1303b19be71fdb Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 07:45:50 -0700 Subject: [PATCH 014/153] black reformat file --- CIME/test_scheduler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index 9a01c5069cc..a3f1670d4d0 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -695,10 +695,13 @@ def _create_newcase_phase(self, test): # Need to examine the long name. # spinup cases are 1850, 2000, 2010 # not HIST, SSP### - spinup_po = re.compile(r'([\d]{1,4})') + spinup_po = re.compile(r"([\d]{1,4})") spinup = spinup_po.match(compset) - expect(spinup == None, - "Gregorian calendar (_cG) cannot be used with spinup compset \n {}".format(compset) + expect( + spinup == None, + "Gregorian calendar (_cG) cannot be used with spinup compset \n {}".format( + compset + ), ) create_newcase_cmd += " --driver {}".format(driver) From df9b0c7dd462e5db6af137e20ca1b178a35ad3e9 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 13:08:41 -0700 Subject: [PATCH 015/153] when using wget on a system with spider mode disabled you may get an error even after connecting --- CIME/Servers/wget.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CIME/Servers/wget.py b/CIME/Servers/wget.py index 56517cffa7b..49a3b204ce7 100644 --- a/CIME/Servers/wget.py +++ b/CIME/Servers/wget.py @@ -26,19 +26,20 @@ def wget_login(cls, address, user="", passwd=""): args += "--password {} ".format(passwd) try: - err = run_cmd("wget {} --spider {}".format(args, address), timeout=60)[0] - except: + err,_,errstr = run_cmd("wget {} --spider {}".format(args, address), timeout=60) + except RuntimeError as e: logger.warning( - "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .(location 1)".format( + "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .(location 1) ".format( address ) ) return None - - if err and not "storage.neonscience.org" in address: + if "connected" in errstr: + logger.warning("spider mode disabled, server otherwise okay") + elif err: logger.warning( - "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .(location 2)".format( - address + "Could not connect to repo '{0}'\n{1}".format( + address, errstr ) ) return None From 835583cb0523e8096445f076a581438218055614 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 13:26:51 -0700 Subject: [PATCH 016/153] black and pylint fixes --- CIME/Servers/wget.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CIME/Servers/wget.py b/CIME/Servers/wget.py index 49a3b204ce7..3dc8023a7f3 100644 --- a/CIME/Servers/wget.py +++ b/CIME/Servers/wget.py @@ -26,8 +26,10 @@ def wget_login(cls, address, user="", passwd=""): args += "--password {} ".format(passwd) try: - err,_,errstr = run_cmd("wget {} --spider {}".format(args, address), timeout=60) - except RuntimeError as e: + err, _, errstr = run_cmd( + "wget {} --spider {}".format(args, address), timeout=60 + ) + except RuntimeError: logger.warning( "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .(location 1) ".format( address @@ -38,9 +40,7 @@ def wget_login(cls, address, user="", passwd=""): logger.warning("spider mode disabled, server otherwise okay") elif err: logger.warning( - "Could not connect to repo '{0}'\n{1}".format( - address, errstr - ) + "Could not connect to repo '{0}'\n{1}".format(address, errstr) ) return None From 6d6e2755f52c1b3c5482912a262f2b732defc657 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 17:07:13 -0700 Subject: [PATCH 017/153] tighter control string --- CIME/Servers/wget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/Servers/wget.py b/CIME/Servers/wget.py index 3dc8023a7f3..b7be2f66eb6 100644 --- a/CIME/Servers/wget.py +++ b/CIME/Servers/wget.py @@ -36,7 +36,7 @@ def wget_login(cls, address, user="", passwd=""): ) ) return None - if "connected" in errstr: + if "Connecting to " in errstr and "... connected" in errstr: logger.warning("spider mode disabled, server otherwise okay") elif err: logger.warning( From d2ddac622c7b4679dbb3c2f07070dd9c7ed57478 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Dec 2025 17:24:24 -0700 Subject: [PATCH 018/153] more informative warning --- CIME/Servers/wget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/Servers/wget.py b/CIME/Servers/wget.py index b7be2f66eb6..fde411460e8 100644 --- a/CIME/Servers/wget.py +++ b/CIME/Servers/wget.py @@ -31,13 +31,13 @@ def wget_login(cls, address, user="", passwd=""): ) except RuntimeError: logger.warning( - "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .(location 1) ".format( + "Could not connect to repo '{0}'\nThis is most likely either a proxy, or network issue .".format( address ) ) return None if "Connecting to " in errstr and "... connected" in errstr: - logger.warning("spider mode disabled, server otherwise okay") + logger.warning("Connection established with nonzero code %s", err) elif err: logger.warning( "Could not connect to repo '{0}'\n{1}".format(address, errstr) From 33b54d8e1a11c6f4be5a68d367ea4bb67ced8d7e Mon Sep 17 00:00:00 2001 From: kdraeder Date: Thu, 4 Dec 2025 09:29:01 -0700 Subject: [PATCH 019/153] Remove the consistency check of Gregorian and compset --- CIME/test_scheduler.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index a3f1670d4d0..a58fcc30f8b 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -691,18 +691,6 @@ def _create_newcase_phase(self, test): create_newcase_cmd += " --pecount {}".format(pesize) elif case_opt.startswith("V"): driver = case_opt[1:] - elif case_opt.startswith("cG"): - # Need to examine the long name. - # spinup cases are 1850, 2000, 2010 - # not HIST, SSP### - spinup_po = re.compile(r"([\d]{1,4})") - spinup = spinup_po.match(compset) - expect( - spinup == None, - "Gregorian calendar (_cG) cannot be used with spinup compset \n {}".format( - compset - ), - ) create_newcase_cmd += " --driver {}".format(driver) From b91c7965dca94542ccad9eb0482bb629cc42811c Mon Sep 17 00:00:00 2001 From: Luca Bertagna Date: Tue, 9 Dec 2025 14:45:57 -0700 Subject: [PATCH 020/153] Fix issue with true/false enum if C standard >= C23 --- CIME/non_py/src/timing/private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/src/timing/private.h b/CIME/non_py/src/timing/private.h index a255224aba5..1de5b880322 100644 --- a/CIME/non_py/src/timing/private.h +++ b/CIME/non_py/src/timing/private.h @@ -43,7 +43,7 @@ */ #define MAX_AUX 9 -#ifndef __cplusplus +#if !defined(__cplusplus) && __STDC_VERSION__ < 202311L typedef enum {false = 0, true = 1} bool; /* mimic C++ */ #endif From 87be616c0b7820b014a0f5e2c6b9bab041ac956e Mon Sep 17 00:00:00 2001 From: alperaltuntas Date: Mon, 15 Dec 2025 11:26:42 -0700 Subject: [PATCH 021/153] pass non-local option to cam.case_setup --- CIME/case/case_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 98671ba24d2..eb3c5643b21 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -484,8 +484,8 @@ def _case_setup_impl( ): logger.info("Running cam.case_setup.py") run_cmd_no_fail( - "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( - cam=camroot, case=caseroot + "python {cam}/cime_config/cam.case_setup.py {cam} {case} {non_local}".format( + cam=camroot, case=caseroot, non_local=str(non_local) ) ) From 256c224f48cdf3e54983f7928f91e17b0050da88 Mon Sep 17 00:00:00 2001 From: Azamat Mametjanov Date: Mon, 15 Dec 2025 16:30:31 -0800 Subject: [PATCH 022/153] Black with python/3.10 --- CIME/XML/pes.py | 24 ++++++++++++------------ CIME/case/case.py | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CIME/XML/pes.py b/CIME/XML/pes.py index 855c428fbc1..666382c0a38 100644 --- a/CIME/XML/pes.py +++ b/CIME/XML/pes.py @@ -173,24 +173,24 @@ def _find_matches( comment = self.text(node) elif "ntasks" in vid: for child in self.get_children(root=node): - pes_ntasks[self.name(child).upper()] = ( - int(self.text(child)) - ) + pes_ntasks[ + self.name(child).upper() + ] = int(self.text(child)) elif "nthrds" in vid: for child in self.get_children(root=node): - pes_nthrds[self.name(child).upper()] = ( - int(self.text(child)) - ) + pes_nthrds[ + self.name(child).upper() + ] = int(self.text(child)) elif "rootpe" in vid: for child in self.get_children(root=node): - pes_rootpe[self.name(child).upper()] = ( - int(self.text(child)) - ) + pes_rootpe[ + self.name(child).upper() + ] = int(self.text(child)) elif "pstrid" in vid: for child in self.get_children(root=node): - pes_pstrid[self.name(child).upper()] = ( - int(self.text(child)) - ) + pes_pstrid[ + self.name(child).upper() + ] = int(self.text(child)) elif "excl_stride" in vid: for child in self.get_children(root=node): pes_excl_stride[ diff --git a/CIME/case/case.py b/CIME/case/case.py index 46e4ac8edc4..bd6db1e503c 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -1094,9 +1094,9 @@ def _get_component_config_data(self, files): ) drv_comp_model_specific = Component(drv_config_file_model_specific, "CPL") - self._component_description["forcing"] = ( - drv_comp_model_specific.get_forcing_description(self._compsetname) - ) + self._component_description[ + "forcing" + ] = drv_comp_model_specific.get_forcing_description(self._compsetname) logger.info( "Compset forcing is {}".format(self._component_description["forcing"]) ) From 4366fa7738dff5f1219aff1fa4db59ca2615efbd Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 22 Dec 2025 13:22:36 -0800 Subject: [PATCH 023/153] Adds test for invalid case directories --- CIME/tests/test_unit_bless_test_results.py | 39 ++++++++++ CIME/tests/utils.py | 87 ++++++++++++++-------- 2 files changed, 94 insertions(+), 32 deletions(-) diff --git a/CIME/tests/test_unit_bless_test_results.py b/CIME/tests/test_unit_bless_test_results.py index 92aab87b4df..59475847f8c 100644 --- a/CIME/tests/test_unit_bless_test_results.py +++ b/CIME/tests/test_unit_bless_test_results.py @@ -1,6 +1,7 @@ import re import unittest import tempfile +from contextlib import ExitStack from unittest import mock from pathlib import Path @@ -13,6 +14,8 @@ is_hist_bless_needed, ) from CIME.test_status import ALL_PHASES, GENERATE_PHASE +from CIME.tests import utils as test_utils +from CIME.utils import CIMEError class TestUnitBlessTestResults(unittest.TestCase): @@ -918,6 +921,42 @@ def test_bless_tests_no_match(self, get_test_status_files, TestStatus, Case): assert success + def test_bless_results_invalid_case(self): + with ExitStack() as stack: + tempdir = stack.enter_context(tempfile.TemporaryDirectory()) + tests = [ + ( + "SMS.T62_oQU120_ais20.A", + "PASS {0} CREATE_NEWCASE\nPASS {0} XML\nPASS {0} SETUP", + ), + ( + "SMS.f19_g16.X", + "PASS {0} CREATE_NEWCASE\nPASS {0} XML\nPASS {0} SETUP", + ), + ] + + # call outer function with None to return mocked objects + cases = [ + test_utils.mock_case( + casename=f"{x[0]}.dane_oneapi-ifx.20251219_120951_epolnk", + tempdir=tempdir, + )(None) + for x in tests + ] + + caseroots = [Path(x[2]) for x in cases] + + # write a simple test status file + for i, x in enumerate(caseroots): + x.mkdir(parents=True, exist_ok=True) + with (x / "TestStatus").open("w") as f: + f.write(tests[i][1].format(tests[i][0])) + + with self.assertRaises(CIMEError): + success = bless_test_results( + "master", "/tmp/baseline", str(caseroots[0].parent), "oneapi-ifx" + ) + @mock.patch("CIME.bless_test_results.Case") @mock.patch("CIME.bless_test_results.TestStatus") @mock.patch("CIME.bless_test_results.get_test_status_files") diff --git a/CIME/tests/utils.py b/CIME/tests/utils.py index 951b5a8390e..5541ab6dfac 100644 --- a/CIME/tests/utils.py +++ b/CIME/tests/utils.py @@ -165,43 +165,66 @@ def __str__(self): return ET.tostring(self.root.xml_element, encoding="unicode", method="xml") -def mock_case(*args, empty_env=False, filename=None, mock_set_value=False, **kwargs): +def mock_case( + *args, + empty_env=False, + filename=None, + mock_set_value=False, + casename=None, + tempdir=None, + **kwargs, +): if filename is None: filename = "env_test.xml" - def outer(func): - # patch "read_xml" function since the source file usually doesn't exist - @mock.patch("CIME.case.case.Case.read_xml") - def wrapper(self, read_xml, *args, **kwargs): - with tempfile.TemporaryDirectory() as tempdir: - caseroot = f"{tempdir}/case" - with Case(caseroot, read_only=False) as case: - case.flush = lambda: None - - env = TestEnv() - env.filename = f"{os.getcwd()}/{filename}" - - if not empty_env: - case._files = [env] - - case._env_entryid_files = [env] - - if mock_set_value: - case.set_value = mock.MagicMock() - - func( - self, - *args, - **kwargs, - case_read_xml=read_xml, - test_env=env, - caseroot=caseroot, - case=case, - ) + if casename is None: + casename = "case" + + with contextlib.ExitStack() as stack: + if tempdir is None: + tempdir = stack.enter_context(tempfile.TemporaryDirectory()) + + def outer(func): + # patch "read_xml" function since the source file usually doesn't exist + @mock.patch("CIME.case.case.Case.read_xml") + def wrapper(self, read_xml, *args, **kwargs): + caseroot = f"{tempdir}/{casename}" + + case = stack.enter_context(Case(caseroot, read_only=False)) + case.flush = lambda: None + + env = TestEnv() + env.filename = f"{os.getcwd()}/{filename}" + + if not empty_env: + case._files = [env] + + case._env_entryid_files = [env] + + if mock_set_value: + case.set_value = mock.MagicMock() + + # + if self is None: + return read_xml, env, caseroot, casename, case + + func( + self, + *args, + **kwargs, + case_read_xml=read_xml, + test_env=env, + caseroot=caseroot, + case=case, + ) + + # not wrapping function + if func is None: + return wrapper(None) - return wrapper + return wrapper - return outer + return outer @contextlib.contextmanager From 8972fb60d424f8e94f7f4cafa5df77f492d02702 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 22 Dec 2025 14:41:59 -0700 Subject: [PATCH 024/153] make sure that when a refcase is set the drv_restart_pointer filename is updated --- CIME/case/check_input_data.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CIME/case/check_input_data.py b/CIME/case/check_input_data.py index 0aa0a3acdf0..94094ec3b97 100644 --- a/CIME/case/check_input_data.py +++ b/CIME/case/check_input_data.py @@ -283,7 +283,7 @@ def stage_refcase(self, input_data_root=None, data_list_dir=None): get_refcase = self.get_value("GET_REFCASE") run_type = self.get_value("RUN_TYPE") continue_run = self.get_value("CONTINUE_RUN") - + drv_restart_pointer = self.get_value("DRV_RESTART_POINTER") # We do not fully populate the inputdata directory on every # machine and do not expect every user to download the 3TB+ of # data in our inputdata repository. This code checks for the @@ -335,17 +335,26 @@ def stage_refcase(self, input_data_root=None, data_list_dir=None): logger.debug("Creating run directory: {}".format(rundir)) os.makedirs(rundir) rpointerfile = None + found_drp = False # copy the refcases' rpointer files to the run directory for rpointerfile in glob.iglob(os.path.join("{}", "*rpointer*").format(refdir)): logger.info("Copy rpointer {}".format(rpointerfile)) safe_copy(rpointerfile, rundir) - os.chmod(os.path.join(rundir, os.path.basename(rpointerfile)), 0o644) + pfile = os.path.basename(rpointerfile) + os.chmod(os.path.join(rundir, pfile), 0o644) + if pfile == drv_restart_pointer: + found_drp = True + elif "cpl" in pfile: + drp = pfile expect( rpointerfile, "Reference case directory {} does not contain any rpointer files".format( refdir ), ) + if not found_drp: + self.set_value("DRV_RESTART_POINTER", drp) + # link everything else for rcfile in glob.iglob(os.path.join(refdir, "*")): From 915c5bfd7b36f0bf1733bd936e1860f9b0d60a2e Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 22 Dec 2025 14:56:23 -0700 Subject: [PATCH 025/153] clean up new code --- CIME/case/check_input_data.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CIME/case/check_input_data.py b/CIME/case/check_input_data.py index 94094ec3b97..f340dcd2af0 100644 --- a/CIME/case/check_input_data.py +++ b/CIME/case/check_input_data.py @@ -335,25 +335,22 @@ def stage_refcase(self, input_data_root=None, data_list_dir=None): logger.debug("Creating run directory: {}".format(rundir)) os.makedirs(rundir) rpointerfile = None - found_drp = False + # copy the refcases' rpointer files to the run directory for rpointerfile in glob.iglob(os.path.join("{}", "*rpointer*").format(refdir)): logger.info("Copy rpointer {}".format(rpointerfile)) safe_copy(rpointerfile, rundir) pfile = os.path.basename(rpointerfile) os.chmod(os.path.join(rundir, pfile), 0o644) - if pfile == drv_restart_pointer: - found_drp = True - elif "cpl" in pfile: - drp = pfile + if "cpl" in pfile and drv_restart_pointer != pfile: + self.set_value("DRV_RESTART_POINTER", pfile) + expect( rpointerfile, "Reference case directory {} does not contain any rpointer files".format( refdir ), ) - if not found_drp: - self.set_value("DRV_RESTART_POINTER", drp) # link everything else From ed13d79d98ca734352d1be68e90ff15a0b0c902d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 22 Dec 2025 15:02:07 -0800 Subject: [PATCH 026/153] Fixes handling errors when test has invalid case directory --- CIME/bless_test_results.py | 282 +++++++++++++-------- CIME/tests/test_unit_bless_test_results.py | 10 +- 2 files changed, 186 insertions(+), 106 deletions(-) diff --git a/CIME/bless_test_results.py b/CIME/bless_test_results.py index e7e601b682e..3028cfa4390 100644 --- a/CIME/bless_test_results.py +++ b/CIME/bless_test_results.py @@ -6,6 +6,7 @@ EnvironmentContext, parse_test_name, match_any, + CIMEError, ) from CIME.config import Config from CIME.test_status import * @@ -22,6 +23,48 @@ logger = logging.getLogger(__name__) +class BlessError(Exception): + def __init__(self, test_name, reasons): + if isinstance(reasons, str): + reasons = list(reasons) + + self._test_name = test_name + self._index = 0 + self._reasons = reasons + + super().__init__() + + @property + def test_name(self): + return self._test_name + + def __iter__(self): + self._index = 0 + + return self + + def __next__(self): + if self._index > len(self._reasons) - 1: + raise StopIteration() + + item = self._reasons[self._index] + + self._index += 1 + + return item + + def __len__(self): + return len(self._reasons) + + def __str__(self): + return f"There were {len(self)} bless errors" + + def __repr__(self): + reason_str = ",".join(self._reasons) if len(self) > 0 else "" + + return f"BlessError(reasons=[{reason_str}])" + + def _bless_throughput( case, test_name, @@ -367,108 +410,28 @@ def bless_test_results( if not force: time.sleep(2) - with Case(test_dir) as case: - # Resolve baseline_name and baseline_root - if baseline_name is None: - baseline_name_resolved = case.get_value("BASELINE_NAME_CMP") - if not baseline_name_resolved: - cime_root = CIME.utils.get_cime_root() - baseline_name_resolved = CIME.utils.get_current_branch( - repo=cime_root - ) - else: - baseline_name_resolved = baseline_name - - if baseline_root is None: - baseline_root_resolved = case.get_value("BASELINE_ROOT") - else: - baseline_root_resolved = baseline_root - - if baseline_name_resolved is None: - broken_blesses.append( - (test_name, "Could not determine baseline name") - ) - continue - - if baseline_root_resolved is None: - broken_blesses.append( - (test_name, "Could not determine baseline root") - ) - continue - - # Bless namelists - if nl_bless: - success, reason = bless_namelists( - test_name, - report_only, - force, - pes_file, - baseline_name_resolved, - baseline_root_resolved, - new_test_root=new_test_root, - new_test_id=new_test_id, - ) - if not success: - broken_blesses.append((test_name, reason)) - - # Bless hist files - if hist_bless: - if "HOMME" in test_name: - success = False - reason = "HOMME tests cannot be blessed with bless_for_tests" - else: - success, reason = bless_history( - test_name, - case, - baseline_name_resolved, - baseline_root_resolved, - report_only, - force, - ) - - if not success: - broken_blesses.append((test_name, reason)) - - if tput_bless: - success, reason = _bless_throughput( - case, - test_name, - baseline_root_resolved, - baseline_name_resolved, - report_only, - force, - ) - - if not success: - broken_blesses.append((test_name, reason)) - - if mem_bless: - success, reason = _bless_memory( - case, - test_name, - baseline_root_resolved, - baseline_name_resolved, - report_only, - force, - ) - - if not success: - broken_blesses.append((test_name, reason)) - - if lock_baselines: - baseline_full_dir = os.path.join( - baseline_root_resolved, - baseline_name_resolved, - case.get_value("CASEBASEID"), - ) - stat, out, _ = run_cmd( - f"chmod -R g-w {baseline_full_dir}", combine_output=True - ) - if stat != 0: - msg = ( - f"Failed to lock baselines for {baseline_full_dir}:\n{out}" - ) - logger.warning(msg) + try: + _bless_test( + test_name, + test_dir, + baseline_name, + baseline_root, + nl_bless, + hist_bless, + tput_bless, + mem_bless, + lock_baselines, + pes_file, + new_test_root, + new_test_id, + report_only, + force, + ) + except BlessError as e: + for reason in e: + broken_blesses.append((e.test_name, reason)) + except CIMEError as e: + broken_blesses.append((test_name, str(e))) # Emit a warning if items in bless_tests did not match anything if bless_tests: @@ -494,6 +457,121 @@ def bless_test_results( return success +def _bless_test( + test_name, + test_dir, + baseline_name, + baseline_root, + nl_bless, + hist_bless, + tput_bless, + mem_bless, + lock_baselines, + pes_file, + new_test_root, + new_test_id, + report_only, + force, +): + with Case(test_dir) as case: + # Resolve baseline_name and baseline_root + if baseline_name is None: + baseline_name_resolved = case.get_value("BASELINE_NAME_CMP") + if not baseline_name_resolved: + cime_root = CIME.utils.get_cime_root() + baseline_name_resolved = CIME.utils.get_current_branch(repo=cime_root) + else: + baseline_name_resolved = baseline_name + + if baseline_root is None: + baseline_root_resolved = case.get_value("BASELINE_ROOT") + else: + baseline_root_resolved = baseline_root + + if baseline_name_resolved is None: + raise BlessError(test_name, "Could not determine baseline name") + + if baseline_root_resolved is None: + raise BlessError(test_name, "Could not determine baseline root") + + bless_errors = [] + + # Bless namelists + if nl_bless: + success, reason = bless_namelists( + test_name, + report_only, + force, + pes_file, + baseline_name_resolved, + baseline_root_resolved, + new_test_root=new_test_root, + new_test_id=new_test_id, + ) + if not success: + bless_errors.append(reason) + + # Bless hist files + if hist_bless: + if "HOMME" in test_name: + success = False + reason = "HOMME tests cannot be blessed with bless_for_tests" + else: + success, reason = bless_history( + test_name, + case, + baseline_name_resolved, + baseline_root_resolved, + report_only, + force, + ) + + if not success: + bless_errors.append(reason) + + if tput_bless: + success, reason = _bless_throughput( + case, + test_name, + baseline_root_resolved, + baseline_name_resolved, + report_only, + force, + ) + + if not success: + bless_errors.append(reason) + + if mem_bless: + success, reason = _bless_memory( + case, + test_name, + baseline_root_resolved, + baseline_name_resolved, + report_only, + force, + ) + + if not success: + bless_errors.append(reason) + + if lock_baselines: + baseline_full_dir = os.path.join( + baseline_root_resolved, + baseline_name_resolved, + case.get_value("CASEBASEID"), + ) + stat, out, _ = run_cmd( + f"chmod -R g-w {baseline_full_dir}", combine_output=True + ) + if stat != 0: + msg = f"Failed to lock baselines for {baseline_full_dir}:\n{out}" + logger.warning(msg) + + if len(bless_errors) > 0: + raise BlessError(test_name, bless_errors) + + def is_hist_bless_needed( test_name, ts, broken_blesses, overall_result, no_skip_pass, phase ): diff --git a/CIME/tests/test_unit_bless_test_results.py b/CIME/tests/test_unit_bless_test_results.py index 59475847f8c..3138dd75c77 100644 --- a/CIME/tests/test_unit_bless_test_results.py +++ b/CIME/tests/test_unit_bless_test_results.py @@ -921,6 +921,7 @@ def test_bless_tests_no_match(self, get_test_status_files, TestStatus, Case): assert success + # TODO should update all tests to use mock_case def test_bless_results_invalid_case(self): with ExitStack() as stack: tempdir = stack.enter_context(tempfile.TemporaryDirectory()) @@ -952,10 +953,11 @@ def test_bless_results_invalid_case(self): with (x / "TestStatus").open("w") as f: f.write(tests[i][1].format(tests[i][0])) - with self.assertRaises(CIMEError): - success = bless_test_results( - "master", "/tmp/baseline", str(caseroots[0].parent), "oneapi-ifx" - ) + success = bless_test_results( + "master", "/tmp/baseline", str(caseroots[0].parent), "oneapi-ifx" + ) + + self.assertFalse(success) @mock.patch("CIME.bless_test_results.Case") @mock.patch("CIME.bless_test_results.TestStatus") From 56b4666bd1ab6f594f02b9cf3c0a273d86c96e4c Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 09:00:47 -0700 Subject: [PATCH 027/153] initialize timestep --- .pre-commit-config.yaml | 2 +- CIME/SystemTests/system_tests_common.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c478a540731..442206da5c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: black files: CIME - repo: https://github.com/PyCQA/pylint - rev: v2.11.1 + rev: v4.0.4 hooks: - id: pylint args: diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index dcdb053ff39..c8b38420695 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -185,7 +185,9 @@ def _set_restart_interval( elif ncpl_base_period == "decade": coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl - + else: + timestep = None + # Convert stop_n to units of coupling intervals factor = 1 if stop_option == "nsteps": From 37a17eae956b037f64c8bc9e78555f0b2e97f81a Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 09:42:55 -0700 Subject: [PATCH 028/153] fix all pylint issues for pylint v4.0.4 --- .pre-commit-config.yaml | 2 +- CIME/SystemTests/system_tests_common.py | 4 +++- CIME/XML/compsets.py | 5 ++++- CIME/XML/env_batch.py | 1 + CIME/XML/namelist_definition.py | 6 ++++++ CIME/XML/tests.py | 3 +-- CIME/XML/workflow.py | 2 +- CIME/case/case_setup.py | 3 ++- CIME/case/case_submit.py | 3 +++ CIME/case/check_input_data.py | 6 +++++- CIME/compare_test_results.py | 3 ++- CIME/get_timing.py | 6 +++++- CIME/hist_utils.py | 1 + CIME/namelist.py | 1 + CIME/scripts/configure | 2 +- CIME/test_utils.py | 2 ++ CIME/utils.py | 1 + 17 files changed, 40 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 442206da5c2..c478a540731 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: black files: CIME - repo: https://github.com/PyCQA/pylint - rev: v4.0.4 + rev: v2.11.1 hooks: - id: pylint args: diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index c8b38420695..e304dced5e1 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -186,8 +186,9 @@ def _set_restart_interval( coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl else: + coupling_secs = None timestep = None - + # Convert stop_n to units of coupling intervals factor = 1 if stop_option == "nsteps": @@ -267,6 +268,7 @@ def _set_restart_interval( elif stop_option == "nyears": rtd = timedelta(days=rest_n * 365) else: + rtd = None expect(False, f"stop_option {stop_option} not available for this test") restdatetime = startdatetime + rtd diff --git a/CIME/XML/compsets.py b/CIME/XML/compsets.py index 23eca6c825c..9fd40657353 100644 --- a/CIME/XML/compsets.py +++ b/CIME/XML/compsets.py @@ -74,6 +74,8 @@ def get_value(self, name, attribute=None, resolved=False, subgroup=None): else: compsets = {} nodes = self.get_children("compset") + lname = None + alias = None for node in nodes: for child in node: logger.debug( @@ -85,7 +87,8 @@ def get_value(self, name, attribute=None, resolved=False, subgroup=None): alias = self.text(child) if self.name(child) == "lname": lname = self.text(child) - compsets[alias] = lname + if alias is not None and lname is not None: + compsets[alias] = lname return compsets def print_values(self, arg_help=True): diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 1973e7c21dd..f37006225e6 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -549,6 +549,7 @@ def get_batch_directives(self, case, job, overrides=None, output_format="default default_queue = self.text(qnode) else: unknown_queue = False + default_queue = None for root in roots: if root is not None: diff --git a/CIME/XML/namelist_definition.py b/CIME/XML/namelist_definition.py index 7869011d573..b375ad07ea9 100644 --- a/CIME/XML/namelist_definition.py +++ b/CIME/XML/namelist_definition.py @@ -140,6 +140,8 @@ def get_group_name(self, node=None): group = self.get(node, "group") elif self.get_version() >= 2.0: group = self.get_element_text("group", root=node) + else: + group = None return group def _get_type(self, node): @@ -147,6 +149,8 @@ def _get_type(self, node): type_info = self.get(node, "type") elif self.get_version() >= 2.0: type_info = self._get_type_info(node) + else: + type_info = None return type_info def _get_valid_values(self, node): @@ -534,6 +538,8 @@ def get_input_pathname(self, name): input_pathname = self.get(node, "input_pathname") elif self.get_version() >= 2.0: input_pathname = self._get_node_element_info(node, "input_pathname") + else: + input_pathname = None return input_pathname # pylint: disable=arguments-differ diff --git a/CIME/XML/tests.py b/CIME/XML/tests.py index 2064bfab26f..b0b8ebce186 100644 --- a/CIME/XML/tests.py +++ b/CIME/XML/tests.py @@ -59,8 +59,7 @@ def support_single_exe(self, case): if not valid: case_base_id = case.get_value("CASEBASEID") - - raise Exception( + raise RuntimeError( f"{case_base_id} does not support the '--single-exe' option as it requires separate builds" ) diff --git a/CIME/XML/workflow.py b/CIME/XML/workflow.py index 4824e03b666..cdb2d27b403 100644 --- a/CIME/XML/workflow.py +++ b/CIME/XML/workflow.py @@ -80,6 +80,6 @@ def get_workflow_jobs(self, machine, workflowid="default"): else: jdict[self.name(child)] = self.text(child) - jobs.append((name, jdict)) + jobs.append((name, jdict)) return jobs diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 98671ba24d2..66f16cbb4fd 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -311,13 +311,14 @@ def _case_setup_impl( ninst = case.get_value("NINST") multi_driver = case.get_value("MULTI_DRIVER") - + ninst = 0 for comp in models: ntasks = case.get_value("NTASKS_{}".format(comp)) if comp == "CPL": continue if comp_interface != "nuopc": ninst = case.get_value("NINST_{}".format(comp)) + if multi_driver: if comp_interface != "nuopc": expect( diff --git a/CIME/case/case_submit.py b/CIME/case/case_submit.py index 21e03c05d02..924f09ad066 100644 --- a/CIME/case/case_submit.py +++ b/CIME/case/case_submit.py @@ -355,6 +355,9 @@ def check_case(self, skip_pnl=False, chksum=False): elif ncpl_base_period == "decade": coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl + else: + coupling_secs = None + timestep = None stop_option = self.get_value("STOP_OPTION") stop_n = self.get_value("STOP_N") if stop_option == "nsteps": diff --git a/CIME/case/check_input_data.py b/CIME/case/check_input_data.py index f340dcd2af0..cc67e1c6aca 100644 --- a/CIME/case/check_input_data.py +++ b/CIME/case/check_input_data.py @@ -32,6 +32,7 @@ def _download_checksum_file(rundir): protocol, user, passwd ) ) + server = None if protocol == "svn": server = CIME.Servers.SVN(address, user, passwd) elif protocol == "gftp": @@ -42,6 +43,7 @@ def _download_checksum_file(rundir): server = CIME.Servers.WGET.wget_login(address, user, passwd) else: expect(False, "Unsupported inputdata protocol: {}".format(protocol)) + if not server: continue @@ -190,7 +192,8 @@ def _check_all_input_data_impl( else: if chksum: chksum_found = _download_checksum_file(self.get_value("RUNDIR")) - + else: + chksum_found = False clm_usrdat_name = self.get_value("CLM_USRDAT_NAME") if clm_usrdat_name and clm_usrdat_name == "UNSET": clm_usrdat_name = None @@ -409,6 +412,7 @@ def _check_input_data_impl( ) no_files_missing = True + server = None if download: if protocol not in vars(CIME.Servers): logger.info("Client protocol {} not enabled".format(protocol)) diff --git a/CIME/compare_test_results.py b/CIME/compare_test_results.py index f14263adf4b..8718d9f0565 100644 --- a/CIME/compare_test_results.py +++ b/CIME/compare_test_results.py @@ -151,7 +151,8 @@ def compare_test_results( logfile_name, test_dir, ) - + compare_result = None + compare_comment = None if nl_do_compare or do_compare: if nl_do_compare: nl_success = compare_namelists( diff --git a/CIME/get_timing.py b/CIME/get_timing.py index a5526ccec8b..e32e78dd3b3 100644 --- a/CIME/get_timing.py +++ b/CIME/get_timing.py @@ -390,7 +390,8 @@ def _getTiming(self, inst=0): inittype = "FALSE" if (run_type == "startup" or run_type == "hybrid") and not continue_run: inittype = "TRUE" - + binfilename = None + finfilename = None if inst > 0: inst_label = "_{:04d}".format(inst) else: @@ -450,6 +451,9 @@ def _getTiming(self, inst=0): logger.warning("Unknown NCPL_BASE_PERIOD={}".format(ncpl_base_period)) # at this point the routine becomes driver specific + nsteps = 0 + nmax = 0 + fmax = 0 if self._driver == "mct" or self._driver == "moab": nprocs, ncount = self.gettime2("CPL:CLOCK_ADVANCE ") nsteps = ncount / nprocs diff --git a/CIME/hist_utils.py b/CIME/hist_utils.py index 8f76c61ec54..fa94f1711eb 100644 --- a/CIME/hist_utils.py +++ b/CIME/hist_utils.py @@ -522,6 +522,7 @@ def cprnc( out = fd.read() comment = "" + files_match = False if cpr_stat == 0: # Successful exit from cprnc if multiinst_driver_compare: diff --git a/CIME/namelist.py b/CIME/namelist.py index 94b9e02d151..c2e60d39033 100644 --- a/CIME/namelist.py +++ b/CIME/namelist.py @@ -1214,6 +1214,7 @@ def write( def _write(self, out_file, groups, format_, sorted_groups): """Unwrapped version of `write` assuming that a file object is input.""" + equals = None if groups is None: groups = list(self._groups.keys()) if format_ == "nml" or format_ == "nmlcontents": diff --git a/CIME/scripts/configure b/CIME/scripts/configure index f31c179518c..73a326da2bf 100755 --- a/CIME/scripts/configure +++ b/CIME/scripts/configure @@ -97,7 +97,7 @@ def parse_command_line(args): argcnt = len(args) args = parser.parse_args() CIME.utils.parse_args_and_handle_standard_logging_options(args) - + machobj = None opts = {} if args.machines_dir is not None: machines_file = os.path.join(args.machines_dir, "config_machines.xml") diff --git a/CIME/test_utils.py b/CIME/test_utils.py index d4daa131496..0c6133b0d74 100644 --- a/CIME/test_utils.py +++ b/CIME/test_utils.py @@ -26,6 +26,8 @@ def get_tests_from_xml( """ listoftests = [] testlistfiles = [] + thismach = None + thiscompiler = None if machine is not None: thismach = machine if compiler is not None: diff --git a/CIME/utils.py b/CIME/utils.py index e05d5ad9e4a..021dd8e71b3 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2697,6 +2697,7 @@ def is_comp_standalone(case): """ stubcnt = 0 classes = case.get_values("COMP_CLASSES") + model = None for comp in classes: if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): stubcnt = stubcnt + 1 From b2811188c8dcc86b2fa551c3af3dee22edb714c5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 09:48:01 -0700 Subject: [PATCH 029/153] fix some spelling errors --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/pull_request_template.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index af60457e340..c132f3bf736 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,7 +5,7 @@ Please provide a clear and concise description of what the problem is e.g. I'm a ## Describe the solution you'd like ## Describe alternative solutions diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 84e9337a3d4..0eea945a6dc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,9 +7,9 @@ - Closes # ## Checklist -- [ ] My code follows the style guidlines of this proejct (black formatting) +- [ ] My code follows the style guidelines of this project (black formatting) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings -- [ ] I have added tests that excerise my feature/fix and existing tests continue to pass +- [ ] I have added tests that exerise my feature/fix and existing tests continue to pass - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding additions and changes to the documentation From eede1225c6486ed26f6481c26409b505bd3a4112 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 13:53:05 -0500 Subject: [PATCH 030/153] Update .github/pull_request_template.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0eea945a6dc..02521c6f349 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,6 +10,6 @@ - [ ] My code follows the style guidelines of this project (black formatting) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings -- [ ] I have added tests that exerise my feature/fix and existing tests continue to pass +- [ ] I have added tests that exercise my feature/fix and existing tests continue to pass - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding additions and changes to the documentation From 9f635b0fc6036587924318cebf82af1ddf90e140 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 14:06:44 -0700 Subject: [PATCH 031/153] response to reviewer comments --- CIME/SystemTests/system_tests_common.py | 3 +-- CIME/XML/compsets.py | 8 ++++---- CIME/XML/namelist_definition.py | 6 +++--- CIME/case/case_setup.py | 3 ++- CIME/case/case_submit.py | 4 ++-- CIME/compare_test_results.py | 5 +---- CIME/get_timing.py | 16 ++++++++++------ CIME/namelist.py | 4 +++- CIME/test_utils.py | 11 +++++------ CIME/utils.py | 2 +- 10 files changed, 32 insertions(+), 30 deletions(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index e304dced5e1..bc9dcdd0853 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -186,8 +186,7 @@ def _set_restart_interval( coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl else: - coupling_secs = None - timestep = None + raise RuntimeError("unhandled ncpl_base_period value") # Convert stop_n to units of coupling intervals factor = 1 diff --git a/CIME/XML/compsets.py b/CIME/XML/compsets.py index 9fd40657353..d4754431068 100644 --- a/CIME/XML/compsets.py +++ b/CIME/XML/compsets.py @@ -25,8 +25,6 @@ def get_compset_match(self, name): is scientifically supported. science_support is returned as an array of grids for this compset """ nodes = self.get_children("compset") - alias = None - lname = None science_support = [] @@ -74,9 +72,9 @@ def get_value(self, name, attribute=None, resolved=False, subgroup=None): else: compsets = {} nodes = self.get_children("compset") - lname = None - alias = None for node in nodes: + alias = None + lname = None for child in node: logger.debug( "Here child is {} with value {}".format( @@ -89,6 +87,8 @@ def get_value(self, name, attribute=None, resolved=False, subgroup=None): lname = self.text(child) if alias is not None and lname is not None: compsets[alias] = lname + else: + raise RuntimeError("Invalid entry in config_compsets.xml") return compsets def print_values(self, arg_help=True): diff --git a/CIME/XML/namelist_definition.py b/CIME/XML/namelist_definition.py index b375ad07ea9..2231dee87c9 100644 --- a/CIME/XML/namelist_definition.py +++ b/CIME/XML/namelist_definition.py @@ -141,7 +141,7 @@ def get_group_name(self, node=None): elif self.get_version() >= 2.0: group = self.get_element_text("group", root=node) else: - group = None + raise RuntimeError("Undefined XML file version") return group def _get_type(self, node): @@ -150,7 +150,7 @@ def _get_type(self, node): elif self.get_version() >= 2.0: type_info = self._get_type_info(node) else: - type_info = None + raise RuntimeError("Undefined XML file version") return type_info def _get_valid_values(self, node): @@ -539,7 +539,7 @@ def get_input_pathname(self, name): elif self.get_version() >= 2.0: input_pathname = self._get_node_element_info(node, "input_pathname") else: - input_pathname = None + raise RuntimeError("Invalid XML file version") return input_pathname # pylint: disable=arguments-differ diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 66f16cbb4fd..f829e5443ab 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -311,13 +311,14 @@ def _case_setup_impl( ninst = case.get_value("NINST") multi_driver = case.get_value("MULTI_DRIVER") - ninst = 0 for comp in models: ntasks = case.get_value("NTASKS_{}".format(comp)) if comp == "CPL": continue if comp_interface != "nuopc": ninst = case.get_value("NINST_{}".format(comp)) + else: + ninst = 1 if multi_driver: if comp_interface != "nuopc": diff --git a/CIME/case/case_submit.py b/CIME/case/case_submit.py index 924f09ad066..54e2af2f112 100644 --- a/CIME/case/case_submit.py +++ b/CIME/case/case_submit.py @@ -356,8 +356,8 @@ def check_case(self, skip_pnl=False, chksum=False): coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl else: - coupling_secs = None - timestep = None + raise RuntimeError("ncpl_base_period handling error") + stop_option = self.get_value("STOP_OPTION") stop_n = self.get_value("STOP_N") if stop_option == "nsteps": diff --git a/CIME/compare_test_results.py b/CIME/compare_test_results.py index 8718d9f0565..35274cc74ba 100644 --- a/CIME/compare_test_results.py +++ b/CIME/compare_test_results.py @@ -151,8 +151,6 @@ def compare_test_results( logfile_name, test_dir, ) - compare_result = None - compare_comment = None if nl_do_compare or do_compare: if nl_do_compare: nl_success = compare_namelists( @@ -183,8 +181,7 @@ def compare_test_results( brief_result += "{} {} {} {}\n".format( nl_compare_result, test_name, NAMELIST_PHASE, nl_compare_comment ) - - if not namelists_only: + if not namelists_only and not build_only: brief_result += "{} {} {}".format( compare_result, test_name, BASELINE_PHASE ) diff --git a/CIME/get_timing.py b/CIME/get_timing.py index e32e78dd3b3..92ac32509de 100644 --- a/CIME/get_timing.py +++ b/CIME/get_timing.py @@ -390,8 +390,7 @@ def _getTiming(self, inst=0): inittype = "FALSE" if (run_type == "startup" or run_type == "hybrid") and not continue_run: inittype = "TRUE" - binfilename = None - finfilename = None + if inst > 0: inst_label = "_{:04d}".format(inst) else: @@ -412,6 +411,8 @@ def _getTiming(self, inst=0): "timing", "{}.ESMF_Profile.summary.{}".format(cime_model, self.lid), ) + else: + raise RuntimeError("Unknown driver set") foutfilename = os.path.join( self.caseroot, @@ -451,14 +452,15 @@ def _getTiming(self, inst=0): logger.warning("Unknown NCPL_BASE_PERIOD={}".format(ncpl_base_period)) # at this point the routine becomes driver specific - nsteps = 0 - nmax = 0 - fmax = 0 + if self._driver == "mct" or self._driver == "moab": nprocs, ncount = self.gettime2("CPL:CLOCK_ADVANCE ") nsteps = ncount / nprocs elif self._driver == "nuopc": nprocs, nsteps = self.gettime2("") + else: + raise RuntimeError("Unknown driver setting") + adays = nsteps * tlen / ncpl odays = nsteps * tlen / ncpl if ocn_ncpl and inittype == "TRUE": @@ -551,7 +553,7 @@ def _getTiming(self, inst=0): fmax = self.gettime("[ensemble] FinalizePhase1")[1] xmax = self.getCOMMtime(inst_label[1:]) - if self._driver == "mct" or self._driver == "moab": + elif self._driver == "mct" or self._driver == "moab": for k in components: if k != "CPL": m = self.models[k] @@ -580,6 +582,8 @@ def _getTiming(self, inst=0): tmax = tmax + wtmin + correction ocn.tmax += ocnrunitime + else: + raise RuntimeError("driver not recognized or not defined") for m in self.models.values(): m.tmaxr = 0 diff --git a/CIME/namelist.py b/CIME/namelist.py index c2e60d39033..486955bca69 100644 --- a/CIME/namelist.py +++ b/CIME/namelist.py @@ -1214,13 +1214,15 @@ def write( def _write(self, out_file, groups, format_, sorted_groups): """Unwrapped version of `write` assuming that a file object is input.""" - equals = None + if groups is None: groups = list(self._groups.keys()) if format_ == "nml" or format_ == "nmlcontents": equals = " =" elif format_ == "rc": equals = ":" + else: + raise RuntimeError("format undefined or unrecognised") if sorted_groups: group_names = sorted(group for group in groups) else: diff --git a/CIME/test_utils.py b/CIME/test_utils.py index 0c6133b0d74..6dea4e3bddc 100644 --- a/CIME/test_utils.py +++ b/CIME/test_utils.py @@ -26,12 +26,6 @@ def get_tests_from_xml( """ listoftests = [] testlistfiles = [] - thismach = None - thiscompiler = None - if machine is not None: - thismach = machine - if compiler is not None: - thiscompiler = compiler if xml_testlist is not None: expect( @@ -64,8 +58,13 @@ def get_tests_from_xml( for test in newtests: if machine is None: thismach = test["machine"] + else: + thismach = machine if compiler is None: thiscompiler = test["compiler"] + else: + thiscompiler = compiler + test["name"] = CIME.utils.get_full_test_name( test["testname"], grid=test["grid"], diff --git a/CIME/utils.py b/CIME/utils.py index 021dd8e71b3..691efafce58 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2697,7 +2697,7 @@ def is_comp_standalone(case): """ stubcnt = 0 classes = case.get_values("COMP_CLASSES") - model = None + model = False for comp in classes: if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): stubcnt = stubcnt + 1 From 8d89e280cf90bd5cb584e07eee5ee446af2bc73c Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 14:27:32 -0700 Subject: [PATCH 032/153] replace RuntimeError with CIMEError --- CIME/XML/namelist_definition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CIME/XML/namelist_definition.py b/CIME/XML/namelist_definition.py index 2231dee87c9..25f7c0e0432 100644 --- a/CIME/XML/namelist_definition.py +++ b/CIME/XML/namelist_definition.py @@ -21,7 +21,7 @@ Namelist, get_fortran_name_only, ) - +from CIME.utils import CIMEError from CIME.XML.standard_module_setup import * from CIME.XML.entry_id import EntryID from CIME.XML.files import Files @@ -141,7 +141,7 @@ def get_group_name(self, node=None): elif self.get_version() >= 2.0: group = self.get_element_text("group", root=node) else: - raise RuntimeError("Undefined XML file version") + raise CIMEError("Undefined XML file version") return group def _get_type(self, node): @@ -150,7 +150,7 @@ def _get_type(self, node): elif self.get_version() >= 2.0: type_info = self._get_type_info(node) else: - raise RuntimeError("Undefined XML file version") + raise CIMEError("Undefined XML file version") return type_info def _get_valid_values(self, node): @@ -539,7 +539,7 @@ def get_input_pathname(self, name): elif self.get_version() >= 2.0: input_pathname = self._get_node_element_info(node, "input_pathname") else: - raise RuntimeError("Invalid XML file version") + raise CIMEError("Invalid XML file version") return input_pathname # pylint: disable=arguments-differ From 9793fa2fb6c17dc8196735e665af8916529090f3 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 14:36:29 -0700 Subject: [PATCH 033/153] replace RuntimeError with CIMEError --- CIME/SystemTests/system_tests_common.py | 8 ++++---- CIME/XML/compsets.py | 3 ++- CIME/XML/tests.py | 4 ++-- CIME/case/case_setup.py | 9 +++++---- CIME/case/case_submit.py | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index bc9dcdd0853..2366aaeae8a 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -186,7 +186,7 @@ def _set_restart_interval( coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl else: - raise RuntimeError("unhandled ncpl_base_period value") + raise CIMEError("unhandled ncpl_base_period value") # Convert stop_n to units of coupling intervals factor = 1 @@ -1302,12 +1302,12 @@ def run_indv( class TESTRUNFAILEXC(TESTRUNPASS): def run_phase(self): - raise RuntimeError("Exception from run_phase") + raise CIMEError("Exception from run_phase") class TESTRUNSTARCFAIL(TESTRUNPASS): def _st_archive_case_test(self): - raise RuntimeError("Exception from st archive") + raise CIMEError("Exception from st archive") class TESTBUILDFAIL(TESTRUNPASS): @@ -1329,7 +1329,7 @@ def build_phase(self, sharedlib_only=False, model_only=False): class TESTBUILDFAILEXC(FakeTest): def build_phase(self, sharedlib_only=False, model_only=False): - raise RuntimeError("Exception from build") + raise CIMEError("Exception from build") class TESTRUNUSERXMLCHANGE(FakeTest): diff --git a/CIME/XML/compsets.py b/CIME/XML/compsets.py index d4754431068..eef9253da0f 100644 --- a/CIME/XML/compsets.py +++ b/CIME/XML/compsets.py @@ -6,6 +6,7 @@ from CIME.XML.generic_xml import GenericXML from CIME.XML.entry_id import EntryID from CIME.XML.files import Files +from CIME.utils import CIMEError logger = logging.getLogger(__name__) @@ -88,7 +89,7 @@ def get_value(self, name, attribute=None, resolved=False, subgroup=None): if alias is not None and lname is not None: compsets[alias] = lname else: - raise RuntimeError("Invalid entry in config_compsets.xml") + raise CIMEError("Invalid entry in config_compsets.xml") return compsets def print_values(self, arg_help=True): diff --git a/CIME/XML/tests.py b/CIME/XML/tests.py index b0b8ebce186..e760d86604f 100644 --- a/CIME/XML/tests.py +++ b/CIME/XML/tests.py @@ -5,7 +5,7 @@ from CIME.XML.generic_xml import GenericXML from CIME.XML.files import Files -from CIME.utils import find_system_test +from CIME.utils import find_system_test, CIMEError from CIME.SystemTests.system_tests_compare_two import SystemTestsCompareTwo from CIME.SystemTests.system_tests_compare_n import SystemTestsCompareN @@ -59,7 +59,7 @@ def support_single_exe(self, case): if not valid: case_base_id = case.get_value("CASEBASEID") - raise RuntimeError( + raise CIMEError( f"{case_base_id} does not support the '--single-exe' option as it requires separate builds" ) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index f829e5443ab..1e6f53e5cb6 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -20,6 +20,7 @@ copy_local_macros_to_dir, batch_jobid, run_cmd_no_fail, + CIMEError, ) from CIME.status import run_and_log_case_status, append_case_status from CIME.test_status import * @@ -409,11 +410,11 @@ def _case_setup_impl( ngpus_per_node = case.get_value("NGPUS_PER_NODE") if gpu_type and str(gpu_type).lower() != "none": if max_gpus_per_node <= 0: - raise RuntimeError( + raise CIMEError( f"MAX_GPUS_PER_NODE must be larger than 0 for machine={mach} and compiler={compiler} in order to configure a GPU run" ) if not gpu_offload: - raise RuntimeError( + raise CIMEError( "GPU_TYPE is defined but none of the GPU OFFLOAD options are enabled" ) case.gpu_enabled = True @@ -425,11 +426,11 @@ def _case_setup_impl( else max_gpus_per_node, ) elif gpu_offload: - raise RuntimeError( + raise CIMEError( "GPU_TYPE is not defined but at least one GPU OFFLOAD option is enabled" ) elif ngpus_per_node and ngpus_per_node != 0: - raise RuntimeError( + raise CIMEError( f"ngpus_per_node is expected to be 0 for a pure CPU run ; {ngpus_per_node} is provided instead ;" ) diff --git a/CIME/case/case_submit.py b/CIME/case/case_submit.py index 54e2af2f112..9251e4ab092 100644 --- a/CIME/case/case_submit.py +++ b/CIME/case/case_submit.py @@ -356,7 +356,7 @@ def check_case(self, skip_pnl=False, chksum=False): coupling_secs = 315360000 / maxncpl timestep = 315360000 / minncpl else: - raise RuntimeError("ncpl_base_period handling error") + raise CIMEError("ncpl_base_period handling error") stop_option = self.get_value("STOP_OPTION") stop_n = self.get_value("STOP_N") From 3ae3808d1d5a414271b9c36293a5d651ad4fc0a2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 2 Jan 2026 15:05:37 -0700 Subject: [PATCH 034/153] futher revisions provided by reviewer --- CIME/case/case_setup.py | 4 ++-- CIME/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 1e6f53e5cb6..313b3386449 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -310,6 +310,8 @@ def _case_setup_impl( comp_interface = case.get_value("COMP_INTERFACE") if comp_interface == "nuopc": ninst = case.get_value("NINST") + else: + ninst = 1 multi_driver = case.get_value("MULTI_DRIVER") for comp in models: @@ -318,8 +320,6 @@ def _case_setup_impl( continue if comp_interface != "nuopc": ninst = case.get_value("NINST_{}".format(comp)) - else: - ninst = 1 if multi_driver: if comp_interface != "nuopc": diff --git a/CIME/utils.py b/CIME/utils.py index 691efafce58..9f4d689bf44 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2697,7 +2697,7 @@ def is_comp_standalone(case): """ stubcnt = 0 classes = case.get_values("COMP_CLASSES") - model = False + model = get_model() for comp in classes: if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): stubcnt = stubcnt + 1 @@ -2706,4 +2706,4 @@ def is_comp_standalone(case): numclasses = len(classes) if stubcnt >= numclasses - 2: return True, model - return False, get_model() + return False, model From 2144f3bf66edbfc8379586ba50f0230f9f4d22fe Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Jan 2026 07:18:58 -0700 Subject: [PATCH 035/153] fix issue in utils --- CIME/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/utils.py b/CIME/utils.py index 9f4d689bf44..b009ff26535 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2697,13 +2697,15 @@ def is_comp_standalone(case): """ stubcnt = 0 classes = case.get_values("COMP_CLASSES") - model = get_model() + model = None for comp in classes: if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): stubcnt = stubcnt + 1 else: model = comp.lower() numclasses = len(classes) + if not model: + model = get_model() if stubcnt >= numclasses - 2: return True, model return False, model From 2fcecc9b8519d9784404b4b0e5effd09d512109a Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Jan 2026 09:22:24 -0700 Subject: [PATCH 036/153] the issue was in case_run, not utils --- CIME/case/case_run.py | 5 ++--- CIME/utils.py | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 3e7c8baa37d..a71a119562e 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -305,9 +305,8 @@ def _post_run_check(case, lid): rundir = case.get_value("RUNDIR") driver = case.get_value("COMP_INTERFACE") - comp_standalone, model = is_comp_standalone(case) - if driver == "nuopc": + comp_standalone, model = is_comp_standalone(case) if comp_standalone: file_prefix = model else: @@ -335,7 +334,7 @@ def _post_run_check(case, lid): cpl_logs.append(os.path.join(rundir, "med.log." + lid)) cpl_logfile = cpl_logs[0] # find the last model.log and cpl.log - model_logfile = os.path.join(rundir, model + ".log." + lid) + model_logfile = os.path.join(rundir, file_prefix + ".log." + lid) if not os.path.isfile(model_logfile): expect(False, "Model did not complete, no {} log file ".format(model_logfile)) elif os.stat(model_logfile).st_size == 0: diff --git a/CIME/utils.py b/CIME/utils.py index b009ff26535..e4a49c4457e 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -2697,15 +2697,13 @@ def is_comp_standalone(case): """ stubcnt = 0 classes = case.get_values("COMP_CLASSES") - model = None + model = "cpl" for comp in classes: if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): stubcnt = stubcnt + 1 else: model = comp.lower() numclasses = len(classes) - if not model: - model = get_model() if stubcnt >= numclasses - 2: return True, model - return False, model + return False, None From b638325dee93827a4c059e430261be88d6c26fbf Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Jan 2026 09:33:09 -0700 Subject: [PATCH 037/153] another issue --- CIME/case/case_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index a71a119562e..59a01a4ab30 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -316,7 +316,7 @@ def _post_run_check(case, lid): cpl_ninst = 1 if case.get_value("MULTI_DRIVER"): - cpl_ninst = case.get_value("NINST_MAX") + cpl_ninst = int(case.get_value("NINST_MAX")) cpl_logs = [] if cpl_ninst > 1: From f4514f21527c572e37d3bd46a57071095f2de43b Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Jan 2026 09:42:58 -0700 Subject: [PATCH 038/153] down the rabbit hole --- CIME/case/case_run.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 59a01a4ab30..590df88d4a6 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -306,17 +306,15 @@ def _post_run_check(case, lid): driver = case.get_value("COMP_INTERFACE") if driver == "nuopc": - comp_standalone, model = is_comp_standalone(case) - if comp_standalone: - file_prefix = model - else: + comp_standalone, file_prefix = is_comp_standalone(case) + if not comp_standalone: file_prefix = "med" else: file_prefix = "cpl" cpl_ninst = 1 if case.get_value("MULTI_DRIVER"): - cpl_ninst = int(case.get_value("NINST_MAX")) + cpl_ninst = case.get_value("NINST_MAX") cpl_logs = [] if cpl_ninst > 1: From 0894a07ba45af84816581b696ee46383d2d5c0ac Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Jan 2026 09:57:37 -0700 Subject: [PATCH 039/153] update mock --- CIME/case/case_run.py | 1 + CIME/tests/case_fake.py | 1 + CIME/tests/test_unit_case_run.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 590df88d4a6..7188c0fb959 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -315,6 +315,7 @@ def _post_run_check(case, lid): cpl_ninst = 1 if case.get_value("MULTI_DRIVER"): cpl_ninst = case.get_value("NINST_MAX") + cpl_logs = [] if cpl_ninst > 1: diff --git a/CIME/tests/case_fake.py b/CIME/tests/case_fake.py index 2b1117edf63..4510165a5d7 100644 --- a/CIME/tests/case_fake.py +++ b/CIME/tests/case_fake.py @@ -29,6 +29,7 @@ def __init__(self, case_root, create_case_root=True): self.set_value("CASE", casename) self.set_value("CASEBASEID", casename) self.set_value("RUN_TYPE", "startup") + self.set_value("NINST_MAX", 1) self.set_exeroot() self.set_rundir() diff --git a/CIME/tests/test_unit_case_run.py b/CIME/tests/test_unit_case_run.py index 8f188925d57..88c746d0d8c 100644 --- a/CIME/tests/test_unit_case_run.py +++ b/CIME/tests/test_unit_case_run.py @@ -10,7 +10,7 @@ def _case_post_run_check(): case = mock.MagicMock() # RUNDIR, COMP_INTERFACE, COMP_CPL, COMP_ATM, COMP_OCN, MULTI_DRIVER - case.get_value.side_effect = ("/tmp/run", "mct", "cpl", "satm", "socn", False) + case.get_value.side_effect = ("/tmp/run", "mct", "cpl", 1, "satm", "socn", False) # COMP_CLASSES case.get_values.return_value = ("CPL", "ATM", "OCN") From 40fffd9b571578fc9f18ae4233aa6e9a114a5fe6 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 5 Jan 2026 15:29:44 -0700 Subject: [PATCH 040/153] Cmake build: do not set offload stuff in the cmake command if they are undefined --- CIME/build.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/CIME/build.py b/CIME/build.py index ed9ca2fd3ec..af1709bb59c 100644 --- a/CIME/build.py +++ b/CIME/build.py @@ -245,12 +245,16 @@ def get_standard_cmake_args(case, sharedpath): cmake_args += " -Dcompile_threaded={} ".format( stringify_bool(case.get_build_threaded()) ) - # check settings for GPU - gpu_type = case.get_value("GPU_TYPE") - openacc_gpu_offload = case.get_value("OPENACC_GPU_OFFLOAD") - openmp_gpu_offload = case.get_value("OPENMP_GPU_OFFLOAD") - kokkos_gpu_offload = case.get_value("KOKKOS_GPU_OFFLOAD") - cmake_args += f" -DGPU_TYPE={gpu_type} -DOPENACC_GPU_OFFLOAD={openacc_gpu_offload} -DOPENMP_GPU_OFFLOAD={openmp_gpu_offload} -DKOKKOS_GPU_OFFLOAD={kokkos_gpu_offload} " + # check for optional settings for GPU + for item in [ + "GPU_TYPE", + "OPENACC_GPU_OFFLOAD", + "OPENMP_GPU_OFFLOAD", + "KOKKOS_GPU_OFFLOAD", + ]: + item_value = case.get_value(item) + if item_value: + cmake_args += f" -D{item}={item_value}" ocn_model = case.get_value("COMP_OCN") atm_dycore = case.get_value("CAM_DYCORE") From 73fdf2bed5edf244889aa9276c981e668d6f337b Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 7 Jan 2026 09:09:12 -0700 Subject: [PATCH 041/153] update minimum required python version and update pylint to lastest --- .github/workflows/testing.yml | 2 +- .pre-commit-config.yaml | 2 +- CIME/Tools/standard_script_setup.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bd6e288cae2..ce15387d08c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -99,7 +99,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.10", "3.12"] + python-version: ["3.9", "3.10", "3.12", "3.13", "3.14"] steps: - name: Checkout model code uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c478a540731..442206da5c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: black files: CIME - repo: https://github.com/PyCQA/pylint - rev: v2.11.1 + rev: v4.0.4 hooks: - id: pylint args: diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index 89fe12868cc..67fa6b672a7 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -36,10 +36,10 @@ def check_minimum_python_version(major, minor, warn_only=False): raise RuntimeError(msg + " - please use a newer version of Python.") -# Require users to be using >=3.6 -check_minimum_python_version(3, 6) -# Warn users if they are using <3.8 -check_minimum_python_version(3, 8, warn_only=True) +# Require users to be using >=3.9 +check_minimum_python_version(3, 9) +# Warn users if they are using <3.10 +check_minimum_python_version(3, 10, warn_only=True) real_file_dir = os.path.dirname(os.path.realpath(__file__)) cimeroot = os.path.abspath(os.path.join(real_file_dir, "..", "..")) From 1df1663e21ea572bfff6ca2a1308aa78f07d3434 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 12 Jan 2026 08:45:00 -0700 Subject: [PATCH 042/153] allow a potential python version warning in tests --- CIME/tests/test_sys_cime_case.py | 31 ++++++++++++++++++++------- CIME/tests/test_sys_create_newcase.py | 5 ++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/CIME/tests/test_sys_cime_case.py b/CIME/tests/test_sys_cime_case.py index c4095972d93..aad61fd1941 100644 --- a/CIME/tests/test_sys_cime_case.py +++ b/CIME/tests/test_sys_cime_case.py @@ -45,9 +45,13 @@ def test_cime_case(self): ) case.flush() - - build_complete = utils.run_cmd_no_fail( - "./xmlquery BUILD_COMPLETE --value", from_dir=casedir + # the strip().splitlines()[-1] avoids a potential warning message in the output. + build_complete = ( + utils.run_cmd_no_fail( + "./xmlquery BUILD_COMPLETE --value", from_dir=casedir + ) + .strip() + .splitlines()[-1] ) self.assertEqual( build_complete, @@ -342,8 +346,13 @@ def test_cime_case_xmlchange_append(self): self.run_cmd_assert_result( "./xmlchange --id PIO_CONFIG_OPTS --val='-opt1'", from_dir=casedir ) - result = self.run_cmd_assert_result( - "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir + # Avoids a potential warning in output about python version + result = ( + self.run_cmd_assert_result( + "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir + ) + .strip() + .splitlines()[-1] ) self.assertEqual(result, "-opt1") @@ -597,10 +606,16 @@ def test_cime_case_test_custom_project(self): env_changes="unset CIME_GLOBAL_WALLTIME &&", ) - result = self.run_cmd_assert_result( - "./xmlquery --non-local --value PROJECT --subgroup=case.test", - from_dir=casedir, + # the strip().splitlines()[-1] avoids a potential warning message in the output. + result = ( + self.run_cmd_assert_result( + "./xmlquery --non-local --value PROJECT --subgroup=case.test", + from_dir=casedir, + ) + .strip() + .splitlines()[-1] ) + self.assertEqual(result, "testproj") def test_create_test_longname(self): diff --git a/CIME/tests/test_sys_create_newcase.py b/CIME/tests/test_sys_create_newcase.py index 1be636aff36..5f3a9e6b582 100644 --- a/CIME/tests/test_sys_create_newcase.py +++ b/CIME/tests/test_sys_create_newcase.py @@ -314,7 +314,10 @@ def test_e_xmlquery(self): COMP_CLASSES = case.get_values("COMP_CLASSES") BUILD_COMPLETE = case.get_value("BUILD_COMPLETE") cmd = xmlquery + " --non-local STOP_N --value" - output = utils.run_cmd_no_fail(cmd, from_dir=casedir) + # avoid a potential warning in output by only looking at the last line + output = ( + utils.run_cmd_no_fail(cmd, from_dir=casedir).strip().splitlines()[-1] + ) self.assertTrue(output == str(STOP_N), msg="%s != %s" % (output, STOP_N)) cmd = xmlquery + " --non-local BUILD_COMPLETE --value" output = utils.run_cmd_no_fail(cmd, from_dir=casedir) From cbc691ea7e1edffe3b185833946cdf8777aedd76 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 12 Jan 2026 09:30:23 -0700 Subject: [PATCH 043/153] fix more tests --- CIME/tests/test_sys_cime_case.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/CIME/tests/test_sys_cime_case.py b/CIME/tests/test_sys_cime_case.py index aad61fd1941..7e2fb35d118 100644 --- a/CIME/tests/test_sys_cime_case.py +++ b/CIME/tests/test_sys_cime_case.py @@ -359,8 +359,13 @@ def test_cime_case_xmlchange_append(self): self.run_cmd_assert_result( "./xmlchange --id PIO_CONFIG_OPTS --val='-opt2' --append", from_dir=casedir ) - result = self.run_cmd_assert_result( - "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir + # the strip().splitlines()[-1] avoids a potential warning message in the output. + result = ( + self.run_cmd_assert_result( + "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir + ) + .strip() + .splitlines()[-1] ) self.assertEqual(result, "-opt1 -opt2") @@ -514,10 +519,14 @@ def test_cime_case_test_walltime_mgmt_6(self): ) self.run_cmd_assert_result("./case.setup --reset", from_dir=casedir) - - result = self.run_cmd_assert_result( - "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", - from_dir=casedir, + # the strip().splitlines()[-1] avoids a potential warning message in the output. + result = ( + self.run_cmd_assert_result( + "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", + from_dir=casedir, + ) + .strip() + .splitlines()[-1] ) with Case(casedir) as case: walltime_format = case.get_value("walltime_format", subgroup=None) @@ -544,9 +553,14 @@ def test_cime_case_test_walltime_mgmt_7(self): self.run_cmd_assert_result("./case.setup --reset", from_dir=casedir) - result = self.run_cmd_assert_result( - "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", - from_dir=casedir, + # the strip().splitlines()[-1] avoids a potential warning message in the output. + result = ( + self.run_cmd_assert_result( + "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", + from_dir=casedir, + ) + .strip() + .splitlines()[-1] ) with Case(casedir) as case: walltime_format = case.get_value("walltime_format", subgroup=None) From 71dbce358bef8d2d7d8295692fec3a389a6da31e Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 12 Jan 2026 10:04:32 -0700 Subject: [PATCH 044/153] fix more tests --- CIME/tests/test_sys_create_newcase.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CIME/tests/test_sys_create_newcase.py b/CIME/tests/test_sys_create_newcase.py index 5f3a9e6b582..746512422d8 100644 --- a/CIME/tests/test_sys_create_newcase.py +++ b/CIME/tests/test_sys_create_newcase.py @@ -339,19 +339,35 @@ def test_e_xmlquery(self): for comp in COMP_CLASSES: caseresult = case.get_value("NTASKS_%s" % comp) cmd = xmlquery + " --non-local NTASKS_%s --value" % comp - output = utils.run_cmd_no_fail(cmd, from_dir=casedir) + # avoid a potential warning in output by only looking at the last line + output = ( + utils.run_cmd_no_fail(cmd, from_dir=casedir) + .strip() + .splitlines()[-1] + ) + self.assertTrue( output == str(caseresult), msg="%s != %s" % (output, caseresult) ) cmd = xmlquery + " --non-local NTASKS --subgroup %s --value" % comp - output = utils.run_cmd_no_fail(cmd, from_dir=casedir) + # avoid a potential warning in output by only looking at the last line + output = ( + utils.run_cmd_no_fail(cmd, from_dir=casedir) + .strip() + .splitlines()[-1] + ) self.assertTrue( output == str(caseresult), msg="%s != %s" % (output, caseresult) ) if self.MACHINE.has_batch_system(): JOB_QUEUE = case.get_value("JOB_QUEUE", subgroup="case.run") cmd = xmlquery + " --non-local JOB_QUEUE --subgroup case.run --value" - output = utils.run_cmd_no_fail(cmd, from_dir=casedir) + # avoid a potential warning in output by only looking at the last line + output = ( + utils.run_cmd_no_fail(cmd, from_dir=casedir) + .strip() + .splitlines()[-1] + ) self.assertTrue( output == JOB_QUEUE, msg="%s != %s" % (output, JOB_QUEUE) ) From b5e728906f5b7c48ca2cd1cf12996ec545e22526 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 12:36:17 -0700 Subject: [PATCH 045/153] fix issue with completion of multi-instance cases --- CIME/case/case_run.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 7188c0fb959..e662d02d2a2 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -304,7 +304,7 @@ def _post_run_check(case, lid): rundir = case.get_value("RUNDIR") driver = case.get_value("COMP_INTERFACE") - + comp_standalone = False if driver == "nuopc": comp_standalone, file_prefix = is_comp_standalone(case) if not comp_standalone: @@ -333,11 +333,12 @@ def _post_run_check(case, lid): cpl_logs.append(os.path.join(rundir, "med.log." + lid)) cpl_logfile = cpl_logs[0] # find the last model.log and cpl.log - model_logfile = os.path.join(rundir, file_prefix + ".log." + lid) - if not os.path.isfile(model_logfile): - expect(False, "Model did not complete, no {} log file ".format(model_logfile)) - elif os.stat(model_logfile).st_size == 0: - expect(False, "Run FAILED") + if comp_standalone: + model_logfile = os.path.join(rundir, file_prefix + ".log." + lid) + if not os.path.isfile(model_logfile): + expect(False, "Model did not complete, no {} log file ".format(model_logfile)) + elif os.stat(model_logfile).st_size == 0: + expect(False, "Run FAILED") else: count_ok = 0 for cpl_logfile in cpl_logs: From ba1c6c214188140fdf391d25bc4586469822b2fd Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 12:38:13 -0700 Subject: [PATCH 046/153] pelayout should work with or without xstride variable --- CIME/Tools/pelayout | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CIME/Tools/pelayout b/CIME/Tools/pelayout index 494db7254f7..2e417ae27ad 100755 --- a/CIME/Tools/pelayout +++ b/CIME/Tools/pelayout @@ -77,7 +77,7 @@ def parse_command_line(args, description): parser.add_argument( "--header", - default="Comp NTASKS NTHRDS ROOTPE PSTRIDE XSTRIDE", + default="Comp NTASKS NTHRDS ROOTPE PSTRIDE", help="Custom header for PE layout display", ) @@ -136,7 +136,11 @@ def format_pelayout(comp, ntasks, nthreads, rootpe, pstride, xstride, arg_format layout_str = re.sub(r"%([-+0-9]*)H", r"{H:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)R", r"{R:\1}", layout_str) layout_str = re.sub(r"%([-+0-9]*)P", r"{P:\1}", layout_str) - layout_str = re.sub(r"%([-+0-9]*)X", r"{X:\1}", layout_str) + if xstride: + layout_str = re.sub(r"%([-+0-9]*)X", r"{X:\1}", layout_str) + else: + layout_str = re.sub(r"%([-+0-9]*)X", r"", layout_str) + layout_str = layout_str.format(**subs) return layout_str @@ -205,7 +209,11 @@ def gather_pelayout(case): nthreads[comp] = int(case.get_value("NTHRDS_" + comp)) rootpes[comp] = int(case.get_value("ROOTPE_" + comp)) pstride[comp] = int(case.get_value("PSTRID_" + comp)) - xstride[comp] = int(case.get_value("EXCL_STRIDE_" + comp)) + excl_stride = case.get_value("EXCL_STRIDE_" + comp) + if excl_stride: + xstride[comp] = int(case.get_value("EXCL_STRIDE_" + comp)) + else: + xstride[comp] = None # End for return ntasks, nthreads, rootpes, pstride, xstride From 09c604b2c46df4d201e9c5d9ad513890e918d4cf Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 12:47:40 -0700 Subject: [PATCH 047/153] black format file --- CIME/case/case_run.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index e662d02d2a2..b9912589e5f 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -1,6 +1,7 @@ """ case_run is a member of Class Case '""" + from CIME.XML.standard_module_setup import * from CIME.config import Config from CIME.utils import gzip_existing_file, new_lid @@ -336,7 +337,9 @@ def _post_run_check(case, lid): if comp_standalone: model_logfile = os.path.join(rundir, file_prefix + ".log." + lid) if not os.path.isfile(model_logfile): - expect(False, "Model did not complete, no {} log file ".format(model_logfile)) + expect( + False, "Model did not complete, no {} log file ".format(model_logfile) + ) elif os.stat(model_logfile).st_size == 0: expect(False, "Run FAILED") else: From 3d14631d7767187f47e3fb28f247af6a680978a3 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 13:11:48 -0700 Subject: [PATCH 048/153] further refinements to print format --- CIME/Tools/pelayout | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CIME/Tools/pelayout b/CIME/Tools/pelayout index 2e417ae27ad..d4c3575359b 100755 --- a/CIME/Tools/pelayout +++ b/CIME/Tools/pelayout @@ -158,6 +158,8 @@ def print_pelayout(case, ntasks, nthreads, rootpes, pstrid, xstrid, arg_format, comp_classes = case.get_values("COMP_CLASSES") if header is not None: + if any(v is not None for v in xstrid.values()): + header += " XSTRIDE" print(header) # End if maxthrds = -1 @@ -214,6 +216,7 @@ def gather_pelayout(case): xstride[comp] = int(case.get_value("EXCL_STRIDE_" + comp)) else: xstride[comp] = None + # End for return ntasks, nthreads, rootpes, pstride, xstride From e3471761d13e342e5437cf28456d9129b8042d04 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 14:25:48 -0700 Subject: [PATCH 049/153] add the CASEROOT file to the archive log directory --- CIME/case/case_st_archive.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 825ae95a676..2fa3440ab42 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -275,6 +275,7 @@ def _archive_log_files(dout_s_root, rundir, archive_incomplete, archive_file_fn) log_search = "*.log.*" logfiles = glob.glob(os.path.join(rundir, log_search)) + logfiles.append(os.path.join(rundir, "CASEROOT")) for logfile in logfiles: srcfile = join(rundir, os.path.basename(logfile)) destfile = join(archive_logdir, os.path.basename(logfile)) From 29341429782dd84e9599bc00406905b7e436f1cd Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 13 Jan 2026 16:14:17 -0700 Subject: [PATCH 050/153] keep it simple --- CIME/case/case_st_archive.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 2fa3440ab42..5203432bb22 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -275,7 +275,6 @@ def _archive_log_files(dout_s_root, rundir, archive_incomplete, archive_file_fn) log_search = "*.log.*" logfiles = glob.glob(os.path.join(rundir, log_search)) - logfiles.append(os.path.join(rundir, "CASEROOT")) for logfile in logfiles: srcfile = join(rundir, os.path.basename(logfile)) destfile = join(archive_logdir, os.path.basename(logfile)) @@ -285,6 +284,8 @@ def _archive_log_files(dout_s_root, rundir, archive_incomplete, archive_file_fn) ) ) archive_file_fn(srcfile, destfile) + # Finally copy the CASEROOT file into the archive directory + safe_copy(os.path.join(rundir, "CASEROOT"), archive_logdir) ############################################################################### From 9c46b362f16757cc4265020a5bf4ba72d3bffd18 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 14 Jan 2026 11:02:02 -0700 Subject: [PATCH 051/153] need to add CASEROOT to st_archive_test directory --- CIME/case/case_st_archive.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 5203432bb22..30784172bd1 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -1183,7 +1183,9 @@ def test_st_archive(self, testdir="st_archive_test"): ) with open(fname, "w") as fd: fd.write(disposition + "\n") - + caseroot = self.get_value("CASEROOT") + with open(os.path.join(testdir, "CASEROOT"), "w") as f: + f.write(caseroot) logger.info("testing components: {} ".format(list(set(components)))) _archive_process( self, From ce1b7ade03f56fada960d02bb2bf066ae7601094 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 14 Jan 2026 11:42:28 -0700 Subject: [PATCH 052/153] ignore CASEROOT in testing --- CIME/case/case_st_archive.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 30784172bd1..fd27753fd26 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -1335,6 +1335,8 @@ def _check_disposition(testdir): copyfilelist = [] for root, _, files in os.walk(testdir): for _file in files: + if "CASEROOT" in _file: + continue with open(os.path.join(root, _file), "r") as fd: disposition = fd.readline() logger.info( From 146a0ac8e9386a6c8129ae6cc5ec16ca00cfefb2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 14 Jan 2026 14:57:23 -0700 Subject: [PATCH 053/153] only copy if exists and needed --- CIME/case/case_st_archive.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index fd27753fd26..631196e5dfc 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -285,7 +285,10 @@ def _archive_log_files(dout_s_root, rundir, archive_incomplete, archive_file_fn) ) archive_file_fn(srcfile, destfile) # Finally copy the CASEROOT file into the archive directory - safe_copy(os.path.join(rundir, "CASEROOT"), archive_logdir) + caseroot = os.path.join(rundir, "CASEROOT") + logdir_caseroot = os.path.join(archive_logdir, "CASEROOT") + if os.path.exists(caseroot) and not os.path.exists(logdir_caseroot): + safe_copy(os.path.join(rundir, "CASEROOT"), archive_logdir) ############################################################################### From f4ecd1d39d869598fb871f67d38353a6810d8047 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 16 Jan 2026 14:28:04 -0700 Subject: [PATCH 054/153] Use different suffixes for the two ERI comparisons Prior to this change, ERI cprnc.out files from the base-hybrid comparison are clobbered by the cprnc.out files from the base-rest comparison. This commit fixes this issue. Resolves ESMCI/cime#4912 --- CIME/SystemTests/eri.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CIME/SystemTests/eri.py b/CIME/SystemTests/eri.py index fdd02455987..5c70637d208 100644 --- a/CIME/SystemTests/eri.py +++ b/CIME/SystemTests/eri.py @@ -302,5 +302,14 @@ def run_phase(self): # do the restart run (short term archiving is off) self.run_indv(suffix="rest") - self._component_compare_test("base", "hybrid") + # Note that, for both of these comparisons, the "control" case comes first: the + # "base" case is the branch run, which is compared against the "hybrid" run (which + # it branched off of, and so serves as its "control"); the "rest" run is a restart + # from this "base" case and so is compared against this "base" case. The order of + # the two suffixes in each call isn't very important, but it *is* somewhat + # important that the first suffix differs between these two comparisons: + # otherwise, the cprnc output files from the second comparison overwrite the files + # from the first comparison (since the cprnc output file names are derived from + # the first suffix). + self._component_compare_test("hybrid", "base") self._component_compare_test("base", "rest") From 12a38af4a746af042f3742647d20e3d54218c934 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 16 Jan 2026 15:24:02 -0700 Subject: [PATCH 055/153] ERI test: give the branch case a "branch" suffix Previously it had a "base" suffix, which was vague for this test. --- CIME/SystemTests/eri.py | 21 ++++++++++----------- doc/source/system_testing.rst | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/CIME/SystemTests/eri.py b/CIME/SystemTests/eri.py index 5c70637d208..bf24eaaeb2a 100644 --- a/CIME/SystemTests/eri.py +++ b/CIME/SystemTests/eri.py @@ -272,7 +272,7 @@ def run_phase(self): self._skip_pnl = False # run branch case (short term archiving is off) - self.run_indv() + self.run_indv(suffix="branch") # # (3b) Test run: @@ -303,13 +303,12 @@ def run_phase(self): self.run_indv(suffix="rest") # Note that, for both of these comparisons, the "control" case comes first: the - # "base" case is the branch run, which is compared against the "hybrid" run (which - # it branched off of, and so serves as its "control"); the "rest" run is a restart - # from this "base" case and so is compared against this "base" case. The order of - # the two suffixes in each call isn't very important, but it *is* somewhat - # important that the first suffix differs between these two comparisons: - # otherwise, the cprnc output files from the second comparison overwrite the files - # from the first comparison (since the cprnc output file names are derived from - # the first suffix). - self._component_compare_test("hybrid", "base") - self._component_compare_test("base", "rest") + # branch case is compared against the hybrid case (which it branched off of, and + # so serves as its "control"); the "rest" run is a restart from the branch case + # and so is compared against this branch case. The order of the two suffixes in + # each call isn't very important, but it *is* somewhat important that the first + # suffix differs between these two comparisons: otherwise, the cprnc output files + # from the second comparison overwrite the files from the first comparison (since + # the cprnc output file names are derived from the first suffix). + self._component_compare_test("hybrid", "branch") + self._component_compare_test("branch", "rest") diff --git a/doc/source/system_testing.rst b/doc/source/system_testing.rst index 8b315405cb2..94804b2da47 100644 --- a/doc/source/system_testing.rst +++ b/doc/source/system_testing.rst @@ -239,14 +239,14 @@ TESTTYPE Description and writing restarts at day 10. ref2case is a clone of the main case. Short term archiving is on. - case + case (Suffix branch) Do a branch run, starting from restarts written in ref2case, for 9 days and writing restarts at day 5. Short term archiving is off. - case (Suffix base) + case - restart (Suffix rest) Do a restart run from the branch run restarts for 4 days. - Compare component history files '.base' and '.hybrid' at day 19. Short term archiving is off. + Comparisons are done between branch vs. hybrid and rest vs. branch ERP PES counts hybrid (OPENMP/MPI) restart bit-for-bit test from startup, (default 6 days + 5 days). Initial PES set up out of the box From 800e2d71d36002211dd33e472e32c5b2b7ff56eb Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 16 Jan 2026 15:31:08 -0700 Subject: [PATCH 056/153] Reorder suffixes in ERI comparisons This way the cprnc output iles will be named with the test case rather than the control case, which is more intuitive. --- CIME/SystemTests/eri.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/CIME/SystemTests/eri.py b/CIME/SystemTests/eri.py index bf24eaaeb2a..f0a237b6819 100644 --- a/CIME/SystemTests/eri.py +++ b/CIME/SystemTests/eri.py @@ -302,13 +302,17 @@ def run_phase(self): # do the restart run (short term archiving is off) self.run_indv(suffix="rest") - # Note that, for both of these comparisons, the "control" case comes first: the - # branch case is compared against the hybrid case (which it branched off of, and - # so serves as its "control"); the "rest" run is a restart from the branch case - # and so is compared against this branch case. The order of the two suffixes in - # each call isn't very important, but it *is* somewhat important that the first - # suffix differs between these two comparisons: otherwise, the cprnc output files - # from the second comparison overwrite the files from the first comparison (since - # the cprnc output file names are derived from the first suffix). - self._component_compare_test("hybrid", "branch") - self._component_compare_test("branch", "rest") + # Note that, for both of these comparisons, the "test" case comes first and the + # "control" case comes second: the branch case is compared against the hybrid case + # (which it branched off of, and so serves as its "control"); the "rest" run is a + # restart from the branch case and so is compared against this branch case. We + # make this choice because the cprnc output file names are derived from the first + # suffix, so: + # - Listing the "test" case as the first suffix means that the cprnc files are + # named with the name of the case we're testing in that cprnc comparison, which + # is more intuitive. + # - Having the first suffix differ between the two comparisons is important to + # avoid having the cprnc output files from the second comparison overwrite the + # files from the first comparison. + self._component_compare_test("branch", "hybrid") + self._component_compare_test("rest", "branch") From a096098add74a26c69c9dc28b5f1324ae14f081f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 11 Dec 2025 16:35:48 -0800 Subject: [PATCH 057/153] Adds unittests for _archive_rpointer_files --- CIME/tests/test_unit_case_st_archive.py | 313 ++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 CIME/tests/test_unit_case_st_archive.py diff --git a/CIME/tests/test_unit_case_st_archive.py b/CIME/tests/test_unit_case_st_archive.py new file mode 100644 index 00000000000..d5192bfef96 --- /dev/null +++ b/CIME/tests/test_unit_case_st_archive.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 + +import os +import io +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +from CIME import date +from CIME.case import case_st_archive +from CIME.XML import archive + +UNSET = r""" + + + unset + $CASE.cpl$NINST_STRING.r.$DATENAME.nc + + + +""" + +UNSET_CONTENT = r""" + + + rpointer.cpl$NINST_STRING.$DATENAME + unset + + + +""" + +SINGLE_NINST_DATE = r""" + + + rpointer.cpl$NINST_STRING.$DATENAME + $CASE.cpl$NINST_STRING.r.$DATENAME.nc + + + +""" + +def write_files(*files): + for file, content in files: + p = Path(file) + p.parent.mkdir(parents=True, exist_ok=True) + p.open('w').write(content or '') + + +class TestArchiveRpointerFiles(unittest.TestCase): + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_generate_rpointer(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_not_called() + + # should have created the file + generated_files = list(rest_dir.glob('*')) + assert generated_files == [ + rest_dir / 'rpointer.cpl.0001-01-01-00000' + ] + + with (rest_dir / 'rpointer.cpl.0001-01-01-00000').open('r') as f: + content = f.read() + + # check content + assert content == "case.cpl.r.0001-01-01-00000.nc \n" + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_generate_rpointer_unset_content(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(UNSET_CONTENT)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_not_called() + + # should have created the file + generated_files = list(rest_dir.glob('*')) + assert generated_files == [] + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_ninst_generate_rpointer(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + case_st_archive._archive_rpointer_files( + "case", + ["01", "02"], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_not_called() + + # should have created the file + generated_files = list(rest_dir.glob('*')) + assert sorted(generated_files) == sorted([ + rest_dir / 'rpointer.cpl01.0001-01-01-00000', + rest_dir / 'rpointer.cpl02.0001-01-01-00000' + ]) + + with (rest_dir / 'rpointer.cpl01.0001-01-01-00000').open('r') as f: + content = f.read() + + # check content + assert content == "case.cpl01.r.0001-01-01-00000.nc \n" + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_datename_is_last(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + write_files( + (run_dir / 'rpointer.cpl.0001-01-01-00000', ''), + ) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + True, + ) + + safe_copy.assert_any_call( + str(run_dir / 'rpointer.cpl.0001-01-01-00000'), + str(rest_dir / 'rpointer.cpl.0001-01-01-00000') + ) + + move.assert_not_called() + + + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_ninst(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + write_files( + (run_dir / 'rpointer.cpl.01.0001-01-01-00000', ''), + (run_dir / 'rpointer.cpl.02.0001-01-01-00000', ''), + ) + + case_st_archive._archive_rpointer_files( + "case", + ["01", "02"], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_any_call( + str(run_dir / 'rpointer.cpl.01.0001-01-01-00000'), + str(rest_dir / 'rpointer.cpl.01.0001-01-01-00000') + ) + move.assert_any_call( + str(run_dir / 'rpointer.cpl.02.0001-01-01-00000'), + str(rest_dir / 'rpointer.cpl.02.0001-01-01-00000') + ) + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_single_rpointer(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + write_files( + (run_dir / 'rpointer.cpl.0001-01-01-00000', ''), + ) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_called_once() + move.assert_any_call( + str(run_dir / 'rpointer.cpl.0001-01-01-00000'), + str(rest_dir / 'rpointer.cpl.0001-01-01-00000') + ) + + + @mock.patch('shutil.move') + @mock.patch('CIME.case.case_st_archive.safe_copy') + def test_unset(self, safe_copy, move): + env_archive = archive.Archive() + env_archive.read_fd(io.StringIO(UNSET)) + + archive_entry = env_archive.get_children('comp_archive_spec')[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, 'run') + rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + rest_dir.mkdir(parents=True) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_not_called() From 17f1b84ad8959ab2187a1a3b4a7247572e0df8bc Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Sat, 13 Dec 2025 12:14:08 -0800 Subject: [PATCH 058/153] Adds function to help mock an XML environment class --- CIME/tests/utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CIME/tests/utils.py b/CIME/tests/utils.py index 5541ab6dfac..db2d3385944 100644 --- a/CIME/tests/utils.py +++ b/CIME/tests/utils.py @@ -227,6 +227,28 @@ def wrapper(self, read_xml, *args, **kwargs): return outer +def mock_env(cls, content=None): + """Mocks an environment class. + + Args: + cls: XML environment class. + content: String with the XML to load. + """ + def decorator(f): + env = cls(read_only=False) + + if content is not None: + env.read_only = True + env.read_fd(io.StringIO(content)) + + def wrapper(self, *args, **kwargs): + return f(self, env, *args, **kwargs) + + return wrapper + + return decorator + + @contextlib.contextmanager def chdir(path): old_path = os.getcwd() From 7c6671db02810ecf79261a9f73a537b7b5d39d17 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Sat, 13 Dec 2025 12:14:51 -0800 Subject: [PATCH 059/153] Updates tests to use mock_env and updated EnvArchive methods --- CIME/XML/env_archive.py | 8 ++ CIME/tests/test_unit_case_st_archive.py | 172 +++++++++++------------- 2 files changed, 85 insertions(+), 95 deletions(-) diff --git a/CIME/XML/env_archive.py b/CIME/XML/env_archive.py index 3642cda40a8..1ae66fcfc0a 100644 --- a/CIME/XML/env_archive.py +++ b/CIME/XML/env_archive.py @@ -16,14 +16,22 @@ def __init__(self, case_root=None, infile="env_archive.xml", read_only=False): schema = os.path.join(utils.get_schema_path(), "env_archive.xsd") EnvBase.__init__(self, case_root, infile, schema=schema, read_only=read_only) + def get_archive_specs(self): + components_element = self.get_child("components") + + return self.get_children("comp_archive_spec", root=components_element) + + # TODO check if used otherwise remove def get_entries(self): return self.get_children("comp_archive_spec") + # TODO check if used otherwise remove def get_entry_info(self, archive_entry): compname = self.get(archive_entry, "compname") compclass = self.get(archive_entry, "compclass") return compname, compclass + # TODO check if used otherwise remove def get_rpointer_contents(self, archive_entry): rpointer_items = [] rpointer_nodes = self.get_children("rpointer", root=archive_entry) diff --git a/CIME/tests/test_unit_case_st_archive.py b/CIME/tests/test_unit_case_st_archive.py index d5192bfef96..de44b841103 100644 --- a/CIME/tests/test_unit_case_st_archive.py +++ b/CIME/tests/test_unit_case_st_archive.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import os -import io import tempfile import unittest from pathlib import Path @@ -9,7 +7,8 @@ from CIME import date from CIME.case import case_st_archive -from CIME.XML import archive +from CIME.XML import env_archive as _env_archive +from CIME.tests import utils UNSET = r""" @@ -41,26 +40,24 @@ """ + def write_files(*files): for file, content in files: p = Path(file) p.parent.mkdir(parents=True, exist_ok=True) - p.open('w').write(content or '') + p.open("w").write(content or "") class TestArchiveRpointerFiles(unittest.TestCase): - - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_generate_rpointer(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) + def test_generate_rpointer(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) case_st_archive._archive_rpointer_files( @@ -80,28 +77,24 @@ def test_generate_rpointer(self, safe_copy, move): move.assert_not_called() # should have created the file - generated_files = list(rest_dir.glob('*')) - assert generated_files == [ - rest_dir / 'rpointer.cpl.0001-01-01-00000' - ] + generated_files = list(rest_dir.glob("*")) + assert generated_files == [rest_dir / "rpointer.cpl.0001-01-01-00000"] - with (rest_dir / 'rpointer.cpl.0001-01-01-00000').open('r') as f: + with (rest_dir / "rpointer.cpl.0001-01-01-00000").open("r") as f: content = f.read() # check content assert content == "case.cpl.r.0001-01-01-00000.nc \n" - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_generate_rpointer_unset_content(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(UNSET_CONTENT)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, UNSET_CONTENT) + def test_generate_rpointer_unset_content(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) case_st_archive._archive_rpointer_files( @@ -121,20 +114,18 @@ def test_generate_rpointer_unset_content(self, safe_copy, move): move.assert_not_called() # should have created the file - generated_files = list(rest_dir.glob('*')) + generated_files = list(rest_dir.glob("*")) assert generated_files == [] - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_ninst_generate_rpointer(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) + def test_ninst_generate_rpointer(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) case_st_archive._archive_rpointer_files( @@ -154,33 +145,33 @@ def test_ninst_generate_rpointer(self, safe_copy, move): move.assert_not_called() # should have created the file - generated_files = list(rest_dir.glob('*')) - assert sorted(generated_files) == sorted([ - rest_dir / 'rpointer.cpl01.0001-01-01-00000', - rest_dir / 'rpointer.cpl02.0001-01-01-00000' - ]) + generated_files = list(rest_dir.glob("*")) + assert sorted(generated_files) == sorted( + [ + rest_dir / "rpointer.cpl01.0001-01-01-00000", + rest_dir / "rpointer.cpl02.0001-01-01-00000", + ] + ) - with (rest_dir / 'rpointer.cpl01.0001-01-01-00000').open('r') as f: + with (rest_dir / "rpointer.cpl01.0001-01-01-00000").open("r") as f: content = f.read() # check content assert content == "case.cpl01.r.0001-01-01-00000.nc \n" - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_datename_is_last(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) + def test_datename_is_last(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) write_files( - (run_dir / 'rpointer.cpl.0001-01-01-00000', ''), + (run_dir / "rpointer.cpl.0001-01-01-00000", ""), ) case_st_archive._archive_rpointer_files( @@ -196,30 +187,26 @@ def test_datename_is_last(self, safe_copy, move): ) safe_copy.assert_any_call( - str(run_dir / 'rpointer.cpl.0001-01-01-00000'), - str(rest_dir / 'rpointer.cpl.0001-01-01-00000') + str(run_dir / "rpointer.cpl.0001-01-01-00000"), + str(rest_dir / "rpointer.cpl.0001-01-01-00000"), ) move.assert_not_called() - - - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_ninst(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) + def test_ninst(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) write_files( - (run_dir / 'rpointer.cpl.01.0001-01-01-00000', ''), - (run_dir / 'rpointer.cpl.02.0001-01-01-00000', ''), + (run_dir / "rpointer.cpl.01.0001-01-01-00000", ""), + (run_dir / "rpointer.cpl.02.0001-01-01-00000", ""), ) case_st_archive._archive_rpointer_files( @@ -237,29 +224,27 @@ def test_ninst(self, safe_copy, move): safe_copy.assert_not_called() move.assert_any_call( - str(run_dir / 'rpointer.cpl.01.0001-01-01-00000'), - str(rest_dir / 'rpointer.cpl.01.0001-01-01-00000') + str(run_dir / "rpointer.cpl.01.0001-01-01-00000"), + str(rest_dir / "rpointer.cpl.01.0001-01-01-00000"), ) move.assert_any_call( - str(run_dir / 'rpointer.cpl.02.0001-01-01-00000'), - str(rest_dir / 'rpointer.cpl.02.0001-01-01-00000') + str(run_dir / "rpointer.cpl.02.0001-01-01-00000"), + str(rest_dir / "rpointer.cpl.02.0001-01-01-00000"), ) - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_single_rpointer(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(SINGLE_NINST_DATE)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) + def test_single_rpointer(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) write_files( - (run_dir / 'rpointer.cpl.0001-01-01-00000', ''), + (run_dir / "rpointer.cpl.0001-01-01-00000", ""), ) case_st_archive._archive_rpointer_files( @@ -278,22 +263,19 @@ def test_single_rpointer(self, safe_copy, move): move.assert_called_once() move.assert_any_call( - str(run_dir / 'rpointer.cpl.0001-01-01-00000'), - str(rest_dir / 'rpointer.cpl.0001-01-01-00000') + str(run_dir / "rpointer.cpl.0001-01-01-00000"), + str(rest_dir / "rpointer.cpl.0001-01-01-00000"), ) - - @mock.patch('shutil.move') - @mock.patch('CIME.case.case_st_archive.safe_copy') - def test_unset(self, safe_copy, move): - env_archive = archive.Archive() - env_archive.read_fd(io.StringIO(UNSET)) - - archive_entry = env_archive.get_children('comp_archive_spec')[0] + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, UNSET) + def test_unset(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] with tempfile.TemporaryDirectory() as tempdir: - run_dir = Path(tempdir, 'run') - rest_dir = Path(tempdir, 'archive', 'rest', '0001-01-01') + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") rest_dir.mkdir(parents=True) case_st_archive._archive_rpointer_files( From a212904f3758e484585f5b222841598ece85e10f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 17 Dec 2025 16:06:42 -0800 Subject: [PATCH 060/153] Refactors _archive_rpointer_files --- CIME/XML/env_archive.py | 13 ++ CIME/case/case_st_archive.py | 155 +++++++++++++----------- CIME/tests/test_unit_case_st_archive.py | 47 +++++++ CIME/tests/utils.py | 1 + 4 files changed, 146 insertions(+), 70 deletions(-) diff --git a/CIME/XML/env_archive.py b/CIME/XML/env_archive.py index 1ae66fcfc0a..e9f412e9510 100644 --- a/CIME/XML/env_archive.py +++ b/CIME/XML/env_archive.py @@ -21,6 +21,19 @@ def get_archive_specs(self): return self.get_children("comp_archive_spec", root=components_element) + def get_rpointer_nodes(self, root): + assert root.name == "comp_archive_spec" + + return self.get_children("rpointer", root=root) + + def get_rpointers(self, root): + for node in self.get_rpointer_nodes(root): + file = self.get_child("rpointer_file", root=node).text + + content = self.get_child("rpointer_content", root=node).text + + yield file, content + # TODO check if used otherwise remove def get_entries(self): return self.get_children("comp_archive_spec") diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 631196e5dfc..96865757972 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -155,7 +155,6 @@ def _get_component_archive_entries(components, archive): yield (archive_entry, compname, compclass) -############################################################################### def _archive_rpointer_files( casename, ninst_strings, @@ -167,93 +166,109 @@ def _archive_rpointer_files( datename, datename_is_last, ): - ############################################################################### - + """Archive rpointer files. + + For a given restart point, an rpointer file is archived. If the rpointer file + for this point exists it will be copied/moved to the archive directory otherwise + it will be created using the `rpointer_file` and `rpointer_content` elements. + + The following variables can be used in `rpointer_file` and `rpointer_content` + and will be substitued with the appropriate values. + + - $CASE: The name of the case. + - $DATENAME: The date of the restart file. + - $MPAS_DATENAME: The date of the restart file in an MPAS specific format. + - $NINST_STRING: The identifier if running multiple instances. + + Args: + casename (str): Name of the case. + ninst_strings (list): List of ninst identifiers. + rundir (str): Path to the run directory. + save_interim_restart_files (bool): Whether to save the intermediate restart files. + archive (CIME.XML.env_archive.EnvArchive): Environment archive object. + archive_entry (CIME.XML.generic_xml.Element): Rpointer XML node. + archive_restdir (str): Path to the directory to write rpointer files. + datename (CIME.date.date): Date object with the rpointer date. + datename_is_last (bool): Whether this rpointer is the latest. + + Raises: + CIMEError: If `rpointer_file` template cannot be resolved. + """ # parse env_archive.xml to determine the rpointer files # and contents for the given archive_entry tag # loop through the possible rpointer files and contents - rpointer_nodes = archive.get_children("rpointer", root=archive_entry) - for rpointer in rpointer_nodes: - file_node = archive.get_child("rpointer_file", root=rpointer) - temp_rpointer_file = archive.text(file_node) - content_node = archive.get_child("rpointer_content", root=rpointer) - temp_rpointer_content = archive.text(content_node) - rpointer_file = temp_rpointer_file.replace("$NINST_STRING", "*") + for rpointer_file, rpointer_content in archive.get_rpointers(archive_entry): if rpointer_file == "unset": continue - if "$DATENAME" in rpointer_file: - rpointer_file = rpointer_file.replace("$DATENAME", _datetime_str(datename)) + + replacements = { + "$CASE": casename, + "$DATENAME": _datetime_str(datename), + "$MPAS_DATENAME": _datetime_str_mpas(datename), + } + + for key, value in replacements.items(): + rpointer_file = rpointer_file.replace(key, value) + rpointer_content = rpointer_content.replace(key, value) + + rpointer_file_glob = rpointer_file.replace("$NINST_STRING", "*") expect( - not "$" in rpointer_file, - "Unrecognized expression in name {}".format(rpointer_file), + not "$" in rpointer_file_glob, + "Unrecognized expression in name {}".format(rpointer_file_glob), ) - rpointers = glob.glob(rundir + "/" + rpointer_file) + rpointers = glob.glob(rundir + "/" + rpointer_file_glob) if datename_is_last: for rpfile in rpointers: safe_copy( rpfile, os.path.join(archive_restdir, os.path.basename(rpfile)) ) - else: + elif save_interim_restart_files: # Generate rpointer file(s) for interim restarts for the one datename and each # possible value of ninst_strings - if save_interim_restart_files: - # If timestamped rpointers exist use them - if rpointers: - for rpfile in rpointers: - logger.info("moving interim rpointer_file {}".format(rpfile)) - shutil.move( - rpfile, - os.path.join(archive_restdir, os.path.basename(rpfile)), - ) - else: - # put in a temporary setting for ninst_strings if they are empty - # in order to have just one loop over ninst_strings below - if ninst_strings: - rpointer_content = temp_rpointer_content.replace( - "$NINST_STRING", ninst_strings[0] - ) - else: - rpointer_content = temp_rpointer_content.replace( - "$NINST_STRING", "" - ) - rpointer_content = rpointer_content.replace( - "$DATENAME", _datetime_str(datename) + # If timestamped rpointers exist use them + if rpointers: + for rpfile in rpointers: + logger.info("moving interim rpointer_file {}".format(rpfile)) + shutil.move( + rpfile, + os.path.join(archive_restdir, os.path.basename(rpfile)), ) - if rpointer_content != "unset": - if not ninst_strings: - ninst_strings = ["empty"] - - for ninst_string in ninst_strings: - rpointer_file = temp_rpointer_file - rpointer_content = temp_rpointer_content - if ninst_string == "empty": - ninst_string = "" - for key, value in [ - ("$CASE", casename), - ("$DATENAME", _datetime_str(datename)), - ("$MPAS_DATENAME", _datetime_str_mpas(datename)), - ("$NINST_STRING", ninst_string), - ]: - rpointer_file = rpointer_file.replace(key, value) - rpointer_content = rpointer_content.replace(key, value) - - # write out the respective files with the correct contents - rpointer_file = os.path.join(archive_restdir, rpointer_file) - logger.info( - "writing rpointer_file {}".format(rpointer_file) - ) - f = open(rpointer_file, "w") - for output in rpointer_content.split(","): - f.write("{} \n".format(output)) - f.close() - else: - logger.info( - "rpointer_content unset, not creating rpointer file {}".format( - rpointer_file - ) + else: + if rpointer_content == "unset": + logger.info( + "rpointer_content unset, not creating rpointer file {}".format( + rpointer_file ) + ) + + return + + # need to loop atleast once + if not ninst_strings: + ninst_strings = [ + "", + ] + + for ninst_string in ninst_strings: + ninst_rpointer_file = rpointer_file.replace( + "$NINST_STRING", ninst_string + ) + ninst_rpointer_content = rpointer_content.replace( + "$NINST_STRING", ninst_string + ) + + # write out the respective files with the correct contents + ninst_rpointer_path = os.path.join( + archive_restdir, ninst_rpointer_file + ) + + logger.info("writing rpointer_file {}".format(ninst_rpointer_path)) + + with open(ninst_rpointer_path, "w") as f: + for output in ninst_rpointer_content.split(","): + f.write("{} \n".format(output)) ############################################################################### diff --git a/CIME/tests/test_unit_case_st_archive.py b/CIME/tests/test_unit_case_st_archive.py index de44b841103..b8c7ee5c95c 100644 --- a/CIME/tests/test_unit_case_st_archive.py +++ b/CIME/tests/test_unit_case_st_archive.py @@ -40,6 +40,16 @@ """ +SINGLE_NINST_MPAS_DATE = r""" + + + rpointer.cpl$NINST_STRING.$DATENAME + $CASE.cpl$NINST_STRING.r.$MPAS_DATENAME.nc + + + +""" + def write_files(*files): for file, content in files: @@ -49,6 +59,43 @@ def write_files(*files): class TestArchiveRpointerFiles(unittest.TestCase): + @mock.patch("shutil.move") + @mock.patch("CIME.case.case_st_archive.safe_copy") + @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_MPAS_DATE) + def test_generate_rpointer_mpas(self, env_archive, safe_copy, move): + archive_entry = env_archive.get_archive_specs()[0] + + with tempfile.TemporaryDirectory() as tempdir: + run_dir = Path(tempdir, "run") + rest_dir = Path(tempdir, "archive", "rest", "0001-01-01") + rest_dir.mkdir(parents=True) + + case_st_archive._archive_rpointer_files( + "case", + [], + str(run_dir), + True, + env_archive, + archive_entry, + str(rest_dir), + date.date(1, 1, 1), + False, + ) + + safe_copy.assert_not_called() + + move.assert_not_called() + + # should have created the file + generated_files = list(rest_dir.glob("*")) + assert generated_files == [rest_dir / "rpointer.cpl.0001-01-01-00000"] + + with (rest_dir / "rpointer.cpl.0001-01-01-00000").open("r") as f: + content = f.read() + + # check content + assert content == "case.cpl.r.0001-01-01_00:00:00.nc \n" + @mock.patch("shutil.move") @mock.patch("CIME.case.case_st_archive.safe_copy") @utils.mock_env(_env_archive.EnvArchive, SINGLE_NINST_DATE) diff --git a/CIME/tests/utils.py b/CIME/tests/utils.py index db2d3385944..5987f0fd18e 100644 --- a/CIME/tests/utils.py +++ b/CIME/tests/utils.py @@ -234,6 +234,7 @@ def mock_env(cls, content=None): cls: XML environment class. content: String with the XML to load. """ + def decorator(f): env = cls(read_only=False) From 7de43bc3e7b75cfe008ee4a05887597c851f1e07 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Sat, 20 Dec 2025 02:38:42 -0800 Subject: [PATCH 061/153] Fixes latest rpointer being removed from run directory --- CIME/case/case_st_archive.py | 3 ++- CIME/tests/test_unit_case_st_archive.py | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 96865757972..932987d1a9a 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -228,7 +228,8 @@ def _archive_rpointer_files( # possible value of ninst_strings # If timestamped rpointers exist use them - if rpointers: + # If only one rpointer file, leave in run directorya and create in archive + if rpointers and len(rpointers) > 1: for rpfile in rpointers: logger.info("moving interim rpointer_file {}".format(rpfile)) shutil.move( diff --git a/CIME/tests/test_unit_case_st_archive.py b/CIME/tests/test_unit_case_st_archive.py index b8c7ee5c95c..bf27ab1c201 100644 --- a/CIME/tests/test_unit_case_st_archive.py +++ b/CIME/tests/test_unit_case_st_archive.py @@ -308,11 +308,17 @@ def test_single_rpointer(self, env_archive, safe_copy, move): safe_copy.assert_not_called() - move.assert_called_once() - move.assert_any_call( - str(run_dir / "rpointer.cpl.0001-01-01-00000"), - str(rest_dir / "rpointer.cpl.0001-01-01-00000"), - ) + move.assert_not_called() + + # should have created the file + generated_files = list(rest_dir.glob("*")) + assert generated_files == [rest_dir / "rpointer.cpl.0001-01-01-00000"] + + with (rest_dir / "rpointer.cpl.0001-01-01-00000").open("r") as f: + content = f.read() + + # check content + assert content == "case.cpl.r.0001-01-01-00000.nc \n" @mock.patch("shutil.move") @mock.patch("CIME.case.case_st_archive.safe_copy") From d18959b7f8ad3bee349b69476daf6d48bc415ebf Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 26 Jan 2026 13:48:06 -0700 Subject: [PATCH 062/153] Add baseline synopsis to teststatus.log This is a useful thing users can look at for a quick summary. --- CIME/SystemTests/system_tests_common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index 2366aaeae8a..db270db8dce 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -1025,6 +1025,10 @@ def _compare_baseline(self): ts_comments = ( os.path.dirname(baseline_name) + ": " + get_ts_synopsis(comments) ) + log_comments = "============ BASELINE COMPARE SYNOPSIS =============" + log_comments += ts_comments + log_comments += "====================================================" + append_testlog(log_comments, self._orig_caseroot) self._test_status.set_status(BASELINE_PHASE, status, comments=ts_comments) def _generate_baseline(self): From 834b63b0a0b2abd4e337606ad270d7e24b7045d9 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 26 Jan 2026 12:55:20 -0800 Subject: [PATCH 063/153] black format --- CIME/SystemTests/system_tests_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index db270db8dce..36ebd09b7ed 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -1025,7 +1025,7 @@ def _compare_baseline(self): ts_comments = ( os.path.dirname(baseline_name) + ": " + get_ts_synopsis(comments) ) - log_comments = "============ BASELINE COMPARE SYNOPSIS =============" + log_comments = "============ BASELINE COMPARE SYNOPSIS =============" log_comments += ts_comments log_comments += "====================================================" append_testlog(log_comments, self._orig_caseroot) From 266badbc24fb4e8ccef96598973a1d783cdbbc3f Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 26 Jan 2026 14:05:05 -0700 Subject: [PATCH 064/153] Add newlines --- CIME/SystemTests/system_tests_common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index 36ebd09b7ed..7676b910c36 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -1025,9 +1025,9 @@ def _compare_baseline(self): ts_comments = ( os.path.dirname(baseline_name) + ": " + get_ts_synopsis(comments) ) - log_comments = "============ BASELINE COMPARE SYNOPSIS =============" - log_comments += ts_comments - log_comments += "====================================================" + log_comments = "\n\n============ BASELINE COMPARE SYNOPSIS =============\n" + log_comments += ts_comments + "\n" + log_comments += "====================================================\n" append_testlog(log_comments, self._orig_caseroot) self._test_status.set_status(BASELINE_PHASE, status, comments=ts_comments) From 635ea76b6a76cd298bfa7549829849335af4a8ff Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 26 Jan 2026 15:03:06 -0800 Subject: [PATCH 065/153] Removes unused functions --- CIME/XML/env_archive.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/CIME/XML/env_archive.py b/CIME/XML/env_archive.py index e9f412e9510..6ee7ef37075 100644 --- a/CIME/XML/env_archive.py +++ b/CIME/XML/env_archive.py @@ -34,25 +34,5 @@ def get_rpointers(self, root): yield file, content - # TODO check if used otherwise remove - def get_entries(self): - return self.get_children("comp_archive_spec") - - # TODO check if used otherwise remove - def get_entry_info(self, archive_entry): - compname = self.get(archive_entry, "compname") - compclass = self.get(archive_entry, "compclass") - return compname, compclass - - # TODO check if used otherwise remove - def get_rpointer_contents(self, archive_entry): - rpointer_items = [] - rpointer_nodes = self.get_children("rpointer", root=archive_entry) - for rpointer_node in rpointer_nodes: - file_node = self.get_child("rpointer_file", root=rpointer_node) - content_node = self.get_child("rpointer_content", root=rpointer_node) - rpointer_items.append([self.text(file_node), self.text(content_node)]) - return rpointer_items - def get_type_info(self, vid): return "char" From 52c1103bd0e6765d3d951490d0ac2baf7936d28f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 26 Jan 2026 23:00:09 -0800 Subject: [PATCH 066/153] Move archive spec and rpointer parsing methods to ArchiveBase --- CIME/XML/archive_base.py | 18 ++++++++++++++++++ CIME/XML/env_archive.py | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CIME/XML/archive_base.py b/CIME/XML/archive_base.py index ff8c096ce9f..360424b734d 100644 --- a/CIME/XML/archive_base.py +++ b/CIME/XML/archive_base.py @@ -9,6 +9,24 @@ class ArchiveBase(GenericXML): + def get_archive_specs(self): + components_element = self.get_child("components") + + return self.get_children("comp_archive_spec", root=components_element) + + def get_rpointer_nodes(self, root): + assert root.name == "comp_archive_spec" + + return self.get_children("rpointer", root=root) + + def get_rpointers(self, root): + for node in self.get_rpointer_nodes(root): + file = self.get_child("rpointer_file", root=node).text + + content = self.get_child("rpointer_content", root=node).text + + yield file, content + def exclude_testing(self, compname): """ Checks if component should be excluded from testing. diff --git a/CIME/XML/env_archive.py b/CIME/XML/env_archive.py index 6ee7ef37075..e3752017aa2 100644 --- a/CIME/XML/env_archive.py +++ b/CIME/XML/env_archive.py @@ -16,23 +16,5 @@ def __init__(self, case_root=None, infile="env_archive.xml", read_only=False): schema = os.path.join(utils.get_schema_path(), "env_archive.xsd") EnvBase.__init__(self, case_root, infile, schema=schema, read_only=read_only) - def get_archive_specs(self): - components_element = self.get_child("components") - - return self.get_children("comp_archive_spec", root=components_element) - - def get_rpointer_nodes(self, root): - assert root.name == "comp_archive_spec" - - return self.get_children("rpointer", root=root) - - def get_rpointers(self, root): - for node in self.get_rpointer_nodes(root): - file = self.get_child("rpointer_file", root=node).text - - content = self.get_child("rpointer_content", root=node).text - - yield file, content - def get_type_info(self, vid): return "char" From 3f0f8720d67b067cb91a993cc8529e68643dc267 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 29 Jan 2026 15:09:03 -0700 Subject: [PATCH 067/153] wait_for_tests: Refactor and restore commit/time upload --- CIME/wait_for_tests.py | 364 ++++++++++++++++++++--------------------- 1 file changed, 175 insertions(+), 189 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 928b010ba30..735815c131b 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -1,6 +1,5 @@ # pylint: disable=import-error -import queue -import os, time, threading, socket, signal, shutil, glob +import queue, os, time, threading, socket, signal, shutil, glob, tempfile # pylint: disable=import-error import logging @@ -92,24 +91,16 @@ def create_cdash_xml_boiler( utc_time, current_time, hostname, - git_commit, ): ############################################################################### site_elem = xmlet.Element("Site") - if "JENKINS_START_TIME" in os.environ: - time_info_str = "Total testing time: {:d} seconds".format( - int(current_time) - int(os.environ["JENKINS_START_TIME"]) - ) - else: - time_info_str = "" - site_elem.attrib["BuildName"] = cdash_build_name site_elem.attrib["BuildStamp"] = "{}-{}".format(utc_time, cdash_build_group) site_elem.attrib["Name"] = hostname site_elem.attrib["OSName"] = "Linux" site_elem.attrib["Hostname"] = hostname - site_elem.attrib["OSVersion"] = "Commit: {}{}".format(git_commit, time_info_str) + site_elem.attrib["OSVersion"] = "Unknown" phase_elem = xmlet.SubElement(site_elem, phase) @@ -130,7 +121,6 @@ def create_cdash_config_xml( current_time, hostname, data_rel_path, - git_commit, ): ############################################################################### site_elem, config_elem = create_cdash_xml_boiler( @@ -140,7 +130,6 @@ def create_cdash_config_xml( utc_time, current_time, hostname, - git_commit, ) xmlet.SubElement(config_elem, "ConfigureCommand").text = "namelists" @@ -165,7 +154,7 @@ def create_cdash_config_xml( xmlet.SubElement(config_elem, "ElapsedMinutes").text = "0" # Skip for now etree = xmlet.ElementTree(site_elem) - etree.write(os.path.join(data_rel_path, "Configure.xml")) + etree.write(data_rel_path / "Configure.xml") ############################################################################### @@ -177,7 +166,6 @@ def create_cdash_build_xml( current_time, hostname, data_rel_path, - git_commit, ): ############################################################################### site_elem, build_elem = create_cdash_xml_boiler( @@ -187,7 +175,6 @@ def create_cdash_build_xml( utc_time, current_time, hostname, - git_commit, ) xmlet.SubElement(build_elem, "ConfigureCommand").text = "case.build" @@ -214,7 +201,7 @@ def create_cdash_build_xml( xmlet.SubElement(build_elem, "ElapsedMinutes").text = "0" # Skip for now etree = xmlet.ElementTree(site_elem) - etree.write(os.path.join(data_rel_path, "Build.xml")) + etree.write(data_rel_path / "Build.xml") ############################################################################### @@ -226,7 +213,6 @@ def create_cdash_test_xml( current_time, hostname, data_rel_path, - git_commit, ): ############################################################################### site_elem, testing_elem = create_cdash_xml_boiler( @@ -236,7 +222,6 @@ def create_cdash_test_xml( utc_time, current_time, hostname, - git_commit, ) test_list_elem = xmlet.SubElement(testing_elem, "TestList") @@ -298,28 +283,14 @@ def create_cdash_test_xml( xmlet.SubElement(testing_elem, "ElapsedMinutes").text = "0" # Skip for now etree = xmlet.ElementTree(site_elem) - - etree.write(os.path.join(data_rel_path, "Test.xml")) + etree.write(data_rel_path / "Test.xml") ############################################################################### def create_cdash_xml_fakes( - results, cdash_build_name, cdash_build_group, utc_time, current_time, hostname + results, cdash_build_name, cdash_build_group, utc_time, current_time, hostname, data_rel_path ): ############################################################################### - # We assume all cases were created from the same code repo - first_result_case = os.path.dirname(list(results.items())[0][1][0]) - try: - srcroot = run_cmd_no_fail( - "./xmlquery --value SRCROOT", from_dir=first_result_case - ) - except CIMEError: - # Use repo containing this script as last resort - srcroot = os.path.join(CIME.utils.get_cime_root(), "..") - - git_commit = CIME.utils.get_current_commit(repo=srcroot) - - data_rel_path = os.path.join("Testing", utc_time) create_cdash_config_xml( results, @@ -329,7 +300,6 @@ def create_cdash_xml_fakes( current_time, hostname, data_rel_path, - git_commit, ) create_cdash_build_xml( @@ -340,7 +310,6 @@ def create_cdash_xml_fakes( current_time, hostname, data_rel_path, - git_commit, ) create_cdash_test_xml( @@ -351,88 +320,72 @@ def create_cdash_xml_fakes( current_time, hostname, data_rel_path, - git_commit, ) - ############################################################################### def create_cdash_upload_xml( - results, cdash_build_name, cdash_build_group, utc_time, hostname, force_log_upload + results, cdash_build_name, cdash_build_group, utc_time, hostname, force_log_upload, tmp_path, data_rel_path ): ############################################################################### - data_rel_path = os.path.join("Testing", utc_time) - - try: - log_dir = "{}_logs".format(cdash_build_name) - - need_to_upload = False + log_dirname = f"{cdash_build_name}_logs" + log_path = tmp_path / log_dirname + + need_to_upload = False + + for test_name, test_data in results.items(): + test_path, test_status, _ = test_data + + if test_status != TEST_PASS_STATUS or force_log_upload: + test_case_dir = os.path.dirname(test_path) + + case_dirs = [test_case_dir] + case_base = os.path.basename(test_case_dir) + test_case2_dir = os.path.join(test_case_dir, "case2", case_base) + if os.path.exists(test_case2_dir): + case_dirs.append(test_case2_dir) + + for case_dir in case_dirs: + for param in ["EXEROOT", "RUNDIR", "CASEDIR"]: + if param == "CASEDIR": + log_src_dir = case_dir + else: + # it's possible that tests that failed very badly/early, and fake cases for testing + # will not be able to support xmlquery + try: + log_src_dir = run_cmd_no_fail( + "./xmlquery {} --value".format(param), + from_dir=case_dir, + ) + except: + continue + + log_dst_dir = log_path / "{}{}_{}_logs".format( + test_name, + "" if case_dir == test_case_dir else ".case2", + param, + ) + log_dst_dir.mkdir(parents=True) + for log_file in glob.glob(os.path.join(log_src_dir, "*log*")): + if os.path.isdir(log_file): + shutil.copytree(log_file, log_dst_dir / os.path.basename(log_file)) + else: + safe_copy(log_file, str(log_dst_dir)) + for log_file in glob.glob(os.path.join(log_src_dir, "*.cprnc.out*")): + safe_copy(log_file, str(log_dst_dir)) - for test_name, test_data in results.items(): - test_path, test_status, _ = test_data + need_to_upload = True - if test_status != TEST_PASS_STATUS or force_log_upload: - test_case_dir = os.path.dirname(test_path) + if need_to_upload: - case_dirs = [test_case_dir] - case_base = os.path.basename(test_case_dir) - test_case2_dir = os.path.join(test_case_dir, "case2", case_base) - if os.path.exists(test_case2_dir): - case_dirs.append(test_case2_dir) + tarball = "{}.tar.gz".format(log_dirname) - for case_dir in case_dirs: - for param in ["EXEROOT", "RUNDIR", "CASEDIR"]: - if param == "CASEDIR": - log_src_dir = case_dir - else: - # it's possible that tests that failed very badly/early, and fake cases for testing - # will not be able to support xmlquery - try: - log_src_dir = run_cmd_no_fail( - "./xmlquery {} --value".format(param), - from_dir=case_dir, - ) - except: - continue - - log_dst_dir = os.path.join( - log_dir, - "{}{}_{}_logs".format( - test_name, - "" if case_dir == test_case_dir else ".case2", - param, - ), - ) - os.makedirs(log_dst_dir) - for log_file in glob.glob(os.path.join(log_src_dir, "*log*")): - if os.path.isdir(log_file): - shutil.copytree( - log_file, - os.path.join( - log_dst_dir, os.path.basename(log_file) - ), - ) - else: - safe_copy(log_file, log_dst_dir) - for log_file in glob.glob( - os.path.join(log_src_dir, "*.cprnc.out*") - ): - safe_copy(log_file, log_dst_dir) - - need_to_upload = True - - if need_to_upload: - - tarball = "{}.tar.gz".format(log_dir) - if os.path.exists(tarball): - os.remove(tarball) - - run_cmd_no_fail( - "tar -cf - {} | gzip -c".format(log_dir), arg_stdout=tarball - ) - base64 = run_cmd_no_fail("base64 {}".format(tarball)) + run_cmd_no_fail( + "tar -cf - {} | gzip -c".format(log_dirname), arg_stdout=tarball, from_dir=str(tmp_path) + ) + base64 = run_cmd_no_fail("base64 {}".format(tarball), from_dir=str(tmp_path)) - xml_text = r""" + xml_text = r""" "?> @@ -444,20 +397,16 @@ def create_cdash_upload_xml( """.format( - cdash_build_name, - utc_time, - cdash_build_group, - hostname, - os.path.abspath(tarball), - base64, - ) - - with open(os.path.join(data_rel_path, "Upload.xml"), "w") as fd: - fd.write(xml_text) + cdash_build_name, + utc_time, + cdash_build_group, + hostname, + str((tmp_path / tarball).absolute()), + base64, +) - finally: - if os.path.isdir(log_dir): - shutil.rmtree(log_dir) + with (data_rel_path / "Upload.xml").open(mode="w") as fd: + fd.write(xml_text) ############################################################################### @@ -482,84 +431,121 @@ def create_cdash_xml( "Could not convert hostname '{}' into an E3SM machine name".format(hostname) ) - for drop_method in ["https", "http"]: - dart_config = """ -SourceDirectory: {0} -BuildDirectory: {0} - -# Site is something like machine.domain, i.e. pragmatic.crd -Site: {1} - -# Build name is osname-revision-compiler, i.e. Linux-2.4.2-2smp-c++ -BuildName: {2} - -# Submission information -IsCDash: TRUE -CDashVersion: -QueryCDashVersion: -DropSite: my.cdash.org -DropLocation: /submit.php?project={3} -DropSiteUser: -DropSitePassword: -DropSiteMode: -DropMethod: {6} -TriggerSite: -ScpCommand: {4} - -# Dashboard start time -NightlyStartTime: {5} UTC - -UseLaunchers: -CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF -""".format( - os.getcwd(), - hostname, - cdash_build_name, - cdash_project, - shutil.which("scp"), - cdash_timestamp, - drop_method, + # We assume all cases were created from the same code repo + first_result_case = os.path.dirname(list(results.items())[0][1][0]) + try: + srcroot = run_cmd_no_fail( + "./xmlquery --value SRCROOT", from_dir=first_result_case ) + except CIMEError: + # Use repo containing this script as last resort + srcroot = os.path.join(CIME.utils.get_cime_root(), "..") - with open("DartConfiguration.tcl", "w") as dart_fd: - dart_fd.write(dart_config) + git_commit = CIME.utils.get_current_commit(repo=srcroot) - utc_time = time.strftime("%Y%m%d-%H%M", utc_time_tuple) - testing_dir = os.path.join("Testing", utc_time) - if os.path.isdir(testing_dir): - shutil.rmtree(testing_dir) + # Get total elapsed time + if "JENKINS_START_TIME" in os.environ: + time_info = int(current_time) - int(os.environ["JENKINS_START_TIME"]) + ) + else: + time_info = "unknown" + + prefixes = [None, first_result_case, os.getcwd()] + for prexix in prefixes: + try: + with tempfile.TemporaryDirectory(prefix=prefix) as tmpdir: + tmp_path = Path(tmpdir) + utc_time = time.strftime("%Y%m%d-%H%M", utc_time_tuple) + dart_path = tmp_path / "DartConfiguration.tcl" + testing_path = tmp_path / "Testing" + testtime_dir = testing_path / utc_time # Most action happens here + tag_file = testing_path / "TAG" + notes_file = tmp_path / "notes.txt" + + testtime_dir.mkdir(parents=True) + + # Make tag file + with tag_file.open(mode="w") as tag_fd: + tag_fd.write(f"{utc_time}\n{cdash_build_group}\n") + + # Make notes file + with notes_file.open(mode="w") as notes_fd: + notes_fd.write(f"Commit {git_commit}\nTotal testing time {time_info} seconds\n") + + create_cdash_xml_fakes( + results, + cdash_build_name, + cdash_build_group, + utc_time, + current_time, + hostname, + testtime_dir + ) - os.makedirs(os.path.join("Testing", utc_time)) + create_cdash_upload_xml( + results, + cdash_build_name, + cdash_build_group, + utc_time, + hostname, + force_log_upload, + tmp_path, + testtime_dir + ) - # Make tag file - with open("Testing/TAG", "w") as tag_fd: - tag_fd.write("{}\n{}\n".format(utc_time, cdash_build_group)) + for drop_method in ["https", "http"]: + dart_config = """ + SourceDirectory: {0} + BuildDirectory: {0} + + # Site is something like machine.domain, i.e. pragmatic.crd + Site: {1} + + # Build name is osname-revision-compiler, i.e. Linux-2.4.2-2smp-c++ + BuildName: {2} + + # Submission information + IsCDash: TRUE + CDashVersion: + QueryCDashVersion: + DropSite: my.cdash.org + DropLocation: /submit.php?project={3} + DropSiteUser: + DropSitePassword: + DropSiteMode: + DropMethod: {6} + TriggerSite: + ScpCommand: {4} + + # Dashboard start time + NightlyStartTime: {5} UTC + + UseLaunchers: + CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF + """.format( + str(tmp_path.absolute()), + hostname, + cdash_build_name, + cdash_project, + shutil.which("scp"), + cdash_timestamp, + drop_method, + ) + with dart_path.open(mode="w") as dart_fd: + dart_fd.write(dart_config) - create_cdash_xml_fakes( - results, - cdash_build_name, - cdash_build_group, - utc_time, - current_time, - hostname, - ) + stat, out, _ = run_cmd("ctest -VV -D NightlySubmit", combine_output=True, from_dir=str(tmp_path)) + if stat != 0: + logging.warning( + "ctest upload drop method {} FAILED:\n{}".format(drop_method, out) + ) + else: + logging.info("Upload SUCCESS:\n{}".format(out)) - create_cdash_upload_xml( - results, - cdash_build_name, - cdash_build_group, - utc_time, - hostname, - force_log_upload, - ) + except Exception as e: + logging.info(f"Prexix '{prefix}' failed with error {e}") - stat, out, _ = run_cmd("ctest -VV -D NightlySubmit", combine_output=True) - if stat != 0: - logging.warning( - "ctest upload drop method {} FAILED:\n{}".format(drop_method, out) - ) else: - logging.info("Upload SUCCESS:\n{}".format(out)) return expect(False, "All cdash upload attempts failed") From 70ddd2df7ff349a53118d1df0101b20613554310 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 29 Jan 2026 15:11:26 -0700 Subject: [PATCH 068/153] Fix --- CIME/wait_for_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 735815c131b..c49f4e601ad 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -446,7 +446,6 @@ def create_cdash_xml( # Get total elapsed time if "JENKINS_START_TIME" in os.environ: time_info = int(current_time) - int(os.environ["JENKINS_START_TIME"]) - ) else: time_info = "unknown" From 4a4e1d09b36107dfc827166cafef21a4199486fe Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 29 Jan 2026 15:14:18 -0700 Subject: [PATCH 069/153] Fix --- CIME/wait_for_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index c49f4e601ad..e8aa3de80e6 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -1,5 +1,6 @@ # pylint: disable=import-error import queue, os, time, threading, socket, signal, shutil, glob, tempfile +from pathlib import Path # pylint: disable=import-error import logging @@ -450,7 +451,7 @@ def create_cdash_xml( time_info = "unknown" prefixes = [None, first_result_case, os.getcwd()] - for prexix in prefixes: + for prefix in prefixes: try: with tempfile.TemporaryDirectory(prefix=prefix) as tmpdir: tmp_path = Path(tmpdir) From 93bea972593966795ae58dfe5ba8920e95ed05ea Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 29 Jan 2026 15:45:42 -0700 Subject: [PATCH 070/153] Fixes --- CIME/wait_for_tests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index e8aa3de80e6..1174aed6bca 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -534,20 +534,18 @@ def create_cdash_xml( with dart_path.open(mode="w") as dart_fd: dart_fd.write(dart_config) - stat, out, _ = run_cmd("ctest -VV -D NightlySubmit", combine_output=True, from_dir=str(tmp_path)) + stat, out, _ = run_cmd("ctest -VV -D NightlySubmit -A notes.txt", combine_output=True, from_dir=str(tmp_path)) if stat != 0: logging.warning( "ctest upload drop method {} FAILED:\n{}".format(drop_method, out) ) else: logging.info("Upload SUCCESS:\n{}".format(out)) + return except Exception as e: logging.info(f"Prexix '{prefix}' failed with error {e}") - else: - return - expect(False, "All cdash upload attempts failed") From 5214b170c3eeb266d01b09b083bb5fcbf654d0ef Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 29 Jan 2026 14:46:54 -0800 Subject: [PATCH 071/153] black --- CIME/wait_for_tests.py | 82 ++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 1174aed6bca..0986eef2205 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -289,7 +289,13 @@ def create_cdash_test_xml( ############################################################################### def create_cdash_xml_fakes( - results, cdash_build_name, cdash_build_group, utc_time, current_time, hostname, data_rel_path + results, + cdash_build_name, + cdash_build_group, + utc_time, + current_time, + hostname, + data_rel_path, ): ############################################################################### @@ -323,14 +329,22 @@ def create_cdash_xml_fakes( data_rel_path, ) + ############################################################################### def create_cdash_upload_xml( - results, cdash_build_name, cdash_build_group, utc_time, hostname, force_log_upload, tmp_path, data_rel_path + results, + cdash_build_name, + cdash_build_group, + utc_time, + hostname, + force_log_upload, + tmp_path, + data_rel_path, ): ############################################################################### log_dirname = f"{cdash_build_name}_logs" - log_path = tmp_path / log_dirname + log_path = tmp_path / log_dirname need_to_upload = False @@ -369,10 +383,14 @@ def create_cdash_upload_xml( log_dst_dir.mkdir(parents=True) for log_file in glob.glob(os.path.join(log_src_dir, "*log*")): if os.path.isdir(log_file): - shutil.copytree(log_file, log_dst_dir / os.path.basename(log_file)) + shutil.copytree( + log_file, log_dst_dir / os.path.basename(log_file) + ) else: safe_copy(log_file, str(log_dst_dir)) - for log_file in glob.glob(os.path.join(log_src_dir, "*.cprnc.out*")): + for log_file in glob.glob( + os.path.join(log_src_dir, "*.cprnc.out*") + ): safe_copy(log_file, str(log_dst_dir)) need_to_upload = True @@ -382,7 +400,9 @@ def create_cdash_upload_xml( tarball = "{}.tar.gz".format(log_dirname) run_cmd_no_fail( - "tar -cf - {} | gzip -c".format(log_dirname), arg_stdout=tarball, from_dir=str(tmp_path) + "tar -cf - {} | gzip -c".format(log_dirname), + arg_stdout=tarball, + from_dir=str(tmp_path), ) base64 = run_cmd_no_fail("base64 {}".format(tarball), from_dir=str(tmp_path)) @@ -398,13 +418,13 @@ def create_cdash_upload_xml( """.format( - cdash_build_name, - utc_time, - cdash_build_group, - hostname, - str((tmp_path / tarball).absolute()), - base64, -) + cdash_build_name, + utc_time, + cdash_build_group, + hostname, + str((tmp_path / tarball).absolute()), + base64, + ) with (data_rel_path / "Upload.xml").open(mode="w") as fd: fd.write(xml_text) @@ -458,7 +478,7 @@ def create_cdash_xml( utc_time = time.strftime("%Y%m%d-%H%M", utc_time_tuple) dart_path = tmp_path / "DartConfiguration.tcl" testing_path = tmp_path / "Testing" - testtime_dir = testing_path / utc_time # Most action happens here + testtime_dir = testing_path / utc_time # Most action happens here tag_file = testing_path / "TAG" notes_file = tmp_path / "notes.txt" @@ -470,7 +490,9 @@ def create_cdash_xml( # Make notes file with notes_file.open(mode="w") as notes_fd: - notes_fd.write(f"Commit {git_commit}\nTotal testing time {time_info} seconds\n") + notes_fd.write( + f"Commit {git_commit}\nTotal testing time {time_info} seconds\n" + ) create_cdash_xml_fakes( results, @@ -479,7 +501,7 @@ def create_cdash_xml( utc_time, current_time, hostname, - testtime_dir + testtime_dir, ) create_cdash_upload_xml( @@ -490,7 +512,7 @@ def create_cdash_xml( hostname, force_log_upload, tmp_path, - testtime_dir + testtime_dir, ) for drop_method in ["https", "http"]: @@ -523,21 +545,27 @@ def create_cdash_xml( UseLaunchers: CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF """.format( - str(tmp_path.absolute()), - hostname, - cdash_build_name, - cdash_project, - shutil.which("scp"), - cdash_timestamp, - drop_method, - ) + str(tmp_path.absolute()), + hostname, + cdash_build_name, + cdash_project, + shutil.which("scp"), + cdash_timestamp, + drop_method, + ) with dart_path.open(mode="w") as dart_fd: dart_fd.write(dart_config) - stat, out, _ = run_cmd("ctest -VV -D NightlySubmit -A notes.txt", combine_output=True, from_dir=str(tmp_path)) + stat, out, _ = run_cmd( + "ctest -VV -D NightlySubmit -A notes.txt", + combine_output=True, + from_dir=str(tmp_path), + ) if stat != 0: logging.warning( - "ctest upload drop method {} FAILED:\n{}".format(drop_method, out) + "ctest upload drop method {} FAILED:\n{}".format( + drop_method, out + ) ) else: logging.info("Upload SUCCESS:\n{}".format(out)) From e43b9acccb26165bd5124a092fd9c057c26b7648 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 30 Jan 2026 09:15:13 -0700 Subject: [PATCH 072/153] Fixes from copilot --- CIME/wait_for_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 0986eef2205..6900997fc52 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -7,7 +7,7 @@ import xml.etree.ElementTree as xmlet import CIME.utils -from CIME.utils import expect, Timeout, run_cmd_no_fail, safe_copy, CIMEError +from CIME.utils import expect, Timeout, run_cmd, run_cmd_no_fail, safe_copy, CIMEError from CIME.XML.machines import Machines from CIME.test_status import * from CIME.provenance import save_test_success @@ -372,7 +372,7 @@ def create_cdash_upload_xml( "./xmlquery {} --value".format(param), from_dir=case_dir, ) - except: + except CIMEError: continue log_dst_dir = log_path / "{}{}_{}_logs".format( @@ -470,10 +470,10 @@ def create_cdash_xml( else: time_info = "unknown" - prefixes = [None, first_result_case, os.getcwd()] - for prefix in prefixes: + tmproots = [None, first_result_case, os.getcwd()] + for tmproot in tmproots: try: - with tempfile.TemporaryDirectory(prefix=prefix) as tmpdir: + with tempfile.TemporaryDirectory(dir=tmproot) as tmpdir: tmp_path = Path(tmpdir) utc_time = time.strftime("%Y%m%d-%H%M", utc_time_tuple) dart_path = tmp_path / "DartConfiguration.tcl" @@ -572,7 +572,7 @@ def create_cdash_xml( return except Exception as e: - logging.info(f"Prexix '{prefix}' failed with error {e}") + logging.info(f"Temp dir '{tmpdir}' failed with error {e}") expect(False, "All cdash upload attempts failed") From 7d7b7b742a4002bf9c1e2fafad396fd1b482c59e Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 30 Jan 2026 11:10:44 -0700 Subject: [PATCH 073/153] Add support for picking a tmproot. Add support for testing --- CIME/Tools/wait_for_tests | 12 ++++++++++-- CIME/tests/test_sys_wait_for_tests.py | 4 ++++ CIME/wait_for_tests.py | 20 +++++++++++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CIME/Tools/wait_for_tests b/CIME/Tools/wait_for_tests index c166061c99b..484b393b6f4 100755 --- a/CIME/Tools/wait_for_tests +++ b/CIME/Tools/wait_for_tests @@ -87,7 +87,7 @@ OR ) parser.add_argument( - "--force-log-upload", + "--cdash-force-log-upload", action="store_true", help="Always upload logs to cdash, even if test passed", ) @@ -105,6 +105,11 @@ OR help="The name of the CDash project where results should be uploaded", ) + parser.add_argument( + "--cdash-tmproot", + help="Where to put temporary files needed to do cdash submission. Default=/tmp", + ) + parser.add_argument( "-g", "--cdash-build-group", @@ -132,9 +137,10 @@ OR args.ignore_memleak, args.cdash_build_name, args.cdash_project, + args.cdash_tmproot, args.cdash_build_group, args.timeout, - args.force_log_upload, + args.cdash_force_log_upload, args.no_run, args.update_success, ) @@ -153,6 +159,7 @@ def _main_func(description): ignore_memleak, cdash_build_name, cdash_project, + cdash_tmproot, cdash_build_group, timeout, force_log_upload, @@ -172,6 +179,7 @@ def _main_func(description): ignore_memleak=ignore_memleak, cdash_build_name=cdash_build_name, cdash_project=cdash_project, + cdash_tmproot=cdash_tmproot, cdash_build_group=cdash_build_group, timeout=timeout, force_log_upload=force_log_upload, diff --git a/CIME/tests/test_sys_wait_for_tests.py b/CIME/tests/test_sys_wait_for_tests.py index 0377d65771e..3484c9af7a5 100644 --- a/CIME/tests/test_sys_wait_for_tests.py +++ b/CIME/tests/test_sys_wait_for_tests.py @@ -9,6 +9,7 @@ from CIME import utils from CIME import test_status +from CIME.wait_for_tests import ENV_VAR_KEEP_CDASH from CIME.tests import base from CIME.tests import utils as test_utils @@ -110,6 +111,8 @@ def tearDown(self): for testdir in self._testdirs: shutil.rmtree(testdir) + os.environ.pop(ENV_VAR_KEEP_CDASH, None) + def simple_test(self, testdir, expected_results, extra_args="", build_name=None): # Need these flags to test dashboard if e3sm if self._config.create_test_flag_mode == "e3sm" and build_name is not None: @@ -270,6 +273,7 @@ def test_wait_for_test_cdash_pass(self): def test_wait_for_test_cdash_kill(self): expected_results = ["PEND" if item == 5 else "PASS" for item in range(10)] + os.environ[ENV_VAR_KEEP_CDASH] = "True" build_name = "regression_test_kill_" + self._timestamp run_thread = threading.Thread( target=self.threaded_test, diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 6900997fc52..91cc60e6206 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -17,6 +17,7 @@ E3SM_MAIN_CDASH = "E3SM" CDASH_DEFAULT_BUILD_GROUP = "ACME_Latest" SLEEP_INTERVAL_SEC = 0.1 +ENV_VAR_KEEP_CDASH = "CIME_TEST_CDASH_WFT" ############################################################################### def signal_handler(*_): @@ -432,7 +433,7 @@ def create_cdash_upload_xml( ############################################################################### def create_cdash_xml( - results, cdash_build_name, cdash_project, cdash_build_group, force_log_upload=False + results, cdash_build_name, cdash_project, cdash_build_group, force_log_upload=False, cdash_tmproot=None ): ############################################################################### @@ -470,7 +471,14 @@ def create_cdash_xml( else: time_info = "unknown" - tmproots = [None, first_result_case, os.getcwd()] + if cdash_tmproot: + tmproots = [cdash_tmproot] + else: + tmproots = [None, first_result_case, os.getcwd()] + + # Try multiple tmproots if necessary. The default /tmp will be tried first + # unless cdash_tmproot was provided. The location of the default can be + # modified via the TMPDIR environment variable. for tmproot in tmproots: try: with tempfile.TemporaryDirectory(dir=tmproot) as tmpdir: @@ -569,6 +577,10 @@ def create_cdash_xml( ) else: logging.info("Upload SUCCESS:\n{}".format(out)) + if ENV_VAR_KEEP_CDASH in os.environ: + logging.info(f"Test mode enabled, copying {str(tmp_path)} to {os.getcwd()}") + safe_copy(str(tmp_path / "Testing"), os.getcwd()) + return except Exception as e: @@ -739,6 +751,7 @@ def wait_for_tests( ignore_memleak=False, cdash_build_name=None, cdash_project=E3SM_MAIN_CDASH, + cdash_tmproot=None, cdash_build_group=CDASH_DEFAULT_BUILD_GROUP, timeout=None, force_log_upload=False, @@ -839,12 +852,13 @@ def wait_for_tests( ) if cdash_build_name: - create_cdash_xml( + tmpdir = create_cdash_xml( test_results, cdash_build_name, cdash_project, cdash_build_group, force_log_upload, + cdash_tmproot ) return all_pass From 303fe74f39c8babb202a8ced15b0485c9f49fa4a Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 30 Jan 2026 10:13:27 -0800 Subject: [PATCH 074/153] black --- CIME/wait_for_tests.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index 91cc60e6206..b971aba6855 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -433,7 +433,12 @@ def create_cdash_upload_xml( ############################################################################### def create_cdash_xml( - results, cdash_build_name, cdash_project, cdash_build_group, force_log_upload=False, cdash_tmproot=None + results, + cdash_build_name, + cdash_project, + cdash_build_group, + force_log_upload=False, + cdash_tmproot=None, ): ############################################################################### @@ -578,7 +583,9 @@ def create_cdash_xml( else: logging.info("Upload SUCCESS:\n{}".format(out)) if ENV_VAR_KEEP_CDASH in os.environ: - logging.info(f"Test mode enabled, copying {str(tmp_path)} to {os.getcwd()}") + logging.info( + f"Test mode enabled, copying {str(tmp_path)} to {os.getcwd()}" + ) safe_copy(str(tmp_path / "Testing"), os.getcwd()) return @@ -858,7 +865,7 @@ def wait_for_tests( cdash_project, cdash_build_group, force_log_upload, - cdash_tmproot + cdash_tmproot, ) return all_pass From 8831bb4f14633c680e7a40ff60618e35df419e48 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 30 Jan 2026 11:51:12 -0700 Subject: [PATCH 075/153] Fix copilot review items --- CIME/wait_for_tests.py | 76 +++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index b971aba6855..f2b58e8cda9 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -530,42 +530,42 @@ def create_cdash_xml( for drop_method in ["https", "http"]: dart_config = """ - SourceDirectory: {0} - BuildDirectory: {0} - - # Site is something like machine.domain, i.e. pragmatic.crd - Site: {1} - - # Build name is osname-revision-compiler, i.e. Linux-2.4.2-2smp-c++ - BuildName: {2} - - # Submission information - IsCDash: TRUE - CDashVersion: - QueryCDashVersion: - DropSite: my.cdash.org - DropLocation: /submit.php?project={3} - DropSiteUser: - DropSitePassword: - DropSiteMode: - DropMethod: {6} - TriggerSite: - ScpCommand: {4} - - # Dashboard start time - NightlyStartTime: {5} UTC - - UseLaunchers: - CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF - """.format( - str(tmp_path.absolute()), - hostname, - cdash_build_name, - cdash_project, - shutil.which("scp"), - cdash_timestamp, - drop_method, - ) +SourceDirectory: {0} +BuildDirectory: {0} + +# Site is something like machine.domain, i.e. pragmatic.crd +Site: {1} + +# Build name is osname-revision-compiler, i.e. Linux-2.4.2-2smp-c++ +BuildName: {2} + +# Submission information +IsCDash: TRUE +CDashVersion: +QueryCDashVersion: +DropSite: my.cdash.org +DropLocation: /submit.php?project={3} +DropSiteUser: +DropSitePassword: +DropSiteMode: +DropMethod: {6} +TriggerSite: +ScpCommand: {4} + +# Dashboard start time +NightlyStartTime: {5} UTC + +UseLaunchers: +CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF +""".format( + str(tmp_path.absolute()), + hostname, + cdash_build_name, + cdash_project, + shutil.which("scp"), + cdash_timestamp, + drop_method, +) with dart_path.open(mode="w") as dart_fd: dart_fd.write(dart_config) @@ -591,7 +591,7 @@ def create_cdash_xml( return except Exception as e: - logging.info(f"Temp dir '{tmpdir}' failed with error {e}") + logging.warning(f"Using temp root '{tmproot}', cdash submission failed with error {e}") expect(False, "All cdash upload attempts failed") @@ -859,7 +859,7 @@ def wait_for_tests( ) if cdash_build_name: - tmpdir = create_cdash_xml( + create_cdash_xml( test_results, cdash_build_name, cdash_project, From b8f71257bec375acd059ff68b07d64e8809ff8b4 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 30 Jan 2026 10:52:29 -0800 Subject: [PATCH 076/153] black --- CIME/wait_for_tests.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CIME/wait_for_tests.py b/CIME/wait_for_tests.py index f2b58e8cda9..1f6940138a9 100644 --- a/CIME/wait_for_tests.py +++ b/CIME/wait_for_tests.py @@ -558,14 +558,14 @@ def create_cdash_xml( UseLaunchers: CurlOptions: CURLOPT_SSL_VERIFYPEER_OFF;CURLOPT_SSL_VERIFYHOST_OFF """.format( - str(tmp_path.absolute()), - hostname, - cdash_build_name, - cdash_project, - shutil.which("scp"), - cdash_timestamp, - drop_method, -) + str(tmp_path.absolute()), + hostname, + cdash_build_name, + cdash_project, + shutil.which("scp"), + cdash_timestamp, + drop_method, + ) with dart_path.open(mode="w") as dart_fd: dart_fd.write(dart_config) @@ -591,7 +591,9 @@ def create_cdash_xml( return except Exception as e: - logging.warning(f"Using temp root '{tmproot}', cdash submission failed with error {e}") + logging.warning( + f"Using temp root '{tmproot}', cdash submission failed with error {e}" + ) expect(False, "All cdash upload attempts failed") From 98b94325ce77dbd4fbc9b01db6cddb832483e97b Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Feb 2026 09:08:06 -0700 Subject: [PATCH 077/153] Change py version warning message from print to warn To avoid pollution stdout. --- CIME/Tools/standard_script_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index 89fe12868cc..3119675ab32 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -31,7 +31,8 @@ def check_minimum_python_version(major, minor, warn_only=False): + str(sys.version_info[1]) ) if warn_only: - print(msg.replace("required", "recommended") + ".") + import logging + logging.warn(msg.replace("required", "recommended") + ".") return raise RuntimeError(msg + " - please use a newer version of Python.") From ca9baa40ef278d77846a881e541be6fddfb9f3ec Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Feb 2026 09:21:38 -0700 Subject: [PATCH 078/153] Copilot recs --- CIME/Tools/standard_script_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index 3119675ab32..c89361a2681 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -32,7 +32,8 @@ def check_minimum_python_version(major, minor, warn_only=False): ) if warn_only: import logging - logging.warn(msg.replace("required", "recommended") + ".") + + logging.warning(msg.replace("required", "recommended") + ".") return raise RuntimeError(msg + " - please use a newer version of Python.") From 6960e1d29706072f9d58b27e048b6a4e12f2971f Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Feb 2026 12:37:10 -0700 Subject: [PATCH 079/153] Move logging import --- CIME/Tools/standard_script_setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index c89361a2681..39003c0e2a1 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -4,7 +4,7 @@ """ # pylint: disable=unused-import -import sys, os +import sys, os, logging import __main__ as main @@ -31,8 +31,6 @@ def check_minimum_python_version(major, minor, warn_only=False): + str(sys.version_info[1]) ) if warn_only: - import logging - logging.warning(msg.replace("required", "recommended") + ".") return raise RuntimeError(msg + " - please use a newer version of Python.") @@ -53,4 +51,4 @@ def check_minimum_python_version(major, minor, warn_only=False): import CIME.utils CIME.utils.stop_buffering_output() -import logging, argparse +import argparse From fc2de6dd919ac69fc672cd3c93c42faaf0f1b695 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 5 Feb 2026 12:55:48 -0700 Subject: [PATCH 080/153] Revert "Merge pull request #4913 from jedwards4b/fix/python_warning" This reverts commit 991e19b9a85f06bcc6e94e45dddd25dfe90c3b42, reversing changes made to e752d032c76ae2de21c9b72a5b1f4d0c5998c04d. This is no longer needed given https://github.com/ESMCI/cime/pull/4927 --- CIME/tests/test_sys_cime_case.py | 63 ++++++++------------------- CIME/tests/test_sys_create_newcase.py | 27 ++---------- 2 files changed, 21 insertions(+), 69 deletions(-) diff --git a/CIME/tests/test_sys_cime_case.py b/CIME/tests/test_sys_cime_case.py index 7e2fb35d118..c4095972d93 100644 --- a/CIME/tests/test_sys_cime_case.py +++ b/CIME/tests/test_sys_cime_case.py @@ -45,13 +45,9 @@ def test_cime_case(self): ) case.flush() - # the strip().splitlines()[-1] avoids a potential warning message in the output. - build_complete = ( - utils.run_cmd_no_fail( - "./xmlquery BUILD_COMPLETE --value", from_dir=casedir - ) - .strip() - .splitlines()[-1] + + build_complete = utils.run_cmd_no_fail( + "./xmlquery BUILD_COMPLETE --value", from_dir=casedir ) self.assertEqual( build_complete, @@ -346,26 +342,16 @@ def test_cime_case_xmlchange_append(self): self.run_cmd_assert_result( "./xmlchange --id PIO_CONFIG_OPTS --val='-opt1'", from_dir=casedir ) - # Avoids a potential warning in output about python version - result = ( - self.run_cmd_assert_result( - "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir - ) - .strip() - .splitlines()[-1] + result = self.run_cmd_assert_result( + "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir ) self.assertEqual(result, "-opt1") self.run_cmd_assert_result( "./xmlchange --id PIO_CONFIG_OPTS --val='-opt2' --append", from_dir=casedir ) - # the strip().splitlines()[-1] avoids a potential warning message in the output. - result = ( - self.run_cmd_assert_result( - "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir - ) - .strip() - .splitlines()[-1] + result = self.run_cmd_assert_result( + "./xmlquery --value PIO_CONFIG_OPTS", from_dir=casedir ) self.assertEqual(result, "-opt1 -opt2") @@ -519,14 +505,10 @@ def test_cime_case_test_walltime_mgmt_6(self): ) self.run_cmd_assert_result("./case.setup --reset", from_dir=casedir) - # the strip().splitlines()[-1] avoids a potential warning message in the output. - result = ( - self.run_cmd_assert_result( - "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", - from_dir=casedir, - ) - .strip() - .splitlines()[-1] + + result = self.run_cmd_assert_result( + "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", + from_dir=casedir, ) with Case(casedir) as case: walltime_format = case.get_value("walltime_format", subgroup=None) @@ -553,14 +535,9 @@ def test_cime_case_test_walltime_mgmt_7(self): self.run_cmd_assert_result("./case.setup --reset", from_dir=casedir) - # the strip().splitlines()[-1] avoids a potential warning message in the output. - result = ( - self.run_cmd_assert_result( - "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", - from_dir=casedir, - ) - .strip() - .splitlines()[-1] + result = self.run_cmd_assert_result( + "./xmlquery JOB_WALLCLOCK_TIME --subgroup=case.test --value", + from_dir=casedir, ) with Case(casedir) as case: walltime_format = case.get_value("walltime_format", subgroup=None) @@ -620,16 +597,10 @@ def test_cime_case_test_custom_project(self): env_changes="unset CIME_GLOBAL_WALLTIME &&", ) - # the strip().splitlines()[-1] avoids a potential warning message in the output. - result = ( - self.run_cmd_assert_result( - "./xmlquery --non-local --value PROJECT --subgroup=case.test", - from_dir=casedir, - ) - .strip() - .splitlines()[-1] + result = self.run_cmd_assert_result( + "./xmlquery --non-local --value PROJECT --subgroup=case.test", + from_dir=casedir, ) - self.assertEqual(result, "testproj") def test_create_test_longname(self): diff --git a/CIME/tests/test_sys_create_newcase.py b/CIME/tests/test_sys_create_newcase.py index 746512422d8..1be636aff36 100644 --- a/CIME/tests/test_sys_create_newcase.py +++ b/CIME/tests/test_sys_create_newcase.py @@ -314,10 +314,7 @@ def test_e_xmlquery(self): COMP_CLASSES = case.get_values("COMP_CLASSES") BUILD_COMPLETE = case.get_value("BUILD_COMPLETE") cmd = xmlquery + " --non-local STOP_N --value" - # avoid a potential warning in output by only looking at the last line - output = ( - utils.run_cmd_no_fail(cmd, from_dir=casedir).strip().splitlines()[-1] - ) + output = utils.run_cmd_no_fail(cmd, from_dir=casedir) self.assertTrue(output == str(STOP_N), msg="%s != %s" % (output, STOP_N)) cmd = xmlquery + " --non-local BUILD_COMPLETE --value" output = utils.run_cmd_no_fail(cmd, from_dir=casedir) @@ -339,35 +336,19 @@ def test_e_xmlquery(self): for comp in COMP_CLASSES: caseresult = case.get_value("NTASKS_%s" % comp) cmd = xmlquery + " --non-local NTASKS_%s --value" % comp - # avoid a potential warning in output by only looking at the last line - output = ( - utils.run_cmd_no_fail(cmd, from_dir=casedir) - .strip() - .splitlines()[-1] - ) - + output = utils.run_cmd_no_fail(cmd, from_dir=casedir) self.assertTrue( output == str(caseresult), msg="%s != %s" % (output, caseresult) ) cmd = xmlquery + " --non-local NTASKS --subgroup %s --value" % comp - # avoid a potential warning in output by only looking at the last line - output = ( - utils.run_cmd_no_fail(cmd, from_dir=casedir) - .strip() - .splitlines()[-1] - ) + output = utils.run_cmd_no_fail(cmd, from_dir=casedir) self.assertTrue( output == str(caseresult), msg="%s != %s" % (output, caseresult) ) if self.MACHINE.has_batch_system(): JOB_QUEUE = case.get_value("JOB_QUEUE", subgroup="case.run") cmd = xmlquery + " --non-local JOB_QUEUE --subgroup case.run --value" - # avoid a potential warning in output by only looking at the last line - output = ( - utils.run_cmd_no_fail(cmd, from_dir=casedir) - .strip() - .splitlines()[-1] - ) + output = utils.run_cmd_no_fail(cmd, from_dir=casedir) self.assertTrue( output == JOB_QUEUE, msg="%s != %s" % (output, JOB_QUEUE) ) From 9b339c331d09074f3a13123aa8059c046144b68d Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 11 Feb 2026 13:26:23 -0700 Subject: [PATCH 081/153] Change version warning output to not use logging Importing logging before CIME.utils.stop_buffering_output makes all the logging.info outputs look terrible. Example: INFO:CIME.test_scheduler:create_test will use up to 80 cores simultaneously create_test will use up to 80 cores simultaneously --- CIME/Tools/standard_script_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index 56db20bb31a..2c38a5c4035 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -4,7 +4,7 @@ """ # pylint: disable=unused-import -import sys, os, logging +import sys, os import __main__ as main @@ -31,7 +31,7 @@ def check_minimum_python_version(major, minor, warn_only=False): + str(sys.version_info[1]) ) if warn_only: - logging.warning(msg.replace("required", "recommended") + ".") + print(msg.replace("required", "recommended") + ".", file=sys.stderr) return raise RuntimeError(msg + " - please use a newer version of Python.") @@ -51,4 +51,4 @@ def check_minimum_python_version(major, minor, warn_only=False): import CIME.utils CIME.utils.stop_buffering_output() -import argparse +import argparse, logging From 718622aff671574f6265a319632ba9ddca1240bb Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 11 Feb 2026 13:26:23 -0700 Subject: [PATCH 082/153] Change version warning output to not use logging Importing logging before CIME.utils.stop_buffering_output makes all the logging.info outputs look terrible. Example: INFO:CIME.test_scheduler:create_test will use up to 80 cores simultaneously create_test will use up to 80 cores simultaneously --- CIME/Tools/standard_script_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CIME/Tools/standard_script_setup.py b/CIME/Tools/standard_script_setup.py index 56db20bb31a..2c38a5c4035 100644 --- a/CIME/Tools/standard_script_setup.py +++ b/CIME/Tools/standard_script_setup.py @@ -4,7 +4,7 @@ """ # pylint: disable=unused-import -import sys, os, logging +import sys, os import __main__ as main @@ -31,7 +31,7 @@ def check_minimum_python_version(major, minor, warn_only=False): + str(sys.version_info[1]) ) if warn_only: - logging.warning(msg.replace("required", "recommended") + ".") + print(msg.replace("required", "recommended") + ".", file=sys.stderr) return raise RuntimeError(msg + " - please use a newer version of Python.") @@ -51,4 +51,4 @@ def check_minimum_python_version(major, minor, warn_only=False): import CIME.utils CIME.utils.stop_buffering_output() -import argparse +import argparse, logging From 06da08cf8f2b67cc4c594f562a1395ddb6ab8e14 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Wed, 18 Feb 2026 16:51:26 -0600 Subject: [PATCH 083/153] Add AGENTS.md for cime Add AGENTS.md for CIME. Also add a hidden CLAUDE.md file that includes it since Claude Code refuses to follow the standard. This was generated by Claude Code init function operating on a fresh checkout of CIME --- .claude/CLAUDE.md | 5 ++ AGENTS.md | 165 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 .claude/CLAUDE.md create mode 100644 AGENTS.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000000..4b6fcbcc01c --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,5 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +@../AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..3edcf28227c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,165 @@ +# AGENTS.md + +This file provides guidance to AI agents when working with code in this repository. + +## Project Overview + +CIME (Common Infrastructure for Modeling the Earth) provides a Case Control System (CCS) for configuring, compiling, and executing Earth System Models, plus a framework for system testing. CIME is a Python-based infrastructure used by CESM and E3SM. It does NOT contain model source code itself, but provides the infrastructure to manage model runs. + +## Running Tests + +### Unit and System Tests + +From the repository root, run tests using either: + +```bash +# Using pytest (recommended) +pytest CIME/tests + +# Using the custom test runner +cd CIME/tests +./scripts_regression_tests.py + +# Run specific test file +pytest CIME/tests/test_unit_foo.py + +# Run specific test class +pytest CIME/tests/test_unit_foo.py::TestClass + +# Run specific test case +pytest CIME/tests/test_unit_foo.py::TestClass::test_method +``` + +Test files follow a naming convention: +- Unit tests: `test_unit_*.py` +- System tests: `test_sys_*.py` + +### Pre-commit Hooks + +Before committing, always run: + +```bash +pip install pre-commit +pre-commit run -a +``` + +This runs: +- `black` formatter on CIME code +- `pylint` with project-specific configuration +- XML validation on config files +- End-of-file and trailing whitespace checks + +## Code Quality + +- Code is formatted with `black` +- Linted with `pylint` (see `.pre-commit-config.yaml` for disabled checks) +- Python 3.5+ required +- Follow PEP8 style guidelines + +## Key Architecture Concepts + +### Case Control System (CCS) + +The heart of CIME is the `Case` class (`CIME/case/case.py`), which manages all interactions with a CIME case. The Case class coordinates between: + +1. **Config XML Classes** (readonly) - Located in `CIME/XML/`, these read CIME distribution config files like `config_*.xml`. Python classes are named after the XML they read (e.g., `Machines` reads machine configs). + +2. **Env XML Classes** (read/write) - Also in `CIME/XML/`, these manage case-specific `env_*.xml` files. Classes are named `Env*` (e.g., `EnvRun`, `EnvBuild`). + +The Case class contains an array of Env classes and uses Config classes to populate them during case creation/configuration. + +### Directory Structure + +``` +CIME/ +├── case/ # Case control modules (setup, run, submit, etc.) +├── XML/ # XML parsers for config and env files +├── SystemTests/ # System test implementations (ERS, ERT, etc.) +├── Tools/ # Case manipulation tools (xmlchange, xmlquery, etc.) +├── scripts/ # Top-level user-facing scripts +├── data/ # Config files, XML schemas +├── tests/ # Unit and system tests +├── BuildTools/ # Build system utilities +└── non_py/ # Non-Python components (C/Fortran) + +scripts/ +├── create_newcase # Create new case +├── create_test # Create and run tests +├── create_clone # Clone existing case +├── query_config # Query available configurations +└── query_testlists # Query test lists + +tools/ +└── mapping/ # Grid mapping file generation tools +``` + +### Common Workflows + +**Create a case** (requires machine configuration): +```bash +./scripts/create_newcase --case CASENAME --compset COMPSET --res GRID [--machine MACHINE] +``` + +**Create and run tests**: +```bash +./scripts/create_test TESTNAME +./scripts/create_test TESTNAME1 TESTNAME2 ... +./scripts/create_test -f TESTFILE # from file +``` + +**Query configurations**: +```bash +./scripts/query_config --compsets +./scripts/query_config --grids +./scripts/query_config --machines +``` + +### System Tests + +System tests inherit from `SystemTestsCommon` base class (`CIME/SystemTests/system_tests_common.py`). Common test types: +- **ERS**: Exact restart test +- **ERT**: Exact restart with different threading +- **SMS**: Smoke test +- **SEQ**: Sequencing test + +Each test type has its own module in `CIME/SystemTests/`. + +### XML-Based Configuration + +CIME is heavily XML-driven. Key concepts: +- Generic XML handling is in `CIME/XML/generic_xml.py` +- All XML classes inherit from `GenericXML` +- XML schemas are in `CIME/data/config/xml_schemas/` +- Config files define machines, compsets, grids, tests + +### Case Management Tools + +Located in `CIME/Tools/`, these are executable scripts: +- `xmlchange`: Modify case XML variables +- `xmlquery`: Query case XML variables +- `case.setup`: Setup case directory structure +- `case.build`: Build the case +- `case.submit`: Submit case to batch system +- `preview_namelists`: Generate and preview namelists + +## Documentation + +Build Sphinx documentation: +```bash +cd doc +make clean +make api +make html +``` + +Requires: `sphinx`, `sphinxcontrib-programoutput`, and custom theme (see `doc/README`). + +Online documentation: https://esmci.github.io/cime + +## Development Notes + +- When modifying Case env classes, changes affect the case's XML files +- The Case class extends across multiple files using imports (see imports at end of `case.py`) +- CIME can run standalone or be integrated with host models (CESM, E3SM) +- Machine-specific configurations are in XML files, not hardcoded +- Git submodules may need initialization: `git submodule update --init` From 1c69c2d4a3d3507771bc09c70452d677a03058c6 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Thu, 19 Feb 2026 12:57:35 -0600 Subject: [PATCH 084/153] Address reviewer comments Update min python version. Add NorESM. Clarify system testing. --- AGENTS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3edcf28227c..13e6b941496 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to AI agents when working with code in this reposito ## Project Overview -CIME (Common Infrastructure for Modeling the Earth) provides a Case Control System (CCS) for configuring, compiling, and executing Earth System Models, plus a framework for system testing. CIME is a Python-based infrastructure used by CESM and E3SM. It does NOT contain model source code itself, but provides the infrastructure to manage model runs. +CIME (Common Infrastructure for Modeling the Earth) provides a Case Control System (CCS) for configuring, compiling, and executing Earth System Models, plus a framework for system testing. CIME is a Python-based infrastructure currently used by CESM, E3SM and NorESM. It does NOT contain model source code itself, but provides the infrastructure to manage model runs. ## Running Tests @@ -53,7 +53,7 @@ This runs: - Code is formatted with `black` - Linted with `pylint` (see `.pre-commit-config.yaml` for disabled checks) -- Python 3.5+ required +- Python 3.9+ required - Follow PEP8 style guidelines ## Key Architecture Concepts @@ -160,6 +160,7 @@ Online documentation: https://esmci.github.io/cime - When modifying Case env classes, changes affect the case's XML files - The Case class extends across multiple files using imports (see imports at end of `case.py`) -- CIME can run standalone or be integrated with host models (CESM, E3SM) +- CIME must be integrated with host models (CESM, E3SM, NorESM) to run system tests and must be +on a supported machine (found using `./scripts/query_config --machines`)/ - Machine-specific configurations are in XML files, not hardcoded - Git submodules may need initialization: `git submodule update --init` From c3fa928110a350416884a756062dee90df8ffa01 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Fri, 20 Feb 2026 11:18:34 -0600 Subject: [PATCH 085/153] More reviewer comments Make sure agents know to not overwrite AGENTS.md. More clarification about Model System tests (ERS, SMS). Try to make Claude Code play nice. add AI things to .gitignore. --- .claude/CLAUDE.md | 6 ++++++ .gitignore | 10 ++++++++++ AGENTS.md | 17 ++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4b6fcbcc01c..121eed3e933 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -3,3 +3,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. @../AGENTS.md + +# Important +This CLAUDE.md file is maintained by the project maintainers and should not be +modified by /init or any automated process. Do not overwrite, replace, or +regenerate this file. If asked to run /init, warn the user that it will +overwrite the project's curated AI instructions. diff --git a/.gitignore b/.gitignore index a1c5112c573..976ac250596 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,13 @@ libraries share test_coverage/** *.bak + +# AI coding tool local state +.gemini/ +.codex/ + +# Special for Claude Code user config +.claude/* +!.claude/CLAUDE.md +CLAUDE.md +CLAUDE.local.md diff --git a/AGENTS.md b/AGENTS.md index 13e6b941496..884a6525aae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,19 @@ This file provides guidance to AI agents when working with code in this repository. +## Note to AI agents + +This AGENTS.md is maintained by the CIME project. Do not overwrite or +regenerate this file with init commands. + +## Note to claude code users + +A CLAUDE.md file is in .claude directory. It includes +this file. Ignore tips to run init. + ## Project Overview -CIME (Common Infrastructure for Modeling the Earth) provides a Case Control System (CCS) for configuring, compiling, and executing Earth System Models, plus a framework for system testing. CIME is a Python-based infrastructure currently used by CESM, E3SM and NorESM. It does NOT contain model source code itself, but provides the infrastructure to manage model runs. +CIME (Common Infrastructure for Modeling the Earth) provides a Case Control System (CCS) for configuring, compiling, and executing Earth System Models, plus a framework for system testing. CIME is a Python-based infrastructure currently used by CESM, E3SM, NorESM and other models. It does NOT contain model source code itself, but provides the infrastructure to manage model runs. ## Running Tests @@ -114,8 +124,9 @@ tools/ ./scripts/query_config --machines ``` -### System Tests +### Model System Tests +These are tests of properties of the model CIME is included in. System tests inherit from `SystemTestsCommon` base class (`CIME/SystemTests/system_tests_common.py`). Common test types: - **ERS**: Exact restart test - **ERT**: Exact restart with different threading @@ -160,7 +171,7 @@ Online documentation: https://esmci.github.io/cime - When modifying Case env classes, changes affect the case's XML files - The Case class extends across multiple files using imports (see imports at end of `case.py`) -- CIME must be integrated with host models (CESM, E3SM, NorESM) to run system tests and must be +- CIME must be integrated with host models (CESM, E3SM, NorESM) to run Model System Tests on a supported machine (found using `./scripts/query_config --machines`)/ - Machine-specific configurations are in XML files, not hardcoded - Git submodules may need initialization: `git submodule update --init` From ca712531031a7187a6c45c90276ac08171bfd238 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Fri, 20 Feb 2026 15:21:43 -0600 Subject: [PATCH 086/153] Remove scripts_regression_tests.py mention Remove scripts_regression_tests.py mention since its being phased out. --- AGENTS.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 884a6525aae..75f517e91a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,10 +26,6 @@ From the repository root, run tests using either: # Using pytest (recommended) pytest CIME/tests -# Using the custom test runner -cd CIME/tests -./scripts_regression_tests.py - # Run specific test file pytest CIME/tests/test_unit_foo.py From 8ec7fdb01eed67a2b3771b1e1d61ed82afe24ad0 Mon Sep 17 00:00:00 2001 From: alperaltuntas Date: Thu, 26 Feb 2026 20:33:50 -0700 Subject: [PATCH 087/153] Fix MOM6 .ic. history file skip logic ...by skipping this file for multi-instance runs too. --- CIME/hist_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/hist_utils.py b/CIME/hist_utils.py index fa94f1711eb..fd9c37dbe0a 100644 --- a/CIME/hist_utils.py +++ b/CIME/hist_utils.py @@ -101,7 +101,7 @@ def copy_histfiles(case, suffix, match_suffix=None): if "ocean_geometry" in test_hist: comments += " skipping '{}'\n".format(test_hist) continue - if "mom6.ic" in test_hist: + if re.search(r"mom6(_[0-9]{4})?\.ic", os.path.basename(test_hist)): comments += " skipping '{}'\n".format(test_hist) continue comments += " Copying hist files for model '{}'\n".format(model) From bd710a378bbb5ad9efef5d958b63dab221e7b1fe Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 27 Feb 2026 11:15:39 -0700 Subject: [PATCH 088/153] Flip cmake macro names to match case --- CIME/case/case_setup.py | 4 +--- CIME/tests/test_sys_unittest.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 19d8cb890e7..5bdda870b02 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -154,11 +154,9 @@ def _create_macros_cmake( mach = mach_obj.get_machine_name() macros = [ "universal.cmake", - os_ + ".cmake", compiler + ".cmake", - "{}_{}.cmake".format(compiler, os), mach + ".cmake", - "{}_{}.cmake".format(compiler, mach), + "{}_{}.cmake".format(mach, compiler), "CMakeLists.txt", ] for macro in macros: diff --git a/CIME/tests/test_sys_unittest.py b/CIME/tests/test_sys_unittest.py index 3baeacac038..8bfd21eb16f 100755 --- a/CIME/tests/test_sys_unittest.py +++ b/CIME/tests/test_sys_unittest.py @@ -30,20 +30,20 @@ def _has_unit_test_support(self): macros_to_check = [ os.path.join( cmake_macros_dir, - "{}_{}.cmake".format(self._compiler, self._machine), + "{}_{}.cmake".format(self._machine, self._compiler), ), os.path.join(cmake_macros_dir, "{}.cmake".format(self._machine)), os.path.join( os.environ.get("HOME"), ".cime", - "{}_{}.cmake".format(self._compiler, self._machine), + "{}_{}.cmake".format(self._machine, self._compiler), ), os.path.join( os.environ.get("HOME"), ".cime", "{}.cmake".format(self._machine) ), os.path.join( cmake_machine_macros_dir, - "{}_{}.cmake".format(self._compiler, self._machine), + "{}_{}.cmake".format(self._machine, self._compiler), ), os.path.join(cmake_machine_macros_dir, "{}.cmake".format(self._machine)), ] From c008a6d64dc4dc6a955a9b940575eba6486cb753 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Mon, 2 Mar 2026 13:51:13 -0700 Subject: [PATCH 089/153] Move xcpl_comps_nuopc to CMEPS --- CIME/data/config/cesm/config_files.xml | 16 +- CIME/data/config/ufs/config_files.xml | 16 +- .../xatm/cime_config/buildlib | 1 - .../xatm/cime_config/buildlib_cmake | 1 - .../xatm/cime_config/buildnml | 32 - .../xatm/cime_config/config_component.xml | 25 - .../xatm/src/atm_comp_nuopc.F90 | 529 ----------- .../xglc/cime_config/buildlib | 1 - .../xglc/cime_config/buildlib_cmake | 1 - .../xglc/cime_config/buildnml | 32 - .../xglc/cime_config/config_component.xml | 26 - .../xglc/src/glc_comp_nuopc.F90 | 457 ---------- .../xice/cime_config/buildlib | 1 - .../xice/cime_config/buildlib_cmake | 1 - .../xice/cime_config/buildnml | 32 - .../xice/cime_config/config_component.xml | 26 - .../xice/src/ice_comp_nuopc.F90 | 552 ------------ .../xlnd/cime_config/buildlib | 1 - .../xlnd/cime_config/buildlib_cmake | 1 - .../xlnd/cime_config/buildnml | 32 - .../xlnd/cime_config/config_component.xml | 26 - .../xlnd/src/lnd_comp_nuopc.F90 | 564 ------------ .../xocn/cime_config/buildlib | 1 - .../xocn/cime_config/buildlib_cmake | 1 - .../xocn/cime_config/buildnml | 32 - .../xocn/cime_config/config_component.xml | 27 - .../xocn/src/ocn_comp_nuopc.F90 | 475 ---------- .../xrof/cime_config/buildlib | 1 - .../xrof/cime_config/buildlib_cmake | 1 - .../xrof/cime_config/buildnml | 32 - .../xrof/cime_config/config_component.xml | 39 - .../xrof/src/rof_comp_nuopc.F90 | 473 ---------- .../xshare/dead_methods_mod.F90 | 853 ------------------ .../xshare/dead_nuopc_mod.F90 | 346 ------- .../xwav/cime_config/buildlib | 1 - .../xwav/cime_config/buildlib_cmake | 1 - .../xwav/cime_config/buildnml | 32 - .../xwav/cime_config/config_component.xml | 26 - .../xwav/src/wav_comp_nuopc.F90 | 465 ---------- 39 files changed, 16 insertions(+), 5163 deletions(-) delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xatm/src/atm_comp_nuopc.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xglc/src/glc_comp_nuopc.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xice/src/ice_comp_nuopc.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/src/lnd_comp_nuopc.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xocn/src/ocn_comp_nuopc.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xrof/src/rof_comp_nuopc.F90 delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_methods_mod.F90 delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_nuopc_mod.F90 delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib delete mode 120000 CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib_cmake delete mode 100755 CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildnml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/config_component.xml delete mode 100644 CIME/non_py/src/components/xcpl_comps_nuopc/xwav/src/wav_comp_nuopc.F90 diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index ea9dd6f0857..07f087549b5 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -134,7 +134,7 @@ $SRCROOT/components/cdeps/datm $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/satm - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xatm + $SRCROOT/components/cmeps/med_test_comps/xatm $SRCROOT/components/cam/ $SRCROOT/components/fv3/ @@ -169,7 +169,7 @@ $SRCROOT/components/blom/ $SRCROOT/components/cdeps/docn $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/socn - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xocn + $SRCROOT/components/cmeps/med_test_comps/xocn case_comps env_case.xml @@ -184,7 +184,7 @@ $SRCROOT/components/ww3/ $SRCROOT/components/cdeps/dwav $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/swav - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xwav + $SRCROOT/components/cmeps/med_test_comps/xwav case_comps env_case.xml @@ -199,7 +199,7 @@ $SRCROOT/components/cism/ $SRCROOT/components/cdeps/dglc $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/sglc - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xglc + $SRCROOT/components/cmeps/med_test_comps/xglc case_comps env_case.xml @@ -216,7 +216,7 @@ $SRCROOT/components/mpas-seaice/ $SRCROOT/components/cdeps/dice $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/sice - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xice + $SRCROOT/components/cmeps/med_test_comps/xice case_comps env_case.xml @@ -233,7 +233,7 @@ $SRCROOT/components/mizuRoute/ $SRCROOT/components/cdeps/drof $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/srof - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xrof + $SRCROOT/components/cmeps/med_test_comps/xrof case_comps env_case.xml @@ -249,7 +249,7 @@ $SRCROOT/components/slim/ $SRCROOT/components/cdeps/dlnd $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/slnd - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xlnd + $SRCROOT/components/cmeps/med_test_comps/xlnd case_comps env_case.xml @@ -262,7 +262,7 @@ unset $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/siac - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xiac + $SRCROOT/components/cmeps/med_test_comps/xiac case_comps env_case.xml diff --git a/CIME/data/config/ufs/config_files.xml b/CIME/data/config/ufs/config_files.xml index 1f7137821fa..5f717ad6144 100644 --- a/CIME/data/config/ufs/config_files.xml +++ b/CIME/data/config/ufs/config_files.xml @@ -109,7 +109,7 @@ $SRCROOT/src/model/CDEPS/datm $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/satm - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xatm + $SRCROOT/components/cmeps/med_test_comps/xatm $SRCROOT/components/cam/ $SRCROOT/src/model/FV3 @@ -141,7 +141,7 @@ $SRCROOT/src/model/HYCOM/ $SRCROOT/src/model/CDEPS/docn $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/socn - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xocn + $SRCROOT/components/cmeps/med_test_comps/xocn case_comps env_case.xml @@ -156,7 +156,7 @@ $SRCROOT/components/ww3/ $SRCROOT/src/model/CDEPS/dwav $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/swav - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xwav + $SRCROOT/components/cmeps/med_test_comps/xwav case_comps env_case.xml @@ -171,7 +171,7 @@ $SRCROOT/components/cism/ $SRCROOT/src/model/CDEPS/dglc $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/sglc - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xglc + $SRCROOT/components/cmeps/med_test_comps/xglc case_comps env_case.xml @@ -186,7 +186,7 @@ $SRCROOT/src/model/CICE/ $SRCROOT/src/model/CDEPS/dice $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/sice - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xice + $SRCROOT/components/cmeps/med_test_comps/xice case_comps env_case.xml @@ -202,7 +202,7 @@ $SRCROOT/components/mosart/ $SRCROOT/src/model/CDEPS/drof $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/srof - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xrof + $SRCROOT/components/cmeps/med_test_comps/xrof case_comps env_case.xml @@ -217,7 +217,7 @@ $SRCROOT/components/clm/ $SRCROOT/src/model/CDEPS/dlnd $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/slnd - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xlnd + $SRCROOT/components/cmeps/med_test_comps/xlnd case_comps env_case.xml @@ -230,7 +230,7 @@ unset $CIMEROOT/CIME/non_py/src/components/stub_comps_$COMP_INTERFACE/siac - $CIMEROOT/CIME/non_py/src/components/xcpl_comps_$COMP_INTERFACE/xiac + $SRCROOT/components/cmeps/med_test_comps/xiac case_comps env_case.xml diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildnml deleted file mode 100755 index e7efcd46b01..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xatm": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xatm") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/config_component.xml deleted file mode 100644 index 76c8be8c7f9..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/cime_config/config_component.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Dead atm component - - - - char - xatm - xatm - case_comp - env_case.xml - Name of atmosphere component - - - - ========================================= - XATM naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/src/atm_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/src/atm_comp_nuopc.F90 deleted file mode 100644 index 64cd5b768da..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xatm/src/atm_comp_nuopc.F90 +++ /dev/null @@ -1,529 +0,0 @@ -module atm_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XATM - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - integer :: flds_scalar_index_nextsw_cday = 0 - - integer :: fldsToAtm_num = 0 - integer :: fldsFrAtm_num = 0 - type (fld_list_type) :: fldsToAtm(fldsMax) - type (fld_list_type) :: fldsFrAtm(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=5) :: inst_suffix ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - logical :: mastertask - integer :: dbug = 0 - character(*),parameter :: modName = "(xatm_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CS) :: stdname - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(len=CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - mastertask = (my_task==0) - - ! determine instance information - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! set logunit and set shr logging to my log file - call set_component_logging(gcomp, mastertask, logunit, shrlogunit, rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! Initialize xatm - call dead_read_inparms('atm', inst_suffix, logunit, nxg, nyg) - - ! advertise import and export fields - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxNextSwCday", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nextsw_cday - write(logmsg,*) flds_scalar_index_nextsw_cday - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nextsw_cday = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxNextSwCday') - endif - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrAtm_num, fldsFrAtm, trim(flds_scalar_name)) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_topo' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_z' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_u' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_v' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_tbot' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_ptem' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_shum' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_pbot' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_dens' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Sa_pslv' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_rainc' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_rainl' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_snowc' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_snowl' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_lwdn' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_swndr' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_swvdr' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_swndf' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_swvdf' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_swnet' ) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_bcph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_ocph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_dstwet', ungridded_lbound=1, ungridded_ubound=4) - call fld_list_add(fldsFrAtm_num, fldsFrAtm, 'Faxa_dstdry', ungridded_lbound=1, ungridded_ubound=4) - - call fld_list_add(fldsToAtm_num, fldsToAtm, trim(flds_scalar_name)) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_anidr' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_avsdf' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_anidf' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_avsdr' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sl_lfrac' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Si_ifrac' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'So_ofrac' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_tref' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_qref' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_t' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'So_t' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sl_fv' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sl_ram1' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sl_snowh' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Si_snowh' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'So_ssq' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'So_re' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Sx_u10' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_taux' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_tauy' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_lat' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_sen' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_lwup' ) - call fld_list_add(fldsToAtm_num, fldsToAtm, 'Faxx_evap' ) - - do n = 1,fldsFrAtm_num - if(mastertask) write(logunit,*)'Advertising From Xatm ',trim(fldsFrAtm(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrAtm(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - - do n = 1,fldsToAtm_num - if(mastertask) write(logunit,*)'Advertising To Xatm',trim(fldsToAtm(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToAtm(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - end if - - ! Reset shr logging to original values - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! input/output arguments - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_Time) :: nextTime - real(r8) :: nextsw_cday - integer :: n - integer :: shrlogunit ! original log unit - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize: xatm) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! Reset shr logging to my log file - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logUnit) - - ! generate the mesh - call NUOPC_CompAttributeGet(gcomp, name='mesh_atm', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - call fld_list_realize( & - state=exportState, & - fldList=fldsFrAtm, & - numflds=fldsFrAtm_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':xatmExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToAtm, & - numflds=fldsToAtm_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':xatmImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Pack export state - call state_setexport(exportState, rc=rc) - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Set time of next radiation computation - call ESMF_ClockGetNextTime(clock, nextTime) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeGet(nextTime, dayOfYear_r8=nextsw_cday) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(nextsw_cday, flds_scalar_index_nextsw_cday, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - real(r8) :: nextsw_cday - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (dbug > 1) then - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - end if - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call State_SetScalar(nextsw_cday, flds_scalar_index_nextsw_cday, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call state_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (mastertask) then - call log_clock_advance(clock, 'XATM', logunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to Skip the scalar field here - do nf = 2,fldsFrAtm_num - if (fldsFrAtm(nf)%ungridded_ubound == 0) then - call field_setexport(exportState, trim(fldsFrAtm(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrAtm(nf)%ungridded_ubound - call field_setexport(exportState, trim(fldsFrAtm(nf)%stdname), lon, lat, nf=nf+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 1 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - do i = 1,size(data1d) - data1d(i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xatm: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module atm_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildnml deleted file mode 100755 index a90f7a189ef..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xglc": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xglc") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/config_component.xml deleted file mode 100644 index f1765811b70..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/cime_config/config_component.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - Dead land-ice component - - - - char - xglc - xglc - case_comp - env_case.xml - Name of land-ice component - - - - - ========================================= - XGLC naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/src/glc_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/src/glc_comp_nuopc.F90 deleted file mode 100644 index 4b498f8d9c6..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xglc/src/glc_comp_nuopc.F90 +++ /dev/null @@ -1,457 +0,0 @@ -module glc_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XGLC - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise, NUOPC_AddNestedState - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - - integer :: fldsToGlc_num = 0 - integer :: fldsFrGlc_num = 0 - type (fld_list_type) :: fldsToGlc(fldsMax) - type (fld_list_type) :: fldsFrGlc(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - integer ,parameter :: master_task=0 ! task number of master task - logical :: mastertask - integer :: dbug = 0 - character(*),parameter :: modName = "(xglc_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - - ! TODO: this must be generalized - but for now is just hard-wired - integer, parameter :: max_icesheets = 1 - integer :: num_icesheets = 1 - type(ESMF_State) :: NStateImp(max_icesheets) - type(ESMF_State) :: NStateExp(max_icesheets) - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CS) :: stdname - integer :: n, ns, nf - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(len=CL) :: logmsg - character(len=CS) :: cnum - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - mastertask = (my_task == master_task) - - ! determine instance information - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set logunit and set shr logging to my log file - call set_component_logging(gcomp, my_task==master_task, logunit, shrlogunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Initialize xglc - call dead_read_inparms('glc', inst_suffix, logunit, nxg, nyg) - - ! advertise import and export fields - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - ! Create nested state for each active ice sheet - do ns = 1,num_icesheets - write(cnum,'(i0)') ns - call NUOPC_AddNestedState(importState, CplSet="GLC"//trim(cnum), nestedState=NStateImp(ns), rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - call NUOPC_AddNestedState(exportState, CplSet="GLC"//trim(cnum), nestedState=NStateExp(ns), rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - end do - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrGlc_num, fldsFrGlc, trim(flds_scalar_name)) - call fld_list_add(fldsFrGlc_num, fldsFrGlc, 'Sg_icemask' ) - call fld_list_add(fldsFrGlc_num, fldsFrGlc, 'Sg_icemask_coupled_fluxes' ) - call fld_list_add(fldsFrGlc_num, fldsFrGlc, 'Sg_ice_covered' ) - call fld_list_add(fldsFrGlc_num, fldsFrGlc, 'Sg_topo' ) - call fld_list_add(fldsFrGlc_num, fldsFrGlc, 'Flgg_hflx' ) - - call fld_list_add(fldsToGlc_num, fldsToGlc, trim(flds_scalar_name)) - call fld_list_add(fldsToGlc_num, fldsToGlc, 'Sl_tsrf') - call fld_list_add(fldsToGlc_num, fldsToGlc, 'Flgl_qice') - - ! Now advertise import and export fields fields - do ns = 1,num_icesheets - if (mastertask) write(logunit,*)'Advertising To Xglc ',trim(fldsToGlc(ns)%stdname) - do nf = 1,fldsToGlc_num - call NUOPC_Advertise(NStateImp(ns), standardName=fldsToGlc(nf)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkErr(rc,__LINE__,u_FILE_u)) return - end do - if (mastertask) write(logunit,*)'Advertising From Xglc ',trim(fldsFrGlc(ns)%stdname) - do nf = 1,fldsFrGlc_num - call NUOPC_Advertise(NStateExp(ns), standardName=fldsFrGlc(nf)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkErr(rc,__LINE__,u_FILE_u)) return - end do - enddo - - end if - - end subroutine InitializeAdvertise - - !=============================================================================== - - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: n, ns - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! generate the mesh - call NUOPC_CompAttributeGet(gcomp, name='mesh_glc', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - do ns = 1,num_icesheets - call fld_list_realize( & - state=NStateExp(ns), & - fldList=fldsFrGlc, & - numflds=fldsFrGlc_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dglcExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=NStateImp(ns), & - fldList=fldsToGlc, & - numflds=fldsToGlc_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dglcImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - - ! Pack export state and set the coupling scalars - call state_setexport(rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - do ns = 1,num_icesheets - call state_setscalar(dble(nxg),flds_scalar_index_nx, NStateExp(ns), flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call state_setscalar(dble(nyg),flds_scalar_index_ny, NStateExp(ns), flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - - end subroutine InitializeRealize - - !=============================================================================== - - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - call memcheck(subname, 3, mastertask) - - call state_setexport(rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(rc) - - ! input/output variables - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind, ns - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - character(len=*),parameter :: subname=trim(modName)//':(state_setexport) ' - !-------------------------------------------------- - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to skip the scalar field - do ns = 1,num_icesheets - do nf = 2,fldsFrGlc_num - if (fldsFrGlc(nf)%ungridded_ubound == 0) then - call field_setexport(NStateExp(ns), trim(fldsFrGlc(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrGlc(nf)%ungridded_ubound - call field_setexport(NStateExp(ns), trim(fldsFrGlc(nf)%stdname), lon, lat, nf=nf+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - if (dbug > 1) then - call State_diagnose(NStateExp(ns), trim(subname)//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 5 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) & - * cos (pi*lat(i)/180.0_R8) * cos (pi*lat(i)/180.0_R8) & - * sin (pi*lon(i)/180.0_R8) * sin (pi*lon(i)/180.0_R8) & - + (ncomp*10.0_R8) - enddo - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) & - * cos (pi*lat(i)/180.0_R8) * cos (pi*lat(i)/180.0_R8) & - * sin (pi*lon(i)/180.0_R8) * sin (pi*lon(i)/180.0_R8) & - + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (fldname == 'Sg_icemask' .or. fldname == 'Sg_icemask_coupled_fluxes' .or. fldname == 'Sg_ice_covered') then - data1d(:) = 1._r8 - else - do i = 1,size(data1d) - data1d(i) = (nf*100) & - * cos (pi*lat(i)/180.0_R8) * cos (pi*lat(i)/180.0_R8) & - * sin (pi*lon(i)/180.0_R8) * sin (pi*lon(i)/180.0_R8) & - + (ncomp*10.0_R8) - end do - end if - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xglc: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module glc_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildnml deleted file mode 100755 index 7d141edd619..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xice": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xice") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/config_component.xml deleted file mode 100644 index a3263a0eed6..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/cime_config/config_component.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - Dead ice component - - - - char - xice - xice - case_comp - env_case.xml - Name of sea-ice component - - - - - ========================================= - XICE naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/src/ice_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xice/src/ice_comp_nuopc.F90 deleted file mode 100644 index 9185b8e532f..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xice/src/ice_comp_nuopc.F90 +++ /dev/null @@ -1,552 +0,0 @@ -module ice_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XICE - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - - integer :: fldsToIce_num = 0 - integer :: fldsFrIce_num = 0 - type (fld_list_type) :: fldsToIce(fldsMax) - type (fld_list_type) :: fldsFrIce(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - integer ,parameter :: master_task=0 ! task number of master task - logical :: mastertask - integer :: dbug = 0 - character(*),parameter :: modName = "(xice_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CL) :: cvalue - character(CS) :: stdname - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(len=CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - if (dbug > 5) call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - mastertask = my_task == master_task - - !---------------------------------------------------------------------------- - ! determine instance information - !---------------------------------------------------------------------------- - - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !---------------------------------------------------------------------------- - ! set logunit and set shr logging to my log file - !---------------------------------------------------------------------------- - - call set_component_logging(gcomp, my_task==master_task, logunit, shrlogunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !---------------------------------------------------------------------------- - ! Initialize xice - !---------------------------------------------------------------------------- - - call dead_read_inparms('ice', inst_suffix, logunit, nxg, nyg) - - !-------------------------------- - ! advertise import and export fields - !-------------------------------- - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrIce_num, fldsFrIce, trim(flds_scalar_name)) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_imask' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_ifrac' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_t' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_tref' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_qref' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_snowh' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_u10' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_avsdr' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_anidr' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_avsdf' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Si_anidf' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_taux' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_tauy' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_lat' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_sen' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_lwup' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_evap' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Faii_swnet' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_melth' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_swpen' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_meltw' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_salt' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_taux' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_tauy' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_bcpho' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_bcphi' ) - call fld_list_add(fldsFrIce_num, fldsFrIce, 'Fioi_flxdst' ) - - call fld_list_add(fldsToIce_num, fldsToIce, trim(flds_scalar_name)) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_dhdx' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_dhdy' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_t' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_s' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_u' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'So_v' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Fioo_q' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_z' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_u' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_v' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_ptem' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_shum' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_dens' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Sa_tbot' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_swvdr' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_swndr' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_swvdf' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_swndf' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_lwdn' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_rain' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_snow' ) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_bcph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_ocph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_dstwet', ungridded_lbound=1, ungridded_ubound=4) - call fld_list_add(fldsToIce_num, fldsToIce, 'Faxa_dstdry', ungridded_lbound=1, ungridded_ubound=4) - - do n = 1,fldsFrIce_num - if(mastertask) write(logunit,*)'Advertising From Xice ',trim(fldsFrIce(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrIce(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - do n = 1,fldsToIce_num - if(mastertask) write(logunit,*)'Advertising To Xice ',trim(fldsToIce(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToIce(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - - - if (dbug > 5) call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - - !---------------------------------------------------------------------------- - ! Reset shr logging to original values - !---------------------------------------------------------------------------- - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: shrlogunit ! original log unit - integer :: n - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - !---------------------------------------------------------------------------- - ! Reset shr logging to my log file - !---------------------------------------------------------------------------- - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logUnit) - - !-------------------------------- - ! generate the mesh - !-------------------------------- - - call NUOPC_CompAttributeGet(gcomp, name='mesh_ice', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - !-------------------------------- - - call fld_list_realize( & - state=ExportState, & - fldlist=fldsFrIce, & - numflds=fldsFrIce_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':diceExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToIce, & - numflds=fldsToIce_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':diceImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call State_SetExport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (my_task == master_task) then - call log_clock_advance(clock, 'XICE', logunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - endif - - call shr_log_setLogUnit (shrlogunit) - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to skip the scalar field - do nf = 2,fldsFrIce_num - if (fldsFrIce(nf)%ungridded_ubound == 0) then - call field_setexport(exportState, trim(fldsFrIce(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrIce(nf)%ungridded_ubound - call field_setexport(exportState, trim(fldsFrIce(nf)%stdname), lon, lat, nf=nf+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 3 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - do i = 1,size(data1d) - data1d(i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - ! Reset some fields - if (fldname == 'Si_ifrac') then - do i = 1,size(data1d) - data1d(i) = min(1.0_R8,max(0.0_R8,data1d(i))) - end do - else if (fldname == 'Si_imask') then - do i = 1,size(data1d) - data1d(i) = float(nint(min(1.0_R8,max(0.0_R8,data1d(i))))) - end do - end if - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xice: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module ice_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildnml deleted file mode 100755 index 72e822771b4..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xlnd": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xlnd") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/config_component.xml deleted file mode 100644 index 8179d03108b..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/cime_config/config_component.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - Dead land component - - - - - char - xlnd - xlnd - case_comp - env_case.xml - Name of land component - - - - ========================================= - XLND naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/src/lnd_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/src/lnd_comp_nuopc.F90 deleted file mode 100644 index a43215939ad..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xlnd/src/lnd_comp_nuopc.F90 +++ /dev/null @@ -1,564 +0,0 @@ -module lnd_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XLND - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - integer :: flds_scalar_index_nextsw_cday = 0._r8 - - integer :: fldsToLnd_num = 0 - integer :: fldsFrLnd_num = 0 - type (fld_list_type) :: fldsToLnd(fldsMax) - type (fld_list_type) :: fldsFrLnd(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - integer :: glc_nec - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - integer ,parameter :: master_task=0 ! task number of master task - logical :: mastertask - integer :: dbug = 1 - character(*),parameter :: modName = "(xlnd_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CS) :: stdname - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - if (dbug > 5) call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - mastertask = (my_task == master_task) - - !---------------------------------------------------------------------------- - ! determine instance information - !---------------------------------------------------------------------------- - - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !---------------------------------------------------------------------------- - ! set logunit and set shr logging to my log file - !---------------------------------------------------------------------------- - - call set_component_logging(gcomp, mastertask, logunit, shrlogunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !---------------------------------------------------------------------------- - ! Initialize xlnd - !---------------------------------------------------------------------------- - - call dead_read_inparms('lnd', inst_suffix, logunit, nxg, nyg) - - !-------------------------------- - ! advertise import and export fields - !-------------------------------- - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - end if - - if (nxg /= 0 .and. nyg /= 0) then - - call NUOPC_CompAttributeGet(gcomp, name='glc_nec', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - read(cvalue,*) glc_nec - call ESMF_LogWrite('glc_nec = '// trim(cvalue), ESMF_LOGMSG_INFO) - - call fld_list_add(fldsFrLnd_num, fldsFrlnd, trim(flds_scalar_name)) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_lfrin' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_t' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_tref' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_qref' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_avsdr' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_anidr' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_avsdf' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_anidf' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_snowh' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_u10' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_fv' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_ram1' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flrl_rofsur' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flrl_rofgwl' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flrl_rofsub' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flrl_rofi' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flrl_irrig' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_taux' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_tauy' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_lat' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_sen' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_lwup' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_evap' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_swnet' ) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Fall_flxdst' , ungridded_lbound=1, ungridded_ubound=4) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Flgl_qice_elev', ungridded_lbound=1, ungridded_ubound=glc_nec+1) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_tsrf_elev' , ungridded_lbound=1, ungridded_ubound=glc_nec+1) - call fld_list_add(fldsFrLnd_num, fldsFrlnd, 'Sl_topo_elev' , ungridded_lbound=1, ungridded_ubound=glc_nec+1) - - call fld_list_add(fldsToLnd_num, fldsToLnd, trim(flds_scalar_name)) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_z' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_topo' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_u' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_v' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_ptem' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_pbot' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_tbot' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Sa_shum' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Flrr_volr' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Flrr_volrmch' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_lwdn' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_rainc' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_rainl' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_snowc' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_snowl' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_swndr' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_swvdr' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_swndf' ) - call fld_list_add(fldsToLnd_num, fldsToLnd, 'Faxa_swvdf' ) - call fld_list_add(fldsTolnd_num, fldsTolnd, 'Faxa_bcph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsTolnd_num, fldsTolnd, 'Faxa_ocph' , ungridded_lbound=1, ungridded_ubound=3) - call fld_list_add(fldsTolnd_num, fldsTolnd, 'Faxa_dstwet' , ungridded_lbound=1, ungridded_ubound=4) - call fld_list_add(fldsTolnd_num, fldsTolnd, 'Faxa_dstdry' , ungridded_lbound=1, ungridded_ubound=4) - call fld_list_add(fldsToLnd_num, fldsTolnd, 'Sg_topo_elev' , ungridded_lbound=1, ungridded_ubound=glc_nec+1) - call fld_list_add(fldsToLnd_num, fldsTolnd, 'Sg_ice_covered_elev' , ungridded_lbound=1, ungridded_ubound=glc_nec+1) - call fld_list_add(fldsToLnd_num, fldsTolnd, 'Flgg_hflx_elev' , ungridded_lbound=1, ungridded_ubound=glc_nec+1) - call fld_list_add(fldsToLnd_num, fldsTolnd, 'Sg_icemask') - call fld_list_add(fldsToLnd_num, fldsTolnd, 'Sg_icemask_coupled_fluxes') - - do n = 1,fldsFrLnd_num - if (mastertask) write(logunit,*)'Advertising From Xlnd ',trim(fldsFrLnd(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrLnd(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - do n = 1,fldsToLnd_num - if(mastertask) write(logunit,*)'Advertising To Xlnd',trim(fldsToLnd(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToLnd(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - end if - - !---------------------------------------------------------------------------- - ! Reset shr logging to original values - !---------------------------------------------------------------------------- - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! intput/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: shrlogunit ! original log unit - integer :: n - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - if (dbug > 5) call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - - !---------------------------------------------------------------------------- - ! Reset shr logging to my log file - !---------------------------------------------------------------------------- - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logUnit) - - !-------------------------------- - ! generate the mesh - !-------------------------------- - - call NUOPC_CompAttributeGet(gcomp, name='mesh_lnd', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - !-------------------------------- - - call fld_list_realize( & - state=ExportState, & - fldlist=fldsFrLnd, & - numflds=fldsFrLnd_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dlndExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToLnd, & - numflds=fldsToLnd_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dlndImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, & - flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call state_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - if (dbug > 5) call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call state_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (mastertask) then - call log_clock_advance(clock, 'LND', logunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - endif - - call shr_log_setLogUnit (shrlogunit) - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - - end subroutine ModelAdvance - - !=============================================================================== - - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to Skip the scalar field here - do nf = 2,fldsFrLnd_num - if (fldsFrLnd(nf)%ungridded_ubound == 0) then - call field_setexport(exportState, trim(fldsFrLnd(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrLnd(nf)%ungridded_ubound - call field_setexport(exportState, trim(fldsFrLnd(nf)%stdname), lon, lat, nf=nf+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 2 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (fldname == 'Sl_lfrin') then - data1d(:) = 1._r8 - else - do i = 1,size(data1d) - data1d(i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xlnd: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module lnd_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildnml deleted file mode 100755 index 7158056b462..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xocn": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xocn") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/config_component.xml deleted file mode 100644 index f68d1ff4701..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/cime_config/config_component.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - Dead ocean component - - - - char - xocn - xocn - case_comp - env_case.xml - Name of ocean component - - - - - - ========================================= - XOCN naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/src/ocn_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/src/ocn_comp_nuopc.F90 deleted file mode 100644 index 87f8ca25102..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xocn/src/ocn_comp_nuopc.F90 +++ /dev/null @@ -1,475 +0,0 @@ -module ocn_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XOCN - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - integer :: flds_scalar_index_nextsw_cday = 0._r8 - - integer :: fldsToOcn_num = 0 - integer :: fldsFrOcn_num = 0 - type (fld_list_type) :: fldsToOcn(fldsMax) - type (fld_list_type) :: fldsFrOcn(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_name ! fullname of current instance (ie. "ocn_0001") - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - integer ,parameter :: master_task=0 ! task number of master task - logical :: mastertask - integer :: dbug = 0 - character(*),parameter :: modName = "(xocn_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(len=CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - mastertask = (my_task == master_task) - - ! determine instance information - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set logunit and set shr logging to my log file - call set_component_logging(gcomp, my_task==master_task, logunit, shrlogunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Initialize xocn - call dead_read_inparms('ocn', inst_suffix, logunit, nxg, nyg) - - ! advertise import and export fields - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrOcn_num, fldsFrOcn, trim(flds_scalar_name)) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_omask" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_t" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_s" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_u" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_v" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_dhdx" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_dhdy" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "So_bldepth" ) - call fld_list_add(fldsFrOcn_num, fldsFrOcn, "Fioo_q" ) - - call fld_list_add(fldsToOcn_num, fldsToOcn, trim(flds_scalar_name)) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_rain" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_snow" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_lwdn" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_swndr" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_swvdr" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_swndf" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Faxa_swvdf" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_taux" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_tauy" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_sen" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_lat" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_lwup" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_evap" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Fioi_salt" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_rofl" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Foxx_rofi" ) - call fld_list_add(fldsToOcn_num, fldsToOcn, "Sa_pslv" ) - - do n = 1,fldsFrOcn_num - if(mastertask) write(logunit,*)'Advertising From Xocn ',trim(fldsFrOcn(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrOcn(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - do n = 1,fldsToOcn_num - if(mastertask) write(logunit,*)'Advertising To Xocn',trim(fldsToOcn(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToOcn(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - end if - - ! Reset shr logging to original values - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: shrlogunit ! original log unit - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize: xocn) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! Reset shr logging to my log file - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - ! generate the mesh - call NUOPC_CompAttributeGet(gcomp, name='mesh_ocn', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - call fld_list_realize( & - state=ExportState, & - fldlist=fldsFrOcn, & - numflds=fldsFrOcn_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':docnExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToOcn, & - numflds=fldsToOcn_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':docnImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Pack export state - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call state_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! intput/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - ! Pack export state - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call state_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to Skip the scalar field here - do nf = 2,fldsFrOcn_num - if (fldsFrOcn(nf)%ungridded_ubound == 0) then - call field_setexport(exportState, trim(fldsFrOcn(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrOcn(nf)%ungridded_ubound - call field_setexport(exportState, trim(fldsFrOcn(nf)%stdname), lon, lat, nf=nf, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 4 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - do i = 1,size(data1d) - data1d(i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - - if (fldname == 'So_omask') then - do i = 1,size(data1d) - !data1d(i) = float(nint(min(1.0_R8,max(0.0_R8,data1d(i))))) - data1d(i) = 0._r8 - end do - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xocn: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module ocn_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildnml deleted file mode 100755 index bf23e8913e5..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xrof": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xrof") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/config_component.xml deleted file mode 100644 index e1663cbbb7b..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/cime_config/config_component.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - Dead river component - - - - char - xrof - xrof - case_comp - env_case.xml - Name of river component - - - - char - ACTIVE,NULL - NULL - - ACTIVE - ACTIVE - - build_component_xrof - env_build.xml - mode for xrof flood feature, NULL means xrof flood is turned off - - - - - ========================================= - XROF naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/src/rof_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/src/rof_comp_nuopc.F90 deleted file mode 100644 index 1b5b9dd4901..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xrof/src/rof_comp_nuopc.F90 +++ /dev/null @@ -1,473 +0,0 @@ -module rof_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XROF - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - integer :: flds_scalar_index_nextsw_cday = 0 - - integer :: fldsToRof_num = 0 - integer :: fldsFrRof_num = 0 - type (fld_list_type) :: fldsToRof(fldsMax) - type (fld_list_type) :: fldsFrRof(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - integer ,parameter :: master_task=0 ! task number of master task - logical :: mastertask - integer :: dbug = 0 - character(*),parameter :: modName = "(xrof_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CS) :: stdname - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(len=CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - mastertask = (my_task == master_task) - - ! determine instance information - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! set logunit and set shr logging to my log file - call set_component_logging(gcomp, mastertask, logunit, shrlogunit, rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! Initialize xrof - call dead_read_inparms('rof', inst_suffix, logunit, nxg, nyg) - - !-------------------------------- - ! advertise import and export fields - !-------------------------------- - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrRof_num, fldsFrRof, trim(flds_scalar_name)) - call fld_list_add(fldsFrRof_num, fldsFrRof, 'Forr_rofl') - call fld_list_add(fldsFrRof_num, fldsFrRof, 'Forr_rofi') - call fld_list_add(fldsFrRof_num, fldsFrRof, 'Flrr_flood') - call fld_list_add(fldsFrRof_num, fldsFrRof, 'Flrr_volr') - call fld_list_add(fldsFrRof_num, fldsFrRof, 'Flrr_volrmch') - - call fld_list_add(fldsToRof_num, fldsToRof, trim(flds_scalar_name)) - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_rofsur') - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_rofgwl') - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_rofsub') - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_rofdto') - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_rofi') - call fld_list_add(fldsToRof_num, fldsToRof, 'Flrl_irrig') - - do n = 1,fldsFrRof_num - if(mastertask) write(logunit,*)'Advertising From Xrof ',trim(fldsFrRof(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrRof(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - do n = 1,fldsToRof_num - if(mastertask) write(logunit,*)'Advertising To Xrof',trim(fldsToRof(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToRof(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - end if - - if (dbug > 5) call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - - !---------------------------------------------------------------------------- - ! Reset shr logging to original values - !---------------------------------------------------------------------------- - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! input/output arguments - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: shrlogunit ! original log unit - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! Reset shr logging to my log file - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logUnit) - - - ! generate the mesh - call NUOPC_CompAttributeGet(gcomp, name='mesh_rof', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - call fld_list_realize( & - state=ExportState, & - fldlist=fldsFrRof, & - numflds=fldsFrRof_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':drofExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToRof, & - numflds=fldsToRof_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':drofImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! Pack export state - !-------------------------------- - - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! diagnostics - !-------------------------------- - - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (dbug > 5) then - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - end if - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - ! Pack export state - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetExport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (mastertask) then - call log_clock_advance(clock, 'XROF', logunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - endif - - call shr_log_setLogUnit (shrlogunit) - - if (dbug > 5) then - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - end if - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer, intent(out) :: rc - - ! local variables - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - ! Start from index 2 in order to skip the scalar field - do nf = 2,fldsFrRof_num - if (fldsFrRof(nf)%ungridded_ubound == 0) then - call field_setexport(exportState, trim(fldsFrRof(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - do nind = 1,fldsFrRof(nf)%ungridded_ubound - call field_setexport(exportState, trim(fldsFrRof(nf)%stdname), lon, lat, nf=nf+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 6 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf+1) * 1.0_r8 - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf+1) * 1.0_r8 - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - do i = 1,size(data1d) - data1d(i) = (nf+1) * 1.0_r8 - end do - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xrof: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module rof_comp_nuopc diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_methods_mod.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_methods_mod.F90 deleted file mode 100644 index a9ad38e2419..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_methods_mod.F90 +++ /dev/null @@ -1,853 +0,0 @@ -module dead_methods_mod - - use ESMF , only : operator(<), operator(/=), operator(+) - use ESMF , only : operator(-), operator(*) , operator(>=) - use ESMF , only : operator(<=), operator(>), operator(==) - use ESMF , only : ESMF_LOGERR_PASSTHRU, ESMF_LogFoundError, ESMF_LOGMSG_ERROR, ESMF_MAXSTR - use ESMF , only : ESMF_SUCCESS, ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_FAILURE - use ESMF , only : ESMF_State, ESMF_StateGet - use ESMF , only : ESMF_Field, ESMF_FieldGet - use ESMF , only : ESMF_GridComp, ESMF_GridCompGet, ESMF_GridCompSet - use ESMF , only : ESMF_GeomType_Flag, ESMF_FieldStatus_Flag - use ESMF , only : ESMF_Mesh, ESMF_MeshGet - use ESMF , only : ESMF_GEOMTYPE_MESH, ESMF_GEOMTYPE_GRID, ESMF_FIELDSTATUS_COMPLETE - use ESMF , only : ESMF_Clock, ESMF_ClockCreate, ESMF_ClockGet, ESMF_ClockSet - use ESMF , only : ESMF_ClockPrint, ESMF_ClockAdvance - use ESMF , only : ESMF_Alarm, ESMF_AlarmCreate, ESMF_AlarmGet, ESMF_AlarmSet - use ESMF , only : ESMF_Calendar, ESMF_CALKIND_NOLEAP, ESMF_CALKIND_GREGORIAN - use ESMF , only : ESMF_Time, ESMF_TimeGet, ESMF_TimeSet - use ESMF , only : ESMF_TimeInterval, ESMF_TimeIntervalSet, ESMF_TimeIntervalGet - use ESMF , only : ESMF_VM, ESMF_VMGet, ESMF_VMBroadcast, ESMF_VMGetCurrent - use NUOPC , only : NUOPC_CompAttributeGet - use NUOPC_Model , only : NUOPC_ModelGet - use shr_kind_mod , only : r8 => shr_kind_r8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_sys_mod , only : shr_sys_abort - use shr_log_mod , only : shr_log_setlogunit, shr_log_getLogUnit - - implicit none - private - - public :: memcheck - public :: get_component_instance - public :: set_component_logging - public :: log_clock_advance - public :: state_getscalar - public :: state_setscalar - public :: state_diagnose - public :: alarmInit - public :: chkerr - - private :: timeInit - private :: field_getfldptr - - ! Clock and alarm options - character(len=*), private, parameter :: & - optNONE = "none" , & - optNever = "never" , & - optNSteps = "nsteps" , & - optNStep = "nstep" , & - optNSeconds = "nseconds" , & - optNSecond = "nsecond" , & - optNMinutes = "nminutes" , & - optNMinute = "nminute" , & - optNHours = "nhours" , & - optNHour = "nhour" , & - optNDays = "ndays" , & - optNDay = "nday" , & - optNMonths = "nmonths" , & - optNMonth = "nmonth" , & - optNYears = "nyears" , & - optNYear = "nyear" , & - optMonthly = "monthly" , & - optYearly = "yearly" , & - optDate = "date" , & - optIfdays0 = "ifdays0" - - ! Module data - integer, parameter :: SecPerDay = 86400 ! Seconds per day - integer, parameter :: memdebug_level=1 - character(len=1024) :: msgString - character(len=*), parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine memcheck(string, level, mastertask) - - ! input/output variables - character(len=*) , intent(in) :: string - integer , intent(in) :: level - logical , intent(in) :: mastertask - - ! local variables - integer :: ierr - integer, external :: GPTLprint_memusage - !----------------------------------------------------------------------- - - if ((mastertask .and. memdebug_level > level) .or. memdebug_level > level+1) then - ierr = GPTLprint_memusage(string) - endif - - end subroutine memcheck - -!=============================================================================== - - subroutine get_component_instance(gcomp, inst_suffix, inst_index, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - character(len=*) , intent(out) :: inst_suffix - integer , intent(out) :: inst_index - integer , intent(out) :: rc - - ! local variables - logical :: isPresent - character(len=4) :: cvalue - !----------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call NUOPC_CompAttributeGet(gcomp, name="inst_suffix", isPresent=isPresent, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (isPresent) then - call NUOPC_CompAttributeGet(gcomp, name="inst_suffix", value=inst_suffix, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - cvalue = inst_suffix(2:) - read(cvalue, *) inst_index - else - inst_suffix = "" - inst_index=1 - endif - - end subroutine get_component_instance - -!=============================================================================== - - subroutine set_component_logging(gcomp, mastertask, logunit, shrlogunit, rc) - use ESMF, only : ESMF_GridCompGet, ESMF_LogWrite - use NUOPC, only: NUOPC_CompAttributeAdd, NUOPC_CompAttributeSet - ! input/output variables - type(ESMF_GridComp) :: gcomp - logical, intent(in) :: mastertask - integer, intent(out) :: logunit - integer, intent(out) :: shrlogunit - integer, intent(out) :: rc - - ! local variables - character(len=CL) :: diro, name - character(len=CL) :: logfile - character(len=*), parameter :: subname ='('//__FILE__//': set_component_logging)' - !----------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - shrlogunit = 6 - - if (mastertask) then - call NUOPC_CompAttributeGet(gcomp, name="diro", value=diro, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompAttributeGet(gcomp, name="logfile", value=logfile, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - open(newunit=logunit,file=trim(diro)//"/"//trim(logfile)) - else - logUnit = 6 - endif - - call shr_log_setLogUnit (logunit) - - call ESMF_GridCompGet(gcomp, name=name, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(trim(subname)//": setting logunit for component: "//trim(name), ESMF_LOGMSG_INFO) - - call NUOPC_CompAttributeAdd(gcomp, attrList=(/'logunit'/), rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompAttributeSet(gcomp, name='logunit',value=logunit, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - - end subroutine set_component_logging - -!=============================================================================== - - subroutine log_clock_advance(clock, component, logunit, rc) - - ! input/output variables - type(ESMF_Clock) :: clock - character(len=*) , intent(in) :: component - integer , intent(in) :: logunit - integer , intent(out) :: rc - - ! local variables - character(len=CL) :: cvalue, prestring - !----------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - write(prestring, *) "------>Advancing ",trim(component)," from: " - call ESMF_ClockPrint(clock, options="currTime", unit=cvalue, preString=trim(prestring), rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - write(logunit, *) trim(cvalue) - - call ESMF_ClockPrint(clock, options="stopTime", unit=cvalue, & - preString="--------------------------------> to: ", rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - write(logunit, *) trim(cvalue) - - end subroutine log_clock_advance - -!=============================================================================== - - subroutine state_getscalar(state, scalar_id, scalar_value, flds_scalar_name, flds_scalar_num, rc) - - ! ---------------------------------------------- - ! Get scalar data from State for a particular name and broadcast it to all other pets - ! ---------------------------------------------- - - ! input/output variables - type(ESMF_State), intent(in) :: state - integer, intent(in) :: scalar_id - real(r8), intent(out) :: scalar_value - character(len=*), intent(in) :: flds_scalar_name - integer, intent(in) :: flds_scalar_num - integer, intent(inout) :: rc - - ! local variables - integer :: mytask, ierr, len - type(ESMF_VM) :: vm - type(ESMF_Field) :: field - real(r8), pointer :: farrayptr(:,:) - real(r8) :: tmp(1) - character(len=*), parameter :: subname='(state_getscalar)' - ! ---------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_VMGetCurrent(vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localPet=mytask, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_StateGet(State, itemName=trim(flds_scalar_name), field=field, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (mytask == 0) then - call ESMF_FieldGet(field, farrayPtr = farrayptr, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (scalar_id < 0 .or. scalar_id > flds_scalar_num) then - call ESMF_LogWrite(trim(subname)//": ERROR in scalar_id", ESMF_LOGMSG_INFO, line=__LINE__, file=u_FILE_u) - rc = ESMF_FAILURE - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - endif - tmp(:) = farrayptr(scalar_id,:) - endif - call ESMF_VMBroadCast(vm, tmp, 1, 0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - scalar_value = tmp(1) - - end subroutine state_getscalar - -!================================================================================ - - subroutine state_setscalar(scalar_value, scalar_id, State, flds_scalar_name, flds_scalar_num, rc) - - ! ---------------------------------------------- - ! Set scalar data from State for a particular name - ! ---------------------------------------------- - - ! input/output arguments - real(r8), intent(in) :: scalar_value - integer, intent(in) :: scalar_id - type(ESMF_State), intent(inout) :: State - character(len=*), intent(in) :: flds_scalar_name - integer, intent(in) :: flds_scalar_num - integer, intent(inout) :: rc - - ! local variables - integer :: mytask - type(ESMF_Field) :: lfield - type(ESMF_VM) :: vm - real(r8), pointer :: farrayptr(:,:) - character(len=*), parameter :: subname='(state_setscalar)' - ! ---------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_VMGetCurrent(vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localPet=mytask, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_StateGet(State, itemName=trim(flds_scalar_name), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (mytask == 0) then - call ESMF_FieldGet(lfield, farrayPtr = farrayptr, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (scalar_id < 0 .or. scalar_id > flds_scalar_num) then - call ESMF_LogWrite(trim(subname)//": ERROR in scalar_id", ESMF_LOGMSG_INFO) - rc = ESMF_FAILURE - return - endif - farrayptr(scalar_id,1) = scalar_value - endif - - end subroutine state_setscalar - -!=============================================================================== - - subroutine state_diagnose(State, string, rc) - - ! ---------------------------------------------- - ! Diagnose status of State - ! ---------------------------------------------- - - type(ESMF_State), intent(in) :: state - character(len=*), intent(in) :: string - integer , intent(out) :: rc - - ! local variables - integer :: i,j,n - type(ESMf_Field) :: lfield - integer :: fieldCount, lrank - character(ESMF_MAXSTR) ,pointer :: lfieldnamelist(:) - real(r8), pointer :: dataPtr1d(:) - real(r8), pointer :: dataPtr2d(:,:) - character(len=*),parameter :: subname='(state_diagnose)' - ! ---------------------------------------------- - - call ESMF_StateGet(state, itemCount=fieldCount, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - allocate(lfieldnamelist(fieldCount)) - - call ESMF_StateGet(state, itemNameList=lfieldnamelist, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - do n = 1, fieldCount - - call ESMF_StateGet(state, itemName=lfieldnamelist(n), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call field_getfldptr(lfield, fldptr1=dataPtr1d, fldptr2=dataPtr2d, rank=lrank, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (lrank == 0) then - ! no local data - elseif (lrank == 1) then - if (size(dataPtr1d) > 0) then - write(msgString,'(A,3g14.7,i8)') trim(string)//': '//trim(lfieldnamelist(n)), & - minval(dataPtr1d), maxval(dataPtr1d), sum(dataPtr1d), size(dataPtr1d) - else - write(msgString,'(A,a)') trim(string)//': '//trim(lfieldnamelist(n))," no data" - endif - elseif (lrank == 2) then - if (size(dataPtr2d) > 0) then - write(msgString,'(A,3g14.7,i8)') trim(string)//': '//trim(lfieldnamelist(n)), & - minval(dataPtr2d), maxval(dataPtr2d), sum(dataPtr2d), size(dataPtr2d) - else - write(msgString,'(A,a)') trim(string)//': '//trim(lfieldnamelist(n))," no data" - endif - else - call ESMF_LogWrite(trim(subname)//": ERROR rank not supported ", ESMF_LOGMSG_ERROR) - rc = ESMF_FAILURE - return - endif - call ESMF_LogWrite(trim(msgString), ESMF_LOGMSG_INFO) - enddo - - deallocate(lfieldnamelist) - - end subroutine state_diagnose - -!=============================================================================== - - subroutine field_getfldptr(field, fldptr1, fldptr2, rank, abort, rc) - - ! ---------------------------------------------- - ! for a field, determine rank and return fldptr1 or fldptr2 - ! abort is true by default and will abort if fldptr is not yet allocated in field - ! rank returns 0, 1, or 2. 0 means fldptr not allocated and abort=false - ! ---------------------------------------------- - - ! input/output variables - type(ESMF_Field) , intent(in) :: field - real(r8), pointer , intent(inout), optional :: fldptr1(:) - real(r8), pointer , intent(inout), optional :: fldptr2(:,:) - integer , intent(out) , optional :: rank - logical , intent(in) , optional :: abort - integer , intent(out) , optional :: rc - - ! local variables - type(ESMF_GeomType_Flag) :: geomtype - type(ESMF_FieldStatus_Flag) :: status - type(ESMF_Mesh) :: lmesh - integer :: lrank, nnodes, nelements - logical :: labort - character(len=*), parameter :: subname='(field_getfldptr)' - ! ---------------------------------------------- - - if (.not.present(rc)) then - call ESMF_LogWrite(trim(subname)//": ERROR rc not present ", & - ESMF_LOGMSG_ERROR, line=__LINE__, file=u_FILE_u) - rc = ESMF_FAILURE - return - endif - - rc = ESMF_SUCCESS - - labort = .true. - if (present(abort)) then - labort = abort - endif - lrank = -99 - - call ESMF_FieldGet(field, status=status, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (status /= ESMF_FIELDSTATUS_COMPLETE) then - lrank = 0 - if (labort) then - call ESMF_LogWrite(trim(subname)//": ERROR data not allocated ", ESMF_LOGMSG_INFO, rc=rc) - rc = ESMF_FAILURE - return - else - call ESMF_LogWrite(trim(subname)//": WARNING data not allocated ", ESMF_LOGMSG_INFO, rc=rc) - endif - else - - call ESMF_FieldGet(field, geomtype=geomtype, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (geomtype == ESMF_GEOMTYPE_GRID) then - call ESMF_FieldGet(field, rank=lrank, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - elseif (geomtype == ESMF_GEOMTYPE_MESH) then - call ESMF_FieldGet(field, rank=lrank, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_FieldGet(field, mesh=lmesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_MeshGet(lmesh, numOwnedNodes=nnodes, numOwnedElements=nelements, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (nnodes == 0 .and. nelements == 0) lrank = 0 - else - call ESMF_LogWrite(trim(subname)//": ERROR geomtype not supported ", & - ESMF_LOGMSG_INFO, rc=rc) - rc = ESMF_FAILURE - return - endif ! geomtype - - if (lrank == 0) then - call ESMF_LogWrite(trim(subname)//": no local nodes or elements ", & - ESMF_LOGMSG_INFO) - elseif (lrank == 1) then - if (.not.present(fldptr1)) then - call ESMF_LogWrite(trim(subname)//": ERROR missing rank=1 array ", & - ESMF_LOGMSG_ERROR, line=__LINE__, file=u_FILE_u) - rc = ESMF_FAILURE - return - endif - call ESMF_FieldGet(field, farrayPtr=fldptr1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - elseif (lrank == 2) then - if (.not.present(fldptr2)) then - call ESMF_LogWrite(trim(subname)//": ERROR missing rank=2 array ", & - ESMF_LOGMSG_ERROR, line=__LINE__, file=u_FILE_u) - rc = ESMF_FAILURE - return - endif - call ESMF_FieldGet(field, farrayPtr=fldptr2, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - call ESMF_LogWrite(trim(subname)//": ERROR in rank ", & - ESMF_LOGMSG_ERROR, line=__LINE__, file=u_FILE_u) - rc = ESMF_FAILURE - return - endif - - endif ! status - - if (present(rank)) then - rank = lrank - endif - - end subroutine field_getfldptr - -!=============================================================================== - - subroutine alarmInit( clock, alarm, option, & - opt_n, opt_ymd, opt_tod, RefTime, alarmname, rc) - - ! Setup an alarm in a clock - ! Notes: The ringtime sent to AlarmCreate MUST be the next alarm - ! time. If you send an arbitrary but proper ringtime from the - ! past and the ring interval, the alarm will always go off on the - ! next clock advance and this will cause serious problems. Even - ! if it makes sense to initialize an alarm with some reference - ! time and the alarm interval, that reference time has to be - ! advance forward to be >= the current time. In the logic below - ! we set an appropriate "NextAlarm" and then we make sure to - ! advance it properly based on the ring interval. - - ! input/output variables - type(ESMF_Clock) , intent(inout) :: clock ! clock - type(ESMF_Alarm) , intent(inout) :: alarm ! alarm - character(len=*) , intent(in) :: option ! alarm option - integer , optional , intent(in) :: opt_n ! alarm freq - integer , optional , intent(in) :: opt_ymd ! alarm ymd - integer , optional , intent(in) :: opt_tod ! alarm tod (sec) - type(ESMF_Time) , optional , intent(in) :: RefTime ! ref time - character(len=*) , optional , intent(in) :: alarmname ! alarm name - integer , intent(inout) :: rc ! Return code - - ! local variables - type(ESMF_Calendar) :: cal ! calendar - integer :: lymd ! local ymd - integer :: ltod ! local tod - integer :: cyy,cmm,cdd,csec ! time info - character(len=64) :: lalarmname ! local alarm name - logical :: update_nextalarm ! update next alarm - type(ESMF_Time) :: CurrTime ! Current Time - type(ESMF_Time) :: NextAlarm ! Next restart alarm time - type(ESMF_TimeInterval) :: AlarmInterval ! Alarm interval - integer :: sec - character(len=*), parameter :: subname = '(set_alarmInit): ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - lalarmname = 'alarm_unknown' - if (present(alarmname)) lalarmname = trim(alarmname) - ltod = 0 - if (present(opt_tod)) ltod = opt_tod - lymd = -1 - if (present(opt_ymd)) lymd = opt_ymd - - call ESMF_ClockGet(clock, CurrTime=CurrTime, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_TimeGet(CurrTime, yy=cyy, mm=cmm, dd=cdd, s=csec, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! initial guess of next alarm, this will be updated below - if (present(RefTime)) then - NextAlarm = RefTime - else - NextAlarm = CurrTime - endif - - ! Determine calendar - call ESMF_ClockGet(clock, calendar=cal) - - ! Determine inputs for call to create alarm - selectcase (trim(option)) - - case (optNONE) - call ESMF_TimeIntervalSet(AlarmInterval, yy=9999, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeSet( NextAlarm, yy=9999, mm=12, dd=1, s=0, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .false. - - case (optNever) - call ESMF_TimeIntervalSet(AlarmInterval, yy=9999, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeSet( NextAlarm, yy=9999, mm=12, dd=1, s=0, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .false. - - case (optDate) - if (.not. present(opt_ymd)) then - call shr_sys_abort(subname//trim(option)//' requires opt_ymd') - end if - if (lymd < 0 .or. ltod < 0) then - call shr_sys_abort(subname//trim(option)//'opt_ymd, opt_tod invalid') - end if - call ESMF_TimeIntervalSet(AlarmInterval, yy=9999, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call timeInit(NextAlarm, lymd, cal, ltod, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .false. - - case (optIfdays0) - if (.not. present(opt_ymd)) then - call shr_sys_abort(subname//trim(option)//' requires opt_ymd') - end if - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, mm=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeSet( NextAlarm, yy=cyy, mm=cmm, dd=opt_n, s=0, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .true. - - case (optNSteps) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_ClockGet(clock, TimeStep=AlarmInterval, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNStep) - if (.not.present(opt_n)) call shr_sys_abort(subname//trim(option)//' requires opt_n') - if (opt_n <= 0) call shr_sys_abort(subname//trim(option)//' invalid opt_n') - call ESMF_ClockGet(clock, TimeStep=AlarmInterval, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNSeconds) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, s=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNSecond) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, s=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNMinutes) - call ESMF_TimeIntervalSet(AlarmInterval, s=60, rc=rc) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNMinute) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, s=60, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNHours) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, s=3600, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNHour) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, s=3600, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNDays) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, d=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNDay) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, d=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNMonths) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, mm=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNMonth) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, mm=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optMonthly) - call ESMF_TimeIntervalSet(AlarmInterval, mm=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeSet( NextAlarm, yy=cyy, mm=cmm, dd=1, s=0, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .true. - - case (optNYears) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, yy=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optNYear) - if (.not.present(opt_n)) then - call shr_sys_abort(subname//trim(option)//' requires opt_n') - end if - if (opt_n <= 0) then - call shr_sys_abort(subname//trim(option)//' invalid opt_n') - end if - call ESMF_TimeIntervalSet(AlarmInterval, yy=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - AlarmInterval = AlarmInterval * opt_n - update_nextalarm = .true. - - case (optYearly) - call ESMF_TimeIntervalSet(AlarmInterval, yy=1, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_TimeSet( NextAlarm, yy=cyy, mm=1, dd=1, s=0, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - update_nextalarm = .true. - - case default - call shr_sys_abort(subname//'unknown option '//trim(option)) - - end select - - ! -------------------------------------------------------------------------------- - ! --- AlarmInterval and NextAlarm should be set --- - ! -------------------------------------------------------------------------------- - - ! --- advance Next Alarm so it won't ring on first timestep for - ! --- most options above. go back one alarminterval just to be careful - - if (update_nextalarm) then - NextAlarm = NextAlarm - AlarmInterval - do while (NextAlarm <= CurrTime) - NextAlarm = NextAlarm + AlarmInterval - enddo - endif - - alarm = ESMF_AlarmCreate( name=lalarmname, clock=clock, ringTime=NextAlarm, & - ringInterval=AlarmInterval, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine alarmInit - -!=============================================================================== - - subroutine timeInit( Time, ymd, cal, tod, rc) - - ! Create the ESMF_Time object corresponding to the given input time, - ! given in YMD (Year Month Day) and TOD (Time-of-day) format. - ! Set the time by an integer as YYYYMMDD and integer seconds in the day - - ! input/output parameters: - type(ESMF_Time) , intent(inout) :: Time ! ESMF time - integer , intent(in) :: ymd ! year, month, day YYYYMMDD - type(ESMF_Calendar) , intent(in) :: cal ! ESMF calendar - integer , intent(in) :: tod ! time of day in seconds - integer , intent(out) :: rc - - ! local variables - integer :: year, mon, day ! year, month, day as integers - integer :: tdate ! temporary date - integer :: date ! coded-date (yyyymmdd) - character(len=*), parameter :: subname='(timeInit)' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if ( (ymd < 0) .or. (tod < 0) .or. (tod > SecPerDay) )then - call shr_sys_abort( subname//'ERROR yymmdd is a negative number or time-of-day out of bounds' ) - end if - - tdate = abs(date) - year = int(tdate/10000) - if (date < 0) year = -year - mon = int( mod(tdate,10000)/ 100) - day = mod(tdate, 100) - - call ESMF_TimeSet( Time, yy=year, mm=mon, dd=day, s=tod, calendar=cal, rc=rc ) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine timeInit - -!=============================================================================== - - logical function chkerr(rc, line, file) - - integer, intent(in) :: rc - integer, intent(in) :: line - character(len=*), intent(in) :: file - - integer :: lrc - - chkerr = .false. - lrc = rc - if (ESMF_LogFoundError(rcToCheck=lrc, msg=ESMF_LOGERR_PASSTHRU, line=line, file=file)) then - chkerr = .true. - endif - end function chkerr - -!=============================================================================== - -end module dead_methods_mod diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_nuopc_mod.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_nuopc_mod.F90 deleted file mode 100644 index ee3ca6b682e..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xshare/dead_nuopc_mod.F90 +++ /dev/null @@ -1,346 +0,0 @@ -module dead_nuopc_mod - - use ESMF , only : ESMF_Gridcomp, ESMF_State, ESMF_StateGet - use ESMF , only : ESMF_Clock, ESMF_Time, ESMF_TimeInterval, ESMF_Alarm - use ESMF , only : ESMF_GridCompGet, ESMF_ClockGet, ESMF_ClockSet, ESMF_ClockAdvance, ESMF_AlarmSet - use ESMF , only : ESMF_SUCCESS, ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_METHOD_INITIALIZE - use ESMF , only : ESMF_FAILURE, ESMF_LOGMSG_ERROR - use ESMF , only : ESMF_VMGetCurrent, ESMF_VM, ESMF_VMBroadcast, ESMF_VMGet - use ESMF , only : ESMF_VM, ESMF_VMGetCurrent, ESMF_VmGet - use ESMF , only : operator(/=), operator(==), operator(+) - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_sys_mod , only : shr_sys_abort - use dead_methods_mod , only : chkerr, alarmInit - - implicit none - private - - public :: dead_read_inparms - public :: ModelInitPhase - public :: ModelSetRunClock - public :: fld_list_add - public :: fld_list_realize - - ! !PUBLIC DATA MEMBERS: - type fld_list_type - character(len=128) :: stdname - integer :: ungridded_lbound = 0 - integer :: ungridded_ubound = 0 - end type fld_list_type - public :: fld_list_type - - integer, parameter, public :: fldsMax = 100 - integer :: dbug_flag = 0 - character(*), parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine dead_read_inparms(model, inst_suffix, logunit, nxg, nyg) - - ! input/output variables - character(len=*) , intent(in) :: model - character(len=*) , intent(in) :: inst_suffix ! char string associated with instance - integer , intent(in) :: logunit ! logging unit number - integer , intent(out) :: nxg ! global dim i-direction - integer , intent(out) :: nyg ! global dim j-direction - - ! local variables - type(ESMF_VM) :: vm - character(CL) :: fileName ! generic file name - integer :: nunit ! unit number - integer :: unitn ! Unit for namelist file - integer :: tmp(2) ! array for broadcast - integer :: localPet ! mpi id of current task in current context - integer :: rc ! return code - character(*), parameter :: F00 = "('(dead_read_inparms) ',8a)" - character(*), parameter :: F01 = "('(dead_read_inparms) ',a,a,4i8)" - character(*), parameter :: F03 = "('(dead_read_inparms) ',a,a,i8,a)" - character(*), parameter :: subName = "(dead_read_inpamrs) " - !------------------------------------------------------------------------------- - - ! read the input parms (used to configure model) - call ESMF_VMGetCurrent(vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call ESMF_VMGet(vm, localPet=localPet, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - nxg = -9999 - nyg = -9999 - - if (localPet==0) then - open(newunit=unitn, file='x'//model//'_in'//trim(inst_suffix), status='old' ) - read(unitn,*) nxg - read(unitn,*) nyg - close (unitn) - endif - - tmp(1) = nxg - tmp(2) = nyg - call ESMF_VMBroadcast(vm, tmp, 3, 0, rc=rc) - nxg = tmp(1) - nyg = tmp(2) - - if (localPet==0) then - write(logunit,*)' Read in X'//model//' input from file= x'//model//'_in' - write(logunit,F00) model - write(logunit,F00) model,' Model : ',model - write(logunit,F01) model,' NGX : ',nxg - write(logunit,F01) model,' NGY : ',nyg - write(logunit,F00) model,' inst_suffix : ',trim(inst_suffix) - write(logunit,F00) model - end if - - end subroutine dead_read_inparms - - !=============================================================================== - subroutine fld_list_add(num, fldlist, stdname, ungridded_lbound, ungridded_ubound) - - ! input/output variables - integer , intent(inout) :: num - type(fld_list_type) , intent(inout) :: fldlist(:) - character(len=*) , intent(in) :: stdname - integer, optional , intent(in) :: ungridded_lbound - integer, optional , intent(in) :: ungridded_ubound - - ! local variables - character(len=*), parameter :: subname='(dead_nuopc_mod:fld_list_add)' - !------------------------------------------------------------------------------- - - ! Set up a list of field information - num = num + 1 - if (num > fldsMax) then - call ESMF_LogWrite(trim(subname)//": ERROR num > fldsMax "//trim(stdname), & - ESMF_LOGMSG_ERROR, line=__LINE__, file=__FILE__) - return - endif - fldlist(num)%stdname = trim(stdname) - - if (present(ungridded_lbound) .and. present(ungridded_ubound)) then - fldlist(num)%ungridded_lbound = ungridded_lbound - fldlist(num)%ungridded_ubound = ungridded_ubound - end if - - end subroutine fld_list_add - - !=============================================================================== - subroutine fld_list_realize(state, fldList, numflds, flds_scalar_name, flds_scalar_num, mesh, tag, rc) - - use NUOPC , only : NUOPC_IsConnected, NUOPC_Realize - use ESMF , only : ESMF_MeshLoc_Element, ESMF_FieldCreate, ESMF_TYPEKIND_R8 - use ESMF , only : ESMF_MAXSTR, ESMF_Field, ESMF_State, ESMF_Mesh, ESMF_StateRemove - use ESMF , only : ESMF_LogFoundError, ESMF_LOGMSG_INFO, ESMF_SUCCESS - use ESMF , only : ESMF_LogWrite, ESMF_LOGMSG_ERROR, ESMF_LOGERR_PASSTHRU - - type(ESMF_State) , intent(inout) :: state - type(fld_list_type) , intent(in) :: fldList(:) - integer , intent(in) :: numflds - character(len=*) , intent(in) :: flds_scalar_name - integer , intent(in) :: flds_scalar_num - character(len=*) , intent(in) :: tag - type(ESMF_Mesh) , intent(in) :: mesh - integer , intent(inout) :: rc - - ! local variables - integer :: n - type(ESMF_Field) :: field - character(len=80) :: stdname - integer :: gridtoFieldMap=2 - character(len=*),parameter :: subname='(dead_nuopc_mod:fld_list_realize)' - ! ---------------------------------------------- - - rc = ESMF_SUCCESS - - do n = 1, numflds - stdname = fldList(n)%stdname - if (NUOPC_IsConnected(state, fieldName=stdname)) then - if (stdname == trim(flds_scalar_name)) then - call ESMF_LogWrite(trim(subname)//trim(tag)//" Field = "//trim(stdname)//" is connected on root pe", & - ESMF_LOGMSG_INFO) - ! Create the scalar field - call SetScalarField(field, flds_scalar_name, flds_scalar_num, rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - else - call ESMF_LogWrite(trim(subname)//trim(tag)//" Field = "//trim(stdname)//" is connected using mesh", & - ESMF_LOGMSG_INFO) - ! Create the field - if (fldlist(n)%ungridded_lbound > 0 .and. fldlist(n)%ungridded_ubound > 0) then - field = ESMF_FieldCreate(mesh, ESMF_TYPEKIND_R8, name=stdname, meshloc=ESMF_MESHLOC_ELEMENT, & - ungriddedLbound=(/fldlist(n)%ungridded_lbound/), & - ungriddedUbound=(/fldlist(n)%ungridded_ubound/), & - gridToFieldMap=(/gridToFieldMap/), rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - field = ESMF_FieldCreate(mesh, ESMF_TYPEKIND_R8, name=stdname, meshloc=ESMF_MESHLOC_ELEMENT, rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - end if - endif - - ! NOW call NUOPC_Realize - call NUOPC_Realize(state, field=field, rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - else - if (stdname /= trim(flds_scalar_name)) then - call ESMF_LogWrite(subname // trim(tag) // " Field = "// trim(stdname) // " is not connected.", & - ESMF_LOGMSG_INFO) - call ESMF_StateRemove(state, (/stdname/), rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - end if - end if - end do - - contains !- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - subroutine SetScalarField(field, flds_scalar_name, flds_scalar_num, rc) - ! ---------------------------------------------- - ! create a field with scalar data on the root pe - ! ---------------------------------------------- - - use ESMF, only : ESMF_Field, ESMF_DistGrid, ESMF_Grid - use ESMF, only : ESMF_DistGridCreate, ESMF_GridCreate, ESMF_LogFoundError, ESMF_LOGERR_PASSTHRU - use ESMF, only : ESMF_FieldCreate, ESMF_GridCreate, ESMF_TYPEKIND_R8 - - type(ESMF_Field) , intent(inout) :: field - character(len=*) , intent(in) :: flds_scalar_name - integer , intent(in) :: flds_scalar_num - integer , intent(inout) :: rc - - ! local variables - type(ESMF_Distgrid) :: distgrid - type(ESMF_Grid) :: grid - character(len=*), parameter :: subname='(dead_nuopc_mod:SetScalarField)' - ! ---------------------------------------------- - - rc = ESMF_SUCCESS - - ! create a DistGrid with a single index space element, which gets mapped onto DE 0. - distgrid = ESMF_DistGridCreate(minIndex=(/1/), maxIndex=(/1/), rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - - grid = ESMF_GridCreate(distgrid, rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - - field = ESMF_FieldCreate(name=trim(flds_scalar_name), grid=grid, typekind=ESMF_TYPEKIND_R8, & - ungriddedLBound=(/1/), ungriddedUBound=(/flds_scalar_num/), gridToFieldMap=(/2/), rc=rc) - if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=u_FILE_u)) return - - end subroutine SetScalarField - - end subroutine fld_list_realize - - !=============================================================================== - subroutine ModelInitPhase(gcomp, importState, exportState, clock, rc) - - use NUOPC, only : NUOPC_CompFilterPhaseMap - - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! Switch to IPDv01 by filtering all other phaseMap entries - call NUOPC_CompFilterPhaseMap(gcomp, ESMF_METHOD_INITIALIZE, acceptStringList=(/"IPDv01p"/), rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine ModelInitPhase - - !=============================================================================== - subroutine ModelSetRunClock(gcomp, rc) - - use ESMF , only : ESMF_ClockGetAlarmList, ESMF_ALARMLIST_ALL - use NUOPC_Model , only : NUOPC_ModelGet - use NUOPC , only : NUOPC_CompAttributeGet - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: mclock, dclock - type(ESMF_Time) :: mcurrtime, dcurrtime - type(ESMF_Time) :: mstoptime - type(ESMF_TimeInterval) :: mtimestep, dtimestep - character(len=256) :: cvalue - character(len=256) :: restart_option ! Restart option units - integer :: restart_n ! Number until restart interval - integer :: restart_ymd ! Restart date (YYYYMMDD) - type(ESMF_ALARM) :: restart_alarm - character(len=128) :: name - integer :: alarmcount - character(len=*),parameter :: subname='dead_nuopc_mod:(ModelSetRunClock) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! query the Component for its clocks - call NUOPC_ModelGet(gcomp, driverClock=dclock, modelClock=mclock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_ClockGet(dclock, currTime=dcurrtime, timeStep=dtimestep, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_ClockGet(mclock, currTime=mcurrtime, timeStep=mtimestep, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! force model clock currtime and timestep to match driver and set stoptime - !-------------------------------- - - mstoptime = mcurrtime + dtimestep - call ESMF_ClockSet(mclock, currTime=dcurrtime, timeStep=dtimestep, stopTime=mstoptime, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - !-------------------------------- - ! set restart alarm - !-------------------------------- - - call ESMF_ClockGetAlarmList(mclock, alarmlistflag=ESMF_ALARMLIST_ALL, alarmCount=alarmCount, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - if (alarmCount == 0) then - - 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 NUOPC_CompAttributeGet(gcomp, name="restart_option", value=restart_option, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompAttributeGet(gcomp, name="restart_n", value=cvalue, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - read(cvalue,*) restart_n - - call NUOPC_CompAttributeGet(gcomp, name="restart_ymd", value=cvalue, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - read(cvalue,*) restart_ymd - - call alarmInit(mclock, restart_alarm, restart_option, & - opt_n = restart_n, & - opt_ymd = restart_ymd, & - RefTime = mcurrTime, & - alarmname = 'alarm_restart', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_AlarmSet(restart_alarm, clock=mclock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end if - - !-------------------------------- - ! Advance model clock to trigger alarms then reset model clock back to currtime - !-------------------------------- - - call ESMF_ClockAdvance(mclock,rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_ClockSet(mclock, currTime=dcurrtime, timeStep=dtimestep, stopTime=mstoptime, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine ModelSetRunClock - -end module dead_nuopc_mod diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib b/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib_cmake b/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib_cmake deleted file mode 120000 index 7766f77f5bc..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildlib_cmake +++ /dev/null @@ -1 +0,0 @@ -../../../../../../build_scripts/buildlib.internal_components \ No newline at end of file diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildnml b/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildnml deleted file mode 100755 index 1ea9dc3a5d8..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/buildnml +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -""" -build data model library -""" - -import sys, os - -_CIMEROOT = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "..", ".." -) -sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) - -from standard_script_setup import * -from CIME.buildnml import build_xcpl_nml, parse_input -from CIME.case import Case - - -def buildnml(case, caseroot, compname): - if compname != "xwav": - raise AttributeError - build_xcpl_nml(case, caseroot, compname) - - -def _main_func(): - caseroot = parse_input(sys.argv) - with Case(caseroot) as case: - buildnml(case, caseroot, "xwav") - - -if __name__ == "__main__": - _main_func() diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/config_component.xml b/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/config_component.xml deleted file mode 100644 index e82944fd3d8..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/cime_config/config_component.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - Dead wave component - - - - - char - xwav - xwav - case_comp - env_case.xml - Name of wave component - - - - ========================================= - XWAV naming conventions in compset name - ========================================= - - - diff --git a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/src/wav_comp_nuopc.F90 b/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/src/wav_comp_nuopc.F90 deleted file mode 100644 index aa4d982e530..00000000000 --- a/CIME/non_py/src/components/xcpl_comps_nuopc/xwav/src/wav_comp_nuopc.F90 +++ /dev/null @@ -1,465 +0,0 @@ -module wav_comp_nuopc - - !---------------------------------------------------------------------------- - ! This is the NUOPC cap for XWAV - !---------------------------------------------------------------------------- - - use ESMF - use NUOPC , only : NUOPC_CompDerive, NUOPC_CompSetEntryPoint, NUOPC_CompSpecialize - use NUOPC , only : NUOPC_CompAttributeGet, NUOPC_Advertise - use NUOPC_Model , only : model_routine_SS => SetServices - use NUOPC_Model , only : model_label_Advance => label_Advance - use NUOPC_Model , only : model_label_SetRunClock => label_SetRunClock - use NUOPC_Model , only : model_label_Finalize => label_Finalize - use NUOPC_Model , only : NUOPC_ModelGet, SetVM - use shr_sys_mod , only : shr_sys_abort - use shr_kind_mod , only : r8=>shr_kind_r8, i8=>shr_kind_i8, cl=>shr_kind_cl, cs=>shr_kind_cs - use shr_log_mod , only : shr_log_getlogunit, shr_log_setlogunit - use dead_methods_mod , only : chkerr, state_setscalar, state_diagnose, alarmInit, memcheck - use dead_methods_mod , only : set_component_logging, get_component_instance, log_clock_advance - use dead_nuopc_mod , only : dead_read_inparms, ModelInitPhase, ModelSetRunClock - use dead_nuopc_mod , only : fld_list_add, fld_list_realize, fldsMax, fld_list_type - - implicit none - private ! except - - public :: SetServices - public :: SetVM - !-------------------------------------------------------------------------- - ! Private module data - !-------------------------------------------------------------------------- - - character(len=CL) :: flds_scalar_name = '' - integer :: flds_scalar_num = 0 - integer :: flds_scalar_index_nx = 0 - integer :: flds_scalar_index_ny = 0 - integer :: flds_scalar_index_nextsw_cday = 0 - - integer :: fldsToWav_num = 0 - integer :: fldsFrWav_num = 0 - type (fld_list_type) :: fldsToWav(fldsMax) - type (fld_list_type) :: fldsFrWav(fldsMax) - integer, parameter :: gridTofieldMap = 2 ! ungridded dimension is innermost - - type(ESMF_Mesh) :: mesh - integer :: nxg ! global dim i-direction - integer :: nyg ! global dim j-direction - integer :: my_task ! my task in mpi communicator mpicom - integer :: inst_index ! number of current instance (ie. 1) - character(len=16) :: inst_suffix = "" ! char string associated with instance (ie. "_0001" or "") - integer :: logunit ! logging unit number - logical :: mastertask - integer :: dbug = 1 - character(*),parameter :: modName = "(xwav_comp_nuopc)" - character(*),parameter :: u_FILE_u = & - __FILE__ - -!=============================================================================== -contains -!=============================================================================== - - subroutine SetServices(gcomp, rc) - - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - character(len=*),parameter :: subname=trim(modName)//':(SetServices) ' - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! the NUOPC gcomp component will register the generic methods - call NUOPC_CompDerive(gcomp, model_routine_SS, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! switching to IPD versions - call ESMF_GridCompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, & - userRoutine=ModelInitPhase, phase=0, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set entry point for methods that require specific implementation - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p1"/), & - userRoutine=InitializeAdvertise, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSetEntryPoint(gcomp, ESMF_METHOD_INITIALIZE, phaseLabelList=(/"IPDv01p3"/), & - userRoutine=InitializeRealize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! attach specializing method(s) - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Advance, specRoutine=ModelAdvance, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_MethodRemove(gcomp, label=model_label_SetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_SetRunClock, specRoutine=ModelSetRunClock, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call NUOPC_CompSpecialize(gcomp, specLabel=model_label_Finalize, specRoutine=ModelFinalize, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - end subroutine SetServices - - !=============================================================================== - subroutine InitializeAdvertise(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - type(ESMF_VM) :: vm - character(CS) :: stdname - integer :: n - integer :: lsize ! local array size - integer :: shrlogunit ! original log unit - character(CL) :: cvalue - character(len=CL) :: logmsg - logical :: isPresent, isSet - character(len=*),parameter :: subname=trim(modName)//':(InitializeAdvertise) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - call ESMF_LogWrite(subname//' called', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call ESMF_VMGet(vm, localpet=my_task, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - mastertask = (my_task == 0) - - ! determine instance information - call get_component_instance(gcomp, inst_suffix, inst_index, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! set logunit and set shr logging to my log file - call set_component_logging(gcomp, mastertask, logunit, shrlogunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Initialize xwav - call dead_read_inparms('wav', inst_suffix, logunit, nxg, nyg) - - ! advertise import and export fields - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldName", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - flds_scalar_name = trim(cvalue) - call ESMF_LogWrite(trim(subname)//' flds_scalar_name = '//trim(flds_scalar_name), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldName') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldCount", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue, *) flds_scalar_num - write(logmsg,*) flds_scalar_num - call ESMF_LogWrite(trim(subname)//' flds_scalar_num = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldCount') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNX", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_nx - write(logmsg,*) flds_scalar_index_nx - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_nx = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNX') - endif - - call NUOPC_CompAttributeGet(gcomp, name="ScalarFieldIdxGridNY", value=cvalue, isPresent=isPresent, isSet=isSet, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - if (isPresent .and. isSet) then - read(cvalue,*) flds_scalar_index_ny - write(logmsg,*) flds_scalar_index_ny - call ESMF_LogWrite(trim(subname)//' : flds_scalar_index_ny = '//trim(logmsg), ESMF_LOGMSG_INFO) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - else - call shr_sys_abort(subname//'Need to set attribute ScalarFieldIdxGridNY') - endif - - if (nxg /= 0 .and. nyg /= 0) then - - call fld_list_add(fldsFrWav_num, fldsFrWav, trim(flds_scalar_name)) - call fld_list_add(fldsFrWav_num, fldsFrWav, 'Sw_lamult' ) - call fld_list_add(fldsFrWav_num, fldsFrWav, 'Sw_ustokes' ) - call fld_list_add(fldsFrWav_num, fldsFrWav, 'Sw_vstokes' ) - call fld_list_add(fldsFrWav_num, fldsFrWav, 'Sw_hstokes' ) - - call fld_list_add(fldsToWav_num, fldsToWav, trim(flds_scalar_name)) - call fld_list_add(fldsToWav_num, fldsToWav, 'Sa_u' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'Sa_v' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'Sa_tbot' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'Si_ifrac' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'So_t' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'So_u' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'So_v' ) - call fld_list_add(fldsToWav_num, fldsToWav, 'So_bldepth' ) - - do n = 1,fldsFrWav_num - if (mastertask) write(logunit,*)'Advertising From Xwav ',trim(fldsFrWav(n)%stdname) - call NUOPC_Advertise(exportState, standardName=fldsFrWav(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - - do n = 1,fldsToWav_num - if(mastertask) write(logunit,*)'Advertising To Xwav ',trim(fldsToWav(n)%stdname) - call NUOPC_Advertise(importState, standardName=fldsToWav(n)%stdname, & - TransferOfferGeomObject='will provide', rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - enddo - end if - - call ESMF_LogWrite(subname//' done', ESMF_LOGMSG_INFO, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Reset shr logging to original values - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeAdvertise - - !=============================================================================== - subroutine InitializeRealize(gcomp, importState, exportState, clock, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - type(ESMF_State) :: importState, exportState - type(ESMF_Clock) :: clock - integer, intent(out) :: rc - - ! local variables - integer :: shrlogunit ! original log unit - character(ESMF_MAXSTR) :: cvalue ! config data - character(len=*),parameter :: subname=trim(modName)//':(InitializeRealize) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - ! Reset shr logging to my log file - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - ! generate the mesh - call NUOPC_CompAttributeGet(gcomp, name='mesh_wav', value=cvalue, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - mesh = ESMF_MeshCreate(filename=trim(cvalue), fileformat=ESMF_FILEFORMAT_ESMFMESH, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - ! realize the actively coupled fields, now that a mesh is established - ! NUOPC_Realize "realizes" a previously advertised field in the importState and exportState - ! by replacing the advertised fields with the newly created fields of the same name. - call fld_list_realize( & - state=ExportState, & - fldlist=fldsFrWav, & - numflds=fldsFrWav_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dwavExport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - call fld_list_realize( & - state=importState, & - fldList=fldsToWav, & - numflds=fldsToWav_num, & - flds_scalar_name=flds_scalar_name, & - flds_scalar_num=flds_scalar_num, & - tag=subname//':dwavImport',& - mesh=mesh, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! Pack export state - call State_SetExport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nxg),flds_scalar_index_nx, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call State_SetScalar(dble(nyg),flds_scalar_index_ny, exportState, flds_scalar_name, flds_scalar_num, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine InitializeRealize - - !=============================================================================== - subroutine ModelAdvance(gcomp, rc) - - ! input/output variables - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - - ! local variables - type(ESMF_Clock) :: clock - type(ESMF_State) :: exportState - integer :: shrlogunit ! original log unit - character(len=*),parameter :: subname=trim(modName)//':(ModelAdvance) ' - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - call memcheck(subname, 3, mastertask) - - call shr_log_getLogUnit (shrlogunit) - call shr_log_setLogUnit (logunit) - - ! Pack export state - call NUOPC_ModelGet(gcomp, modelClock=clock, exportState=exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - call state_setexport(exportState, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ! diagnostics - if (dbug > 1) then - call State_diagnose(exportState,subname//':ES',rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if ( mastertask) then - call log_clock_advance(clock, 'XWAV', logunit, rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - endif - endif - - call shr_log_setLogUnit (shrlogunit) - - end subroutine ModelAdvance - - !=============================================================================== - subroutine state_setexport(exportState, rc) - - ! input/output variables - type(ESMF_State) , intent(inout) :: exportState - integer , intent(out) :: rc - - ! local variables - integer :: nfstart, ubound - integer :: n, nf, nind - real(r8), pointer :: lat(:) - real(r8), pointer :: lon(:) - integer :: spatialDim - integer :: numOwnedElements - real(R8), pointer :: ownedElemCoords(:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_MeshGet(mesh, spatialDim=spatialDim, numOwnedElements=numOwnedElements, rc=rc) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - allocate(ownedElemCoords(spatialDim*numOwnedElements)) - call ESMF_MeshGet(mesh, ownedElemCoords=ownedElemCoords) - if (ChkErr(rc,__LINE__,u_FILE_u)) return - - allocate(lon(numownedElements)) - allocate(lat(numownedElements)) - do n = 1,numownedElements - lon(n) = ownedElemCoords(2*n-1) - lat(n) = ownedElemCoords(2*n) - end do - - nfstart = 0 ! for fields that have ubound > 0 - do nf = 2,fldsFrWav_num ! Start from index 2 in order to skip the scalar field - ubound = fldsFrWav(nf)%ungridded_ubound - if (ubound == 0) then - call field_setexport(exportState, trim(fldsFrWav(nf)%stdname), lon, lat, nf=nf, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - else - nfstart = nfstart + nf + ubound - 1 - do nind = 1,ubound - call field_setexport(exportState, trim(fldsFrWav(nf)%stdname), lon, lat, nf=nfstart+nind-1, & - ungridded_index=nind, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - end do - end if - end do - - deallocate(lon) - deallocate(lat) - - end subroutine state_setexport - - !=============================================================================== - - subroutine field_setexport(exportState, fldname, lon, lat, nf, ungridded_index, rc) - - use shr_const_mod , only : pi=>shr_const_pi - - ! intput/otuput variables - type(ESMF_State) , intent(inout) :: exportState - character(len=*) , intent(in) :: fldname - real(r8) , intent(in) :: lon(:) - real(r8) , intent(in) :: lat(:) - integer , intent(in) :: nf - integer, optional , intent(in) :: ungridded_index - integer , intent(out) :: rc - - ! local variables - integer :: i, ncomp - type(ESMF_Field) :: lfield - real(r8), pointer :: data1d(:) - real(r8), pointer :: data2d(:,:) - !-------------------------------------------------- - - rc = ESMF_SUCCESS - - call ESMF_StateGet(exportState, itemName=trim(fldname), field=lfield, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - - ncomp = 7 - if (present(ungridded_index)) then - call ESMF_FieldGet(lfield, farrayPtr=data2d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - if (gridToFieldMap == 1) then - do i = 1,size(data2d, dim=1) - data2d(i,ungridded_index) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - else if (gridToFieldMap == 2) then - do i = 1,size(data2d, dim=2) - data2d(ungridded_index,i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - else - call ESMF_FieldGet(lfield, farrayPtr=data1d, rc=rc) - if (chkerr(rc,__LINE__,u_FILE_u)) return - do i = 1,size(data1d) - data1d(i) = (nf*100) * cos(pi*lat(i)/180.0_R8) * & - sin((pi*lon(i)/180.0_R8) - (ncomp-1)*(pi/3.0_R8) ) + (ncomp*10.0_R8) - end do - end if - - end subroutine field_setexport - - !=============================================================================== - subroutine ModelFinalize(gcomp, rc) - type(ESMF_GridComp) :: gcomp - integer, intent(out) :: rc - !------------------------------------------------------------------------------- - - rc = ESMF_SUCCESS - - if (mastertask) then - write(logunit,*) - write(logunit,*) 'xwav: end of main integration loop' - write(logunit,*) - end if - end subroutine ModelFinalize - -end module wav_comp_nuopc From a80e567f22f3e80998f8271f65c4bb2b52fff309 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 13 Nov 2025 17:14:27 -0700 Subject: [PATCH 090/153] Don't ignore externals directory There is a valid subdirectory named 'components': CIME/non_py/src/components. We don't want to ignore that. I thought about making the gitignore line more specific, but I'm not sure what we're actually trying to ignore here, so for now I'm just removing it. And while I'm at it, I'm removing the ignore for some other things that I don't think need to be here, or are too general. --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 976ac250596..978286a820e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,10 +23,7 @@ scripts/Tools/JENKINS* # Ignore anything that are produced under scripts "cases" directory /scripts/cases/ -#Ignore Externals -components -libraries -share +# Ignore some other files test_coverage/** *.bak From d23935cb0654c12b35bdd2b89e0165c03a87ac46 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Mar 2026 11:35:10 -0800 Subject: [PATCH 091/153] Remove unused var --- CIME/case/case_setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 5bdda870b02..376dab697e2 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -150,7 +150,6 @@ def _create_macros_cmake( os.mkdir(case_cmake_path) # This impl is coupled to contents of Macros.cmake - os_ = mach_obj.get_value("OS") mach = mach_obj.get_machine_name() macros = [ "universal.cmake", From e60e410968f19deac2bf2a92298b92ff12d2e284 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Mar 2026 13:16:53 -0700 Subject: [PATCH 092/153] Restore support for OS in cmake macros --- CIME/case/case_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 376dab697e2..587ed7f175b 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -150,10 +150,13 @@ def _create_macros_cmake( os.mkdir(case_cmake_path) # This impl is coupled to contents of Macros.cmake + os_ = mach_obj.get_value("OS") mach = mach_obj.get_machine_name() macros = [ "universal.cmake", + os_ + ".cmake", compiler + ".cmake", + "{}_{}.cmake".format(compiler, os), mach + ".cmake", "{}_{}.cmake".format(mach, compiler), "CMakeLists.txt", From 06ff204b047660b1e37bd04005134f3798b08825 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 4 Mar 2026 13:58:14 -0700 Subject: [PATCH 093/153] Allow comp_mach macros with a warning --- CIME/case/case_setup.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 587ed7f175b..38d6a4b0336 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -152,6 +152,7 @@ def _create_macros_cmake( # This impl is coupled to contents of Macros.cmake os_ = mach_obj.get_value("OS") mach = mach_obj.get_machine_name() + deprecated = "{}_{}.cmake".format(compiler, mach) macros = [ "universal.cmake", os_ + ".cmake", @@ -159,6 +160,7 @@ def _create_macros_cmake( "{}_{}.cmake".format(compiler, os), mach + ".cmake", "{}_{}.cmake".format(mach, compiler), + deprecated, "CMakeLists.txt", ] for macro in macros: @@ -166,10 +168,19 @@ def _create_macros_cmake( mach_repo_macro = os.path.join(cmake_macros_dir, "..", mach, macro) case_macro = os.path.join(case_cmake_path, macro) if not os.path.exists(case_macro): + copied = False if os.path.exists(mach_repo_macro): safe_copy(mach_repo_macro, case_cmake_path) + copied = True elif os.path.exists(repo_macro): safe_copy(repo_macro, case_cmake_path) + copied = True + + if copied and macro == deprecated: + logger.warning( + "\nWARNING: Macros of the form COMPILER_MACHINE.cmake are deprecated " + "and should be replaced with the form MACHINE_COMPILER.cmake\n" + ) copy_depends_files(mach, mach_obj.machines_dir, caseroot, compiler) From 336edbfc443b3a7f4a2cb52dbfd48c66ed04912d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 9 Feb 2026 15:10:02 -0800 Subject: [PATCH 094/153] Update Dockerfile, cime.yaml, and entrypoint.sh in docker/ --- docker/Dockerfile | 34 +++++------------- docker/cime.yaml | 4 +-- docker/entrypoint.sh | 82 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b9362775bcf..29f9aa91c76 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,46 +1,28 @@ -ARG BASE_TAG=latest -FROM condaforge/miniforge3:${BASE_TAG} AS base +ARG BASE_TAG=25.11.0-1 +FROM quay.io/condaforge/miniforge3:${BASE_TAG} AS base WORKDIR /home/cime -RUN mkdir -p \ - /home/cime/inputdata/cpl/gridmaps/oQU240 \ - /home/cime/inputdata/share/domains \ - /home/cime/timings \ - /home/cime/cases \ - /home/cime/archive \ - /home/cime/baselines \ - /home/cime/tools \ - && wget -O /home/cime/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ - && wget -O /home/cime/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ - && wget -O /home/cime/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive \ + apt-get install -y --no-install-recommends perl libxml-libxml-perl gosu \ + && rm -rf /var/lib/apt/lists/* COPY cime.yaml cime.yaml -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - perl libxml-libxml-perl && \ - rm -rf /var/lib/apt/lists/* - -RUN mamba install -y gosu tree - RUN mamba create -n e3sm -y --file cime.yaml python=3.10 'libnetcdf=4.9.1=*openmpi*' \ - && conda remove -n e3sm --force perl \ && conda clean -afy \ && rm -rf /opt/conda/pkgs/* RUN mamba create -n cesm -y --file cime.yaml python=3.10 'libnetcdf>=4.9.2=*openmpi*' \ - && conda remove -n cesm --force perl \ && conda clean -afy \ && rm -rf /opt/conda/pkgs/* COPY .cime .cime COPY entrypoint.sh /entrypoint.sh +RUN bash -c "SKIP_ENTRYPOINT=true . /entrypoint.sh && download_input_data" + ENTRYPOINT [ "/entrypoint.sh" ] FROM base AS slurm diff --git a/docker/cime.yaml b/docker/cime.yaml index 52d58039fc1..cbaaf588108 100644 --- a/docker/cime.yaml +++ b/docker/cime.yaml @@ -11,6 +11,7 @@ dependencies: - m4 - pkg-config - vim + - nvim - rsync - openssh - tree @@ -21,8 +22,7 @@ dependencies: # libraries - lapack - blas - # Testing hdf5 is out of scope for CIME - # - hdf5<1.14.0=*openmpi* + - hdf5=*=*openmpi* - netcdf-fortran=*=*openmpi* - libpnetcdf=*=*openmpi* - esmf=*=*openmpi* diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 5e78a7c5ccf..8e46f0febb6 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -21,38 +21,94 @@ function fix_arflags() { fi } -if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then +function build_cprnc() { + cprnc_dir="${SRC_PATH:-`pwd`}/CIME/non_py/cprnc" + + pushd `mktemp -d` + + cmake "${cprnc_dir}" + + make + + cp cprnc /home/cime/tools/cprnc + + popd +} + +function download_input_data() { + mkdir -p /home/cime/inputdata/cpl/gridmaps/oQU240 \ + /home/cime/inputdata/share/domains \ + /home/cime/timings \ + /home/cime/cases \ + /home/cime/archive \ + /home/cime/baselines \ + /home/cime/tools + + wget -O /home/cime/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc + + wget -O /home/cime/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc + + wget -O /home/cime/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc +} + +function link_config_machines() { if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf /home/cime/.cime/config_machines.v2.xml /home/cime/.cime/config_machines.xml + ln -sf ${1}/.cime/config_machines.v2.xml ${1}/.cime/config_machines.xml elif [[ "${CIME_MODEL}" == "cesm" ]]; then export ESMFMKFILE=/opt/conda/envs/cesm/lib/esmf.mk - ln -sf /home/cime/.cime/config_machines.v3.xml /home/cime/.cime/config_machines.xml + ln -sf ${1}/.cime/config_machines.v3.xml ${1}/.cime/config_machines.xml fi +} - if [[ "${USER_ID}" == "0" ]]; then - cp -rf /home/cime/.cime /root/ +user_home=/home/cime + +if [[ "${USER_ID}" == "0" ]]; then + user_home=/root + + cp -rf /home/cime/.cime /root/ +fi + +{ + echo "source /opt/conda/etc/profile.d/conda.sh" + echo "conda activate base" + echo "if [[ ${CIME_MODEL} == 'e3sm' ]]; then" + echo " conda activate e3sm" + echo "elif [[ ${CIME_MODEL} == 'cesm' ]]; then" + echo " conda activate cesm" + echo "fi" + # need this to perfer host perl over conda + echo "export PATH=/usr/bin:\$PATH" +} > ${user_home}/.bashrc - git config --global --add safe.directory "*" +link_config_machines ${user_home} +if [[ -e "${PWD}/.git" ]]; then + git config --global --add safe.directory "*" +elif [[ -n "${SRC_PATH}" ]]; then + pushd "${SRC_PATH}" + git config --global --add safe.directory "*" + popd +fi + +if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then + if [[ "${USER_ID}" == "0" ]]; then exec "${@}" else groupmod -g "${GROUP_ID}" -n cime ubuntu usermod -d /home/cime -u "${USER_ID}" -g "${GROUP_ID}" -l cime ubuntu chown -R cime:cime /home/cime + chmod -R 775 /home/cime if [[ -n "${SRC_PATH}" ]] && [[ -e "${SRC_PATH}" ]]; then chown -R cime:cime "${SRC_PATH}" - - git config --global --add safe.directory "*" + chmod -R 775 "${SRC_PATH}" fi - { - echo "source /opt/conda/etc/profile.d/conda.sh" - echo "conda activate base" - } > /home/cime/.bashrc - gosu "${USER_ID}" "${@}" fi fi From db703889cc8174132c1533944dada4c3e493e66f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 10 Feb 2026 10:34:14 -0800 Subject: [PATCH 095/153] Fixes base image to always use latest --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 29f9aa91c76..66e201d7e6f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BASE_TAG=25.11.0-1 +ARG BASE_TAG=latest FROM quay.io/condaforge/miniforge3:${BASE_TAG} AS base WORKDIR /home/cime From 3bb231c0747cbd6b2bfad64783003029349d0fbe Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 10 Feb 2026 11:22:58 -0800 Subject: [PATCH 096/153] Cleans up entrypoint --- .github/workflows/testing.yml | 14 +++-- docker/entrypoint.sh | 109 +++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 46 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ce15387d08c..45574c0cbc2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -132,6 +132,9 @@ jobs: CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" run: | + # TODO remove just for testing + export + source /opt/conda/etc/profile.d/conda.sh conda activate cesm @@ -140,9 +143,6 @@ jobs: pip install -r test-requirements.txt - # GitHub runner home is different than container - cp -rf /home/cime/.cime /github/home/ - pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 @@ -223,20 +223,24 @@ jobs: CIME_TEST_PLATFORM: ubuntu-latest SKIP_ENTRYPOINT: "true" run: | + # TODO remove just for testing + export + source /opt/conda/etc/profile.d/conda.sh conda activate ${{ matrix.model.name }} pip install -r test-requirements.txt + # load functions source /entrypoint.sh if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then fix_mct_makefiles ../externals/mct fi - # GitHub runner home is different than container - cp -rf /home/cime/.cime /github/home/ + # TODO remove just for testing + ls -la "${HOME}" if [[ "${CIME_MODEL}" == "e3sm" ]]; then ln -sf /github/home/.cime/config_machines.v2.xml /github/home/.cime/config_machines.xml diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 8e46f0febb6..4962c333d75 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,18 +1,37 @@ #!/bin/bash -export USER=root -export LOGNAME=root -export USER_ID=${USER_ID:-1000} -export GROUP_ID=${GROUP_ID:-1000} - +# Set up basic user, logname, and default group/user IDs +export USER=`id -nu` +export LOGNAME="${USER}" +export USER_ID="${USER_ID:-1000}" +export GROUP_ID="${GROUP_ID:-1000}" + +# Set static home path where .cime exists and container entrypoint options +CIME_HOME="/home/cime" SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" +SRC_PATH="${SRC_PATH:-${PWD}}" + + +# Determine HOME directory: use provided HOME in CI, /root if root, default otherwise +if [[ "${CI:-false}" == "true" ]]; then + HOME="${HOME}" +elif [[ "${USER_ID}" == "0" ]]; then + HOME="/root" +else + # Not populated in $HOME until gosu switches user + HOME="/home/cime" +fi + +# Fix AR variable in MCT-related Makefiles function fix_mct_makefiles() { fix_arflags "${1}/mct/Makefile" fix_arflags "${1}/mpeu/Makefile" fix_arflags "${1}/mpi-serial/Makefile" } + +# Add ARFLAGS to AR in a Makefile if .bak does not exist function fix_arflags() { if [[ ! -e "${1}.bak" ]]; then echo "Fixing AR variable in ${1}" @@ -21,8 +40,15 @@ function fix_arflags() { fi } + +# Build the cprnc tool from CIME sources function build_cprnc() { - cprnc_dir="${SRC_PATH:-`pwd`}/CIME/non_py/cprnc" + cprnc_dir="${PWD}/CIME/non_py/cprnc" + + if [[ ! -e "${cprnc_dir}" ]]; then + echo "CPRNC path does not exist. Change to CIME's root directory." + exit 0 + fi pushd `mktemp -d` @@ -30,48 +56,53 @@ function build_cprnc() { make - cp cprnc /home/cime/tools/cprnc + # Needs to be copied into the machines configured tool path + cp cprnc "${CIME_HOME}/tools/cprnc" popd } + +# Download input data needed for model setup function download_input_data() { - mkdir -p /home/cime/inputdata/cpl/gridmaps/oQU240 \ - /home/cime/inputdata/share/domains \ - /home/cime/timings \ - /home/cime/cases \ - /home/cime/archive \ - /home/cime/baselines \ - /home/cime/tools - - wget -O /home/cime/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ + mkdir -p "${CIME_HOME}/inputdata/cpl/gridmaps/oQU240" \ + "${CIME_HOME}/inputdata/share/domains" \ + "${CIME_HOME}/timings" \ + "${CIME_HOME}/cases" \ + "${CIME_HOME}/archive" \ + "${CIME_HOME}/baselines" \ + "${CIME_HOME}/tools" + + wget -O "${CIME_HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc - wget -O /home/cime/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ + wget -O "${CIME_HOME}/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc - wget -O /home/cime/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc \ + wget -O "${CIME_HOME}/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc } + +# Link correct config_machines file based on CIME_MODEL, also set ESMFMKFILE for cesm function link_config_machines() { if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf ${1}/.cime/config_machines.v2.xml ${1}/.cime/config_machines.xml + ln -sf "${CIME_HOME}/.cime/config_machines.v2.xml" "${HOME}/.cime/config_machines.xml" elif [[ "${CIME_MODEL}" == "cesm" ]]; then export ESMFMKFILE=/opt/conda/envs/cesm/lib/esmf.mk - ln -sf ${1}/.cime/config_machines.v3.xml ${1}/.cime/config_machines.xml + ln -sf "${CIME_HOME}/.cime/config_machines.v3.xml" "${HOME}/.cime/config_machines.xml" fi } -user_home=/home/cime - -if [[ "${USER_ID}" == "0" ]]; then - user_home=/root - cp -rf /home/cime/.cime /root/ +# If root or in CI, move .cime config to the appropriate home directory +if [[ "${USER_ID}" == "0" ]] || [[ "${CI:-false}" == "true" ]]; then + cp -rf "${CIME_HOME}/.cime" "${HOME}" fi + +# Write minimal .bashrc to activate correct conda environment and ensure system perl is preferred { echo "source /opt/conda/etc/profile.d/conda.sh" echo "conda activate base" @@ -80,35 +111,31 @@ fi echo "elif [[ ${CIME_MODEL} == 'cesm' ]]; then" echo " conda activate cesm" echo "fi" - # need this to perfer host perl over conda + # Clobber PATH so system perl is used instead of conda perl echo "export PATH=/usr/bin:\$PATH" -} > ${user_home}/.bashrc +} > "${HOME}/.bashrc" -link_config_machines ${user_home} -if [[ -e "${PWD}/.git" ]]; then - git config --global --add safe.directory "*" -elif [[ -n "${SRC_PATH}" ]]; then - pushd "${SRC_PATH}" +link_config_machines + +# Allow git to operate in any directory, for container/dev scenarios +if [[ -e "${SRC_PATH}/.git" ]]; then git config --global --add safe.directory "*" - popd fi + +# If not skipping entrypoint, set up user/group IDs and exec given command. if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then if [[ "${USER_ID}" == "0" ]]; then exec "${@}" else + # Modify user/group, useful for local in-container development groupmod -g "${GROUP_ID}" -n cime ubuntu - usermod -d /home/cime -u "${USER_ID}" -g "${GROUP_ID}" -l cime ubuntu + usermod -d "${HOME}" -u "${USER_ID}" -g "${GROUP_ID}" -l cime ubuntu - chown -R cime:cime /home/cime - chmod -R 775 /home/cime - - if [[ -n "${SRC_PATH}" ]] && [[ -e "${SRC_PATH}" ]]; then - chown -R cime:cime "${SRC_PATH}" - chmod -R 775 "${SRC_PATH}" - fi + chown -R cime:cime "${HOME}" gosu "${USER_ID}" "${@}" fi fi + From d462fce7bc15e9184d28a2215bdc699ebe3a3f55 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 10:24:40 -0800 Subject: [PATCH 097/153] Updates testing workflow --- .github/workflows/testing.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 45574c0cbc2..fe59e71d3d2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -132,8 +132,8 @@ jobs: CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" run: | - # TODO remove just for testing - export + # manually run the entrypoint + source /entrypoint.sh source /opt/conda/etc/profile.d/conda.sh @@ -223,8 +223,8 @@ jobs: CIME_TEST_PLATFORM: ubuntu-latest SKIP_ENTRYPOINT: "true" run: | - # TODO remove just for testing - export + # manually run the entrypoint + source /entrypoint.sh source /opt/conda/etc/profile.d/conda.sh @@ -232,9 +232,6 @@ jobs: pip install -r test-requirements.txt - # load functions - source /entrypoint.sh - if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then fix_mct_makefiles ../externals/mct fi From 7e39a0615d3e52bd96295ae968e4b8c40dd61b20 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 10:28:04 -0800 Subject: [PATCH 098/153] Fixes formatting --- docker/entrypoint.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 4962c333d75..0a52440233c 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -138,4 +138,3 @@ if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then gosu "${USER_ID}" "${@}" fi fi - From d8e7cd7fd479d0d8aa19d294be6d19d98f672c11 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 11:12:44 -0800 Subject: [PATCH 099/153] Explicitly installs wget/curl/subversion in container --- docker/Dockerfile | 2 +- docker/cime.yaml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 66e201d7e6f..4398a579baa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /home/cime RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends perl libxml-libxml-perl gosu \ + apt-get install -y --no-install-recommends perl libxml-libxml-perl gosu wget curl subversion \ && rm -rf /var/lib/apt/lists/* COPY cime.yaml cime.yaml diff --git a/docker/cime.yaml b/docker/cime.yaml index cbaaf588108..61b67f17ba8 100644 --- a/docker/cime.yaml +++ b/docker/cime.yaml @@ -5,9 +5,6 @@ dependencies: # development tools - cmake<4.0 # some libraries still require 3.5.x - make - - wget - - curl - - subversion - m4 - pkg-config - vim From 9c60f90ce32cd7e7646279e252a8726f436ce1e9 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 13:39:05 -0800 Subject: [PATCH 100/153] Replaces legacy backticks --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 0a52440233c..fdea1d3fe3d 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash # Set up basic user, logname, and default group/user IDs -export USER=`id -nu` +export USER="$(id -nu)" export LOGNAME="${USER}" export USER_ID="${USER_ID:-1000}" export GROUP_ID="${GROUP_ID:-1000}" From 286597c191979836a2f86161459ab7f4a777704d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 13:39:39 -0800 Subject: [PATCH 101/153] Only modify HOME if required --- docker/entrypoint.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index fdea1d3fe3d..a695b10c1e4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -13,11 +13,9 @@ SRC_PATH="${SRC_PATH:-${PWD}}" # Determine HOME directory: use provided HOME in CI, /root if root, default otherwise -if [[ "${CI:-false}" == "true" ]]; then - HOME="${HOME}" -elif [[ "${USER_ID}" == "0" ]]; then +if [[ "${USER_ID}" == "0" ]]; then HOME="/root" -else +elif [[ "${CI:-false}" == "false" ]]; then # Not populated in $HOME until gosu switches user HOME="/home/cime" fi From 78eb63a4d6be836ba3a44dbb82a190650fbd77ce Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 13:39:58 -0800 Subject: [PATCH 102/153] Fixes exit code --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a695b10c1e4..1b0550c5fdb 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -45,7 +45,7 @@ function build_cprnc() { if [[ ! -e "${cprnc_dir}" ]]; then echo "CPRNC path does not exist. Change to CIME's root directory." - exit 0 + exit 1 fi pushd `mktemp -d` From e05f5a1a05a7c5a3355be76ee9e07a6d2c4e1473 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 13:40:31 -0800 Subject: [PATCH 103/153] Fixes exit if cprnc temp directory cannot be created --- docker/entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 1b0550c5fdb..6df3fd6caa8 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -48,7 +48,7 @@ function build_cprnc() { exit 1 fi - pushd `mktemp -d` + pushd "$(mktemp -d)" || exit 1 cmake "${cprnc_dir}" @@ -57,7 +57,7 @@ function build_cprnc() { # Needs to be copied into the machines configured tool path cp cprnc "${CIME_HOME}/tools/cprnc" - popd + popd || exit 1 } From d3e06e4da18c7854825d89a4195b8d4e532ed69c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Feb 2026 13:41:04 -0800 Subject: [PATCH 104/153] Fixes forcing host over conda perl for shell and build tools --- docker/entrypoint.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 6df3fd6caa8..3f929f83ca8 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -109,8 +109,11 @@ fi echo "elif [[ ${CIME_MODEL} == 'cesm' ]]; then" echo " conda activate cesm" echo "fi" - # Clobber PATH so system perl is used instead of conda perl - echo "export PATH=/usr/bin:\$PATH" + + echo "export PERL=/usr/bin/perl" + + # prefer host perl + echo "hash -p /usr/bin/perl perl" } > "${HOME}/.bashrc" From c811b106cd1a5ceb47cdb458aec08ac45db6091a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 3 Mar 2026 23:16:18 -0800 Subject: [PATCH 105/153] Switches to spack for idempotent environment --- .github/workflows/testing.yml | 1 - docker/.cime/config_machines.v2.xml | 11 +-- docker/.cime/docker.cmake | 14 ++-- docker/.cime/docker/config_machines.xml | 11 +-- docker/Dockerfile | 104 ++++++++---------------- docker/cime.yaml | 37 --------- docker/entrypoint.sh | 72 +++++----------- docker/spack.yaml | 18 ++++ 8 files changed, 86 insertions(+), 182 deletions(-) delete mode 100644 docker/cime.yaml create mode 100644 docker/spack.yaml diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index fe59e71d3d2..e0a6da627f5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -62,7 +62,6 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - target: base context: docker/ push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/docker/.cime/config_machines.v2.xml b/docker/.cime/config_machines.v2.xml index 73b7ea56fb2..3cdd08084cd 100644 --- a/docker/.cime/config_machines.v2.xml +++ b/docker/.cime/config_machines.v2.xml @@ -37,11 +37,12 @@ 1 1 - /opt/conda/envs/e3sm - FALSE - /opt/conda/envs/e3sm - /opt/conda/envs/e3sm - /opt/conda/envs/e3sm + /opt/spack-envs/view + /opt/spack-envs/view + /opt/spack-envs/view + /opt/spack-envs/view/bin:$ENV{PATH} + /opt/spack-envs/view/lib + /opt/spack-envs/view/lib/pkgconfig diff --git a/docker/.cime/docker.cmake b/docker/.cime/docker.cmake index f8439e5e9a2..9251b999c2c 100644 --- a/docker/.cime/docker.cmake +++ b/docker/.cime/docker.cmake @@ -3,18 +3,14 @@ if (COMP_NAME STREQUAL gptl) string(APPEND CPPDEFS " -DHAVE_NANOTIME -DBIT64 -DHAVE_SLASHPROC -DHAVE_GETTIMEOFDAY") endif() -string(APPEND CMAKE_C_FLAGS_RELEASE " -O2 -g") -string(APPEND CMAKE_Fortran_FLAGS_RELEASE " -O2 -g") -string(APPEND CMAKE_CXX_FLAGS_RELEASE " -O2 -g") - -string(APPEND CMAKE_C_FLAGS_DEBUG " -O0") -string(APPEND CMAKE_Fortran_FLAGS_DEBUG " -O0") -string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O0") +string(APPEND CMAKE_C_FLAGS " -I/opt/spack-envs/view/include -O1 -g -fno-fast-math -frounding-math -fsignaling-nans -fno-inline -fno-aggressive-loop-optimizations") +string(APPEND CMAKE_Fortran_FLAGS " -I/opt/spack-envs/view/include -O1 -g -fno-fast-math -frounding-math -fsignaling-nans -fno-inline -fno-aggressive-loop-optimizations -ffpe-trap=invalid,zero,overflow") +string(APPEND CMAKE_CXX_FLAGS " -I/opt/spack-envs/view/include") # required for grid generation tests that use make if (CMAKE_SOURCE_DIR MATCHES "^.*TestGridGeneration.*$") - string(APPEND FFLAGS " -I/opt/conda/envs/$ENV{CIME_MODEL}/include") - string(APPEND SLIBS " -L/opt/conda/envs/$ENV{CIME_MODEL} -lnetcdf -lnetcdff") + string(APPEND FFLAGS " -I/opt/spack-envs/view/include") + string(APPEND SLIBS " -L/opt/spack-envs/view/lib -lnetcdf -lnetcdff") endif() # DEBUGGING variables diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml index 79fb6f4898d..588b8d62746 100644 --- a/docker/.cime/docker/config_machines.xml +++ b/docker/.cime/docker/config_machines.xml @@ -36,11 +36,12 @@ 1 1 - /opt/conda/envs/cesm - FALSE - /opt/conda/envs/cesm - /opt/conda/envs/cesm - /opt/conda/envs/cesm + /opt/spack-envs/view + /opt/spack-envs/view + /opt/spack-envs/view + /opt/spack-envs/view/bin:$ENV{PATH} + /opt/spack-envs/view/lib + /opt/spack-envs/view/lib/pkgconfig diff --git a/docker/Dockerfile b/docker/Dockerfile index 4398a579baa..ddf7808deb5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,78 +1,38 @@ -ARG BASE_TAG=latest -FROM quay.io/condaforge/miniforge3:${BASE_TAG} AS base - -WORKDIR /home/cime +FROM ghcr.io/spack/ubuntu-noble:1.0.4 AS builder RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends perl libxml-libxml-perl gosu wget curl subversion \ - && rm -rf /var/lib/apt/lists/* - -COPY cime.yaml cime.yaml - -RUN mamba create -n e3sm -y --file cime.yaml python=3.10 'libnetcdf=4.9.1=*openmpi*' \ - && conda clean -afy \ - && rm -rf /opt/conda/pkgs/* + && apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + libopenblas-openmp-dev \ + libopenblas0-openmp \ + libxml-libxml-perl \ + libxml2-utils \ + openmpi-bin \ + libopenmpi-dev \ + perl \ + pkg-config \ + python3-dev \ + subversion \ + wget \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /opt/spack-envs + +COPY spack.yaml /opt/spack-envs/spack.yaml + +RUN spack compiler find \ + && spack external find --not-buildable \ + && spack external find --not-buildable openmpi \ + && spack -e /opt/spack-envs install --fail-fast -RUN mamba create -n cesm -y --file cime.yaml python=3.10 'libnetcdf>=4.9.2=*openmpi*' \ - && conda clean -afy \ - && rm -rf /opt/conda/pkgs/* +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + vim \ + neovim \ + && rm -rf /var/lib/apt/lists/* \ + && curl -LsSf https://astral.sh/uv/install.sh | sh -COPY .cime .cime COPY entrypoint.sh /entrypoint.sh +COPY .cime /root/.cime -RUN bash -c "SKIP_ENTRYPOINT=true . /entrypoint.sh && download_input_data" - -ENTRYPOINT [ "/entrypoint.sh" ] - -FROM base AS slurm - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - munge slurmd slurm-client slurmctld && \ - rm -rf /var/lib/apt/lists/* && \ - sed -i"" "s/\(.*\)[^<]*\(<\/BATCH_SYSTEM>\)/\1slurm\2/g" ~/.cime/config_machines.xml - -COPY slurm/slurm.conf /etc/slurm-llnl/ -COPY slurm/config_batch.xml /root/.cime/ -COPY slurm/entrypoint_batch.sh /entrypoint_batch.sh - -FROM base AS pbs - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - curl ca-certificates software-properties-common \ - gcc make libtool libhwloc-dev libx11-dev libxt-dev libedit-dev \ - libical-dev ncurses-dev python-dev tcl-dev tk-dev swig libexpat-dev libssl-dev \ - libxext-dev libxft-dev autoconf automake \ - postgresql-12 postgresql-server-dev-all postgresql-contrib \ - expat libedit2 python3 sendmail-bin sudo tcl tk && \ - add-apt-repository ppa:deadsnakes/ppa && \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y python3.7 python3.7-dev && \ - rm -rf /var/lib/apt/lists/* - -RUN mkdir /src && pushd /src && \ - curl -LO https://github.com/openpbs/openpbs/archive/refs/tags/v20.0.1.tar.gz && \ - tar -xvf v20.0.1.tar.gz && \ - cd openpbs-20.0.1 && \ - sed -i"" 's/\(#include "list_link.h"\)/\1\n#include /' /src/openpbs-20.0.1/src/lib/Libifl/list_link.c && \ - export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && \ - ./autogen.sh && \ - PYTHON=/usr/bin/python3.7 \ - CFLAGS="`/usr/bin/python3.7m-config --cflags`" \ - LDFLAGS="`/usr/bin/python3.7m-config --ldflags`" \ - LIBS="-lpthread -lm -lpython3.7m" \ - ./configure --prefix=/opt/pbs && \ - make -j8 && \ - make install && \ - popd && \ - rm -rf /src && \ - sed -i"" "s/\(.*\)[^<]*\(<\/BATCH_SYSTEM>\)/\1pbs\2/g" ~/.cime/config_machines.xml - -COPY pbs/pbs.conf /etc/ -COPY pbs/config_batch.xml /root/.cime/ -COPY pbs/entrypoint_batch.sh /entrypoint_batch.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/cime.yaml b/docker/cime.yaml deleted file mode 100644 index 61b67f17ba8..00000000000 --- a/docker/cime.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: base -channels: - - conda-forge -dependencies: -# development tools - - cmake<4.0 # some libraries still require 3.5.x - - make - - m4 - - pkg-config - - vim - - nvim - - rsync - - openssh - - tree -# python packages - - pytest - - pytest-cov - - pyyaml -# libraries - - lapack - - blas - - hdf5=*=*openmpi* - - netcdf-fortran=*=*openmpi* - - libpnetcdf=*=*openmpi* - - esmf=*=*openmpi* - - openmpi - - gcc_linux-64 - - gxx_linux-64 - - gfortran_linux-64 - - gcc=12.* - - gxx=12.* - - gfortran=12.* - - compilers - - c-compiler - - cxx-compiler - - fortran-compiler -prefix: /opt/conda diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 3f929f83ca8..786f0d966e5 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -7,20 +7,10 @@ export USER_ID="${USER_ID:-1000}" export GROUP_ID="${GROUP_ID:-1000}" # Set static home path where .cime exists and container entrypoint options -CIME_HOME="/home/cime" SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" SRC_PATH="${SRC_PATH:-${PWD}}" -# Determine HOME directory: use provided HOME in CI, /root if root, default otherwise -if [[ "${USER_ID}" == "0" ]]; then - HOME="/root" -elif [[ "${CI:-false}" == "false" ]]; then - # Not populated in $HOME until gosu switches user - HOME="/home/cime" -fi - - # Fix AR variable in MCT-related Makefiles function fix_mct_makefiles() { fix_arflags "${1}/mct/Makefile" @@ -55,7 +45,7 @@ function build_cprnc() { make # Needs to be copied into the machines configured tool path - cp cprnc "${CIME_HOME}/tools/cprnc" + cp cprnc "${HOME}/tools/cprnc" popd || exit 1 } @@ -63,21 +53,21 @@ function build_cprnc() { # Download input data needed for model setup function download_input_data() { - mkdir -p "${CIME_HOME}/inputdata/cpl/gridmaps/oQU240" \ - "${CIME_HOME}/inputdata/share/domains" \ - "${CIME_HOME}/timings" \ - "${CIME_HOME}/cases" \ - "${CIME_HOME}/archive" \ - "${CIME_HOME}/baselines" \ - "${CIME_HOME}/tools" - - wget -O "${CIME_HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ + mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" \ + "${HOME}/inputdata/share/domains" \ + "${HOME}/timings" \ + "${HOME}/cases" \ + "${HOME}/archive" \ + "${HOME}/baselines" \ + "${HOME}/tools" + + wget -O "${HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc - wget -O "${CIME_HOME}/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc" \ + wget -O "${HOME}/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc - wget -O "${CIME_HOME}/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc" \ + wget -O "${HOME}/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc } @@ -85,35 +75,21 @@ function download_input_data() { # Link correct config_machines file based on CIME_MODEL, also set ESMFMKFILE for cesm function link_config_machines() { if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf "${CIME_HOME}/.cime/config_machines.v2.xml" "${HOME}/.cime/config_machines.xml" + ln -sf "${HOME}/.cime/config_machines.v2.xml" "${HOME}/.cime/config_machines.xml" elif [[ "${CIME_MODEL}" == "cesm" ]]; then export ESMFMKFILE=/opt/conda/envs/cesm/lib/esmf.mk - ln -sf "${CIME_HOME}/.cime/config_machines.v3.xml" "${HOME}/.cime/config_machines.xml" + ln -sf "${HOME}/.cime/config_machines.v3.xml" "${HOME}/.cime/config_machines.xml" fi } -# If root or in CI, move .cime config to the appropriate home directory -if [[ "${USER_ID}" == "0" ]] || [[ "${CI:-false}" == "true" ]]; then - cp -rf "${CIME_HOME}/.cime" "${HOME}" -fi - - # Write minimal .bashrc to activate correct conda environment and ensure system perl is preferred { - echo "source /opt/conda/etc/profile.d/conda.sh" - echo "conda activate base" - echo "if [[ ${CIME_MODEL} == 'e3sm' ]]; then" - echo " conda activate e3sm" - echo "elif [[ ${CIME_MODEL} == 'cesm' ]]; then" - echo " conda activate cesm" - echo "fi" - - echo "export PERL=/usr/bin/perl" - - # prefer host perl - echo "hash -p /usr/bin/perl perl" + echo "export PATH=\"/opt/spack-envs/view/bin:$PATH\"" + echo "export PKG_CONFIG_PATH=\"/opt/spack-envs/view/lib/pkgconfig\"" + echo "export LD_LIBRARY_PATH=\"/opt/spack-envs/view/lib\"" + echo "source \$HOME/.local/bin/env" } > "${HOME}/.bashrc" @@ -127,15 +103,5 @@ fi # If not skipping entrypoint, set up user/group IDs and exec given command. if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then - if [[ "${USER_ID}" == "0" ]]; then - exec "${@}" - else - # Modify user/group, useful for local in-container development - groupmod -g "${GROUP_ID}" -n cime ubuntu - usermod -d "${HOME}" -u "${USER_ID}" -g "${GROUP_ID}" -l cime ubuntu - - chown -R cime:cime "${HOME}" - - gosu "${USER_ID}" "${@}" - fi + exec "${@}" fi diff --git a/docker/spack.yaml b/docker/spack.yaml new file mode 100644 index 00000000000..a069663508b --- /dev/null +++ b/docker/spack.yaml @@ -0,0 +1,18 @@ +spack: + specs: + - hdf5@1.12.3+cxx+fortran+hl+mpi+shared %gcc@13.3.0 + - netcdf-c@4.9.2+mpi %gcc@13.3.0 + - netcdf-fortran@4.6.1 %gcc@13.3.0 + - parallel-netcdf@1.12.3 %gcc@13.3.0 + - esmf@8.8.1+mpi+netcdf~pnetcdf~external-parallelio %gcc@13.3.0 + concretizer: + unify: when_possible + reuse: true + targets: + granularity: generic + host_compatible: false + packages: + all: + target: [x86_64_v3] + view: + default: /opt/spack-envs/view From a917d73bc79abc409793625da86cf2a1ea6c6caa Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 00:09:44 -0800 Subject: [PATCH 106/153] Fixes CI home directory --- docker/entrypoint.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 786f0d966e5..04a8379cf92 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -8,8 +8,6 @@ export GROUP_ID="${GROUP_ID:-1000}" # Set static home path where .cime exists and container entrypoint options SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" -SRC_PATH="${SRC_PATH:-${PWD}}" - # Fix AR variable in MCT-related Makefiles function fix_mct_makefiles() { @@ -83,6 +81,10 @@ function link_config_machines() { fi } +if [[ "${CI:-false}" == "true" ]]; then + cp -rf /root/.cime "${HOME}" +fi + # Write minimal .bashrc to activate correct conda environment and ensure system perl is preferred { @@ -96,7 +98,7 @@ function link_config_machines() { link_config_machines # Allow git to operate in any directory, for container/dev scenarios -if [[ -e "${SRC_PATH}/.git" ]]; then +if [[ -e "${PWD}/.git" ]]; then git config --global --add safe.directory "*" fi From 28c06a6a6b7ea0a93d995b080f2fd126a74f4786 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 00:50:31 -0800 Subject: [PATCH 107/153] Fixes testing workflow --- .github/workflows/testing.yml | 37 +++++++++++++---------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e0a6da627f5..f242ae5afd1 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -122,6 +122,10 @@ jobs: with: path: cesm/cime submodules: "true" + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} - name: Run tests shell: bash working-directory: cesm/cime @@ -132,15 +136,10 @@ jobs: SKIP_ENTRYPOINT: "true" run: | # manually run the entrypoint - source /entrypoint.sh - - source /opt/conda/etc/profile.d/conda.sh - - conda activate cesm + SKIP_ENTRYPOINT="true" source /entrypoint.sh - mamba install -y 'python=${{ matrix.python-version }}' - - pip install -r test-requirements.txt + uv pip install -r test-requirements.txt + uv pip install pytest pytest-cov pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov @@ -213,6 +212,10 @@ jobs: with: path: /home/cime/inputdata key: inputdata-2 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.10" - name: Run tests shell: bash working-directory: ${{ matrix.model.name }}/cime @@ -223,27 +226,15 @@ jobs: SKIP_ENTRYPOINT: "true" run: | # manually run the entrypoint - source /entrypoint.sh - - source /opt/conda/etc/profile.d/conda.sh + SKIP_ENTRYPOINT=true source /entrypoint.sh - conda activate ${{ matrix.model.name }} - - pip install -r test-requirements.txt + uv pip install -r test-requirements.txt + uv pip install pytest pytest-cov if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then fix_mct_makefiles ../externals/mct fi - # TODO remove just for testing - ls -la "${HOME}" - - if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf /github/home/.cime/config_machines.v2.xml /github/home/.cime/config_machines.xml - else - ln -sf /github/home/.cime/config_machines.v3.xml /github/home/.cime/config_machines.xml - fi - git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" From 6fa1167a9ac3fb344bc389c1cfd7b5e6317ebd7e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 00:55:59 -0800 Subject: [PATCH 108/153] Fixes using uv pip --- .github/workflows/testing.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f242ae5afd1..c887e5f1af2 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -134,9 +134,10 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" + UV_SYSTEM_PYTHON: 1 run: | # manually run the entrypoint - SKIP_ENTRYPOINT="true" source /entrypoint.sh + source /entrypoint.sh uv pip install -r test-requirements.txt uv pip install pytest pytest-cov @@ -224,9 +225,10 @@ jobs: CIME_DRIVER: ${{ matrix.driver }} CIME_TEST_PLATFORM: ubuntu-latest SKIP_ENTRYPOINT: "true" + UV_SYSTEM_PYTHON: 1 run: | # manually run the entrypoint - SKIP_ENTRYPOINT=true source /entrypoint.sh + source /entrypoint.sh uv pip install -r test-requirements.txt uv pip install pytest pytest-cov From 8191658b914a56bdfffda0c653b99a8e16877a01 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:11:46 -0800 Subject: [PATCH 109/153] Fixes using uv in testing workflow --- .github/workflows/testing.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c887e5f1af2..9c2af3108d1 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -139,10 +139,13 @@ jobs: # manually run the entrypoint source /entrypoint.sh + uv python install '${{ matrix.python-version }}' + uv venv + source .venv/bin/activate uv pip install -r test-requirements.txt uv pip install pytest pytest-cov - pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* + uv run pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: @@ -230,6 +233,9 @@ jobs: # manually run the entrypoint source /entrypoint.sh + uv python install 3.10 + uv venv + source .venv/bin/activate uv pip install -r test-requirements.txt uv pip install pytest pytest-cov @@ -240,7 +246,7 @@ jobs: git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" - pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + uv run pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: From a4dd31b363a1c07473ba017d513574f479736bff Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:25:38 -0800 Subject: [PATCH 110/153] Updates contributing guide --- CONTRIBUTING.md | 44 +++------ doc/source/contributing-guide.rst | 152 +++++++----------------------- 2 files changed, 47 insertions(+), 149 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a70f247e9d4..4278cb975f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,11 +20,13 @@ For more information on contributing to open source projects, is a great starting point. Also, checkout the [Zen of Scientific Software Maintenance](https://jrleeman.github.io/ScientificSoftwareMaintenance/) for some guiding principles on how to create high quality scientific software contributions. +The canonical, detailed contributing guide for this repository is included in the source tree at `doc/source/contributing-guide.rst`; please consult that file as the single source of truth for developer workflows, testing, and container usage. + ## Getting Started Interested in helping extend CIME? Have code from your research that you believe others will -find useful? Have a few minutes to tackle an issue? In this guide we will get you setup and -integrated into contributing to CIME! +find useful? Have a few minutes to tackle an issue? This guide will get you set up to contribute +to CIME. ## What Can I Do? * Tackle any unassigned [issues](https://github.com/ESMCI/CIME/issues) you wish! @@ -44,7 +46,7 @@ The goal is to maintain a diverse community that's pleasant for everyone. include multiple bug fixes in a single pull request, but they should be related. For unrelated changes, please submit multiple pull requests. * Do not commit changes to files that are irrelevant to your feature or bugfix - (eg: .gitignore). + (e.g., .gitignore). * Be willing to accept constructive criticism as part of issuing a pull request, since the CIME developers are dedicated to ensuring that new features extend the system robustly and do not introduce new bugs. @@ -53,7 +55,7 @@ The goal is to maintain a diverse community that's pleasant for everyone. ## Reporting a bug When creating a new issue, please be as specific as possible. Include the version -of the code you were using, as well as what operating system you are running. +of the code you were using, as well as what operating system you are running. Include the commit SHA or tag (e.g., output of `git rev-parse HEAD`) and the branch name. If possible, include complete, minimal example code that reproduces the problem. ## Pull Requests @@ -62,34 +64,17 @@ We love pull requests from everyone. Fork, then clone the repo: git clone git@github.com:your-username/CIME.git -Additionally you may need to checkout the submodules with: +You will need to initialize and update submodules: cd CIME - git submodule update --init - -You will need to install CIME dependencies and edit config files -to tell CIME about your development machine. See the [CIME users guide](https://esmci.github.io/cime/users_guide/porting-cime.html) - -Run the scripts_regression_tests: - - cd CIME/tests - python scripts_regression_tests.py - -Alternatively with `pytest`: - - pytest CIME/tests - -Make your change. Add tests for your change. Make the tests pass to the same level as before your changes. - - cd CIME/tests - python scripts_regression_tests.py + git submodule update --init --recursive -Run [pre-commit](https://pre-commit.com/#usage) before committing changes and submitting a PR. +From here you can edit the code and run the unit tests following this [guide](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#pytest). - pip install pre-commit - pre-commit run -a +If you need to run the system tests you will need to have your respective model +checked out and on a supported machine. -Commit the changes you made. Chris Beams has written a [guide](https://chris.beams.io/posts/git-commit/) on how to write good commit messages. +Before creating your PR you will need to run the code quality checkers; see this [guide](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#code-quality). Push to your fork and [submit a pull request][pr]. @@ -102,8 +87,9 @@ We may suggest some changes or improvements or alternatives. Some things that will increase the chance that your pull request is accepted: * Write tests. -* Follow [PEP8][pep8] for style. (The `flake8` utility can help with this.) -* Write a [good commit message][commit]. +* Write documentation. +* Follow the [Code Quality guide](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#code-quality) +* Write a good commit message, we recommend using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) Pull requests will automatically have tests run by a Github Action. This includes running both the unit tests as well as `pre-commit`, which checks diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index c479ed864ca..05bd7bcdbd8 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -21,9 +21,9 @@ The `Case` class includes an array of the Case env classes. In the `configure` f Testing ------- -CIME splits it's testing into two categories `unit` and `sys`. +CIME splits its tests into two categories: `unit` and `sys`. -The `unit` category covers both doctests and unit tests. While the `sys` category covers regression tests. The tests are named accordingly e.g. `unit` tests are found as `CIME/tests/test_unit*`. +The `unit` category covers doctests and unit tests, while the `sys` category covers regression tests. Tests are named accordingly (e.g., unit tests: `CIME/tests/test_unit*`). How to run the tests ``````````````````````` @@ -41,13 +41,12 @@ To get started install `pytest` and `pytest-cov`. .. code-block:: bash + pip install -r test-requirements.txt pip install pytest pytest-cov - # or - # conda install -c conda-forge pytest pytest-cov Examples ........ -Runing all the ``sys`` and ``unit`` tests. +Running all the ``sys`` and ``unit`` tests. .. code-block:: bash @@ -59,13 +58,13 @@ Running only ``sys`` tests, ``sys`` can be replaced with ``unit`` to run only un pytest CIME/tests/test_sys.* -Runnig a specific test case. +Running a specific test case. .. code-block:: bash pytest CIME/tests/test_unit_case.py -A specific test can be ran with the followin. +A specific test can be run with the following. .. code-block:: bash @@ -80,7 +79,7 @@ You can pass either the module name or the file path of a test. Examples ........ -Runing all the ``sys`` and ``unit`` tests. +Running all the ``sys`` and ``unit`` tests. .. code-block:: bash @@ -98,7 +97,7 @@ Runnig a specific test case. python CIME/tests/scripts_regression_tests.py CIME.tests.test_unit_case -A specific test can be ran with the followin. +A specific test can be run with the following. .. code-block:: bash @@ -117,9 +116,7 @@ Installing pre-commit .. code-block:: bash - pip install pre_commit - # or - # conda install -c conda-forge pre_commit + pip install pre-commit Running pre-commit `````````````````` @@ -138,127 +135,42 @@ If you install these scripts then `pre-commit` will automatically run on `git co Docker container ---------------- -GitHub actions runs all CIME's tests in containers. The dockerfile can be found under the `docker/` directory. - -You can skip building the container and use the same container from the GitHub actions using the following commands. This will pull the latest [image](https://hub.docker.com/r/jasonb87/cime/tags), see the available [run modifiers](#running-the-container) to customize the container. - -The current container supports the ``GNU`` compiler and ``OpenMPI`` library. - -Running -``````` -The default environment is similar to the one used by GitHub Actions. It will clone CIME into `/src/cime`, set `CIME_MODEL=cesm` and run CESM's `checkout_externals`. This will create a minimum base environment to run both unit and system tests. - -The `CIME_MODEL` environment vairable will change the environment that is created. - -Setting it to `E3SM` will clone E3SM into `/src/E3SM`, checkout the submodules and update the CIME repository using `CIME_REPO` and `CIME_BRANCH`. - -Setting it to `CESM` will clone CESM into `/src/CESM`, run `checkout_externals` and update the CIME repository using `CIME_REPO` and `CIME_BRANCH`. - -The container can further be modified using the environment variables defined below. - -.. code-block:: bash - - docker run -it --name cime --hostname docker cime:latest bash - - -.. code-block:: bash - - docker run -it --name cime --hostname docker -e CIME_MODEL=e3sm cime:latest bash +CIME provides a container that the CI uses to run all the testing. This container -.. note:: - - It's recommended when running the container to pass `--hostname docker` as it will match the custom machine defined in `config_machines.xml`. If this is omitted, `--machine docker` must be passed to CIME commands in order to use the correct machine definition. +can also be used to test locally providing a reproducible environment. The -Environment variables -::::::::::::::::::::: +compiler is ``GNU`` and the MPI implementation is ``OpenMPI``. -Environment variables to modify the container environment. - -| Name | Description | Default | -| ---- | ----------- | ------- | -| INIT | Set to false to skip init | true | -| GIT_SHALLOW | Performs shallow checkouts, to save time | false | -| UPDATE_CIME | Setting this will cause the CIME repository to be updated using `CIME_REPO` and `CIME_BRANCH` | "false" | -| CIME_MODEL | Setting this will change which environment is loaded | | -| CIME_REPO | CIME repository URL | https://github.com/ESMCI/cime | -| CIME_BRANCH | CIME branch that will be cloned | master | -| E3SM_REPO | E3SM repository URL | https://github.com/E3SM-Project/E3SM | -| E3SM_BRANCH | E3SM branch that will be cloned | master | -| CESM_REPO | CESM repository URL | https://github.com/ESCOMP/CESM | -| CESM_BRANCH | CESM branch that will be cloned | master | - -Examples -:::::::: -.. code-block:: bash - - docker run -it -e INIT=false cime:latest bash - -.. code-block:: bash - - docker run -it -e CIME_REPO=https://github.com/user/cime -e CIME_BRANCH=updates_xyz cime:latest bash - -Persisting data -::::::::::::::: - -The `config_machines.xml` definition as been setup to provided persistance for inputdata, cases, archives and tools. The following paths can be mounted as volumes to provide persistance. - -* /storage/inputdata -* /storage/cases -* /storage/archives -* /storage/tools +The image can be pulled from ``ghcr.io``. .. code-block:: bash - docker run -it -v ${PWD}/data-cache:/storage/inputdata cime:latest bash + docker pull ghcr.io/esmci/cime:latest -It's also possible to persist the source git repositories. +or can be built locally. .. code-block:: bash - docker run -it -v ${PWD}/src:/src cime:latest bash + docker build -t ghcr.io/esmci/cime:latest docker/ -Local git respositories can be mounted as well. +Running +``````` +The container does not provide any source, as such you will need to bind +mount the model+cime directory and define which model is being used. The +following example assumes the model is checked out in ``$SRC_PATH``. .. code-block:: bash - docker run -v ${PWD}:/src/cime cime:latest bash - - docker run -v ${PWD}:/src/E3SM cime:latest bash - -Building -```````` -The container provides 3 targets. + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest bash -* base - Base image with no batch system. -* slurm - Slurm batch system with configuration and single queue. -* pbs - PBS batch system with configuration and single queue. +This example will drop into a shell where CIME commands or tests can be run. +The options are broken down below. -.. code-block:: bash - - docker build -t ghcr.io/ESMCI/cime:latest --target docker/ - -Customizing -::::::::::: -When building the container some features can be customized. Multiple `--build-arg` arguments can be passed. - -.. code-block:: bash - - docker build -t ghcr.io/ESMCI/cime:latest --build-arg {name}={value} docker/ - -+------------------------+-----------------------------------------------+---------+ -| Argument | Description | Default | -+========================+===============================================+=========+ -| MAMBAFORGE_VERSION | Version of the condaforge/mambaforge image | 4.11.0-0| -| | used as a base | | -+------------------------+-----------------------------------------------+---------+ -| PNETCDF_VERSION | Parallel NetCDF version to build | 1.12.1 | -+------------------------+-----------------------------------------------+---------+ -| LIBNETCDF_VERSION | Version of libnetcdf, the default will | 4.8.1 | -| | install the latest | | -+------------------------+-----------------------------------------------+---------+ -| NETCDF_FORTRAN_VERSION | Version of netcdf-fortran, the default will | 4.5.4 | -| | install the latest | | -+------------------------+-----------------------------------------------+---------+ -| ESMF_VERSION | Version of ESMF, the default will install the | 8.2.0 | -| | latest | | -+------------------------+-----------------------------------------------+---------+ +- ``--hostname docker`` is required to tell CIME which machine definition to use. +- ``-e CIME_MODEL=e3sm`` defines the model. +- ``-v ${SRC_PATH}:/root/model`` passes through the model source. +- ``-v `pwd`/test-cases:/root/cases`` stores cases in the current directory under ``test-cases``. +- ``-v `pwd`/inputdata:/root/inputdata`` stores inputdata in the current directory under ``inputdata``. +- ``-w /root/E3SM/cime`` set the current working directory to CIME's root. +- ``ghcr.io/esmci/cime:latest`` container image. +- ``bash`` the command to run in the container. From a612bcbcd3ad7a2f40eeb3d5af5c209283c1aa2a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:27:17 -0800 Subject: [PATCH 111/153] Fixes black formatting --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 9c2af3108d1..50340d36cff 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -134,7 +134,7 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" - UV_SYSTEM_PYTHON: 1 + UV_SYSTEM_PYTHON: 1 run: | # manually run the entrypoint source /entrypoint.sh From 7d66fe50bfd748f6eafa25e7804eec5d058b80e5 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:28:04 -0800 Subject: [PATCH 112/153] Fixes setting up uv python --- .github/workflows/testing.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 50340d36cff..d2672c5dd19 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -139,7 +139,6 @@ jobs: # manually run the entrypoint source /entrypoint.sh - uv python install '${{ matrix.python-version }}' uv venv source .venv/bin/activate uv pip install -r test-requirements.txt @@ -233,7 +232,6 @@ jobs: # manually run the entrypoint source /entrypoint.sh - uv python install 3.10 uv venv source .venv/bin/activate uv pip install -r test-requirements.txt From 2e730de29b32c9bb0e92c2b8b276229f4999ecf3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:35:00 -0800 Subject: [PATCH 113/153] Fixes pip install --- .github/workflows/testing.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d2672c5dd19..dd49a36a4b4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -141,8 +141,8 @@ jobs: uv venv source .venv/bin/activate - uv pip install -r test-requirements.txt - uv pip install pytest pytest-cov + pip install -r test-requirements.txt + pip install pytest pytest-cov uv run pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov @@ -234,8 +234,8 @@ jobs: uv venv source .venv/bin/activate - uv pip install -r test-requirements.txt - uv pip install pytest pytest-cov + pip install -r test-requirements.txt + pip install pytest pytest-cov if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then fix_mct_makefiles ../externals/mct From 5ddf24490fac58118d9fe7c86a78dd1101bf2952 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 01:41:09 -0800 Subject: [PATCH 114/153] Fixes uv management --- .github/workflows/testing.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index dd49a36a4b4..5ee7edc2be7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -124,8 +124,6 @@ jobs: submodules: "true" - name: Install uv uses: astral-sh/setup-uv@v7 - with: - python-version: ${{ matrix.python-version }} - name: Run tests shell: bash working-directory: cesm/cime @@ -134,15 +132,15 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" - UV_SYSTEM_PYTHON: 1 run: | # manually run the entrypoint source /entrypoint.sh + uv python install "${{ matrix.python-version }}" uv venv source .venv/bin/activate - pip install -r test-requirements.txt - pip install pytest pytest-cov + uv pip install -r test-requirements.txt + uv pip install pytest pytest-cov uv run pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov @@ -227,15 +225,15 @@ jobs: CIME_DRIVER: ${{ matrix.driver }} CIME_TEST_PLATFORM: ubuntu-latest SKIP_ENTRYPOINT: "true" - UV_SYSTEM_PYTHON: 1 run: | # manually run the entrypoint source /entrypoint.sh + uv python install 3.10 uv venv source .venv/bin/activate - pip install -r test-requirements.txt - pip install pytest pytest-cov + uv pip install -r test-requirements.txt + uv pip install pytest pytest-cov if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then fix_mct_makefiles ../externals/mct From ffc660d1e6a0e582204a0e289b927e9b7252817f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 10:46:01 -0800 Subject: [PATCH 115/153] Fixes entrypoint and adds a default venv --- docker/Dockerfile | 10 ++++++++-- docker/entrypoint.sh | 15 +++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ddf7808deb5..02bb7433be6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,8 +29,14 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ vim \ neovim \ - && rm -rf /var/lib/apt/lists/* \ - && curl -LsSf https://astral.sh/uv/install.sh | sh + && rm -rf /var/lib/apt/lists/* + +RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ + && source ~/.local/bin/env \ + && uv python install 3.10 \ + && uv venv \ + && source .venv/bin/activate \ + && uv pip install pytest pytest-cov COPY entrypoint.sh /entrypoint.sh COPY .cime /root/.cime diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 04a8379cf92..684bb64d03f 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -85,16 +85,6 @@ if [[ "${CI:-false}" == "true" ]]; then cp -rf /root/.cime "${HOME}" fi - -# Write minimal .bashrc to activate correct conda environment and ensure system perl is preferred -{ - echo "export PATH=\"/opt/spack-envs/view/bin:$PATH\"" - echo "export PKG_CONFIG_PATH=\"/opt/spack-envs/view/lib/pkgconfig\"" - echo "export LD_LIBRARY_PATH=\"/opt/spack-envs/view/lib\"" - echo "source \$HOME/.local/bin/env" -} > "${HOME}/.bashrc" - - link_config_machines # Allow git to operate in any directory, for container/dev scenarios @@ -102,6 +92,11 @@ if [[ -e "${PWD}/.git" ]]; then git config --global --add safe.directory "*" fi +export PATH=/opt/spack-envs/view/bin:$PATH +export PKG_CONFIG_PATH=/opt/spakc-envs/view/pkgconfig +export LD_LIBRARY_PATH=/opt/spack-envs/view/lib +source ${HOME}/.local/bin/env +source ${HOME}/.venv/bin/activate # If not skipping entrypoint, set up user/group IDs and exec given command. if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then From 88c557e6264e2814880f44e8e3d8f795384552d3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 10:54:57 -0800 Subject: [PATCH 116/153] Updates documentation --- CONTRIBUTING.md | 5 +++-- doc/source/contributing-guide.rst | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4278cb975f5..88207281515 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,8 +71,9 @@ You will need to initialize and update submodules: From here you can edit the code and run the unit tests following this [guide](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#pytest). -If you need to run the system tests you will need to have your respective model -checked out and on a supported machine. +When running the ``unit`` tests you can specify any valid machine e.g. docker and the tests will run. + +If you need to run the ``system`` tests you will need to have your respective model checked out and on a supported machine. Alternatively you can use CIME [container](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#docker-container) which is used in our GitHub CI testing. Before creating your PR you will need to run the code quality checkers; see this [guide](https://esmci.github.io/cime/versions/master/html/contributing-guide.html#code-quality). diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index 05bd7bcdbd8..d0138d3b448 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -168,9 +168,20 @@ The options are broken down below. - ``--hostname docker`` is required to tell CIME which machine definition to use. - ``-e CIME_MODEL=e3sm`` defines the model. -- ``-v ${SRC_PATH}:/root/model`` passes through the model source. +- ``-v ${SRC_PATH}:/root/E3SM`` passes through the model source. - ``-v `pwd`/test-cases:/root/cases`` stores cases in the current directory under ``test-cases``. - ``-v `pwd`/inputdata:/root/inputdata`` stores inputdata in the current directory under ``inputdata``. - ``-w /root/E3SM/cime`` set the current working directory to CIME's root. - ``ghcr.io/esmci/cime:latest`` container image. - ``bash`` the command to run in the container. + +You can even run CIME or testing without a shell. + +.. code-block:: bash + + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest pytest CIME/tests/test_unit* + +.. code-block:: bash + + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest ./scripts/create_test SMS.f19_g16.S + From 2969950f0e4ffb789c19271618591bb09e2c8b7f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 11:32:54 -0800 Subject: [PATCH 117/153] Fixes not loading uv python when running in CI --- docker/entrypoint.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 684bb64d03f..999e5601758 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -95,8 +95,11 @@ fi export PATH=/opt/spack-envs/view/bin:$PATH export PKG_CONFIG_PATH=/opt/spakc-envs/view/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib -source ${HOME}/.local/bin/env -source ${HOME}/.venv/bin/activate + +if [[ "${CI:-false}" == "true" ]]; then + source ${HOME}/.local/bin/env + source ${HOME}/.venv/bin/activate +fi # If not skipping entrypoint, set up user/group IDs and exec given command. if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then From 9811e64f7fa4103243dc99b810eab07d85a212a3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 11:38:52 -0800 Subject: [PATCH 118/153] Adds missing ESMFMKFILE --- docker/entrypoint.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 999e5601758..a9107b2ed7b 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -95,6 +95,7 @@ fi export PATH=/opt/spack-envs/view/bin:$PATH export PKG_CONFIG_PATH=/opt/spakc-envs/view/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib +export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk if [[ "${CI:-false}" == "true" ]]; then source ${HOME}/.local/bin/env From 124cd0f847b4e27b441ce4e46d8517d6200bcd4f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 4 Mar 2026 13:45:37 -0800 Subject: [PATCH 119/153] fix: not loading uv in CI --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a9107b2ed7b..3eee5ca08eb 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -97,7 +97,7 @@ export PKG_CONFIG_PATH=/opt/spakc-envs/view/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk -if [[ "${CI:-false}" == "true" ]]; then +if [[ "${CI:-false}" == "false" ]]; then source ${HOME}/.local/bin/env source ${HOME}/.venv/bin/activate fi From df08a6107039f122e932abd804473f2d02550f94 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 10 Mar 2026 10:58:13 -0700 Subject: [PATCH 120/153] fix: adds debug --- .github/workflows/testing.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 5ee7edc2be7..062bb18598c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -229,6 +229,9 @@ jobs: # manually run the entrypoint source /entrypoint.sh + # TODO remove, debug only + export + uv python install 3.10 uv venv source .venv/bin/activate From bcf2be4cdf3244c0c27bb6044abce63c201cf581 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 10 Mar 2026 14:20:26 -0700 Subject: [PATCH 121/153] fix: disable fixing mct ARFLAGS --- .github/workflows/testing.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 062bb18598c..155e76e8a79 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -238,9 +238,9 @@ jobs: uv pip install -r test-requirements.txt uv pip install pytest pytest-cov - if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then - fix_mct_makefiles ../externals/mct - fi + # if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then + # fix_mct_makefiles ../externals/mct + # fi git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" From a0fb2ca4515ef94436c5b07902bb3803e42b9881 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 12:05:10 -0700 Subject: [PATCH 122/153] fix: machine directories --- docker/.cime/config_machines.v2.xml | 14 +++++++------- docker/.cime/docker/config_machines.xml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docker/.cime/config_machines.v2.xml b/docker/.cime/config_machines.v2.xml index 3cdd08084cd..6ed4d6c9a6b 100644 --- a/docker/.cime/config_machines.v2.xml +++ b/docker/.cime/config_machines.v2.xml @@ -9,14 +9,14 @@ gnu,gnuX openmpi CIME - /home/cime/timings + /root/timings CIME - /home/cime/cases - /home/cime/inputdata - /home/cime/inputdata-clmforc - /home/cime/archive/$CASE - /home/cime/baselines/$COMPILER - /home/cime/tools/cprnc + /root/cases + /root/inputdata + /root/inputdata-clmforc + /root/archive/$CASE + /root/baselines/$COMPILER + /root/tools/cprnc make 4 e3sm_developer diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml index 588b8d62746..cbe42413ba3 100644 --- a/docker/.cime/docker/config_machines.xml +++ b/docker/.cime/docker/config_machines.xml @@ -8,14 +8,14 @@ gnu,gnuX openmpi CIME - /home/cime/timings + /root/timings CIME - /home/cime/cases - /home/cime/inputdata - /home/cime/inputdata-clmforc - /home/cime/archive/$CASE - /home/cime/baselines/$COMPILER - /home/cime/tools/cprnc + /root/cases + /root/inputdata + /root/inputdata-clmforc + /root/archive/$CASE + /root/baselines/$COMPILER + /root/tools/cprnc make 4 e3sm_developer From 1fbec2472cca5c815e926b2511ad0aafde3f1d44 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 12:05:31 -0700 Subject: [PATCH 123/153] fix: PKG_CONFIG_PATH typo and path --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 3eee5ca08eb..de10b68f5ab 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -93,7 +93,7 @@ if [[ -e "${PWD}/.git" ]]; then fi export PATH=/opt/spack-envs/view/bin:$PATH -export PKG_CONFIG_PATH=/opt/spakc-envs/view/pkgconfig +export PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk From cf103359ae21f481968a19ad119522bf2f5c77e2 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 12:24:31 -0700 Subject: [PATCH 124/153] fix: test requirements and build context --- .github/workflows/testing.yml | 3 --- docker/Dockerfile | 10 ++++++---- test-requirements.txt | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 155e76e8a79..3686e658d4e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -140,7 +140,6 @@ jobs: uv venv source .venv/bin/activate uv pip install -r test-requirements.txt - uv pip install pytest pytest-cov uv run pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov @@ -236,8 +235,6 @@ jobs: uv venv source .venv/bin/activate uv pip install -r test-requirements.txt - uv pip install pytest pytest-cov - # if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then # fix_mct_makefiles ../externals/mct # fi diff --git a/docker/Dockerfile b/docker/Dockerfile index 02bb7433be6..6405ed28410 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,7 +18,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /opt/spack-envs -COPY spack.yaml /opt/spack-envs/spack.yaml +COPY docker/spack.yaml /opt/spack-envs/spack.yaml RUN spack compiler find \ && spack external find --not-buildable \ @@ -31,14 +31,16 @@ RUN apt-get update \ neovim \ && rm -rf /var/lib/apt/lists/* +COPY test-requirements.txt /requirements.txt + RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ && source ~/.local/bin/env \ && uv python install 3.10 \ && uv venv \ && source .venv/bin/activate \ - && uv pip install pytest pytest-cov + && uv pip install -r /requirements.txt -COPY entrypoint.sh /entrypoint.sh -COPY .cime /root/.cime +COPY docker/entrypoint.sh /entrypoint.sh +COPY docker/.cime /root/.cime ENTRYPOINT ["/entrypoint.sh"] diff --git a/test-requirements.txt b/test-requirements.txt index d4abdec4d0f..9d22f450869 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,3 @@ evv4esm +pytest +pytest-cov From 3a3cd4e3269a9205ad3f09a91ca523739e7c8e60 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 12:25:03 -0700 Subject: [PATCH 125/153] fix: removes debugging and unused scripting --- .github/workflows/testing.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 3686e658d4e..0c5968f9bf1 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -228,16 +228,10 @@ jobs: # manually run the entrypoint source /entrypoint.sh - # TODO remove, debug only - export - uv python install 3.10 uv venv source .venv/bin/activate uv pip install -r test-requirements.txt - # if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then - # fix_mct_makefiles ../externals/mct - # fi git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" From 47a0e7e8172fd7b357ebd28a57cec1057b97e0e4 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 12:25:16 -0700 Subject: [PATCH 126/153] fix: updates doc --- doc/source/contributing-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index d0138d3b448..04b0253876f 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -147,11 +147,11 @@ The image can be pulled from ``ghcr.io``. docker pull ghcr.io/esmci/cime:latest -or can be built locally. +or can be built locally. The build context needs to be set to the root of the CIME repository. .. code-block:: bash - docker build -t ghcr.io/esmci/cime:latest docker/ + docker build -t ghcr.io/esmci/cime:latest -f docker/Dockerfile . Running ``````` From a230764921cc61776f258eaada57725ca686dac2 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 13:01:56 -0700 Subject: [PATCH 127/153] fix: build job config --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0c5968f9bf1..4a13d1f7cbe 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -62,7 +62,7 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - context: docker/ + file: docker/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From cecca55c2fb58b323c524a731c9ad0445064c97c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 13:44:49 -0700 Subject: [PATCH 128/153] fix: sets context to current directory --- .github/workflows/testing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4a13d1f7cbe..8d07e4d9392 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -63,6 +63,7 @@ jobs: uses: docker/build-push-action@v6 with: file: docker/Dockerfile + context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 4ee43c34a724b2ff24bba23429f1e6bcdc7dd472 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 14:11:14 -0700 Subject: [PATCH 129/153] fix: download input data --- docker/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6405ed28410..3d34a883b40 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,4 +43,7 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ COPY docker/entrypoint.sh /entrypoint.sh COPY docker/.cime /root/.cime +RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ + && download_input_data + ENTRYPOINT ["/entrypoint.sh"] From 3ff3abbfd5dd4082809d37d7acf06d1067730d43 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 11 Mar 2026 15:14:28 -0700 Subject: [PATCH 130/153] fix: archive paths for workflow failures --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8d07e4d9392..60e85c40007 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -249,7 +249,7 @@ jobs: - name: Create testing log archive if: ${{ failure() }} shell: bash - run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}-${{ matrix.model.name }}-${{ matrix.driver }}.tar.gz /home/cime/cases /home/cime/baselines /home/cime/archive + run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}-${{ matrix.model.name }}-${{ matrix.driver }}.tar.gz /root/cases /root/baselines /root/archive # How to download artifacts: # https://docs.github.com/en/actions/managing-workflow-runs/downloading-workflow-artifacts - name: Upload testing logs From 82929f76d54e6391b578b1eb5373465a53a30498 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Mar 2026 10:19:09 -0700 Subject: [PATCH 131/153] fix: removes unused functions --- docker/entrypoint.sh | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index de10b68f5ab..562035a6cf4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -9,24 +9,6 @@ export GROUP_ID="${GROUP_ID:-1000}" # Set static home path where .cime exists and container entrypoint options SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" -# Fix AR variable in MCT-related Makefiles -function fix_mct_makefiles() { - fix_arflags "${1}/mct/Makefile" - fix_arflags "${1}/mpeu/Makefile" - fix_arflags "${1}/mpi-serial/Makefile" -} - - -# Add ARFLAGS to AR in a Makefile if .bak does not exist -function fix_arflags() { - if [[ ! -e "${1}.bak" ]]; then - echo "Fixing AR variable in ${1}" - - sed -i".bak" "s/\$(AR)/\$(AR) \$(ARFLAGS)/g" "${1}" - fi -} - - # Build the cprnc tool from CIME sources function build_cprnc() { cprnc_dir="${PWD}/CIME/non_py/cprnc" @@ -50,6 +32,7 @@ function build_cprnc() { # Download input data needed for model setup +# required for grid generation tests function download_input_data() { mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" \ "${HOME}/inputdata/share/domains" \ From d3fd50689e4ee0b66d6b1aff7d0529e99217a9ee Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 12 Mar 2026 11:41:20 -0700 Subject: [PATCH 132/153] fix: coalesce cime directories for easier persistence --- .github/workflows/testing.yml | 2 +- doc/source/contributing-guide.rst | 10 +++++----- docker/.cime/config_machines.v2.xml | 10 +++++----- docker/.cime/docker/config_machines.xml | 10 +++++----- docker/entrypoint.sh | 18 +++++++++--------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 60e85c40007..8114d401444 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -249,7 +249,7 @@ jobs: - name: Create testing log archive if: ${{ failure() }} shell: bash - run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}-${{ matrix.model.name }}-${{ matrix.driver }}.tar.gz /root/cases /root/baselines /root/archive + run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}-${{ matrix.model.name }}-${{ matrix.driver }}.tar.gz /root/storage/cases /root/storage/baselines /root/storage/archive # How to download artifacts: # https://docs.github.com/en/actions/managing-workflow-runs/downloading-workflow-artifacts - name: Upload testing logs diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index 04b0253876f..5fbb9cb4f78 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -161,7 +161,7 @@ following example assumes the model is checked out in ``$SRC_PATH``. .. code-block:: bash - docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest bash + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v ./storage:/root/storage -v ./inputdata:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest bash This example will drop into a shell where CIME commands or tests can be run. The options are broken down below. @@ -169,8 +169,8 @@ The options are broken down below. - ``--hostname docker`` is required to tell CIME which machine definition to use. - ``-e CIME_MODEL=e3sm`` defines the model. - ``-v ${SRC_PATH}:/root/E3SM`` passes through the model source. -- ``-v `pwd`/test-cases:/root/cases`` stores cases in the current directory under ``test-cases``. -- ``-v `pwd`/inputdata:/root/inputdata`` stores inputdata in the current directory under ``inputdata``. +- ``-v ./inputdata:/root/inputdata`` persistent input data. +- ``-v ./storage:/root/storage`` persistent cases, baselines, timings, etc. - ``-w /root/E3SM/cime`` set the current working directory to CIME's root. - ``ghcr.io/esmci/cime:latest`` container image. - ``bash`` the command to run in the container. @@ -179,9 +179,9 @@ You can even run CIME or testing without a shell. .. code-block:: bash - docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest pytest CIME/tests/test_unit* + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v ./storage:/root/storage -w /root/E3SM/cime ghcr.io/esmci/cime:latest pytest CIME/tests/test_unit* .. code-block:: bash - docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v `pwd`/test-cases:/root/cases -v `pwd`/input-data:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest ./scripts/create_test SMS.f19_g16.S + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v ./storage:/root/storage -w /root/E3SM/cime ghcr.io/esmci/cime:latest ./scripts/create_test SMS.f19_g16.S diff --git a/docker/.cime/config_machines.v2.xml b/docker/.cime/config_machines.v2.xml index 6ed4d6c9a6b..b36a660488a 100644 --- a/docker/.cime/config_machines.v2.xml +++ b/docker/.cime/config_machines.v2.xml @@ -9,14 +9,14 @@ gnu,gnuX openmpi CIME - /root/timings + /root/storage/timings CIME - /root/cases + /root/storage/cases /root/inputdata /root/inputdata-clmforc - /root/archive/$CASE - /root/baselines/$COMPILER - /root/tools/cprnc + /root/storage/archive/$CASE + /root/storage/baselines/$COMPILER + /root/storage/tools/cprnc make 4 e3sm_developer diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml index cbe42413ba3..3aee97ec316 100644 --- a/docker/.cime/docker/config_machines.xml +++ b/docker/.cime/docker/config_machines.xml @@ -8,14 +8,14 @@ gnu,gnuX openmpi CIME - /root/timings + /root/storage/timings CIME - /root/cases + /root/storage/cases /root/inputdata /root/inputdata-clmforc - /root/archive/$CASE - /root/baselines/$COMPILER - /root/tools/cprnc + /root/storage/archive/$CASE + /root/storage/baselines/$COMPILER + /root/storage/tools/cprnc make 4 e3sm_developer diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 562035a6cf4..194538c8e7f 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -3,8 +3,6 @@ # Set up basic user, logname, and default group/user IDs export USER="$(id -nu)" export LOGNAME="${USER}" -export USER_ID="${USER_ID:-1000}" -export GROUP_ID="${GROUP_ID:-1000}" # Set static home path where .cime exists and container entrypoint options SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" @@ -34,13 +32,15 @@ function build_cprnc() { # Download input data needed for model setup # required for grid generation tests function download_input_data() { - mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" \ - "${HOME}/inputdata/share/domains" \ - "${HOME}/timings" \ - "${HOME}/cases" \ - "${HOME}/archive" \ - "${HOME}/baselines" \ - "${HOME}/tools" + local storage="${HOME}/storage" + + mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" + mkdir -p "${HOME}/inputdata/share/domains" + mkdir -p "${storage}/cases" + mkdir -p "${storage}/timings" + mkdir -p "${storage}/archive" + mkdir -p "${storage}/baselines" + mkdir -p "${storage}/tools" wget -O "${HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc From d82431bceb453d4ea5699350aec62db2f1fac4da Mon Sep 17 00:00:00 2001 From: noel Date: Thu, 26 Mar 2026 14:32:51 -0700 Subject: [PATCH 133/153] minor improvements to the oddball HOMME cime case --- CIME/SystemTests/hommebaseclass.py | 47 +++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/CIME/SystemTests/hommebaseclass.py b/CIME/SystemTests/hommebaseclass.py index 8ea4a03c6b9..d275553a545 100644 --- a/CIME/SystemTests/hommebaseclass.py +++ b/CIME/SystemTests/hommebaseclass.py @@ -1,11 +1,12 @@ """ CIME HOMME test. This class inherits from SystemTestsCommon """ + from CIME.XML.standard_module_setup import * from CIME.SystemTests.system_tests_common import SystemTestsCommon from CIME.build import post_build from CIME.status import append_testlog -from CIME.utils import SharedArea +from CIME.utils import SharedArea, new_lid, gzip_existing_file from CIME.test_status import * import shutil @@ -19,6 +20,8 @@ def __init__(self, case, **kwargs): initialize an object interface to the SMS system test """ SystemTestsCommon.__init__(self, case, **kwargs) + compiler = case.get_value("COMPILER") + logger.info(f"[DEBUG] Detected compiler: {compiler}") case.load_env() self.csnd = "not defined" self.cmakesuffix = self.csnd @@ -36,6 +39,11 @@ def build_phase(self, sharedlib_only=False, model_only=False): gmake = self._case.get_value("GMAKE") gmake_j = self._case.get_value("GMAKE_J") cprnc = self._case.get_value("CCSM_CPRNC") + is_debug = self._case.get_value("DEBUG") + + # Assign LID at start + lid = new_lid(case=self._case) + log_path = os.path.join(exeroot, f"homme.bldlog.{lid}") if compare: basename = basecmp @@ -49,29 +57,45 @@ def build_phase(self, sharedlib_only=False, model_only=False): "ERROR in hommebaseclass: Must have cmakesuffix set up", ) + logger.info( + f"[DEBUG] Machine: {mach}, Source root: {srcroot}, Total Procs: {procs}, Is Debug: {is_debug}" + ) + logger.info("[DEBUG] Preparing to invoke CMake with constructed command:") cmake_cmd = "cmake -C {0}/components/homme/cmake/machineFiles/{1}{6}.cmake -DUSE_NUM_PROCS={2} {0}/components/homme -DHOMME_BASELINE_DIR={3}/{4} -DCPRNC_DIR={5}/..".format( srcroot, mach, procs, baselinedir, basename, cprnc, self.cmakesuffix ) + if is_debug: + cmake_cmd += " -DCMAKE_BUILD_TYPE=Debug" + run_cmd_no_fail( cmake_cmd, - arg_stdout="homme.bldlog", + arg_stdout=log_path, combine_output=True, from_dir=exeroot, ) run_cmd_no_fail( "{} -j{} VERBOSE=1 test-execs".format(gmake, gmake_j), - arg_stdout="homme.bldlog", + arg_stdout=log_path, combine_output=True, from_dir=exeroot, ) - post_build( - self._case, [os.path.join(exeroot, "homme.bldlog")], build_complete=True - ) + post_build(self._case, [log_path], build_complete=True) - def run_phase(self): + # Prepend the LID as first line if log file exists + if os.path.exists(log_path): + with open(log_path, "r") as original: + original_content = original.read() + with open(log_path, "w") as modified: + modified.write(f"LID: {lid}\n" + original_content) + gzip_existing_file(log_path) + else: + logger.warning( + f"[WARN] Build log does not exist: {log_path}. Skipping LID prepend and gzip step." + ) + def run_phase(self): rundir = self._case.get_value("RUNDIR") exeroot = self._case.get_value("EXEROOT") baseline = self._case.get_value("BASELINE_ROOT") @@ -80,9 +104,8 @@ def run_phase(self): basegen = self._case.get_value("BASEGEN_CASE") gmake = self._case.get_value("GMAKE") - log = os.path.join(rundir, "homme.log") - if os.path.exists(log): - os.remove(log) + lid = new_lid(case=self._case) + log = os.path.join(rundir, "homme.log." + lid) if generate: full_baseline_dir = os.path.join(baseline, basegen, "tests", "baseline") @@ -130,6 +153,10 @@ def run_phase(self): # is pretty useless for this test. append_testlog(open(log, "r").read()) + if stat == 0: + # Gzip the log file to be consistent with other CIME cases (bldglogs) + gzip_existing_file(log) + expect(stat == 0, "RUN FAIL for HOMME") # Homme is a bit of an oddball test since it's not really running the E3SM model From db50465eb7b703e8c742a4345174c8f28984e46a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 26 Mar 2026 16:02:49 -0700 Subject: [PATCH 134/153] fix: prebuild cprnc --- docker/Dockerfile | 15 ++++++++++++++- docker/entrypoint.sh | 7 ++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3d34a883b40..4137eb10f66 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,6 +25,16 @@ RUN spack compiler find \ && spack external find --not-buildable openmpi \ && spack -e /opt/spack-envs install --fail-fast +FROM builder AS cprnc_builder + +COPY CIME/non_py/cprnc /src/cprnc +COPY docker/entrypoint.sh /entrypoint.sh + +RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ + && CPRNC_DIR=/src/cprnc build_cprnc + +FROM builder + RUN apt-get update \ && apt-get install -y --no-install-recommends \ vim \ @@ -41,9 +51,12 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ && uv pip install -r /requirements.txt COPY docker/entrypoint.sh /entrypoint.sh -COPY docker/.cime /root/.cime RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ + && mkdir -p /root/{tools,inputdata,cases,timings,archive,baselines} \ && download_input_data +COPY --from=cprnc_builder /root/tools/cprnc /root/tools/cprnc +COPY docker/.cime /root/.cime + ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 194538c8e7f..021f0da4ef7 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -9,7 +9,7 @@ SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" # Build the cprnc tool from CIME sources function build_cprnc() { - cprnc_dir="${PWD}/CIME/non_py/cprnc" + cprnc_dir="${CPRNC_DIR:-${PWD}/CIME/non_py/cprnc}" if [[ ! -e "${cprnc_dir}" ]]; then echo "CPRNC path does not exist. Change to CIME's root directory." @@ -22,6 +22,8 @@ function build_cprnc() { make + mkdir "${HOME}/tools" + # Needs to be copied into the machines configured tool path cp cprnc "${HOME}/tools/cprnc" @@ -80,12 +82,11 @@ export PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk -if [[ "${CI:-false}" == "false" ]]; then +if [[ "${CI:-false}" == "false" ]] && [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then source ${HOME}/.local/bin/env source ${HOME}/.venv/bin/activate fi -# If not skipping entrypoint, set up user/group IDs and exec given command. if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then exec "${@}" fi From 230ae8fddcd215a8f89927ccda9f25434b0964ad Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 10:52:03 -0700 Subject: [PATCH 135/153] fix: loading spack environment --- docker/entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 021f0da4ef7..e5e963bb6b4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -82,8 +82,9 @@ export PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig export LD_LIBRARY_PATH=/opt/spack-envs/view/lib export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk +source ${HOME}/.local/bin/env + if [[ "${CI:-false}" == "false" ]] && [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then - source ${HOME}/.local/bin/env source ${HOME}/.venv/bin/activate fi From f7c3ebab0e1a4de2453e8eb529e33a497ddf8ebe Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 11:26:20 -0700 Subject: [PATCH 136/153] fix: move spack env variables into correct stage --- docker/Dockerfile | 13 +++++++++---- docker/entrypoint.sh | 13 +------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4137eb10f66..526cac2b2a5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/spack/ubuntu-noble:1.0.4 AS builder +FROM ghcr.io/spack/ubuntu-noble:1.0.4 AS spack RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -25,7 +25,12 @@ RUN spack compiler find \ && spack external find --not-buildable openmpi \ && spack -e /opt/spack-envs install --fail-fast -FROM builder AS cprnc_builder +ENV PATH=/opt/spack-envs/view/bin:$PATH +ENV PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig +ENV LD_LIBRARY_PATH=/opt/spack-envs/view/lib +ENV ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk + +FROM spack AS cprnc COPY CIME/non_py/cprnc /src/cprnc COPY docker/entrypoint.sh /entrypoint.sh @@ -33,7 +38,7 @@ COPY docker/entrypoint.sh /entrypoint.sh RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ && CPRNC_DIR=/src/cprnc build_cprnc -FROM builder +FROM spack AS main RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -56,7 +61,7 @@ RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ && mkdir -p /root/{tools,inputdata,cases,timings,archive,baselines} \ && download_input_data -COPY --from=cprnc_builder /root/tools/cprnc /root/tools/cprnc +COPY --from=cprnc /root/tools/cprnc /root/tools/cprnc COPY docker/.cime /root/.cime ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index e5e963bb6b4..0355c9c8674 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -38,11 +38,6 @@ function download_input_data() { mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" mkdir -p "${HOME}/inputdata/share/domains" - mkdir -p "${storage}/cases" - mkdir -p "${storage}/timings" - mkdir -p "${storage}/archive" - mkdir -p "${storage}/baselines" - mkdir -p "${storage}/tools" wget -O "${HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc @@ -77,14 +72,8 @@ if [[ -e "${PWD}/.git" ]]; then git config --global --add safe.directory "*" fi -export PATH=/opt/spack-envs/view/bin:$PATH -export PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig -export LD_LIBRARY_PATH=/opt/spack-envs/view/lib -export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk - -source ${HOME}/.local/bin/env - if [[ "${CI:-false}" == "false" ]] && [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then + source ${HOME}/.local/bin/env source ${HOME}/.venv/bin/activate fi From dec8ca24ff7519318386a5bb3e2c08b4e5b625c6 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 11:56:43 -0700 Subject: [PATCH 137/153] fix: explicit paths for cmake --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 0355c9c8674..656a7e7839a 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -18,7 +18,7 @@ function build_cprnc() { pushd "$(mktemp -d)" || exit 1 - cmake "${cprnc_dir}" + cmake -S "${cprnc_dir}" -B . make From a6911b0803645a624735f2fdff4bc524ca27b233 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 12:00:38 -0700 Subject: [PATCH 138/153] chore: debug statement --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 526cac2b2a5..560e592387b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,6 +36,7 @@ COPY CIME/non_py/cprnc /src/cprnc COPY docker/entrypoint.sh /entrypoint.sh RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ + && ls -la /src/cprnc \ && CPRNC_DIR=/src/cprnc build_cprnc FROM spack AS main From c18e4c15be74d8ba76edc6a4c2498c4e594c74d1 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 12:07:30 -0700 Subject: [PATCH 139/153] fix: missing cprnc submodule --- .github/workflows/testing.yml | 2 ++ docker/Dockerfile | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8114d401444..6089ec98adf 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,6 +40,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + submodules: "true" - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx diff --git a/docker/Dockerfile b/docker/Dockerfile index 560e592387b..526cac2b2a5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -36,7 +36,6 @@ COPY CIME/non_py/cprnc /src/cprnc COPY docker/entrypoint.sh /entrypoint.sh RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ - && ls -la /src/cprnc \ && CPRNC_DIR=/src/cprnc build_cprnc FROM spack AS main From 3df94b9876bf2a3d9fd30f793fa2ad2f34988432 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 12:07:44 -0700 Subject: [PATCH 140/153] chore: remove old path --- docker/entrypoint.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 656a7e7839a..afcdb343308 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -34,8 +34,6 @@ function build_cprnc() { # Download input data needed for model setup # required for grid generation tests function download_input_data() { - local storage="${HOME}/storage" - mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" mkdir -p "${HOME}/inputdata/share/domains" From 23ba529183207f8748682df351f0f6989ed2ccac Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 12:38:49 -0700 Subject: [PATCH 141/153] fix: add exports incase env is cleared --- docker/entrypoint.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index afcdb343308..5918b759290 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -59,6 +59,11 @@ function link_config_machines() { fi } +export PATH=/opt/spack-envs/view/bin:$PATH +export PKG_CONFIG_PATH=/opt/spack-envs/view/lib/pkgconfig +export LD_LIBRARY_PATH=/opt/spack-envs/view/lib +export ESMFMKFILE=/opt/spack-envs/view/lib/esmf.mk + if [[ "${CI:-false}" == "true" ]]; then cp -rf /root/.cime "${HOME}" fi From ca388eb9c214df4d996f31f798e79ea5b70d0012 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 12:56:50 -0700 Subject: [PATCH 142/153] fix: remove old ESMFMKFILE --- docker/entrypoint.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 5918b759290..ca1c38537a4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -53,8 +53,6 @@ function link_config_machines() { if [[ "${CIME_MODEL}" == "e3sm" ]]; then ln -sf "${HOME}/.cime/config_machines.v2.xml" "${HOME}/.cime/config_machines.xml" elif [[ "${CIME_MODEL}" == "cesm" ]]; then - export ESMFMKFILE=/opt/conda/envs/cesm/lib/esmf.mk - ln -sf "${HOME}/.cime/config_machines.v3.xml" "${HOME}/.cime/config_machines.xml" fi } From 56132c09c2e501dc7983e7d200a27dc8a009e981 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 15:48:30 -0700 Subject: [PATCH 143/153] fix: container paths --- .github/workflows/testing.yml | 2 +- doc/source/contributing-guide.rst | 5 ++--- docker/.cime/config_machines.v2.xml | 4 ++-- docker/.cime/docker/config_machines.xml | 4 ++-- docker/Dockerfile | 4 ++-- docker/entrypoint.sh | 16 +++++++++------- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6089ec98adf..3ddf29c1663 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -213,7 +213,7 @@ jobs: - name: Cache inputdata uses: actions/cache@v3 with: - path: /home/cime/inputdata + path: /root/storage/inputdata key: inputdata-2 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index 5fbb9cb4f78..e43b1785f92 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -161,7 +161,7 @@ following example assumes the model is checked out in ``$SRC_PATH``. .. code-block:: bash - docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v ./storage:/root/storage -v ./inputdata:/root/inputdata -w /root/E3SM/cime ghcr.io/esmci/cime:latest bash + docker run -it --rm --hostname docker -e CIME_MODEL=e3sm -v ${SRC_PATH}:/root/model -v ./storage:/root/storage -w /root/E3SM/cime ghcr.io/esmci/cime:latest bash This example will drop into a shell where CIME commands or tests can be run. The options are broken down below. @@ -169,8 +169,7 @@ The options are broken down below. - ``--hostname docker`` is required to tell CIME which machine definition to use. - ``-e CIME_MODEL=e3sm`` defines the model. - ``-v ${SRC_PATH}:/root/E3SM`` passes through the model source. -- ``-v ./inputdata:/root/inputdata`` persistent input data. -- ``-v ./storage:/root/storage`` persistent cases, baselines, timings, etc. +- ``-v ./storage:/root/storage`` persist all data; cases, baselines, archive, inputdata. the bind mounts can be broken out if you only want to persist certain input/outputs. - ``-w /root/E3SM/cime`` set the current working directory to CIME's root. - ``ghcr.io/esmci/cime:latest`` container image. - ``bash`` the command to run in the container. diff --git a/docker/.cime/config_machines.v2.xml b/docker/.cime/config_machines.v2.xml index b36a660488a..e93b632c6c7 100644 --- a/docker/.cime/config_machines.v2.xml +++ b/docker/.cime/config_machines.v2.xml @@ -12,8 +12,8 @@ /root/storage/timings CIME /root/storage/cases - /root/inputdata - /root/inputdata-clmforc + /root/storage/inputdata + /root/storage/inputdata-clmforc /root/storage/archive/$CASE /root/storage/baselines/$COMPILER /root/storage/tools/cprnc diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml index 3aee97ec316..c0ea01f4b05 100644 --- a/docker/.cime/docker/config_machines.xml +++ b/docker/.cime/docker/config_machines.xml @@ -11,8 +11,8 @@ /root/storage/timings CIME /root/storage/cases - /root/inputdata - /root/inputdata-clmforc + /root/storage/inputdata + /root/storage/inputdata-clmforc /root/storage/archive/$CASE /root/storage/baselines/$COMPILER /root/storage/tools/cprnc diff --git a/docker/Dockerfile b/docker/Dockerfile index 526cac2b2a5..de90ef73365 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -58,10 +58,10 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ COPY docker/entrypoint.sh /entrypoint.sh RUN SKIP_ENTRYPOINT=true source /entrypoint.sh \ - && mkdir -p /root/{tools,inputdata,cases,timings,archive,baselines} \ + && mkdir -p /root/storage/{tools,inputdata,cases,timings,archive,baselines} \ && download_input_data -COPY --from=cprnc /root/tools/cprnc /root/tools/cprnc +COPY --from=cprnc /root/storage/tools/cprnc /root/storage/tools/cprnc COPY docker/.cime /root/.cime ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index ca1c38537a4..d11c2373484 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -6,10 +6,12 @@ export LOGNAME="${USER}" # Set static home path where .cime exists and container entrypoint options SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" +STORAGE_DIR="${HOME}/storage" # Build the cprnc tool from CIME sources function build_cprnc() { cprnc_dir="${CPRNC_DIR:-${PWD}/CIME/non_py/cprnc}" + tools_dir="${STORAGE_DIR}/tools" if [[ ! -e "${cprnc_dir}" ]]; then echo "CPRNC path does not exist. Change to CIME's root directory." @@ -22,10 +24,10 @@ function build_cprnc() { make - mkdir "${HOME}/tools" + [[ ! -e "${tools_dir}" ]] && mkdir -p "${tools_dir}" # Needs to be copied into the machines configured tool path - cp cprnc "${HOME}/tools/cprnc" + cp cprnc "${tools_dir}/cprnc" popd || exit 1 } @@ -34,16 +36,16 @@ function build_cprnc() { # Download input data needed for model setup # required for grid generation tests function download_input_data() { - mkdir -p "${HOME}/inputdata/cpl/gridmaps/oQU240" - mkdir -p "${HOME}/inputdata/share/domains" + mkdir -p "${STORAGE_DIR}/inputdata/cpl/gridmaps/oQU240" + mkdir -p "${STORAGE_DIR}/inputdata/share/domains" - wget -O "${HOME}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ + wget -O "${STORAGE_DIR}/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc - wget -O "${HOME}/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc" \ + wget -O "${STORAGE_DIR}/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc - wget -O "${HOME}/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc" \ + wget -O "${STORAGE_DIR}/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc" \ https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc } From d85529efc110db3f79b7ff5706fa702428ec94b0 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 27 Mar 2026 22:01:49 -0700 Subject: [PATCH 144/153] fix: pin sphinx --- doc/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index bedeb8ab75d..5415ccaf0b3 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,8 +1,8 @@ -sphinx +sphinx<8.0 sphinxcontrib-programoutput sphinx-rtd-theme sphinx-copybutton evv4esm xmlschema sphinx-toolbox -sphinx-autobuild \ No newline at end of file +sphinx-autobuild From dfeac44a79e6cc909ef404947ffd57312326ca5a Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 25 Mar 2026 14:50:13 -0600 Subject: [PATCH 145/153] Improve baseline handling when regenerating baselines When regenerating baselines, we should never remove the existing baselines until we know the current run has passed. The idea here is that we never want to be in a situation where we've removed existing baselines, but the case being run to regenerate them has FAILed, leaving us with no baselines now. The approach is, instead of cleaning baselines at the beginning of TestScheduler, just do the cleaning of existing baselines at the point just before new baselines are being copied over. --- CIME/case/case_cmpgen_namelists.py | 11 +++++++---- CIME/hist_utils.py | 14 ++++++++++++++ CIME/test_scheduler.py | 6 +----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CIME/case/case_cmpgen_namelists.py b/CIME/case/case_cmpgen_namelists.py index 10ded368147..6a2579ada35 100644 --- a/CIME/case/case_cmpgen_namelists.py +++ b/CIME/case/case_cmpgen_namelists.py @@ -95,11 +95,14 @@ def _do_full_nl_gen_impl(case, test, generate_name, baseline_root=None): shutil.copytree(casedoc_dir, baseline_casedocs) - for item in glob.glob(os.path.join(test_dir, "user_nl*")): - preexisting_baseline = os.path.join(baseline_dir, os.path.basename(item)) - if os.path.exists(preexisting_baseline): - os.remove(preexisting_baseline) + # Note: If it is ever the case that nml cmp/gen affects more files than + # just user_nl* and CaseDocs, this will break assumptions all across CIME. + + # Remove any previous user_nl files + for item in glob.glob(os.path.join(baseline_dir, "user_nl*")): + os.remove(item) + for item in glob.glob(os.path.join(test_dir, "user_nl*")): safe_copy(item, baseline_dir, preserve_meta=False) diff --git a/CIME/hist_utils.py b/CIME/hist_utils.py index fd9c37dbe0a..cbadccc1a00 100644 --- a/CIME/hist_utils.py +++ b/CIME/hist_utils.py @@ -5,6 +5,7 @@ import os import re import filecmp +import shutil from CIME.XML.standard_module_setup import * from CIME.config import Config @@ -651,6 +652,19 @@ def _generate_baseline_impl(case, baseline_dir=None, allow_baseline_overwrite=Fa ): expect(False, " Cowardly refusing to overwrite existing baseline directory") + # Remove stale baseline files from a previous run so they don't linger when the + # new run no longer produces them. CaseDocs and user_nl* are managed by the + # namelist-generation phase (which already ran), so we preserve those. + if os.path.isdir(basegen_dir): + for item in os.listdir(basegen_dir): + if item == "CaseDocs" or item.startswith("user_nl"): + continue + item_path = os.path.join(basegen_dir, item) + if os.path.isfile(item_path): + os.remove(item_path) + elif os.path.isdir(item_path): + shutil.rmtree(item_path) + comments = "Generating baselines into '{}'\n".format(basegen_dir) num_gen = 0 for model in _iter_model_file_substrs(case): diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index a58fcc30f8b..d94a99e08fa 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -28,7 +28,6 @@ get_project, get_timestamp, get_cime_default_driver, - clear_folder, CIMEError, ) from CIME.config import Config @@ -360,10 +359,7 @@ def __init__( if os.path.isdir(test_baseline): existing_baselines.append(test_baseline) if allow_baseline_overwrite and run_count == 0: - if self._namelists_only: - clear_folder(os.path.join(test_baseline, "CaseDocs")) - else: - clear_folder(test_baseline) + pass elif skip_tests_with_existing_baselines: tests_to_skip.append(test_name) expect( From bd5db8e82be8883733b0f24904055d897eb7e1b4 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 25 Mar 2026 15:46:31 -0600 Subject: [PATCH 146/153] Preserve TestStatus and bless_log --- CIME/hist_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/hist_utils.py b/CIME/hist_utils.py index cbadccc1a00..fa48f942c56 100644 --- a/CIME/hist_utils.py +++ b/CIME/hist_utils.py @@ -655,9 +655,10 @@ def _generate_baseline_impl(case, baseline_dir=None, allow_baseline_overwrite=Fa # Remove stale baseline files from a previous run so they don't linger when the # new run no longer produces them. CaseDocs and user_nl* are managed by the # namelist-generation phase (which already ran), so we preserve those. + preserve_list = ["CaseDocs", BLESS_LOG_NAME, TEST_STATUS_FILENAME] if os.path.isdir(basegen_dir): for item in os.listdir(basegen_dir): - if item == "CaseDocs" or item.startswith("user_nl"): + if item in preserve_list or item.startswith("user_nl"): continue item_path = os.path.join(basegen_dir, item) if os.path.isfile(item_path): From 632d65d0af98c4bcccefb54b1e349344c632e119 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 26 Mar 2026 14:39:40 -0600 Subject: [PATCH 147/153] Add test for baseline preservation --- CIME/tests/test_sys_test_scheduler.py | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/CIME/tests/test_sys_test_scheduler.py b/CIME/tests/test_sys_test_scheduler.py index 6c90932bb7a..c5dcd718336 100755 --- a/CIME/tests/test_sys_test_scheduler.py +++ b/CIME/tests/test_sys_test_scheduler.py @@ -586,6 +586,82 @@ def test_e_test_inferred_compiler(self): ) self.assertEqual(os.path.split(result)[1], "gnuX") + def test_f_baseline_not_cleared_on_failure(self): + """ + Verify that existing baselines are preserved when a regeneration attempt + fails. Before the fix, clear_folder ran in the TestScheduler constructor + (before the test executed), so a failing run left the case with no + baselines. After the fix, clearing is deferred to _generate_baseline_impl + which is only reached when the run phase succeeds. + + Steps: + 1. Generate baselines with TESTRUNFAIL_PASS set (test passes). + 2. Attempt regeneration WITHOUT TESTRUNFAIL_PASS (run fails). + Baselines must remain intact. + 3. Compare with TESTRUNFAIL_PASS set – should succeed because the + original baselines from step 1 are still present. + """ + test_name = "TESTRUNFAIL_P1.f19_g16.A" + if self._config.create_test_flag_mode == "e3sm": + genargs = ["-g", "-o", "-b", self._baseline_name, test_name] + compargs = ["-c", "-b", self._baseline_name, test_name] + else: + genargs = ["-g", self._baseline_name, "-o", test_name] + compargs = ["-c", self._baseline_name, test_name] + + # Step 1: Generate initial baselines with a passing run. + os.environ["TESTRUNFAIL_PASS"] = "True" + try: + self._create_test(genargs) + finally: + del os.environ["TESTRUNFAIL_PASS"] + + baseline_dir = os.path.join(self._baseline_area, self._baseline_name) + self.assertTrue(os.path.isdir(baseline_dir), "Baseline dir should exist after generation") + baseline_cases = glob.glob(os.path.join(baseline_dir, "*TESTRUNFAIL*")) + self.assertEqual(len(baseline_cases), 1, "Expected exactly one TESTRUNFAIL baseline dir") + + # Collect the set of regular files created by the baseline generation. + # CaseDocs is a directory managed separately by the namelist phase and + # may be legitimately refreshed during a subsequent SETUP, so we only + # track files here. + baseline_case_dir = baseline_cases[0] + baseline_files_before = { + f + for f in os.listdir(baseline_case_dir) + if os.path.isfile(os.path.join(baseline_case_dir, f)) + } + self.assertGreater( + len(baseline_files_before), + 0, + "Baseline must contain at least one file after generation", + ) + + # Step 2: Attempt regeneration WITHOUT TESTRUNFAIL_PASS – run will fail. + # Our fix ensures that baseline files are NOT cleared before the run + # completes successfully. + self._create_test(genargs, run_errors=True) + + baseline_cases_after = glob.glob(os.path.join(baseline_dir, "*TESTRUNFAIL*")) + self.assertEqual(len(baseline_cases_after), 1) + baseline_files_after = { + f + for f in os.listdir(baseline_cases_after[0]) + if os.path.isfile(os.path.join(baseline_cases_after[0], f)) + } + self.assertEqual( + baseline_files_before, + baseline_files_after, + msg="Baseline files were modified by a failed generation attempt", + ) + + # Step 3: Comparison against the preserved baselines should succeed. + os.environ["TESTRUNFAIL_PASS"] = "True" + try: + self._create_test(compargs) + finally: + del os.environ["TESTRUNFAIL_PASS"] + if __name__ == "__main__": unittest.main() From c35928765df2ab5225eb5c38916b6f0c9473aea8 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 26 Mar 2026 14:02:32 -0700 Subject: [PATCH 148/153] black --- CIME/tests/test_sys_test_scheduler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CIME/tests/test_sys_test_scheduler.py b/CIME/tests/test_sys_test_scheduler.py index c5dcd718336..1b72ed78d0b 100755 --- a/CIME/tests/test_sys_test_scheduler.py +++ b/CIME/tests/test_sys_test_scheduler.py @@ -617,9 +617,13 @@ def test_f_baseline_not_cleared_on_failure(self): del os.environ["TESTRUNFAIL_PASS"] baseline_dir = os.path.join(self._baseline_area, self._baseline_name) - self.assertTrue(os.path.isdir(baseline_dir), "Baseline dir should exist after generation") + self.assertTrue( + os.path.isdir(baseline_dir), "Baseline dir should exist after generation" + ) baseline_cases = glob.glob(os.path.join(baseline_dir, "*TESTRUNFAIL*")) - self.assertEqual(len(baseline_cases), 1, "Expected exactly one TESTRUNFAIL baseline dir") + self.assertEqual( + len(baseline_cases), 1, "Expected exactly one TESTRUNFAIL baseline dir" + ) # Collect the set of regular files created by the baseline generation. # CaseDocs is a directory managed separately by the namelist phase and From 05ee2160bcad1d2e6af5c656e7ff111684a2e6b5 Mon Sep 17 00:00:00 2001 From: Matvey Debolskiy Date: Thu, 9 Apr 2026 19:59:48 +0200 Subject: [PATCH 149/153] fix mf partition memery bs in most dirty way --- CIME/case/case_st_archive.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index 932987d1a9a..c115ed2497e 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -1162,6 +1162,13 @@ def case_st_archive( verbose=True, ) else: + # this is the only way you can jump from a partition with mem specs allowed + # to the one where are they prohibited by whoever set up slurm configs on HPC + # TODO: add betzy + if self.get_value("MACH") in ["olivia"]: + logger.info("remove environment variable") + os.unsetenv("SLURM_MEM_PER_NODE") + os.environ["SLURM_MEM_PER_NODE"]="{}".format(self.get_value("MAX_MEM_PER_NODE")*1024) self.submit(resubmit=True) return True From 06dc97a65420a918bdf7ce52cc71a22297fcc7fa Mon Sep 17 00:00:00 2001 From: mvdebolskiy <80036033+mvdebolskiy@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:19:46 +0200 Subject: [PATCH 150/153] Update CIME/case/case_st_archive.py Co-authored-by: goldy <1588651+gold2718@users.noreply.github.com> --- CIME/case/case_st_archive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index c115ed2497e..d443ae7ac85 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -1166,7 +1166,7 @@ def case_st_archive( # to the one where are they prohibited by whoever set up slurm configs on HPC # TODO: add betzy if self.get_value("MACH") in ["olivia"]: - logger.info("remove environment variable") + logger.info("redefine SLURM_MEM_PER_NODE environment variable") os.unsetenv("SLURM_MEM_PER_NODE") os.environ["SLURM_MEM_PER_NODE"]="{}".format(self.get_value("MAX_MEM_PER_NODE")*1024) self.submit(resubmit=True) From e3fb594b17c0d4443a983fa970966cbfba26c385 Mon Sep 17 00:00:00 2001 From: Matvey Debolskiy Date: Sun, 12 Apr 2026 19:21:29 +0200 Subject: [PATCH 151/153] add compiler attribute for olivia gnu to work on <= 4 nodes --- CIME/data/config/xml_schemas/config_pes.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/CIME/data/config/xml_schemas/config_pes.xsd b/CIME/data/config/xml_schemas/config_pes.xsd index f82a55c8f38..251f59d7471 100644 --- a/CIME/data/config/xml_schemas/config_pes.xsd +++ b/CIME/data/config/xml_schemas/config_pes.xsd @@ -5,6 +5,7 @@ + From 7cfe7157ab289f3cae6cc3ec9a56a0fda0356855 Mon Sep 17 00:00:00 2001 From: Matvey Debolskiy Date: Sun, 12 Apr 2026 19:50:16 +0200 Subject: [PATCH 152/153] Revert "add compiler attribute for olivia gnu to work on <= 4 nodes" This reverts commit e3fb594b17c0d4443a983fa970966cbfba26c385. --- CIME/data/config/xml_schemas/config_pes.xsd | 1 - 1 file changed, 1 deletion(-) diff --git a/CIME/data/config/xml_schemas/config_pes.xsd b/CIME/data/config/xml_schemas/config_pes.xsd index 251f59d7471..f82a55c8f38 100644 --- a/CIME/data/config/xml_schemas/config_pes.xsd +++ b/CIME/data/config/xml_schemas/config_pes.xsd @@ -5,7 +5,6 @@ - From 1d04669bf1ccafa29beca2795aeb23e4d9363f8c Mon Sep 17 00:00:00 2001 From: mvdebolskiy Date: Tue, 21 Apr 2026 19:20:46 +0200 Subject: [PATCH 153/153] add betzy to st_archive exception --- CIME/case/case_st_archive.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/case/case_st_archive.py b/CIME/case/case_st_archive.py index d443ae7ac85..1b7fac4d25b 100644 --- a/CIME/case/case_st_archive.py +++ b/CIME/case/case_st_archive.py @@ -1165,9 +1165,10 @@ def case_st_archive( # this is the only way you can jump from a partition with mem specs allowed # to the one where are they prohibited by whoever set up slurm configs on HPC # TODO: add betzy - if self.get_value("MACH") in ["olivia"]: + if self.get_value("MACH") in ["olivia","betzy"]: logger.info("redefine SLURM_MEM_PER_NODE environment variable") os.unsetenv("SLURM_MEM_PER_NODE") + # this slurm var is in MiB we set MEM_PER_TASK and MAX_MEM_PER_NODE in GiB os.environ["SLURM_MEM_PER_NODE"]="{}".format(self.get_value("MAX_MEM_PER_NODE")*1024) self.submit(resubmit=True)