Source code for CIME.case.case_setup

"""
Library for case.setup.
case_setup is a member of class Case from file case.py
"""

from CIME.XML.standard_module_setup import *

from CIME.XML.machines      import Machines
from CIME.BuildTools.configure import configure
from CIME.utils             import get_cime_root, run_and_log_case_status, get_model, get_batch_script_for_job, safe_copy
from CIME.test_status       import *
from CIME.locked_files      import unlock_file, lock_file

logger = logging.getLogger(__name__)

###############################################################################
def _build_usernl_files(case, model, comp):
###############################################################################
    """
    Create user_nl_xxx files, expects cwd is caseroot
    """
    model = model.upper()
    if model == "DRV":
        model_file = case.get_value("CONFIG_CPL_FILE")
    else:
        model_file = case.get_value("CONFIG_{}_FILE".format(model))
    expect(model_file is not None,
           "Could not locate CONFIG_{}_FILE in config_files.xml".format(model))
    model_dir = os.path.dirname(model_file)

    expect(os.path.isdir(model_dir),
           "cannot find cime_config directory {} for component {}".format(model_dir, comp))
    comp_interface = case.get_value("COMP_INTERFACE")
    multi_driver = case.get_value("MULTI_DRIVER")
    ninst = 1

    if multi_driver:
        ninst_max = case.get_value("NINST_MAX")
        if comp_interface != "nuopc" and model not in ("DRV","CPL","ESP"):
            ninst_model = case.get_value("NINST_{}".format(model))
            expect(ninst_model==ninst_max,"MULTI_DRIVER mode, all components must have same NINST value.  NINST_{} != {}".format(model,ninst_max))
    if comp == "cpl":
        if not os.path.exists("user_nl_cpl"):
            safe_copy(os.path.join(model_dir, "user_nl_cpl"), ".")
    else:
        if comp_interface == "nuopc":
            ninst = case.get_value("NINST")
        elif ninst == 1:
            ninst = case.get_value("NINST_{}".format(model))
        nlfile = "user_nl_{}".format(comp)
        model_nl = os.path.join(model_dir, nlfile)
        if ninst > 1:
            for inst_counter in range(1, ninst+1):
                inst_nlfile = "{}_{:04d}".format(nlfile, inst_counter)
                if not os.path.exists(inst_nlfile):
                    # If there is a user_nl_foo in the case directory, copy it
                    # to user_nl_foo_INST; otherwise, copy the original
                    # user_nl_foo from model_dir
                    if os.path.exists(nlfile):
                        safe_copy(nlfile, inst_nlfile)
                    elif os.path.exists(model_nl):
                        safe_copy(model_nl, inst_nlfile)
        else:
            # ninst = 1
            if not os.path.exists(nlfile):
                if os.path.exists(model_nl):
                    safe_copy(model_nl, nlfile)

