Skip to content

Commit

Permalink
Merge pull request #1546 from jedwards4b/improve_user_compset_error_c…
Browse files Browse the repository at this point in the history
…hecking

Improve user compset error checking

Add individual component specs to README.case, determine pesfile for user-compsets if it is not provided. replace principle_component with primary_component.

Test suite: scripts_regression_tests.py
Test baseline:
Test namelist changes:
Test status: bit for bit

Fixes #608
Fixes #714

User interface changes?:

Code review:
  • Loading branch information
jgfouca authored May 16, 2017
2 parents 980f15d + e9b90d6 commit 98d386f
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 28 deletions.
4 changes: 0 additions & 4 deletions scripts/create_newcase
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 11 additions & 0 deletions scripts/lib/CIME/XML/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 72 additions & 11 deletions scripts/lib/CIME/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -442,28 +443,78 @@ 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)
+ "You may need the --user-compset argument.")

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 = []
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
#--------------------------------------------
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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.
Expand Down
42 changes: 42 additions & 0 deletions scripts/tests/scripts_regression_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
27 changes: 14 additions & 13 deletions src/components/data_comps/docn/cime_config/config_component.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
</desc>
</entry>

Expand Down Expand Up @@ -223,10 +223,11 @@

<description>
<desc compset="_DOCN%NULL">docn null mode</desc>
<desc compset="_DOCN%DOM" >docn prescribed ocean mode</desc>
<desc compset="_DOCN%SOM" >docn slab ocean mode</desc>
<desc compset="_DOCN%SOMAQP">docn aquaplanet slab ocean mode</desc>
<desc compset="_DOCN%IAF" >docn interannual mode</desc>
<desc compset="_DOCN%SST_AQUAP">docn aquaplanet mode:</desc>
<desc compset="_DOCN%SST_AQUAP">docn aquaplanet mode:</desc>
<desc compset="_DOCN%AQP1">docn prescribed aquaplanet sst - option 1</desc>
<desc compset="_DOCN%AQP2">docn prescribed aquaplanet sst - option 2</desc>
<desc compset="_DOCN%AQP3">docn prescribed aquaplanet sst - option 3</desc>
Expand Down

0 comments on commit 98d386f

Please sign in to comment.