diff --git a/scripts/create_newcase b/scripts/create_newcase index 65b5275b1a18..2fb2cfd33a4f 100755 --- a/scripts/create_newcase +++ b/scripts/create_newcase @@ -140,10 +140,6 @@ OR expect(os.path.isfile(args.pesfile), "Pes specification file %s cannot be found " %args.pesfile) - if args.user_compset: - expect(args.pesfile is not None, - "--pesfile is required if --user-compset is set") - if args.user_grid: expect(args.gridfile is not None, "User grid specification file must be set if the user grid is requested") diff --git a/scripts/lib/CIME/XML/component.py b/scripts/lib/CIME/XML/component.py index 57b79e145a13..463b35185297 100644 --- a/scripts/lib/CIME/XML/component.py +++ b/scripts/lib/CIME/XML/component.py @@ -92,6 +92,17 @@ def _get_value_match(self, node, attributes=None, exact_match=False): return match_value + def get_description(self, compsetname): + rootnode = self.get_node("description") + desc_nodes = self.get_nodes("desc", root=rootnode) + desc = "" + for node in desc_nodes: + compsetmatch = node.get("compset") + if re.search(compsetmatch, compsetname): + desc += node.text + + return desc + def print_values(self): """ print values for help and description in target config_component.xml file diff --git a/scripts/lib/CIME/case.py b/scripts/lib/CIME/case.py index 15646d597342..636314dba0a0 100644 --- a/scripts/lib/CIME/case.py +++ b/scripts/lib/CIME/case.py @@ -72,7 +72,7 @@ def __init__(self, case_root=None, read_only=True): self._env_files_that_need_rewrite = set() self._read_only_mode = True self._force_read_only = read_only - self._principle_component = None + self._primary_component = None self._env_entryid_files = [] self._env_generic_files = [] @@ -96,6 +96,7 @@ def __init__(self, case_root=None, read_only=True): self._gridfile = None self._components = [] self._component_classes = [] + self._component_description = {} self._is_env_loaded = False # these are user_mods as defined in the compset # Command Line user_mods are handled seperately @@ -431,7 +432,7 @@ def _set_compset_and_pesfile(self, compset_name, files, user_compset=False, pesf compsets = Compsets(compsets_filename) match, compset_alias, science_support = compsets.get_compset_match(name=compset_name) if match is not None: - if pesfile is None: + if self._pesfile is None: self._pesfile = files.get_value("PES_SPEC_FILE" , {"component":component}) self.set_lookup_value("PES_SPEC_FILE" , files.get_value("PES_SPEC_FILE" , {"component":component}, resolved=False)) @@ -442,21 +443,19 @@ def _set_compset_and_pesfile(self, compset_name, files, user_compset=False, pesf user_mods_dir = files.get_value("USER_MODS_DIR" , {"component":component}, resolved=False) self.set_lookup_value("COMPSETS_SPEC_FILE" , files.get_value("COMPSETS_SPEC_FILE", {"component":component}, resolved=False)) - self._principle_component = component.upper() + self._primary_component = component.upper() self.set_lookup_value("TESTS_SPEC_FILE" , tests_filename) self.set_lookup_value("TESTS_MODS_DIR" , tests_mods_dir) self.set_lookup_value("USER_MODS_DIR" , user_mods_dir) logger.info("Compset longname is %s"%(match)) logger.info("Compset specification file is %s" %(compsets_filename)) - logger.info("Pes specification file is %s" %(pesfile)) + logger.info("Pes specification file is %s" %(self._pesfile)) return compset_alias, science_support if user_compset is True: #Do not error out for user_compset logger.warn("Could not find a compset match for either alias or longname in %s" %(compset_name)) self._compsetname = compset_name - logger.info("Pes specification file is %s" %(pesfile)) - self.set_lookup_value("PES_SPEC_FILE", pesfile) else: expect(False, "Could not find a compset match for either alias or longname in %s\n"%(compset_name) @@ -464,6 +463,58 @@ def _set_compset_and_pesfile(self, compset_name, files, user_compset=False, pesf return None, science_support + def _find_primary_component(self): + """ + try to glean the primary component based on compset name + """ + progcomps = {} + spec = {} + primary_component = None + + for comp in self._component_classes: + + if comp == "CPL": + continue + spec[comp] = self.get_value("COMP_%s"%comp) + notprogcomps = ("D%s"%comp,"X%s"%comp,"S%s"%comp) + if spec[comp].upper() in notprogcomps: + progcomps[comp] = False + else: + progcomps[comp] = True + expect("ATM" in progcomps and "LND" in progcomps and "OCN" in progcomps and \ + "ICE" in progcomps, " Not finding expected components in %s"%self._component_classes) + if progcomps["ATM"] and progcomps["LND"] and progcomps["OCN"] and \ + progcomps["ICE"]: + primary_component = "allactive" + elif progcomps["LND"] and progcomps["OCN"] and progcomps["ICE"]: + # this is a "J" compset + primary_component = "allactive" + elif progcomps["ATM"]: + if "DOCN%SOM" in self._compsetname: + # This is an "E" compset + primary_component = "allactive" + else: + # This is an "F" or "Q" compset + primary_component = spec["ATM"] + elif progcomps["LND"]: + # This is an "I" compset + primary_component = spec["LND"] + elif progcomps["OCN"]: + # This is a "C" or "G" compset + primary_component = spec["OCN"] + elif progcomps["ICE"]: + # This is a "D" compset + primary_component = spec["ICE"] + elif "GLC" in progcomps and progcomps["GLC"]: + # This is a "TG" compset + primary_component = spec["GLC"] + else: + # This is "A", "X" or "S" + primary_component = "drv" + + return primary_component + + def get_compset_components(self): #If are doing a create_clone then, self._compsetname is not set yet components = [] @@ -545,9 +596,19 @@ def _get_component_config_data(self, files): expect(comp_config_file is not None and os.path.isfile(comp_config_file), "Config file %s for component %s not found."%(comp_config_file, comp_name)) compobj = Component(comp_config_file) + self._component_description[comp_class] = compobj.get_description(self._compsetname) + expect(self._component_description[comp_class] is not None,"No description found in file %s for component %s"%(comp_config_file, comp_name)) + logger.info("%s component is %s"%(comp_class, self._component_description[comp_class])) for env_file in self._env_entryid_files: env_file.add_elements_by_group(compobj, attributes=attlist) + if self._primary_component is None: + self._primary_component = self._find_primary_component() + if self._pesfile is None: + self._pesfile = files.get_value("PES_SPEC_FILE" , {"component":self._primary_component}) + logger.info("Pes specification file is %s" %(self._pesfile)) + self.set_lookup_value("PES_SPEC_FILE", self._pesfile) + # final cleanup of lookups table for key,value in self.lookups.items(): result = self.set_value(key,value) @@ -674,9 +735,6 @@ def configure(self, compset_name, grid_name, machine_name=None, compset_alias, science_support = self._set_compset_and_pesfile(compset_name, files, user_compset=user_compset, pesfile=pesfile) self._components = self.get_compset_components() - #FIXME - if --user-compset is True then need to determine that - #all of the compset settings are valid - #-------------------------------------------- # grid #-------------------------------------------- @@ -972,7 +1030,7 @@ def create_caseroot(self, clone=False): for newdir in newdirs: os.makedirs(newdir) - user_mods = self.get_value("%s_USER_MODS"%(self._principle_component)) + user_mods = self.get_value("%s_USER_MODS"%(self._primary_component)) # Open a new README.case file in $self._caseroot append_status(" ".join(sys.argv), "README.case", caseroot=self._caseroot) @@ -991,8 +1049,11 @@ def create_caseroot(self, clone=False): if component_class == "CPL": continue comp_grid = "%s_GRID"%component_class + append_status("Component %s is %s"%(component_class, self._component_description[component_class]), + "README.case", caseroot=self._caseroot) append_status("%s is %s"%(comp_grid,self.get_value(comp_grid)), "README.case", caseroot=self._caseroot) + if user_mods is not None: note = "This compset includes user_mods %s"%user_mods append_status(note, "README.case", caseroot=self._caseroot) @@ -1007,7 +1068,7 @@ def apply_user_mods(self, user_mods_dir=None): or they can be in the compset definition, or both. """ - component_user_mods = self.get_value("%s_USER_MODS"%(self._principle_component)) + component_user_mods = self.get_value("%s_USER_MODS"%(self._primary_component)) # This looping order will lead to the specified user_mods_dir taking # precedence over self._user_mods, if there are any conflicts. diff --git a/scripts/tests/scripts_regression_tests.py b/scripts/tests/scripts_regression_tests.py index 37486bf96249..036f8ae3047e 100755 --- a/scripts/tests/scripts_regression_tests.py +++ b/scripts/tests/scripts_regression_tests.py @@ -260,6 +260,48 @@ def test_a_createnewcase(self): run_cmd_assert_result(self, "./case.setup", from_dir=testdir) run_cmd_assert_result(self, "./case.build", from_dir=testdir) + with Case(testdir, read_only=True) as case: + case.set_comp_classes(case.get_values("COMP_CLASSES")) + primary = case._find_primary_component() + self.assertEqual(primary, "drv", msg="primary component test expected drv but got %s"%primary) + # now we are going to corrupt the case so that we can do more primary_component testing + case.set_valid_values("COMP_GLC","%s,fred"%case.get_value("COMP_GLC")) + case.set_value("COMP_GLC","fred") + primary = case._find_primary_component() + self.assertEqual(primary, "fred", msg="primary component test expected fred but got %s"%primary) + case.set_valid_values("COMP_ICE","%s,wilma"%case.get_value("COMP_ICE")) + case.set_value("COMP_ICE","wilma") + primary = case._find_primary_component() + self.assertEqual(primary, "wilma", msg="primary component test expected wilma but got %s"%primary) + + case.set_valid_values("COMP_OCN","%s,bambam,docn"%case.get_value("COMP_OCN")) + case.set_value("COMP_OCN","bambam") + primary = case._find_primary_component() + self.assertEqual(primary, "bambam", msg="primary component test expected bambam but got %s"%primary) + + case.set_valid_values("COMP_LND","%s,barney"%case.get_value("COMP_LND")) + case.set_value("COMP_LND","barney") + primary = case._find_primary_component() + # This is a "J" compset + self.assertEqual(primary, "allactive", msg="primary component test expected allactive but got %s"%primary) + case.set_value("COMP_OCN","docn") + case.set_valid_values("COMP_LND","%s,barney"%case.get_value("COMP_LND")) + case.set_value("COMP_LND","barney") + primary = case._find_primary_component() + self.assertEqual(primary, "barney", msg="primary component test expected barney but got %s"%primary) + case.set_valid_values("COMP_ATM","%s,wilma"%case.get_value("COMP_ATM")) + case.set_value("COMP_ATM","wilma") + primary = case._find_primary_component() + self.assertEqual(primary, "wilma", msg="primary component test expected wilma but got %s"%primary) + # this is a "E" compset + case._compsetname.replace("XOCN","DOCN%SOM") + primary = case._find_primary_component() + self.assertEqual(primary, "allactive", msg="primary component test expected allactive but got %s"%primary) + # finally a "B" compset + case.set_value("COMP_OCN","bambam") + primary = case._find_primary_component() + self.assertEqual(primary, "allactive", msg="primary component test expected allactive but got %s"%primary) + cls._do_teardown.append(testdir) def test_b_user_mods(self): diff --git a/src/components/data_comps/docn/cime_config/config_component.xml b/src/components/data_comps/docn/cime_config/config_component.xml index ff71aa17f1b2..f1f969e25ff0 100644 --- a/src/components/data_comps/docn/cime_config/config_component.xml +++ b/src/components/data_comps/docn/cime_config/config_component.xml @@ -50,13 +50,13 @@ from the driver. --- A setting of prescribed assumes the only field in the input stream is SST. It also assumes the SST is in Celsius and must be converted to Kelvin. - All other fields are set to zero except for ocean salinity, which is set to a - constant reference salinity value. Normally the ice fraction data is found in - the same data files that provide SST data to the data ocean model. They are - normally found in the same file because the SST and ice fraction data are derived + All other fields are set to zero except for ocean salinity, which is set to a + constant reference salinity value. Normally the ice fraction data is found in + the same data files that provide SST data to the data ocean model. They are + normally found in the same file because the SST and ice fraction data are derived from the same observational data sets and are consistent with each other. - --- Settings of som (slab ocean model) or som_aquap (aquaplanet slab ocean) are - prognostic modes which compute a prognostic sea surface temperature and a + --- Settings of som (slab ocean model) or som_aquap (aquaplanet slab ocean) are + prognostic modes which compute a prognostic sea surface temperature and a freeze/melt potential (surface Q-flux) used by the sea ice model. This calculation requires an external SOM forcing data file that includes ocean mixed layer depths and bottom-of-the-slab Q-fluxes. @@ -67,13 +67,13 @@ appropriate and is provided for testing and development purposes only. Users must create scientifically appropriate data for their particular application. A tool is available to derive valid SOM forcing. - --- A setting of sst_aquapN (where “N” is an integer index value) is a - type of prescribed SST mode used specifically for an aquaplanet setup in - which global SSTs correspond to an analytic form set by the index value. + --- A setting of sst_aquapN (where “N” is an integer index value) is a + type of prescribed SST mode used specifically for an aquaplanet setup in + which global SSTs correspond to an analytic form set by the index value. Currently, indices for 10 SST profiles are supported [e.g., index 3 corresponds - to the “QOBS” profile of Neale and Hoskins (2001, Atmos. Sci. Lett.)]. - With source code modifications, it is possible for users to create their own - analytic SST distributions and match them to indices 11 or greater. + to the “QOBS” profile of Neale and Hoskins (2001, Atmos. Sci. Lett.)]. + With source code modifications, it is possible for users to create their own + analytic SST distributions and match them to indices 11 or greater. @@ -223,10 +223,11 @@ docn null mode + docn prescribed ocean mode docn slab ocean mode docn aquaplanet slab ocean mode docn interannual mode - docn aquaplanet mode: + docn aquaplanet mode: docn prescribed aquaplanet sst - option 1 docn prescribed aquaplanet sst - option 2 docn prescribed aquaplanet sst - option 3