###############################################################################
def _case_setup_impl(case, caseroot, clean=False, test_mode=False, reset=False, keep=None):
###############################################################################
    os.chdir(caseroot)

    non_local = case.get_value("NONLOCAL")

    models = case.get_values("COMP_CLASSES")
    mach = case.get_value("MACH")
    compiler = case.get_value("COMPILER")
    debug = case.get_value("DEBUG")
    mpilib = case.get_value("MPILIB")
    sysos = case.get_value("OS")
    comp_interface = case.get_value("COMP_INTERFACE")
    extra_machines_dir = case.get_value("EXTRA_MACHDIR")
    expect(mach is not None, "xml variable MACH is not set")

    # Check that $DIN_LOC_ROOT exists - and abort if not a namelist compare tests
    if not non_local:
        din_loc_root = case.get_value("DIN_LOC_ROOT")
        testcase     = case.get_value("TESTCASE")
        expect(not (not os.path.isdir(din_loc_root) and testcase != "SBN"),
               "inputdata root is not a directory or is not readable: {}".format(din_loc_root))

    # Remove batch scripts
    if reset or clean:
        # clean setup-generated files
        batch_script = get_batch_script_for_job(case.get_primary_job())
        files_to_clean = [batch_script, "env_mach_specific.xml", "Macros.make", "Macros.cmake"]
        for file_to_clean in files_to_clean:
            if os.path.exists(file_to_clean) and not (keep and file_to_clean in keep):
                os.remove(file_to_clean)
                logger.info("Successfully cleaned {}".format(file_to_clean))

        if not test_mode:
            # rebuild the models (even on restart)
            case.set_value("BUILD_COMPLETE", False)

        # Cannot leave case in bad state (missing env_mach_specific.xml)
        if clean and not os.path.isfile("env_mach_specific.xml"):
            case.flush()
            configure(Machines(machine=mach, extra_machines_dir=extra_machines_dir),
                      caseroot, ["Makefile"], compiler, mpilib, debug, comp_interface, sysos, noenv=True,
                      extra_machines_dir=extra_machines_dir)
            case.read_xml()

    if not clean:
        if not non_local:
            case.load_env()

        # creates the Macros.make, Depends.compiler, Depends.machine, Depends.machine.compiler
        # and env_mach_specific.xml if they don't already exist.
        if not os.path.isfile("Macros.make") or not os.path.isfile("env_mach_specific.xml"):
            reread = not os.path.isfile("env_mach_specific.xml")
            if reread:
                case.flush()
            configure(Machines(machine=mach, extra_machines_dir=extra_machines_dir),
                      caseroot, ["Makefile"], compiler, mpilib, debug, comp_interface, sysos, noenv=True,
                      extra_machines_dir=extra_machines_dir)
            if reread:
                case.read_xml()

        # Also write out Cmake macro file
        if not os.path.isfile("Macros.cmake"):
            configure(Machines(machine=mach, extra_machines_dir=extra_machines_dir),
                      caseroot, ["CMake"], compiler, mpilib, debug, comp_interface, sysos, noenv=True,
                      extra_machines_dir=extra_machines_dir)

        # Set tasks to 1 if mpi-serial library
        if mpilib == "mpi-serial":
            case.set_value("NTASKS", 1)

        # Check ninst.
        # In CIME there can be multiple instances of each component model (an ensemble) NINST is the instance of that component.
        comp_interface = case.get_value("COMP_INTERFACE")
        if comp_interface == "nuopc":
            ninst  = case.get_value("NINST")

        multi_driver = case.get_value("MULTI_DRIVER")

        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(case.get_value("NINST_LAYOUT_{}".format(comp)) == "concurrent",
                           "If multi_driver is TRUE, NINST_LAYOUT_{} must be concurrent".format(comp))
                case.set_value("NTASKS_PER_INST_{}".format(comp), ntasks)
            else:
                if ninst > ntasks:
                    if ntasks == 1:
                        case.set_value("NTASKS_{}".format(comp), ninst)
                        ntasks = ninst
                    else:
                        expect(False, "NINST_{comp} value {ninst} greater than NTASKS_{comp} {ntasks}".format(comp=comp, ninst=ninst, ntasks=ntasks))

                case.set_value("NTASKS_PER_INST_{}".format(comp), max(1,int(ntasks / ninst)))

        if os.path.exists(get_batch_script_for_job(case.get_primary_job())):
            logger.info("Machine/Decomp/Pes configuration has already been done ...skipping")

            case.initialize_derived_attributes()

            case.set_value("SMP_PRESENT", case.get_build_threaded())

        else:
            case.check_pelayouts_require_rebuild(models)

            unlock_file("env_build.xml")
            unlock_file("env_batch.xml")

            case.flush()
            case.check_lockedfiles()

            case.initialize_derived_attributes()

            cost_per_node = case.get_value("COSTPES_PER_NODE")
            case.set_value("COST_PES", case.num_nodes * cost_per_node)
            threaded = case.get_build_threaded()
            case.set_value("SMP_PRESENT", threaded)
            if threaded and case.total_tasks * case.thread_count > cost_per_node:
                smt_factor = max(1.0,int(case.get_value("MAX_TASKS_PER_NODE") / cost_per_node))
                case.set_value("TOTALPES", int(case.total_tasks * max(1.0,float(case.thread_count) / smt_factor)))
            else:
                case.set_value("TOTALPES", case.total_tasks*case.thread_count)

            # May need to select new batch settings if pelayout changed (e.g. problem is now too big for prev-selected queue)
            env_batch = case.get_env("batch")
            env_batch.set_job_defaults([(case.get_primary_job(), {})], case)

            # create batch files
            env_batch.make_all_batch_files(case)
            if get_model() == "e3sm" and not case.get_value("TEST"):
                input_batch_script = os.path.join(case.get_value("MACHDIR"), "template.case.run.sh")
                env_batch.make_batch_script(input_batch_script, "case.run", case, outfile=get_batch_script_for_job("case.run.sh"))

            # Make a copy of env_mach_pes.xml in order to be able
            # to check that it does not change once case.setup is invoked
            case.flush()
            logger.debug("at copy TOTALPES = {}".format(case.get_value("TOTALPES")))
            lock_file("env_mach_pes.xml")
            lock_file("env_batch.xml")

        # Create user_nl files for the required number of instances
        if not os.path.exists("user_nl_cpl"):
            logger.info("Creating user_nl_xxx files for components and cpl")

        # loop over models
        for model in models:
            comp = case.get_value("COMP_{}".format(model))
            logger.debug("Building {} usernl files".format(model))
            _build_usernl_files(case, model, comp)
            if comp == "cism":
                glcroot = case.get_value("COMP_ROOT_DIR_GLC")
                run_cmd_no_fail("{}/cime_config/cism.template {}".format(glcroot, caseroot))

        _build_usernl_files(case, "drv", "cpl")

        # Create needed directories for case
        case.create_dirs()

        logger.info("If an old case build already exists, might want to run \'case.build --clean\' before building")

        # Some tests need namelists created here (ERP) - so do this if we are in test mode
        if (test_mode or get_model() == "e3sm") and not non_local:
            logger.info("Generating component namelists as part of setup")
            case.create_namelists()

        # Record env information
        env_module = case.get_env("mach_specific")
        env_module.make_env_mach_specific_file("sh", case)
        env_module.make_env_mach_specific_file("csh", case)
        if not non_local:
            env_module.save_all_env_info("software_environment.txt")

        logger.info("You can now run './preview_run' to get more info on how your case will be run")

###############################################################################
[docs]def case_setup(self, clean=False, test_mode=False, reset=False, keep=None): ############################################################################### caseroot, casebaseid = self.get_value("CASEROOT"), self.get_value("CASEBASEID") phase = "setup.clean" if clean else "case.setup" functor = lambda: _case_setup_impl(self, caseroot, clean=clean, test_mode=test_mode, reset=reset, keep=keep) if self.get_value("TEST") and not test_mode: test_name = casebaseid if casebaseid is not None else self.get_value("CASE") with TestStatus(test_dir=caseroot, test_name=test_name) as ts: try: run_and_log_case_status(functor, phase, caseroot=caseroot) except BaseException: # Want to catch KeyboardInterrupt too ts.set_status(SETUP_PHASE, TEST_FAIL_STATUS) raise else: if clean: ts.set_status(SETUP_PHASE, TEST_PEND_STATUS) else: ts.set_status(SETUP_PHASE, TEST_PASS_STATUS) else: run_and_log_case_status(functor, phase, caseroot=caseroot)