def set_predef_grid_params():
    """ Sets grid parameters for the specified predfined grid 

    Args:
        None
    Returns:
        None
    """
    # import all environement variables
    import_vars()

    params_dict = load_config_file("predef_grid_params.yaml")
    params_dict = params_dict[PREDEF_GRID_NAME]

    # if QUILTING = False, skip variables that start with "WRTCMP_"
    if not QUILTING:
        params_dict = {k: v for k,v in params_dict.items() \
                            if not k.startswith("WRTCMP_") }

    # take care of special vars
    special_vars = ['DT_ATMOS', 'LAYOUT_X', 'LAYOUT_Y', 'BLOCKSIZE']
    for var in special_vars:
        if globals()[var] is not None:
            params_dict[var] = globals()[var]

    #export variables to environment
    export_vars(source_dict=params_dict)
Ejemplo n.º 2
0
def delete_crontab_line(called_from_cron):
    """ Delete crontab line after job is complete i.e. either SUCCESS/FAILURE
    but not IN PROGRESS status"""

    print_input_args(locals())

    #import selected env vars
    IMPORTS = ["MACHINE", "USER", "CRONTAB_LINE"]
    import_vars(env_vars=IMPORTS)

    #
    # Get the full contents of the user's cron table.
    #
    (crontab_cmd, crontab_contents) = get_crontab_contents(called_from_cron)
    #
    # Remove the line in the contents of the cron table corresponding to the
    # current forecast experiment (if that line is part of the contents).
    # Then record the results back into the user's cron table.
    #
    if (CRONTAB_LINE + '\n') in crontab_contents:
        crontab_contents = crontab_contents.replace(CRONTAB_LINE + '\n', '')
    else:
        crontab_contents = crontab_contents.replace(CRONTAB_LINE, '')

    run_command(f'''echo '{crontab_contents}' | {crontab_cmd}''')
def set_extrn_mdl_params():
    """ Sets parameters associated with the external model used for initial 
    conditions (ICs) and lateral boundary conditions (LBCs).
    Args:
        None
    Returns:
        None
    """

    #import all env variables
    import_vars()

    global EXTRN_MDL_LBCS_OFFSET_HRS

    #
    #-----------------------------------------------------------------------
    #
    # Set EXTRN_MDL_LBCS_OFFSET_HRS, which is the number of hours to shift
    # the starting time of the external model that provides lateral boundary
    # conditions.
    #
    #-----------------------------------------------------------------------
    #
    if EXTRN_MDL_NAME_LBCS == "RAP":
        EXTRN_MDL_LBCS_OFFSET_HRS = EXTRN_MDL_LBCS_OFFSET_HRS or "3"
    else:
        EXTRN_MDL_LBCS_OFFSET_HRS = EXTRN_MDL_LBCS_OFFSET_HRS or "0"

    # export values we set above
    env_vars = ["EXTRN_MDL_LBCS_OFFSET_HRS"]
    export_vars(env_vars=env_vars)
Ejemplo n.º 4
0
def add_crontab_line():
    """ Add crontab line to cron table """

    #import selected env vars
    IMPORTS = ["MACHINE", "USER", "CRONTAB_LINE", "VERBOSE", "EXPTDIR"]
    import_vars(env_vars=IMPORTS)

    #
    # Make a backup copy of the user's crontab file and save it in a file.
    #
    time_stamp = datetime.now().strftime("%F_%T")
    crontab_backup_fp = os.path.join(EXPTDIR, f"crontab.bak.{time_stamp}")
    print_info_msg(f'''
      Copying contents of user cron table to backup file:
        crontab_backup_fp = \"{crontab_backup_fp}\"''',
                   verbose=VERBOSE)

    global called_from_cron
    try:
        called_from_cron
    except:
        called_from_cron = False

    # Get crontab contents
    crontab_cmd, crontab_contents = get_crontab_contents(
        called_from_cron=called_from_cron)

    # Create backup
    run_command(
        f'''printf "%s" '{crontab_contents}' > "{crontab_backup_fp}"''')

    # Add crontab line
    if CRONTAB_LINE in crontab_contents:

        print_info_msg(f'''
          The following line already exists in the cron table and thus will not be
          added:
            CRONTAB_LINE = \"{CRONTAB_LINE}\"''')

    else:

        print_info_msg(f'''
          Adding the following line to the user's cron table in order to automatically
          resubmit SRW workflow:
            CRONTAB_LINE = \"{CRONTAB_LINE}\"''',
                       verbose=VERBOSE)

        #add new line to crontab contents if it doesn't have one
        NEWLINE_CHAR = ""
        if crontab_contents and crontab_contents[-1] != "\n":
            NEWLINE_CHAR = "\n"

        #add the crontab line
        run_command(
            f'''printf "%s%b%s\n" '{crontab_contents}' '{NEWLINE_CHAR}' '{CRONTAB_LINE}' | {crontab_cmd}'''
        )
Ejemplo n.º 5
0
def create_diag_table_file(run_dir):
    """ Creates a diagnostic table file for each cycle to be run

    Args:
        run_dir: run directory
    Returns:
        Boolean
    """

    print_input_args(locals())

    #import all environment variables
    import_vars()

    #create a diagnostic table file within the specified run directory
    print_info_msg(f'''
        Creating a diagnostics table file (\"{DIAG_TABLE_FN}\") in the specified
        run directory...
        
          run_dir = \"{run_dir}\"''',
                   verbose=VERBOSE)

    diag_table_fp = os.path.join(run_dir, DIAG_TABLE_FN)

    print_info_msg(f'''
        
        Using the template diagnostics table file:
        
            diag_table_tmpl_fp = {DIAG_TABLE_TMPL_FP}
        
        to create:
        
            diag_table_fp = \"{diag_table_fp}\"''',
                   verbose=VERBOSE)

    settings = {'starttime': CDATE, 'cres': CRES}
    settings_str = cfg_to_yaml_str(settings)

    #call fill jinja
    try:
        fill_jinja_template([
            "-q", "-u", settings_str, "-t", DIAG_TABLE_TMPL_FP, "-o",
            diag_table_fp
        ])
    except:
        print_err_msg_exit(f'''
            !!!!!!!!!!!!!!!!!
            
            fill_jinja_template.py failed!
            
            !!!!!!!!!!!!!!!!!''')
        return False
    return True
Ejemplo n.º 6
0
def get_crontab_contents(called_from_cron):
    """
    #-----------------------------------------------------------------------
    #
    # This function returns the contents of the user's 
    # cron table as well as the command to use to manipulate the cron table
    # (i.e. the "crontab" command, but on some platforms the version or 
    # location of this may change depending on other circumstances, e.g. on
    # Cheyenne, this depends on whether a script that wants to call "crontab"
    # is itself being called from a cron job).  Arguments are as follows:
    #
    # called_from_cron:
    # Boolean flag that specifies whether this function (and the scripts or
    # functions that are calling it) are called as part of a cron job.  Must
    # be set to "TRUE" or "FALSE".
    #
    # outvarname_crontab_cmd:
    # Name of the output variable that will contain the command to issue for
    # the system "crontab" command.
    #
    # outvarname_crontab_contents:
    # Name of the output variable that will contain the contents of the 
    # user's cron table.
    # 
    #-----------------------------------------------------------------------
    """

    print_input_args(locals())

    #import selected env vars
    IMPORTS = ["MACHINE", "USER"]
    import_vars(env_vars=IMPORTS)

    __crontab_cmd__ = "crontab"
    #
    # On Cheyenne, simply typing "crontab" will launch the crontab command
    # at "/glade/u/apps/ch/opt/usr/bin/crontab".  This is a containerized
    # version of crontab that will work if called from scripts that are
    # themselves being called as cron jobs.  In that case, we must instead
    # call the system version of crontab at /usr/bin/crontab.
    #
    if MACHINE == "CHEYENNE":
        if called_from_cron:
            __crontab_cmd__ = "/usr/bin/crontab"
    (_, __crontab_contents__, _) = run_command(f'''{__crontab_cmd__} -l''')

    # replace single quotes (hopefully in comments) with double quotes
    __crontab_contents__ = __crontab_contents__.replace("'", '"')

    return __crontab_cmd__, __crontab_contents__
def get_crontab_contents(called_from_cron):
    """
    #-----------------------------------------------------------------------
    #
    # This function returns the contents of the user's 
    # cron table as well as the command to use to manipulate the cron table
    # (i.e. the "crontab" command, but on some platforms the version or 
    # location of this may change depending on other circumstances, e.g. on
    # Cheyenne, this depends on whether a script that wants to call "crontab"
    # is itself being called from a cron job).  Arguments are as follows:
    #
    # called_from_cron:
    # Boolean flag that specifies whether this function (and the scripts or
    # functions that are calling it) are called as part of a cron job.  Must
    # be set to "TRUE" or "FALSE".
    #
    # outvarname_crontab_cmd:
    # Name of the output variable that will contain the command to issue for
    # the system "crontab" command.
    #
    # outvarname_crontab_contents:
    # Name of the output variable that will contain the contents of the 
    # user's cron table.
    # 
    #-----------------------------------------------------------------------
    """

    print_input_args(locals())

    #import all env vars
    IMPORTS = ["MACHINE", "USER"]
    import_vars(env_vars=IMPORTS)

    #
    # Make sure called_from_cron is set to a valid value.
    #
    check_var_valid_value(called_from_cron, valid_vals_BOOLEAN)

    if MACHINE == "WCOSS_DELL_P3":
        __crontab_cmd__ = ""
        (_, __crontab_contents__,
         _) = run_command(f'''cat "/u/{USER}/cron/mycrontab"''')
    else:
        __crontab_cmd__ = "crontab"
        #
        # On Cheyenne, simply typing "crontab" will launch the crontab command
        # at "/glade/u/apps/ch/opt/usr/bin/crontab".  This is a containerized
        # version of crontab that will work if called from scripts that are
        # themselves being called as cron jobs.  In that case, we must instead
        # call the system version of crontab at /usr/bin/crontab.
        #
        if MACHINE == "CHEYENNE":
            if called_from_cron:
                __crontab_cmd__ = "/usr/bin/crontab"
        (_, __crontab_contents__, _) = run_command(f'''{__crontab_cmd__} -l''')
    #
    # On Cheyenne, the output of the "crontab -l" command contains a 3-line
    # header (comments) at the top that is not actually part of the user's
    # cron table.  This needs to be removed to avoid adding an unnecessary
    # copy of this header to the user's cron table.
    #
    if MACHINE == "CHEYENNE":
        (_, __crontab_contents__, _) = run_command(
            f'''printf "%s" "{__crontab_contents__}" | tail -n +4 ''')

    return __crontab_cmd__, __crontab_contents__
Ejemplo n.º 8
0
def set_FV3nml_sfc_climo_filenames():
    """
    This function sets the values of the variables in
    the forecast model's namelist file that specify the paths to the surface
    climatology files on the FV3LAM native grid (which are either pregenerated
    or created by the MAKE_SFC_CLIMO_TN task).  Note that the workflow
    generation scripts create symlinks to these surface climatology files
    in the FIXLAM directory, and the values in the namelist file that get
    set by this function are relative or full paths to these links.

    Args:
        None
    Returns:
        None
    """

    # import all environment variables
    import_vars()

    # The regular expression regex_search set below will be used to extract
    # from the elements of the array FV3_NML_VARNAME_TO_SFC_CLIMO_FIELD_MAPPING
    # the name of the namelist variable to set and the corresponding surface
    # climatology field from which to form the name of the surface climatology file
    regex_search = "^[ ]*([^| ]+)[ ]*[|][ ]*([^| ]+)[ ]*$"

    # Set the suffix of the surface climatology files.
    suffix = "tileX.nc"

    # create yaml-complaint string
    settings = {}

    dummy_run_dir = os.path.join(EXPTDIR, "any_cyc")
    if DO_ENSEMBLE == "TRUE":
        dummy_run_dir += os.sep + "any_ensmem"

    namsfc_dict = {}
    for mapping in FV3_NML_VARNAME_TO_SFC_CLIMO_FIELD_MAPPING:
        tup = find_pattern_in_str(regex_search, mapping)
        nml_var_name = tup[0]
        sfc_climo_field_name = tup[1]

        check_var_valid_value(sfc_climo_field_name, SFC_CLIMO_FIELDS)

        fp = os.path.join(FIXLAM, f'{CRES}.{sfc_climo_field_name}.{suffix}')
        if RUN_ENVIR != "nco":
            fp = os.path.relpath(os.path.realpath(fp), start=dummy_run_dir)

        namsfc_dict[nml_var_name] = fp
     
    settings['namsfc_dict'] = namsfc_dict
    settings_str = cfg_to_yaml_str(settings)


    print_info_msg(dedent(f'''
        The variable \"settings\" specifying values of the namelist variables
        has been set as follows:
        
        settings =\n\n''') + settings_str, verbose=VERBOSE)

    # Rename the FV3 namelist and call set_namelist
    fv3_nml_base_fp = f'{FV3_NML_FP}.base' 
    mv_vrfy(f'{FV3_NML_FP} {fv3_nml_base_fp}')

    try:
        set_namelist(["-q", "-n", fv3_nml_base_fp, "-u", settings_str, "-o", FV3_NML_FP])
    except:
        print_err_msg_exit(dedent(f'''
            Call to python script set_namelist.py to set the variables in the FV3
            namelist file that specify the paths to the surface climatology files
            failed.  Parameters passed to this script are:
              Full path to base namelist file:
                fv3_nml_base_fp = \"{fv3_nml_base_fp}\"
              Full path to output namelist file:
                FV3_NML_FP = \"{FV3_NML_FP}\"
              Namelist settings specified on command line (these have highest precedence):\n
                settings =\n\n''') + settings_str)

    rm_vrfy(f'{fv3_nml_base_fp}')
def set_FV3nml_ens_stoch_seeds(cdate):
    """
    This function, for an ensemble-enabled experiment 
    (i.e. for an experiment for which the workflow configuration variable 
    DO_ENSEMBLE has been set to "TRUE"), creates new namelist files with
    unique stochastic "seed" parameters, using a base namelist file in the 
    ${EXPTDIR} directory as a template. These new namelist files are stored 
    within each member directory housed within each cycle directory. Files 
    of any two ensemble members differ only in their stochastic "seed" 
    parameter values.  These namelist files are generated when this file is
    called as part of the RUN_FCST_TN task.  

    Args:
        cdate
    Returns:
        None
    """

    print_input_args(locals())

    # import all environment variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # For a given cycle and member, generate a namelist file with unique
    # seed values.
    #
    #-----------------------------------------------------------------------
    #
    ensmem_name = f"mem{ENSMEM_INDX}"

    fv3_nml_ensmem_fp = os.path.join(
        CYCLE_BASEDIR,
        f'{date_to_str(cdate,format="%Y%m%d%H")}{os.sep}{ensmem_name}{os.sep}{FV3_NML_FN}'
    )

    ensmem_num = ENSMEM_INDX

    cdate_i = int(cdate.strftime('%Y%m%d%H'))

    settings = {}
    nam_stochy_dict = {}

    if DO_SPPT:
        iseed_sppt = cdate_i * 1000 + ensmem_num * 10 + 1
        nam_stochy_dict.update({'iseed_sppt': iseed_sppt})

    if DO_SHUM:
        iseed_shum = cdate_i * 1000 + ensmem_num * 10 + 2
        nam_stochy_dict.update({'iseed_shum': iseed_shum})

    if DO_SKEB:
        iseed_skeb = cdate_i * 1000 + ensmem_num * 10 + 3
        nam_stochy_dict.update({'iseed_skeb': iseed_skeb})

    settings['nam_stochy'] = nam_stochy_dict

    if DO_SPP:
        num_iseed_spp = len(ISEED_SPP)
        iseed_spp = [None] * num_iseed_spp
        for i in range(num_iseed_spp):
            iseed_spp[i] = cdate_i * 1000 + ensmem_num * 10 + ISEED_SPP[i]

        settings['nam_sppperts'] = {'iseed_spp': iseed_spp}
    else:
        settings['nam_sppperts'] = {}

    if DO_LSM_SPP:
        iseed_lsm_spp = cdate_i * 1000 + ensmem_num * 10 + 9

        settings['nam_sppperts'] = {'iseed_lndp': [iseed_lsm_spp]}

    settings_str = cfg_to_yaml_str(settings)

    print_info_msg(dedent(f'''
        The variable \"settings\" specifying seeds in \"{FV3_NML_FP}\"
        has been set as follows:

        settings =\n\n''') + settings_str,
                   verbose=VERBOSE)

    try:
        set_namelist([
            "-q", "-n", FV3_NML_FP, "-u", settings_str, "-o", fv3_nml_ensmem_fp
        ])
    except:
        print_err_msg_exit(
            dedent(f'''
            Call to python script set_namelist.py to set the variables in the FV3
            namelist file that specify the paths to the surface climatology files
            failed.  Parameters passed to this script are:
              Full path to base namelist file:
                FV3_NML_FP = \"{FV3_NML_FP}\"
              Full path to output namelist file:
                fv3_nml_ensmem_fp = \"{fv3_nml_ensmem_fp}\"
              Namelist settings specified on command line (these have highest precedence):\n
                settings =\n\n''') + settings_str)
Ejemplo n.º 10
0
def set_gridparams_ESGgrid(lon_ctr, lat_ctr, nx, ny, halo_width, delx, dely,
                           pazi):
    """ Sets the parameters for a grid that is to be generated using the "ESGgrid" 
    grid generation method (i.e. GRID_GEN_METHOD set to "ESGgrid").

    Args:
        lon_ctr
        lat_ctr
        nx
        ny
        halo_width
        delx
        dely
        pazi
    Returns:
        Tuple of inputs, and 4 outputs (see return statement)
    """

    print_input_args(locals())

    # get needed environment variables
    IMPORTS = ['RADIUS_EARTH', 'DEGS_PER_RADIAN']
    import_vars(env_vars=IMPORTS)
    #
    #-----------------------------------------------------------------------
    #
    # For a ESGgrid-type grid, the orography filtering is performed by pass-
    # ing to the orography filtering the parameters for an "equivalent" glo-
    # bal uniform cubed-sphere grid.  These are the parameters that a global
    # uniform cubed-sphere grid needs to have in order to have a nominal
    # grid cell size equal to that of the (average) cell size on the region-
    # al grid.  These globally-equivalent parameters include a resolution
    # (in units of number of cells in each of the two horizontal directions)
    # and a stretch factor.  The equivalent resolution is calculated in the
    # script that generates the grid, and the stretch factor needs to be set
    # to 1 because we are considering an equivalent globally UNIFORM grid.
    # However, it turns out that with a non-symmetric regional grid (one in
    # which nx is not equal to ny), setting stretch_factor to 1 fails be-
    # cause the orography filtering program is designed for a global cubed-
    # sphere grid and thus assumes that nx and ny for a given tile are equal
    # when stretch_factor is exactly equal to 1.
    # ^^-- Why is this?  Seems like symmetry btwn x and y should still hold when the stretch factor is not equal to 1.
    # It turns out that the program will work if we set stretch_factor to a
    # value that is not exactly 1.  This is what we do below.
    #
    #-----------------------------------------------------------------------
    #
    stretch_factor = 0.999  # Check whether the orography program has been fixed so that we can set this to 1...
    #
    #-----------------------------------------------------------------------
    #
    # Set parameters needed as inputs to the regional_grid grid generation
    # code.
    #
    #-----------------------------------------------------------------------
    #
    del_angle_x_sg = (delx / (2.0 * RADIUS_EARTH)) * DEGS_PER_RADIAN
    del_angle_y_sg = (dely / (2.0 * RADIUS_EARTH)) * DEGS_PER_RADIAN
    neg_nx_of_dom_with_wide_halo = -(nx + 2 * halo_width)
    neg_ny_of_dom_with_wide_halo = -(ny + 2 * halo_width)
    #
    #-----------------------------------------------------------------------
    #
    # return output variables.
    #
    #-----------------------------------------------------------------------
    #
    return (lon_ctr, lat_ctr, nx, ny, pazi, halo_width, stretch_factor,
            del_angle_x_sg, del_angle_y_sg, int(neg_nx_of_dom_with_wide_halo),
            int(neg_ny_of_dom_with_wide_halo))
Ejemplo n.º 11
0
def create_diag_table_file(run_dir):
    """ Creates a diagnostic table file for each cycle to be run

    Args:
        run_dir: run directory
    Returns:
        Boolean
    """

    print_input_args(locals())

    #import all environment variables
    import_vars()

    #create a diagnostic table file within the specified run directory
    print_info_msg(f'''
        Creating a diagnostics table file (\"{DIAG_TABLE_FN}\") in the specified
        run directory...
        
          run_dir = \"{run_dir}\"''',
                   verbose=VERBOSE)

    diag_table_fp = os.path.join(run_dir, DIAG_TABLE_FN)

    print_info_msg(f'''
        
        Using the template diagnostics table file:
        
            diag_table_tmpl_fp = {DIAG_TABLE_TMPL_FP}
        
        to create:
        
            diag_table_fp = \"{diag_table_fp}\"''',
                   verbose=VERBOSE)

    settings = {'starttime': CDATE, 'cres': CRES}
    settings_str = cfg_to_yaml_str(settings)

    print_info_msg(dedent(f'''
        The variable \"settings\" specifying values to be used in the \"{DIAG_TABLE_FN}\"
        file has been set as follows:\n
        settings =\n\n''') + settings_str,
                   verbose=VERBOSE)

    #call fill jinja
    try:
        fill_jinja_template([
            "-q", "-u", settings_str, "-t", DIAG_TABLE_TMPL_FP, "-o",
            diag_table_fp
        ])
    except:
        print_err_msg_exit(
            dedent(f'''
            Call to python script fill_jinja_template.py to create a \"{DIAG_TABLE_FN}\"
            file from a jinja2 template failed.  Parameters passed to this script are:
              Full path to template diag table file:
                DIAG_TABLE_TMPL_FP = \"{DIAG_TABLE_TMPL_FP}\"
              Full path to output diag table file:
                diag_table_fp = \"{diag_table_fp}\"
              Namelist settings specified on command line:\n
                settings =\n\n''') + settings_str)
        return False
    return True
Ejemplo n.º 12
0
def set_gridparams_GFDLgrid(lon_of_t6_ctr, lat_of_t6_ctr, res_of_t6g, stretch_factor,
                            refine_ratio_t6g_to_t7g, 
                            istart_of_t7_on_t6g, iend_of_t7_on_t6g,
                            jstart_of_t7_on_t6g, jend_of_t7_on_t6g):
    """ Sets the parameters for a grid that is to be generated using the "GFDLgrid" 
    grid generation method (i.e. GRID_GEN_METHOD set to "ESGgrid").

    Args:
         lon_of_t6_ctr
         lat_of_t6_ctr
         res_of_t6g
         stretch_factor
         refine_ratio_t6g_to_t7g 
         istart_of_t7_on_t6g
         iend_of_t7_on_t6g
         jstart_of_t7_on_t6g
         jend_of_t7_on_t6g):
    Returns:
        Tuple of inputs and outputs (see return statement)
    """

    print_input_args(locals())

    # get needed environment variables
    IMPORTS = ['VERBOSE', 'RUN_ENVIR', 'NH4']
    import_vars(env_vars=IMPORTS)

    #
    #-----------------------------------------------------------------------
    #
    # To simplify the grid setup, we require that tile 7 be centered on tile 
    # 6.  Note that this is not really a restriction because tile 6 can al-
    # ways be moved so that it is centered on tile 7 [the location of tile 6 
    # doesn't really matter because for a regional setup, the forecast model 
    # will only run on tile 7 (not on tiles 1-6)].
    #
    # We now check that tile 7 is centered on tile 6 by checking (1) that 
    # the number of cells (on tile 6) between the left boundaries of these 
    # two tiles is equal to that between their right boundaries and (2) that 
    # the number of cells (on tile 6) between the bottom boundaries of these
    # two tiles is equal to that between their top boundaries.  If not, we 
    # print out an error message and exit.  If so, we set the longitude and 
    # latitude of the center of tile 7 to those of tile 6 and continue.
    #
    #-----------------------------------------------------------------------
    #

    nx_of_t6_on_t6g = res_of_t6g
    ny_of_t6_on_t6g = res_of_t6g

    num_left_margin_cells_on_t6g = istart_of_t7_on_t6g - 1
    num_right_margin_cells_on_t6g = nx_of_t6_on_t6g - iend_of_t7_on_t6g

    # This if-statement can hopefully be removed once EMC agrees to make their
    # GFDLgrid type grids (tile 7) symmetric about tile 6.
    if RUN_ENVIR != "nco":
        if num_left_margin_cells_on_t6g != num_right_margin_cells_on_t6g:
            print_err_msg_exit(f'''
                In order for tile 7 to be centered in the x direction on tile 6, the x-
                direction tile 6 cell indices at which tile 7 starts and ends (given by
                istart_of_t7_on_t6g and iend_of_t7_on_t6g, respectively) must be set 
                such that the number of tile 6 cells in the margin between the left 
                boundaries of tiles 6 and 7 (given by num_left_margin_cells_on_t6g) is
                equal to that in the margin between their right boundaries (given by 
                num_right_margin_cells_on_t6g):
                  istart_of_t7_on_t6g = {istart_of_t7_on_t6g}
                  iend_of_t7_on_t6g = {iend_of_t7_on_t6g}
                  num_left_margin_cells_on_t6g = {num_left_margin_cells_on_t6g}
                  num_right_margin_cells_on_t6g = {num_right_margin_cells_on_t6g}
                Note that the total number of cells in the x-direction on tile 6 is gi-
                ven by:
                  nx_of_t6_on_t6g = {nx_of_t6_on_t6g}
                Please reset istart_of_t7_on_t6g and iend_of_t7_on_t6g and rerun.''')

    num_bot_margin_cells_on_t6g = jstart_of_t7_on_t6g - 1
    num_top_margin_cells_on_t6g = ny_of_t6_on_t6g - jend_of_t7_on_t6g

    # This if-statement can hopefully be removed once EMC agrees to make their
    # GFDLgrid type grids (tile 7) symmetric about tile 6.
    if RUN_ENVIR != "nco":
        if num_bot_margin_cells_on_t6g != num_top_margin_cells_on_t6g:
            print_err_msg_exit(f'''
                In order for tile 7 to be centered in the y direction on tile 6, the y-
                direction tile 6 cell indices at which tile 7 starts and ends (given by
                jstart_of_t7_on_t6g and jend_of_t7_on_t6g, respectively) must be set 
                such that the number of tile 6 cells in the margin between the left 
                boundaries of tiles 6 and 7 (given by num_left_margin_cells_on_t6g) is
                equal to that in the margin between their right boundaries (given by 
                num_right_margin_cells_on_t6g):
                  jstart_of_t7_on_t6g = {jstart_of_t7_on_t6g}
                  jend_of_t7_on_t6g = {jend_of_t7_on_t6g}
                  num_bot_margin_cells_on_t6g = {num_bot_margin_cells_on_t6g}
                  num_top_margin_cells_on_t6g = {num_top_margin_cells_on_t6g}
                Note that the total number of cells in the y-direction on tile 6 is gi-
                ven by:
                  ny_of_t6_on_t6g = {ny_of_t6_on_t6g}
                Please reset jstart_of_t7_on_t6g and jend_of_t7_on_t6g and rerun.''')

    lon_of_t7_ctr = lon_of_t6_ctr
    lat_of_t7_ctr = lat_of_t6_ctr
    #
    #-----------------------------------------------------------------------
    #
    # The grid generation script grid_gen_scr called below in turn calls the
    # make_hgrid utility/executable to construct the regional grid.  make_-
    # hgrid accepts as arguments the index limits (i.e. starting and ending
    # indices) of the regional grid on the supergrid of the regional grid's
    # parent tile.  The regional grid's parent tile is tile 6, and the su-
    # pergrid of any given tile is defined as the grid obtained by doubling
    # the number of cells in each direction on that tile's grid.  We will
    # denote these index limits by
    #
    #   istart_of_t7_on_t6sg
    #   iend_of_t7_on_t6sg
    #   jstart_of_t7_on_t6sg
    #   jend_of_t7_on_t6sg
    #
    # The "_T6SG" suffix in these names is used to indicate that the indices
    # are on the supergrid of tile 6.  Recall, however, that we have as in-
    # puts the index limits of the regional grid on the tile 6 grid, not its
    # supergrid.  These are given by
    #
    #   istart_of_t7_on_t6g
    #   iend_of_t7_on_t6g
    #   jstart_of_t7_on_t6g
    #   jend_of_t7_on_t6g
    #
    # We can obtain the former from the latter by recalling that the super-
    # grid has twice the resolution of the original grid.  Thus,
    #
    #   istart_of_t7_on_t6sg = 2*istart_of_t7_on_t6g - 1
    #   iend_of_t7_on_t6sg = 2*iend_of_t7_on_t6g
    #   jstart_of_t7_on_t6sg = 2*jstart_of_t7_on_t6g - 1
    #   jend_of_t7_on_t6sg = 2*jend_of_t7_on_t6g
    #
    # These are obtained assuming that grid cells on tile 6 must either be
    # completely within the regional domain or completely outside of it,
    # i.e. the boundary of the regional grid must coincide with gridlines
    # on the tile 6 grid; it cannot cut through tile 6 cells.  (Note that
    # this implies that the starting indices on the tile 6 supergrid must be
    # odd while the ending indices must be even; the above expressions sa-
    # tisfy this requirement.)  We perfrom these calculations next.
    #
    #-----------------------------------------------------------------------
    #
    istart_of_t7_on_t6sg = 2*istart_of_t7_on_t6g - 1
    iend_of_t7_on_t6sg = 2*iend_of_t7_on_t6g
    jstart_of_t7_on_t6sg = 2*jstart_of_t7_on_t6g - 1
    jend_of_t7_on_t6sg = 2*jend_of_t7_on_t6g
    #
    #-----------------------------------------------------------------------
    #
    # If we simply pass to make_hgrid the index limits of the regional grid
    # on the tile 6 supergrid calculated above, make_hgrid will generate a
    # regional grid without a halo.  To obtain a regional grid with a halo,
    # we must pass to make_hgrid the index limits (on the tile 6 supergrid)
    # of the regional grid including a halo.  We will let the variables
    #
    #   istart_of_t7_with_halo_on_t6sg
    #   iend_of_t7_with_halo_on_t6sg
    #   jstart_of_t7_with_halo_on_t6sg
    #   jend_of_t7_with_halo_on_t6sg
    #
    # denote these limits.  The reason we include "_wide_halo" in these va-
    # riable names is that the halo of the grid that we will first generate
    # will be wider than the halos that are actually needed as inputs to the
    # FV3LAM model (i.e. the 0-cell-wide, 3-cell-wide, and 4-cell-wide halos
    # described above).  We will generate the grids with narrower halos that
    # the model needs later on by "shaving" layers of cells from this wide-
    # halo grid.  Next, we describe how to calculate the above indices.
    #
    # Let halo_width_on_t7g denote the width of the "wide" halo in units of number of
    # grid cells on the regional grid (i.e. tile 7) that we'd like to have
    # along all four edges of the regional domain (left, right, bottom, and
    # top).  To obtain the corresponding halo width in units of number of
    # cells on the tile 6 grid -- which we denote by halo_width_on_t6g -- we simply di-
    # vide halo_width_on_t7g by the refinement ratio, i.e.
    #
    #   halo_width_on_t6g = halo_width_on_t7g/refine_ratio_t6g_to_t7g
    #
    # The corresponding halo width on the tile 6 supergrid is then given by
    #
    #   halo_width_on_t6sg = 2*halo_width_on_t6g
    #                      = 2*halo_width_on_t7g/refine_ratio_t6g_to_t7g
    #
    # Note that halo_width_on_t6sg must be an integer, but the expression for it de-
    # rived above may not yield an integer.  To ensure that the halo has a
    # width of at least halo_width_on_t7g cells on the regional grid, we round up the
    # result of the expression above for halo_width_on_t6sg, i.e. we redefine halo_width_on_t6sg
    # to be
    #
    #   halo_width_on_t6sg = ceil(2*halo_width_on_t7g/refine_ratio_t6g_to_t7g)
    #
    # where ceil(...) is the ceiling function, i.e. it rounds its floating
    # point argument up to the next larger integer.  Since in bash division
    # of two integers returns a truncated integer and since bash has no
    # built-in ceil(...) function, we perform the rounding-up operation by
    # adding the denominator (of the argument of ceil(...) above) minus 1 to
    # the original numerator, i.e. by redefining halo_width_on_t6sg to be
    #
    #   halo_width_on_t6sg = (2*halo_width_on_t7g + refine_ratio_t6g_to_t7g - 1)/refine_ratio_t6g_to_t7g
    #
    # This trick works when dividing one positive integer by another.
    #
    # In order to calculate halo_width_on_t6g using the above expression, we must
    # first specify halo_width_on_t7g.  Next, we specify an initial value for it by
    # setting it to one more than the largest-width halo that the model ac-
    # tually needs, which is NH4.  We then calculate halo_width_on_t6sg using the
    # above expression.  Note that these values of halo_width_on_t7g and halo_width_on_t6sg will
    # likely not be their final values; their final values will be calcula-
    # ted later below after calculating the starting and ending indices of
    # the regional grid with wide halo on the tile 6 supergrid and then ad-
    # justing the latter to satisfy certain conditions.
    #
    #-----------------------------------------------------------------------
    #
    halo_width_on_t7g = NH4 + 1
    halo_width_on_t6sg = (2*halo_width_on_t7g + refine_ratio_t6g_to_t7g - 1)/refine_ratio_t6g_to_t7g
    #
    #-----------------------------------------------------------------------
    #
    # With an initial value of halo_width_on_t6sg now available, we can obtain the
    # tile 6 supergrid index limits of the regional domain (including the
    # wide halo) from the index limits for the regional domain without a ha-
    # lo by simply subtracting halo_width_on_t6sg from the lower index limits and add-
    # ing halo_width_on_t6sg to the upper index limits, i.e.
    #
    #   istart_of_t7_with_halo_on_t6sg = istart_of_t7_on_t6sg - halo_width_on_t6sg
    #   iend_of_t7_with_halo_on_t6sg = iend_of_t7_on_t6sg + halo_width_on_t6sg
    #   jstart_of_t7_with_halo_on_t6sg = jstart_of_t7_on_t6sg - halo_width_on_t6sg
    #   jend_of_t7_with_halo_on_t6sg = jend_of_t7_on_t6sg + halo_width_on_t6sg
    #
    # We calculate these next.
    #
    #-----------------------------------------------------------------------
    #
    istart_of_t7_with_halo_on_t6sg = int(istart_of_t7_on_t6sg - halo_width_on_t6sg)
    iend_of_t7_with_halo_on_t6sg = int(iend_of_t7_on_t6sg + halo_width_on_t6sg)
    jstart_of_t7_with_halo_on_t6sg = int(jstart_of_t7_on_t6sg - halo_width_on_t6sg)
    jend_of_t7_with_halo_on_t6sg = int(jend_of_t7_on_t6sg + halo_width_on_t6sg)
    #
    #-----------------------------------------------------------------------
    #
    # As for the regional grid without a halo, the regional grid with a wide
    # halo that make_hgrid will generate must be such that grid cells on
    # tile 6 either lie completely within this grid or outside of it, i.e.
    # they cannot lie partially within/outside of it.  This implies that the
    # starting indices on the tile 6 supergrid of the grid with wide halo
    # must be odd while the ending indices must be even.  Thus, below, we
    # subtract 1 from the starting indices if they are even (which ensures
    # that there will be at least halo_width_on_t7g halo cells along the left and bot-
    # tom boundaries), and we add 1 to the ending indices if they are odd
    # (which ensures that there will be at least halo_width_on_t7g halo cells along the
    # right and top boundaries).
    #
    #-----------------------------------------------------------------------
    #
    if istart_of_t7_with_halo_on_t6sg % 2 == 0:
        istart_of_t7_with_halo_on_t6sg = istart_of_t7_with_halo_on_t6sg - 1
    
    if iend_of_t7_with_halo_on_t6sg % 2 == 1:
        iend_of_t7_with_halo_on_t6sg = iend_of_t7_with_halo_on_t6sg + 1
    
    if jstart_of_t7_with_halo_on_t6sg % 2 == 0:
        jstart_of_t7_with_halo_on_t6sg = jstart_of_t7_with_halo_on_t6sg - 1
    
    if jend_of_t7_with_halo_on_t6sg % 2 == 1:
        jend_of_t7_with_halo_on_t6sg = jend_of_t7_with_halo_on_t6sg + 1
    #
    #-----------------------------------------------------------------------
    #
    # Now that the starting and ending tile 6 supergrid indices of the re-
    # gional grid with the wide halo have been calculated (and adjusted), we
    # recalculate the width of the wide halo on:
    #
    # 1) the tile 6 supergrid;
    # 2) the tile 6 grid; and
    # 3) the tile 7 grid.
    #
    # These are the final values of these quantities that are guaranteed to
    # correspond to the starting and ending indices on the tile 6 supergrid.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(f'''
        Original values of the halo width on the tile 6 supergrid and on the 
        tile 7 grid are:
          halo_width_on_t6sg = {halo_width_on_t6sg}
          halo_width_on_t7g  = {halo_width_on_t7g}''', verbose=VERBOSE)
    
    halo_width_on_t6sg = istart_of_t7_on_t6sg - istart_of_t7_with_halo_on_t6sg
    halo_width_on_t6g = halo_width_on_t6sg//2
    halo_width_on_t7g = int(halo_width_on_t6g*refine_ratio_t6g_to_t7g)
    
    print_info_msg(f'''
        Values of the halo width on the tile 6 supergrid and on the tile 7 grid 
        AFTER adjustments are:
          halo_width_on_t6sg = {halo_width_on_t6sg}
          halo_width_on_t7g  = {halo_width_on_t7g}''', verbose=VERBOSE)
    #
    #-----------------------------------------------------------------------
    #
    # Calculate the number of cells that the regional domain (without halo)
    # has in each of the two horizontal directions (say x and y).  We denote
    # these by nx_of_t7_on_t7g and ny_of_t7_on_t7g, respectively.  These 
    # will be needed in the "shave" steps in the grid generation task of the
    # workflow.
    #
    #-----------------------------------------------------------------------
    #
    nx_of_t7_on_t6sg = iend_of_t7_on_t6sg - istart_of_t7_on_t6sg + 1
    nx_of_t7_on_t6g = nx_of_t7_on_t6sg/2
    nx_of_t7_on_t7g = int(nx_of_t7_on_t6g*refine_ratio_t6g_to_t7g)
    
    ny_of_t7_on_t6sg = jend_of_t7_on_t6sg - jstart_of_t7_on_t6sg + 1
    ny_of_t7_on_t6g = ny_of_t7_on_t6sg/2
    ny_of_t7_on_t7g = int(ny_of_t7_on_t6g*refine_ratio_t6g_to_t7g)
    #
    # The following are set only for informational purposes.
    #
    nx_of_t6_on_t6sg = 2*nx_of_t6_on_t6g
    ny_of_t6_on_t6sg = 2*ny_of_t6_on_t6g
    
    prime_factors_nx_of_t7_on_t7g = prime_factors(nx_of_t7_on_t7g)
    prime_factors_ny_of_t7_on_t7g = prime_factors(ny_of_t7_on_t7g)
    
    print_info_msg(f'''
        The number of cells in the two horizontal directions (x and y) on the 
        parent tile's (tile 6) grid and supergrid are:
          nx_of_t6_on_t6g = {nx_of_t6_on_t6g}
          ny_of_t6_on_t6g = {ny_of_t6_on_t6g}
          nx_of_t6_on_t6sg = {nx_of_t6_on_t6sg}
          ny_of_t6_on_t6sg = {ny_of_t6_on_t6sg}
        
        The number of cells in the two horizontal directions on the tile 6 grid
        and supergrid that the regional domain (tile 7) WITHOUT A HALO encompas-
        ses are:
          nx_of_t7_on_t6g = {nx_of_t7_on_t6g}
          ny_of_t7_on_t6g = {ny_of_t7_on_t6g}
          nx_of_t7_on_t6sg = {nx_of_t7_on_t6sg}
          ny_of_t7_on_t6sg = {ny_of_t7_on_t6sg}
        
        The starting and ending i and j indices on the tile 6 grid used to gene-
        rate this regional grid are:
          istart_of_t7_on_t6g = {istart_of_t7_on_t6g}
          iend_of_t7_on_t6g   = {iend_of_t7_on_t6g}
          jstart_of_t7_on_t6g = {jstart_of_t7_on_t6g}
          jend_of_t7_on_t6g   = {jend_of_t7_on_t6g}
        
        The corresponding starting and ending i and j indices on the tile 6 su-
        pergrid are:
          istart_of_t7_on_t6sg = {istart_of_t7_on_t6sg}
          iend_of_t7_on_t6sg   = {iend_of_t7_on_t6sg}
          jstart_of_t7_on_t6sg = {jstart_of_t7_on_t6sg}
          jend_of_t7_on_t6sg   = {jend_of_t7_on_t6sg}
        
        The refinement ratio (ratio of the number of cells in tile 7 that abut
        a single cell in tile 6) is:
          refine_ratio_t6g_to_t7g = {refine_ratio_t6g_to_t7g}
        
        The number of cells in the two horizontal directions on the regional do-
        main's (i.e. tile 7's) grid WITHOUT A HALO are:
          nx_of_t7_on_t7g = {nx_of_t7_on_t7g}
          ny_of_t7_on_t7g = {ny_of_t7_on_t7g}
        
        The prime factors of nx_of_t7_on_t7g and ny_of_t7_on_t7g are (useful for
        determining an MPI task layout):
          prime_factors_nx_of_t7_on_t7g: {prime_factors_nx_of_t7_on_t7g}
          prime_factors_ny_of_t7_on_t7g: {prime_factors_ny_of_t7_on_t7g}''', verbose=VERBOSE)
    #
    #-----------------------------------------------------------------------
    #
    # For informational purposes, calculate the number of cells in each di-
    # rection on the regional grid including the wide halo (of width halo_-
    # width_on_t7g cells).  We denote these by nx_of_t7_with_halo_on_t7g and
    # ny_of_t7_with_halo_on_t7g, respectively.
    #
    #-----------------------------------------------------------------------
    #
    nx_of_t7_with_halo_on_t6sg = iend_of_t7_with_halo_on_t6sg - istart_of_t7_with_halo_on_t6sg + 1
    nx_of_t7_with_halo_on_t6g = nx_of_t7_with_halo_on_t6sg/2
    nx_of_t7_with_halo_on_t7g = nx_of_t7_with_halo_on_t6g*refine_ratio_t6g_to_t7g
    
    ny_of_t7_with_halo_on_t6sg = jend_of_t7_with_halo_on_t6sg - jstart_of_t7_with_halo_on_t6sg + 1
    ny_of_t7_with_halo_on_t6g = ny_of_t7_with_halo_on_t6sg/2
    ny_of_t7_with_halo_on_t7g = ny_of_t7_with_halo_on_t6g*refine_ratio_t6g_to_t7g
    
    print_info_msg(f'''
        nx_of_t7_with_halo_on_t7g = {nx_of_t7_with_halo_on_t7g}
        (istart_of_t7_with_halo_on_t6sg = {istart_of_t7_with_halo_on_t6sg},
        iend_of_t7_with_halo_on_t6sg = {iend_of_t7_with_halo_on_t6sg})''', verbose=VERBOSE)
    
    print_info_msg(f'''
        ny_of_t7_with_halo_on_t7g = {ny_of_t7_with_halo_on_t7g}
        (jstart_of_t7_with_halo_on_t6sg = {jstart_of_t7_with_halo_on_t6sg},
        jend_of_t7_with_halo_on_t6sg = {jend_of_t7_with_halo_on_t6sg})''', verbose=VERBOSE)
    #
    #-----------------------------------------------------------------------
    #
    # Return output variables.
    #
    #-----------------------------------------------------------------------
    #
    return (lon_of_t7_ctr, lat_of_t7_ctr, nx_of_t7_on_t7g, ny_of_t7_on_t7g,
            halo_width_on_t7g, stretch_factor,
            istart_of_t7_with_halo_on_t6sg,
            iend_of_t7_with_halo_on_t6sg,
            jstart_of_t7_with_halo_on_t6sg,
            jend_of_t7_with_halo_on_t6sg)
def set_ozone_param(ccpp_phys_suite_fp):
    """ Function that does the following:
    (1) Determines the ozone parameterization being used by checking in the
        CCPP physics suite XML.
   
    (2) Sets the name of the global ozone production/loss file in the FIXgsm
        FIXgsm system directory to copy to the experiment's FIXam directory.
   
    (3) Resets the last element of the workflow array variable
        FIXgsm_FILES_TO_COPY_TO_FIXam that contains the files to copy from
        FIXgsm to FIXam (this last element is initially set to a dummy 
        value) to the name of the ozone production/loss file set in the
        previous step.
   
    (4) Resets the element of the workflow array variable 
        CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING (this array contains the 
        mapping between the symlinks to create in any cycle directory and
        the files in the FIXam directory that are their targets) that 
        specifies the mapping for the ozone symlink/file such that the 
        target FIXam file name is set to the name of the ozone production/
        loss file set above.

    Args:
        ccpp_phys_suite_fp: full path to CCPP physics suite
    Returns:
        ozone_param: a string
    """

    print_input_args(locals())

    # import all environment variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # Get the name of the ozone parameterization being used.  There are two
    # possible ozone parameterizations:
    #
    # (1) A parameterization developed/published in 2015.  Here, we refer to
    #     this as the 2015 parameterization.  If this is being used, then we
    #     set the variable ozone_param to the string "ozphys_2015".
    #
    # (2) A parameterization developed/published sometime after 2015.  Here,
    #     we refer to this as the after-2015 parameterization.  If this is
    #     being used, then we set the variable ozone_param to the string
    #     "ozphys".
    #
    # We check the CCPP physics suite definition file (SDF) to determine the
    # parameterization being used.  If this file contains the line
    #
    #   <scheme>ozphys_2015</scheme>
    #
    # then the 2015 parameterization is being used.  If it instead contains
    # the line
    #
    #   <scheme>ozphys</scheme>
    #
    # then the after-2015 parameterization is being used.  (The SDF should
    # contain exactly one of these lines; not both nor neither; we check for
    # this.)
    #
    #-----------------------------------------------------------------------
    #
    tree = load_xml_file(ccpp_phys_suite_fp)
    ozone_param = ""
    if has_tag_with_value(tree, "scheme", "ozphys_2015"):
        fixgsm_ozone_fn = "ozprdlos_2015_new_sbuvO3_tclm15_nuchem.f77"
        ozone_param = "ozphys_2015"
    elif has_tag_with_value(tree, "scheme", "ozphys"):
        fixgsm_ozone_fn = "global_o3prdlos.f77"
        ozone_param = "ozphys"
    else:
        print_err_msg_exit(f'''
        Unknown or no ozone parameterization
        specified in the CCPP physics suite file (ccpp_phys_suite_fp):
          ccpp_phys_suite_fp = \"{ccpp_phys_suite_fp}\"
          ozone_param = \"{ozone_param}\"''')
    #
    #-----------------------------------------------------------------------
    #
    # Set the last element of the array FIXgsm_FILES_TO_COPY_TO_FIXam to the
    # name of the ozone production/loss file to copy from the FIXgsm to the
    # FIXam directory.
    #
    #-----------------------------------------------------------------------
    #
    i = len(FIXgsm_FILES_TO_COPY_TO_FIXam) - 1
    FIXgsm_FILES_TO_COPY_TO_FIXam[i] = f"{fixgsm_ozone_fn}"
    #
    #-----------------------------------------------------------------------
    #
    # Set the element in the array CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING that
    # specifies the mapping between the symlink for the ozone production/loss
    # file that must be created in each cycle directory and its target in the
    # FIXam directory.  The name of the symlink is alrady in the array, but
    # the target is not because it depends on the ozone parameterization that
    # the physics suite uses.  Since we determined the ozone parameterization
    # above, we now set the target of the symlink accordingly.
    #
    #-----------------------------------------------------------------------
    #
    ozone_symlink = "global_o3prdlos.f77"
    fixgsm_ozone_fn_is_set = False
    regex_search = "^[ ]*([^| ]*)[ ]*[|][ ]*([^| ]*)[ ]*$"
    num_symlinks = len(CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING)

    for i in range(num_symlinks):
        mapping = CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING[i]
        symlink = find_pattern_in_str(regex_search, mapping)
        if symlink is not None:
            symlink = symlink[0]
        if symlink == ozone_symlink:
            regex_search = "^[ ]*([^| ]+[ ]*)[|][ ]*([^| ]*)[ ]*$"
            mapping_ozone = find_pattern_in_str(regex_search, mapping)[0]
            mapping_ozone = f"{mapping_ozone}| {fixgsm_ozone_fn}"
            CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING[i] = f"{mapping_ozone}"
            fixgsm_ozone_fn_is_set = True
            break
    #
    #-----------------------------------------------------------------------
    #
    # If fixgsm_ozone_fn_is_set is set to True, then the appropriate element
    # of the array CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING was set successfully.
    # In this case, print out the new version of this array.  Otherwise, print
    # out an error message and exit.
    #
    #-----------------------------------------------------------------------
    #
    if fixgsm_ozone_fn_is_set:

        msg = dedent(f'''
        After setting the file name of the ozone production/loss file in the
        FIXgsm directory (based on the ozone parameterization specified in the
        CCPP suite definition file), the array specifying the mapping between
        the symlinks that need to be created in the cycle directories and the
        files in the FIXam directory is:
        
        ''')
        msg += dedent(f'''
          CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING = {list_to_str(CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING)}
        ''')
        print_info_msg(msg, verbose=VERBOSE)

    else:

        print_err_msg_exit(f'''
        Unable to set name of the ozone production/loss file in the FIXgsm directory
        in the array that specifies the mapping between the symlinks that need to
        be created in the cycle directories and the files in the FIXgsm directory:
          fixgsm_ozone_fn_is_set = \"{fixgsm_ozone_fn_is_set}\"''')

    EXPORTS = [
        "CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING",
        "FIXgsm_FILES_TO_COPY_TO_FIXam"
    ]
    export_vars(env_vars=EXPORTS)

    return ozone_param
Ejemplo n.º 14
0
def link_fix(verbose, file_group):
    """ This file defines a function that ...
    Args:
        verbose: True or False
        file_group: could be on of ["grid", "orog", "sfc_climo"]
    Returns:
        a string: resolution
    """

    print_input_args(locals())

    valid_vals_file_group = ["grid", "orog", "sfc_climo"]
    check_var_valid_value(file_group, valid_vals_file_group)

    #import all environement variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # Create symlinks in the FIXLAM directory pointing to the grid files.
    # These symlinks are needed by the make_orog, make_sfc_climo, make_ic,
    # make_lbc, and/or run_fcst tasks.
    #
    # Note that we check that each target file exists before attempting to
    # create symlinks.  This is because the "ln" command will create sym-
    # links to non-existent targets without returning with a nonzero exit
    # code.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(
        f'Creating links in the FIXLAM directory to the grid files...',
        verbose=verbose)
    #
    #-----------------------------------------------------------------------
    #
    # Create globbing patterns for grid, orography, and surface climatology
    # files.
    #
    #
    # For grid files (i.e. file_group set to "grid"), symlinks are created
    # in the FIXLAM directory to files (of the same names) in the GRID_DIR.
    # These symlinks/files and the reason each is needed is listed below:
    #
    # 1) "C*.mosaic.halo${NHW}.nc"
    #    This mosaic file for the wide-halo grid (i.e. the grid with a ${NHW}-
    #    cell-wide halo) is needed as an input to the orography filtering
    #    executable in the orography generation task.  The filtering code
    #    extracts from this mosaic file the name of the file containing the
    #    grid on which it will generate filtered topography.  Note that the
    #    orography generation and filtering are both performed on the wide-
    #    halo grid.  The filtered orography file on the wide-halo grid is then
    #    shaved down to obtain the filtered orography files with ${NH3}- and
    #    ${NH4}-cell-wide halos.
    #
    #    The raw orography generation step in the make_orog task requires the
    #    following symlinks/files:
    #
    #    a) C*.mosaic.halo${NHW}.nc
    #       The script for the make_orog task extracts the name of the grid
    #       file from this mosaic file; this name should be
    #       "C*.grid.tile${TILE_RGNL}.halo${NHW}.nc".
    #
    #    b) C*.grid.tile${TILE_RGNL}.halo${NHW}.nc
    #       This is the
    #       The script for the make_orog task passes the name of the grid
    #       file (extracted above from the mosaic file) to the orography
    #       generation executable.  The executable then
    #       reads in this grid file and generates a raw orography
    #       file on the grid.  The raw orography file is initially renamed "out.oro.nc",
    #       but for clarity, it is then renamed "C*.raw_orog.tile${TILE_RGNL}.halo${NHW}.nc".
    #
    #    c) The fixed files thirty.second.antarctic.new.bin, landcover30.fixed,
    #       and gmted2010.30sec.int.
    #
    #    The orography filtering step in the make_orog task requires the
    #    following symlinks/files:
    #
    #    a) C*.mosaic.halo${NHW}.nc
    #       This is the mosaic file for the wide-halo grid.  The orography
    #       filtering executable extracts from this file the name of the grid
    #       file containing the wide-halo grid (which should be
    #       "${CRES}.grid.tile${TILE_RGNL}.halo${NHW}.nc").  The executable then
    #       looks for this grid file IN THE DIRECTORY IN WHICH IT IS RUNNING.
    #       Thus, before running the executable, the script creates a symlink in this run directory that
    #       points to the location of the actual wide-halo grid file.
    #
    #    b) C*.raw_orog.tile${TILE_RGNL}.halo${NHW}.nc
    #       This is the raw orography file on the wide-halo grid.  The script
    #       for the make_orog task copies this file to a new file named
    #       "C*.filtered_orog.tile${TILE_RGNL}.halo${NHW}.nc" that will be
    #       used as input to the orography filtering executable.  The executable
    #       will then overwrite the contents of this file with the filtered orography.
    #       Thus, the output of the orography filtering executable will be
    #       the file C*.filtered_orog.tile${TILE_RGNL}.halo${NHW}.nc.
    #
    #    The shaving step in the make_orog task requires the following:
    #
    #    a) C*.filtered_orog.tile${TILE_RGNL}.halo${NHW}.nc
    #       This is the filtered orography file on the wide-halo grid.
    #       This gets shaved down to two different files:
    #
    #        i) ${CRES}.oro_data.tile${TILE_RGNL}.halo${NH0}.nc
    #           This is the filtered orography file on the halo-0 grid.
    #
    #       ii) ${CRES}.oro_data.tile${TILE_RGNL}.halo${NH4}.nc
    #           This is the filtered orography file on the halo-4 grid.
    #
    #       Note that the file names of the shaved files differ from that of
    #       the initial unshaved file on the wide-halo grid in that the field
    #       after ${CRES} is now "oro_data" (not "filtered_orog") to comply
    #       with the naming convention used more generally.
    #
    # 2) "C*.mosaic.halo${NH4}.nc"
    #    This mosaic file for the grid with a 4-cell-wide halo is needed as
    #    an input to the surface climatology generation executable.  The
    #    surface climatology generation code reads from this file the number
    #    of tiles (which should be 1 for a regional grid) and the tile names.
    #    More importantly, using the ESMF function ESMF_GridCreateMosaic(),
    #    it creates a data object of type esmf_grid; the grid information
    #    in this object is obtained from the grid file specified in the mosaic
    #    file, which should be "C*.grid.tile${TILE_RGNL}.halo${NH4}.nc".  The
    #    dimensions specified in this grid file must match the ones specified
    #    in the (filtered) orography file "C*.oro_data.tile${TILE_RGNL}.halo${NH4}.nc"
    #    that is also an input to the surface climatology generation executable.
    #    If they do not, then the executable will crash with an ESMF library
    #    error (something like "Arguments are incompatible").
    #
    #    Thus, for the make_sfc_climo task, the following symlinks/files must
    #    exist:
    #    a) "C*.mosaic.halo${NH4}.nc"
    #    b) "C*.grid.tile${TILE_RGNL}.halo${NH4}.nc"
    #    c) "C*.oro_data.tile${TILE_RGNL}.halo${NH4}.nc"
    #
    # 3)
    #
    #
    #-----------------------------------------------------------------------
    #
    #
    if file_group == "grid":
        fns = [
            f"C*{DOT_OR_USCORE}mosaic.halo{NHW}.nc",
            f"C*{DOT_OR_USCORE}mosaic.halo{NH4}.nc",
            f"C*{DOT_OR_USCORE}mosaic.halo{NH3}.nc",
            f"C*{DOT_OR_USCORE}grid.tile{TILE_RGNL}.halo{NHW}.nc",
            f"C*{DOT_OR_USCORE}grid.tile{TILE_RGNL}.halo{NH3}.nc",
            f"C*{DOT_OR_USCORE}grid.tile{TILE_RGNL}.halo{NH4}.nc"
        ]
        fps = [os.path.join(GRID_DIR, itm) for itm in fns]
        run_task = f"{RUN_TASK_MAKE_GRID}"
    #
    elif file_group == "orog":
        fns = [
            f"C*{DOT_OR_USCORE}oro_data.tile{TILE_RGNL}.halo{NH0}.nc",
            f"C*{DOT_OR_USCORE}oro_data.tile{TILE_RGNL}.halo{NH4}.nc"
        ]
        if CCPP_PHYS_SUITE == "FV3_HRRR":
            fns += [
                f"C*{DOT_OR_USCORE}oro_data_ss.tile{TILE_RGNL}.halo{NH0}.nc",
                f"C*{DOT_OR_USCORE}oro_data_ls.tile{TILE_RGNL}.halo{NH0}.nc",
            ]
        fps = [os.path.join(OROG_DIR, itm) for itm in fns]
        run_task = f"{RUN_TASK_MAKE_OROG}"
    #
    # The following list of symlinks (which have the same names as their
    # target files) need to be created made in order for the make_ics and
    # make_lbcs tasks (i.e. tasks involving chgres_cube) to work.
    #
    elif file_group == "sfc_climo":
        num_fields = len(SFC_CLIMO_FIELDS)
        fns = [None] * (2 * num_fields)
        for i in range(num_fields):
            ii = 2 * i
            fns[ii] = f"C*.{SFC_CLIMO_FIELDS[i]}.tile{TILE_RGNL}.halo{NH0}.nc"
            fns[ii +
                1] = f"C*.{SFC_CLIMO_FIELDS[i]}.tile{TILE_RGNL}.halo{NH4}.nc"
        fps = [os.path.join(SFC_CLIMO_DIR, itm) for itm in fns]
        run_task = f"{RUN_TASK_MAKE_SFC_CLIMO}"
    #

    #
    #-----------------------------------------------------------------------
    #
    # Find all files matching the globbing patterns and make sure that they
    # all have the same resolution (an integer) in their names.
    #
    #-----------------------------------------------------------------------
    #
    i = 0
    res_prev = ""
    res = ""
    fp_prev = ""

    for pattern in fps:
        files = glob.glob(pattern)
        for fp in files:

            fn = os.path.basename(fp)

            regex_search = "^C([0-9]*).*"
            res = find_pattern_in_str(regex_search, fn)
            if res is None:
                print_err_msg_exit(f'''
                The resolution could not be extracted from the current file's name.  The
                full path to the file (fp) is:
                  fp = \"{fp}\"
                This may be because fp contains the * globbing character, which would
                imply that no files were found that match the globbing pattern specified
                in fp.''')
            else:
                res = res[0]

            if (i > 0) and (res != res_prev):
                print_err_msg_exit(f'''
                The resolutions (as obtained from the file names) of the previous and 
                current file (fp_prev and fp, respectively) are different:
                  fp_prev = \"{fp_prev}\"
                  fp      = \"{fp}\"
                Please ensure that all files have the same resolution.''')

            i = i + 1
            fp_prev = f"{fp}"
            res_prev = res
    #
    #-----------------------------------------------------------------------
    #
    # Replace the * globbing character in the set of globbing patterns with
    # the resolution.  This will result in a set of (full paths to) specific
    # files.
    #
    #-----------------------------------------------------------------------
    #
    fps = [itm.replace('*', res) for itm in fps]
    #
    #-----------------------------------------------------------------------
    #
    # In creating the various symlinks below, it is convenient to work in
    # the FIXLAM directory.  We will change directory back to the original
    # later below.
    #
    #-----------------------------------------------------------------------
    #
    SAVE_DIR = os.getcwd()
    cd_vrfy(FIXLAM)
    #
    #-----------------------------------------------------------------------
    #
    # Use the set of full file paths generated above as the link targets to
    # create symlinks to these files in the FIXLAM directory.
    #
    #-----------------------------------------------------------------------
    #
    # If the task in consideration (which will be one of the pre-processing
    # tasks MAKE_GRID_TN, MAKE_OROG_TN, and MAKE_SFC_CLIMO_TN) was run, then
    # the target files will be located under the experiment directory.  In
    # this case, we use relative symlinks in order the experiment directory
    # more portable and the symlinks more readable.  However, if the task
    # was not run, then pregenerated grid, orography, or surface climatology
    # files will be used, and those will be located in an arbitrary directory
    # (specified by the user) that is somwehere outside the experiment
    # directory.  Thus, in this case, there isn't really an advantage to using
    # relative symlinks, so we use symlinks with absolute paths.
    #
    if run_task:
        relative_link_flag = True
    else:
        relative_link_flag = False

    for fp in fps:
        fn = os.path.basename(fp)
        create_symlink_to_file(fp, fn, relative_link_flag)
    #
    #-----------------------------------------------------------------------
    #
    # Set the C-resolution based on the resolution appearing in the file
    # names.
    #
    #-----------------------------------------------------------------------
    #
    cres = f"C{res}"
    #
    #-----------------------------------------------------------------------
    #
    # If considering grid files, create a symlink to the halo4 grid file
    # that does not contain the halo size in its name.  This is needed by
    # the tasks that generate the initial and lateral boundary condition
    # files.
    #
    #-----------------------------------------------------------------------
    #
    if file_group == "grid":

        target = f"{cres}{DOT_OR_USCORE}grid.tile{TILE_RGNL}.halo{NH4}.nc"
        symlink = f"{cres}{DOT_OR_USCORE}grid.tile{TILE_RGNL}.nc"
        create_symlink_to_file(target, symlink, True)
        #
        # The surface climatology file generation code looks for a grid file
        # having a name of the form "C${GFDLgrid_RES}_grid.tile7.halo4.nc" (i.e.
        # the C-resolution used in the name of this file is the number of grid
        # points per horizontal direction per tile, just like in the global model).
        # Thus, if we are running the MAKE_SFC_CLIMO_TN task, if the grid is of
        # GFDLgrid type, and if we are not using GFDLgrid_RES in filenames (i.e.
        # we are using the equivalent global uniform grid resolution instead),
        # then create a link whose name uses the GFDLgrid_RES that points to the
        # link whose name uses the equivalent global uniform resolution.
        #
        if RUN_TASK_MAKE_SFC_CLIMO and \
           GRID_GEN_METHOD == "GFDLgrid" and \
           not GFDLgrid_USE_GFDLgrid_RES_IN_FILENAMES:
            target = f"{cres}{DOT_OR_USCORE}grid.tile{TILE_RGNL}.halo{NH4}.nc"
            symlink = f"C{GFDLgrid_RES}{DOT_OR_USCORE}grid.tile{TILE_RGNL}.nc"
            create_symlink_to_file(target, symlink, relative)
    #
    #-----------------------------------------------------------------------
    #
    # If considering surface climatology files, create symlinks to the surface
    # climatology files that do not contain the halo size in their names.
    # These are needed by the task that generates the initial condition files.
    #
    #-----------------------------------------------------------------------
    #
    if file_group == "sfc_climo":

        tmp = [f"{cres}.{itm}" for itm in SFC_CLIMO_FIELDS]
        fns_sfc_climo_with_halo_in_fn = [
            f"{itm}.tile{TILE_RGNL}.halo{NH4}.nc" for itm in tmp
        ]
        fns_sfc_climo_no_halo_in_fn = [
            f"{itm}.tile{TILE_RGNL}.nc" for itm in tmp
        ]

        for i in range(num_fields):
            target = f"{fns_sfc_climo_with_halo_in_fn[i]}"
            symlink = f"{fns_sfc_climo_no_halo_in_fn[i]}"
            create_symlink_to_file(target, symlink, True)
    #
    # In order to be able to specify the surface climatology file names in
    # the forecast model's namelist file, in the FIXLAM directory a symlink
    # must be created for each surface climatology field that has "tile1" in
    # its name (and no "halo") and which points to the corresponding "tile7.halo0"
    # file.
    #
        tmp = [f"{cres}.{itm}" for itm in SFC_CLIMO_FIELDS]
        fns_sfc_climo_tile7_halo0_in_fn = [
            f"{itm}.tile{TILE_RGNL}.halo{NH0}.nc" for itm in tmp
        ]
        fns_sfc_climo_tile1_no_halo_in_fn = [f"{itm}.tile1.nc" for itm in tmp]

        for i in range(num_fields):
            target = f"{fns_sfc_climo_tile7_halo0_in_fn[i]}"
            symlink = f"{fns_sfc_climo_tile1_no_halo_in_fn[i]}"
            create_symlink_to_file(target, symlink, True)
    #
    #-----------------------------------------------------------------------
    #
    # Change directory back to original one.
    #
    #-----------------------------------------------------------------------
    #
    cd_vrfy(SAVE_DIR)

    return res
                        required=True,
                        help='Forecast model\'s main time step.')

    parser.add_argument('-p',
                        '--path-to-defns',
                        dest='path_to_defns',
                        required=True,
                        help='Path to var_defns file.')

    return parser.parse_args(argv)


if __name__ == '__main__':
    args = parse_args(sys.argv[1:])
    cfg = load_shell_config(args.path_to_defns)
    import_vars(dictionary=cfg)
    create_model_configure_file( \
        run_dir = args.run_dir, \
        cdate = str_to_type(args.cdate), \
        sub_hourly_post = str_to_type(args.sub_hourly_post), \
        dt_subhourly_post_mnts = str_to_type(args.dt_subhourly_post_mnts), \
        dt_atmos = str_to_type(args.dt_atmos) )


class Testing(unittest.TestCase):
    def test_create_model_configure_file(self):
        path = os.path.join(os.getenv('USHDIR'), "test_data")
        self.assertTrue(\
                create_model_configure_file( \
                      run_dir=path,
                      cdate=datetime(2021,1,1),
def create_model_configure_file(cdate, run_dir, sub_hourly_post,
                                dt_subhourly_post_mnts, dt_atmos):
    """ Creates a model configuration file in the specified
    run directory

    Args:
        cdate: cycle date
        run_dir: run directory
        sub_hourly_post
        dt_subhourly_post_mnts
        dt_atmos
    Returns:
        Boolean
    """

    print_input_args(locals())

    #import all environment variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # Create a model configuration file in the specified run directory.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(f'''
        Creating a model configuration file (\"{MODEL_CONFIG_FN}\") in the specified
        run directory (run_dir):
          run_dir = \"{run_dir}\"''',
                   verbose=VERBOSE)
    #
    # Extract from cdate the starting year, month, day, and hour of the forecast.
    #
    yyyy = cdate.year
    mm = cdate.month
    dd = cdate.day
    hh = cdate.hour
    #
    # Set parameters in the model configure file.
    #
    dot_quilting_dot = f".{lowercase(str(QUILTING))}."
    dot_print_esmf_dot = f".{lowercase(str(PRINT_ESMF))}."
    dot_cpl_dot = f".{lowercase(str(CPL))}."
    dot_write_dopost = f".{lowercase(str(WRITE_DOPOST))}."
    #
    #-----------------------------------------------------------------------
    #
    # Create a multiline variable that consists of a yaml-compliant string
    # specifying the values that the jinja variables in the template
    # model_configure file should be set to.
    #
    #-----------------------------------------------------------------------
    #
    settings = {
        'PE_MEMBER01': PE_MEMBER01,
        'print_esmf': dot_print_esmf_dot,
        'start_year': yyyy,
        'start_month': mm,
        'start_day': dd,
        'start_hour': hh,
        'nhours_fcst': FCST_LEN_HRS,
        'dt_atmos': DT_ATMOS,
        'cpl': dot_cpl_dot,
        'atmos_nthreads': OMP_NUM_THREADS_RUN_FCST,
        'restart_interval': RESTART_INTERVAL,
        'write_dopost': dot_write_dopost,
        'quilting': dot_quilting_dot,
        'output_grid': WRTCMP_output_grid
    }
    #
    # If the write-component is to be used, then specify a set of computational
    # parameters and a set of grid parameters.  The latter depends on the type
    # (coordinate system) of the grid that the write-component will be using.
    #
    if QUILTING:
        settings.update({
            'write_groups': WRTCMP_write_groups,
            'write_tasks_per_group': WRTCMP_write_tasks_per_group,
            'cen_lon': WRTCMP_cen_lon,
            'cen_lat': WRTCMP_cen_lat,
            'lon1': WRTCMP_lon_lwr_left,
            'lat1': WRTCMP_lat_lwr_left
        })

        if WRTCMP_output_grid == "lambert_conformal":
            settings.update({
                'stdlat1': WRTCMP_stdlat1,
                'stdlat2': WRTCMP_stdlat2,
                'nx': WRTCMP_nx,
                'ny': WRTCMP_ny,
                'dx': WRTCMP_dx,
                'dy': WRTCMP_dy,
                'lon2': "",
                'lat2': "",
                'dlon': "",
                'dlat': "",
            })
        elif WRTCMP_output_grid == "regional_latlon" or \
             WRTCMP_output_grid == "rotated_latlon":
            settings.update({
                'lon2': WRTCMP_lon_upr_rght,
                'lat2': WRTCMP_lat_upr_rght,
                'dlon': WRTCMP_dlon,
                'dlat': WRTCMP_dlat,
                'stdlat1': "",
                'stdlat2': "",
                'nx': "",
                'ny': "",
                'dx': "",
                'dy': ""
            })
    #
    # If sub_hourly_post is set to "TRUE", then the forecast model must be
    # directed to generate output files on a sub-hourly interval.  Do this
    # by specifying the output interval in the model configuration file
    # (MODEL_CONFIG_FN) in units of number of forecat model time steps (nsout).
    # nsout is calculated using the user-specified output time interval
    # dt_subhourly_post_mnts (in units of minutes) and the forecast model's
    # main time step dt_atmos (in units of seconds).  Note that nsout is
    # guaranteed to be an integer because the experiment generation scripts
    # require that dt_subhourly_post_mnts (after conversion to seconds) be
    # evenly divisible by dt_atmos.  Also, in this case, the variable output_fh
    # [which specifies the output interval in hours;
    # see the jinja model_config template file] is set to 0, although this
    # doesn't matter because any positive of nsout will override output_fh.
    #
    # If sub_hourly_post is set to "FALSE", then the workflow is hard-coded
    # (in the jinja model_config template file) to direct the forecast model
    # to output files every hour.  This is done by setting (1) output_fh to 1
    # here, and (2) nsout to -1 here which turns off output by time step interval.
    #
    # Note that the approach used here of separating how hourly and subhourly
    # output is handled should be changed/generalized/simplified such that
    # the user should only need to specify the output time interval (there
    # should be no need to specify a flag like sub_hourly_post); the workflow
    # should then be able to direct the model to output files with that time
    # interval and to direct the post-processor to process those files
    # regardless of whether that output time interval is larger than, equal
    # to, or smaller than one hour.
    #
    if sub_hourly_post:
        nsout = (dt_subhourly_post_mnts * 60) // dt_atmos
        output_fh = 0
    else:
        output_fh = 1
        nsout = -1

    settings.update({'output_fh': output_fh, 'nsout': nsout})

    settings_str = cfg_to_yaml_str(settings)

    print_info_msg(dedent(f'''
        The variable \"settings\" specifying values to be used in the \"{MODEL_CONFIG_FN}\"
        file has been set as follows:\n
        settings =\n\n''') + settings_str,
                   verbose=VERBOSE)
    #
    #-----------------------------------------------------------------------
    #
    # Call a python script to generate the experiment's actual MODEL_CONFIG_FN
    # file from the template file.
    #
    #-----------------------------------------------------------------------
    #
    model_config_fp = os.path.join(run_dir, MODEL_CONFIG_FN)

    try:
        fill_jinja_template([
            "-q", "-u", settings_str, "-t", MODEL_CONFIG_TMPL_FP, "-o",
            model_config_fp
        ])
    except:
        print_err_msg_exit(
            dedent(f'''
            Call to python script fill_jinja_template.py to create a \"{MODEL_CONFIG_FN}\"
            file from a jinja2 template failed.  Parameters passed to this script are:
              Full path to template model config file:
                MODEL_CONFIG_TMPL_FP = \"{MODEL_CONFIG_TMPL_FP}\"
              Full path to output model config file:
                model_config_fp = \"{model_config_fp}\"
              Namelist settings specified on command line:\n
                settings =\n\n''') + settings_str)
        return False

    return True
def calculate_cost(config_fn):
    global PREDEF_GRID_NAME, QUILTING, GRID_GEN_METHOD

    #import all environment variables
    import_vars()

    #get grid config parameters (predefined or custom)
    if PREDEF_GRID_NAME:
        set_env_var('QUILTING', False)
        set_predef_grid_params()
        import_vars()
    else:
        cfg_u = load_config_file(config_fn)
        cfg_u = flatten_dict(cfg_u)
        import_vars(dictionary=cfg_u)

    #number of gridpoints (nx*ny) depends on grid generation method
    if GRID_GEN_METHOD == "GFDLgrid":
        (\
        LON_CTR,LAT_CTR,NX,NY,NHW,STRETCH_FAC,
        ISTART_OF_RGNL_DOM_WITH_WIDE_HALO_ON_T6SG,
        IEND_OF_RGNL_DOM_WITH_WIDE_HALO_ON_T6SG,
        JSTART_OF_RGNL_DOM_WITH_WIDE_HALO_ON_T6SG,
        JEND_OF_RGNL_DOM_WITH_WIDE_HALO_ON_T6SG \
        ) = \
          set_gridparams_GFDLgrid( \
          lon_of_t6_ctr=GFDLgrid_LON_T6_CTR, \
          lat_of_t6_ctr=GFDLgrid_LAT_T6_CTR, \
          res_of_t6g=GFDLgrid_NUM_CELLS, \
          stretch_factor=GFDLgrid_STRETCH_FAC, \
          refine_ratio_t6g_to_t7g=GFDLgrid_REFINE_RATIO, \
          istart_of_t7_on_t6g=GFDLgrid_ISTART_OF_RGNL_DOM_ON_T6G, \
          iend_of_t7_on_t6g=GFDLgrid_IEND_OF_RGNL_DOM_ON_T6G, \
          jstart_of_t7_on_t6g=GFDLgrid_JSTART_OF_RGNL_DOM_ON_T6G, \
          jend_of_t7_on_t6g=GFDLgrid_JEND_OF_RGNL_DOM_ON_T6G)

    elif GRID_GEN_METHOD == "ESGgrid":
        (\
        LON_CTR,LAT_CTR,NX,NY,PAZI,
        NHW,STRETCH_FAC,DEL_ANGLE_X_SG,DEL_ANGLE_Y_SG,
        NEG_NX_OF_DOM_WITH_WIDE_HALO,
        NEG_NY_OF_DOM_WITH_WIDE_HALO \
        ) = \
          set_gridparams_ESGgrid( \
          lon_ctr=ESGgrid_LON_CTR, \
          lat_ctr=ESGgrid_LAT_CTR, \
          nx=ESGgrid_NX, \
          ny=ESGgrid_NY, \
          pazi=ESGgrid_PAZI, \
          halo_width=ESGgrid_WIDE_HALO_WIDTH, \
          delx=ESGgrid_DELX, \
          dely=ESGgrid_DELY)

    cost = [DT_ATMOS, NX * NY]

    #reference grid (6-hour forecast on RRFS_CONUS_25km)
    PREDEF_GRID_NAME = "RRFS_CONUS_25km"

    export_vars()
    set_predef_grid_params()
    import_vars()
    cost.extend([DT_ATMOS, ESGgrid_NX * ESGgrid_NY])

    return cost
def generate_FV3LAM_wflow():
    """ Function to setup a forecast experiment and create a workflow
    (according to the parameters specified in the config file

    Args:
        None
    Returns:
        None
    """

    print(
        dedent('''
        ========================================================================
        ========================================================================
        
        Starting experiment generation...
        
        ========================================================================
        ========================================================================'''
               ))

    #set ushdir
    ushdir = os.path.dirname(os.path.abspath(__file__))

    #check python version
    major, minor, patch = platform.python_version_tuple()
    if int(major) < 3 or int(minor) < 6:
        print_info_msg(f'''
            
            Error: python version must be 3.6 or higher
            python version: {major}.{minor}''')

    #define macros
    define_macos_utilities()

    #
    #-----------------------------------------------------------------------
    #
    # Source the file that defines and then calls the setup function.  The
    # setup function in turn first sources the default configuration file
    # (which contains default values for the experiment/workflow parameters)
    # and then sources the user-specified configuration file (which contains
    # user-specified values for a subset of the experiment/workflow parame-
    # ters that override their default values).
    #
    #-----------------------------------------------------------------------
    #
    setup()

    #import all environment variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # Set the full path to the experiment's rocoto workflow xml file.  This
    # file will be placed at the top level of the experiment directory and
    # then used by rocoto to run the workflow.
    #
    #-----------------------------------------------------------------------
    #
    WFLOW_XML_FP = os.path.join(EXPTDIR, WFLOW_XML_FN)

    #
    #-----------------------------------------------------------------------
    #
    # Create a multiline variable that consists of a yaml-compliant string
    # specifying the values that the jinja variables in the template rocoto
    # XML should be set to.  These values are set either in the user-specified
    # workflow configuration file (EXPT_CONFIG_FN) or in the setup.sh script
    # sourced above.  Then call the python script that generates the XML.
    #
    #-----------------------------------------------------------------------
    #
    if WORKFLOW_MANAGER == "rocoto":

        template_xml_fp = os.path.join(TEMPLATE_DIR, WFLOW_XML_FN)

        print_info_msg(f''' 
            Creating rocoto workflow XML file (WFLOW_XML_FP) from jinja template XML
            file (template_xml_fp):
              template_xml_fp = \"{template_xml_fp}\"
              WFLOW_XML_FP = \"{WFLOW_XML_FP}\"''')

        ensmem_indx_name = ""
        uscore_ensmem_name = ""
        slash_ensmem_subdir = ""
        if DO_ENSEMBLE:
            ensmem_indx_name = "mem"
            uscore_ensmem_name = f"_mem#{ensmem_indx_name}#"
            slash_ensmem_subdir = f"/mem#{ensmem_indx_name}#"

        #get time string
        d = DATE_FIRST_CYCL + timedelta(seconds=DT_ATMOS)
        time_str = d.strftime("%M:%S")

        cycl_hrs_str = [f"{c:02d}" for c in CYCL_HRS]
        cdate_first_cycl = DATE_FIRST_CYCL + timedelta(hours=CYCL_HRS[0])

        # Dictionary of settings
        settings = {
            #
            # Parameters needed by the job scheduler.
            #
            'account': ACCOUNT,
            'sched': SCHED,
            'partition_default': PARTITION_DEFAULT,
            'queue_default': QUEUE_DEFAULT,
            'partition_hpss': PARTITION_HPSS,
            'queue_hpss': QUEUE_HPSS,
            'partition_fcst': PARTITION_FCST,
            'queue_fcst': QUEUE_FCST,
            'machine': MACHINE,
            'slurm_native_cmd': SLURM_NATIVE_CMD,
            #
            # Workflow task names.
            #
            'make_grid_tn': MAKE_GRID_TN,
            'make_orog_tn': MAKE_OROG_TN,
            'make_sfc_climo_tn': MAKE_SFC_CLIMO_TN,
            'get_extrn_ics_tn': GET_EXTRN_ICS_TN,
            'get_extrn_lbcs_tn': GET_EXTRN_LBCS_TN,
            'make_ics_tn': MAKE_ICS_TN,
            'make_lbcs_tn': MAKE_LBCS_TN,
            'run_fcst_tn': RUN_FCST_TN,
            'run_post_tn': RUN_POST_TN,
            'get_obs_ccpa_tn': GET_OBS_CCPA_TN,
            'get_obs_ndas_tn': GET_OBS_NDAS_TN,
            'get_obs_mrms_tn': GET_OBS_MRMS_TN,
            'vx_tn': VX_TN,
            'vx_gridstat_tn': VX_GRIDSTAT_TN,
            'vx_gridstat_refc_tn': VX_GRIDSTAT_REFC_TN,
            'vx_gridstat_retop_tn': VX_GRIDSTAT_RETOP_TN,
            'vx_gridstat_03h_tn': VX_GRIDSTAT_03h_TN,
            'vx_gridstat_06h_tn': VX_GRIDSTAT_06h_TN,
            'vx_gridstat_24h_tn': VX_GRIDSTAT_24h_TN,
            'vx_pointstat_tn': VX_POINTSTAT_TN,
            'vx_ensgrid_tn': VX_ENSGRID_TN,
            'vx_ensgrid_refc_tn': VX_ENSGRID_REFC_TN,
            'vx_ensgrid_retop_tn': VX_ENSGRID_RETOP_TN,
            'vx_ensgrid_03h_tn': VX_ENSGRID_03h_TN,
            'vx_ensgrid_06h_tn': VX_ENSGRID_06h_TN,
            'vx_ensgrid_24h_tn': VX_ENSGRID_24h_TN,
            'vx_ensgrid_mean_tn': VX_ENSGRID_MEAN_TN,
            'vx_ensgrid_prob_tn': VX_ENSGRID_PROB_TN,
            'vx_ensgrid_mean_03h_tn': VX_ENSGRID_MEAN_03h_TN,
            'vx_ensgrid_prob_03h_tn': VX_ENSGRID_PROB_03h_TN,
            'vx_ensgrid_mean_06h_tn': VX_ENSGRID_MEAN_06h_TN,
            'vx_ensgrid_prob_06h_tn': VX_ENSGRID_PROB_06h_TN,
            'vx_ensgrid_mean_24h_tn': VX_ENSGRID_MEAN_24h_TN,
            'vx_ensgrid_prob_24h_tn': VX_ENSGRID_PROB_24h_TN,
            'vx_ensgrid_prob_refc_tn': VX_ENSGRID_PROB_REFC_TN,
            'vx_ensgrid_prob_retop_tn': VX_ENSGRID_PROB_RETOP_TN,
            'vx_enspoint_tn': VX_ENSPOINT_TN,
            'vx_enspoint_mean_tn': VX_ENSPOINT_MEAN_TN,
            'vx_enspoint_prob_tn': VX_ENSPOINT_PROB_TN,
            #
            # Entity used to load the module file for each GET_OBS_* task.
            #
            'get_obs': GET_OBS,
            #
            # Number of nodes to use for each task.
            #
            'nnodes_make_grid': NNODES_MAKE_GRID,
            'nnodes_make_orog': NNODES_MAKE_OROG,
            'nnodes_make_sfc_climo': NNODES_MAKE_SFC_CLIMO,
            'nnodes_get_extrn_ics': NNODES_GET_EXTRN_ICS,
            'nnodes_get_extrn_lbcs': NNODES_GET_EXTRN_LBCS,
            'nnodes_make_ics': NNODES_MAKE_ICS,
            'nnodes_make_lbcs': NNODES_MAKE_LBCS,
            'nnodes_run_fcst': NNODES_RUN_FCST,
            'nnodes_run_post': NNODES_RUN_POST,
            'nnodes_get_obs_ccpa': NNODES_GET_OBS_CCPA,
            'nnodes_get_obs_mrms': NNODES_GET_OBS_MRMS,
            'nnodes_get_obs_ndas': NNODES_GET_OBS_NDAS,
            'nnodes_vx_gridstat': NNODES_VX_GRIDSTAT,
            'nnodes_vx_pointstat': NNODES_VX_POINTSTAT,
            'nnodes_vx_ensgrid': NNODES_VX_ENSGRID,
            'nnodes_vx_ensgrid_mean': NNODES_VX_ENSGRID_MEAN,
            'nnodes_vx_ensgrid_prob': NNODES_VX_ENSGRID_PROB,
            'nnodes_vx_enspoint': NNODES_VX_ENSPOINT,
            'nnodes_vx_enspoint_mean': NNODES_VX_ENSPOINT_MEAN,
            'nnodes_vx_enspoint_prob': NNODES_VX_ENSPOINT_PROB,
            #
            # Number of cores used for a task
            #
            'ncores_run_fcst': PE_MEMBER01,
            'native_run_fcst':
            f"--cpus-per-task {OMP_NUM_THREADS_RUN_FCST} --exclusive",
            #
            # Number of logical processes per node for each task.  If running without
            # threading, this is equal to the number of MPI processes per node.
            #
            'ppn_make_grid': PPN_MAKE_GRID,
            'ppn_make_orog': PPN_MAKE_OROG,
            'ppn_make_sfc_climo': PPN_MAKE_SFC_CLIMO,
            'ppn_get_extrn_ics': PPN_GET_EXTRN_ICS,
            'ppn_get_extrn_lbcs': PPN_GET_EXTRN_LBCS,
            'ppn_make_ics': PPN_MAKE_ICS,
            'ppn_make_lbcs': PPN_MAKE_LBCS,
            'ppn_run_fcst': PPN_RUN_FCST,
            'ppn_run_post': PPN_RUN_POST,
            'ppn_get_obs_ccpa': PPN_GET_OBS_CCPA,
            'ppn_get_obs_mrms': PPN_GET_OBS_MRMS,
            'ppn_get_obs_ndas': PPN_GET_OBS_NDAS,
            'ppn_vx_gridstat': PPN_VX_GRIDSTAT,
            'ppn_vx_pointstat': PPN_VX_POINTSTAT,
            'ppn_vx_ensgrid': PPN_VX_ENSGRID,
            'ppn_vx_ensgrid_mean': PPN_VX_ENSGRID_MEAN,
            'ppn_vx_ensgrid_prob': PPN_VX_ENSGRID_PROB,
            'ppn_vx_enspoint': PPN_VX_ENSPOINT,
            'ppn_vx_enspoint_mean': PPN_VX_ENSPOINT_MEAN,
            'ppn_vx_enspoint_prob': PPN_VX_ENSPOINT_PROB,
            #
            # Maximum wallclock time for each task.
            #
            'wtime_make_grid': WTIME_MAKE_GRID,
            'wtime_make_orog': WTIME_MAKE_OROG,
            'wtime_make_sfc_climo': WTIME_MAKE_SFC_CLIMO,
            'wtime_get_extrn_ics': WTIME_GET_EXTRN_ICS,
            'wtime_get_extrn_lbcs': WTIME_GET_EXTRN_LBCS,
            'wtime_make_ics': WTIME_MAKE_ICS,
            'wtime_make_lbcs': WTIME_MAKE_LBCS,
            'wtime_run_fcst': WTIME_RUN_FCST,
            'wtime_run_post': WTIME_RUN_POST,
            'wtime_get_obs_ccpa': WTIME_GET_OBS_CCPA,
            'wtime_get_obs_mrms': WTIME_GET_OBS_MRMS,
            'wtime_get_obs_ndas': WTIME_GET_OBS_NDAS,
            'wtime_vx_gridstat': WTIME_VX_GRIDSTAT,
            'wtime_vx_pointstat': WTIME_VX_POINTSTAT,
            'wtime_vx_ensgrid': WTIME_VX_ENSGRID,
            'wtime_vx_ensgrid_mean': WTIME_VX_ENSGRID_MEAN,
            'wtime_vx_ensgrid_prob': WTIME_VX_ENSGRID_PROB,
            'wtime_vx_enspoint': WTIME_VX_ENSPOINT,
            'wtime_vx_enspoint_mean': WTIME_VX_ENSPOINT_MEAN,
            'wtime_vx_enspoint_prob': WTIME_VX_ENSPOINT_PROB,
            #
            # Maximum number of tries for each task.
            #
            'maxtries_make_grid': MAXTRIES_MAKE_GRID,
            'maxtries_make_orog': MAXTRIES_MAKE_OROG,
            'maxtries_make_sfc_climo': MAXTRIES_MAKE_SFC_CLIMO,
            'maxtries_get_extrn_ics': MAXTRIES_GET_EXTRN_ICS,
            'maxtries_get_extrn_lbcs': MAXTRIES_GET_EXTRN_LBCS,
            'maxtries_make_ics': MAXTRIES_MAKE_ICS,
            'maxtries_make_lbcs': MAXTRIES_MAKE_LBCS,
            'maxtries_run_fcst': MAXTRIES_RUN_FCST,
            'maxtries_run_post': MAXTRIES_RUN_POST,
            'maxtries_get_obs_ccpa': MAXTRIES_GET_OBS_CCPA,
            'maxtries_get_obs_mrms': MAXTRIES_GET_OBS_MRMS,
            'maxtries_get_obs_ndas': MAXTRIES_GET_OBS_NDAS,
            'maxtries_vx_gridstat': MAXTRIES_VX_GRIDSTAT,
            'maxtries_vx_gridstat_refc': MAXTRIES_VX_GRIDSTAT_REFC,
            'maxtries_vx_gridstat_retop': MAXTRIES_VX_GRIDSTAT_RETOP,
            'maxtries_vx_gridstat_03h': MAXTRIES_VX_GRIDSTAT_03h,
            'maxtries_vx_gridstat_06h': MAXTRIES_VX_GRIDSTAT_06h,
            'maxtries_vx_gridstat_24h': MAXTRIES_VX_GRIDSTAT_24h,
            'maxtries_vx_pointstat': MAXTRIES_VX_POINTSTAT,
            'maxtries_vx_ensgrid': MAXTRIES_VX_ENSGRID,
            'maxtries_vx_ensgrid_refc': MAXTRIES_VX_ENSGRID_REFC,
            'maxtries_vx_ensgrid_retop': MAXTRIES_VX_ENSGRID_RETOP,
            'maxtries_vx_ensgrid_03h': MAXTRIES_VX_ENSGRID_03h,
            'maxtries_vx_ensgrid_06h': MAXTRIES_VX_ENSGRID_06h,
            'maxtries_vx_ensgrid_24h': MAXTRIES_VX_ENSGRID_24h,
            'maxtries_vx_ensgrid_mean': MAXTRIES_VX_ENSGRID_MEAN,
            'maxtries_vx_ensgrid_prob': MAXTRIES_VX_ENSGRID_PROB,
            'maxtries_vx_ensgrid_mean_03h': MAXTRIES_VX_ENSGRID_MEAN_03h,
            'maxtries_vx_ensgrid_prob_03h': MAXTRIES_VX_ENSGRID_PROB_03h,
            'maxtries_vx_ensgrid_mean_06h': MAXTRIES_VX_ENSGRID_MEAN_06h,
            'maxtries_vx_ensgrid_prob_06h': MAXTRIES_VX_ENSGRID_PROB_06h,
            'maxtries_vx_ensgrid_mean_24h': MAXTRIES_VX_ENSGRID_MEAN_24h,
            'maxtries_vx_ensgrid_prob_24h': MAXTRIES_VX_ENSGRID_PROB_24h,
            'maxtries_vx_ensgrid_prob_refc': MAXTRIES_VX_ENSGRID_PROB_REFC,
            'maxtries_vx_ensgrid_prob_retop': MAXTRIES_VX_ENSGRID_PROB_RETOP,
            'maxtries_vx_enspoint': MAXTRIES_VX_ENSPOINT,
            'maxtries_vx_enspoint_mean': MAXTRIES_VX_ENSPOINT_MEAN,
            'maxtries_vx_enspoint_prob': MAXTRIES_VX_ENSPOINT_PROB,
            #
            # Flags that specify whether to run the preprocessing or
            # verification-related tasks.
            #
            'run_task_make_grid': RUN_TASK_MAKE_GRID,
            'run_task_make_orog': RUN_TASK_MAKE_OROG,
            'run_task_make_sfc_climo': RUN_TASK_MAKE_SFC_CLIMO,
            'run_task_get_extrn_ics': RUN_TASK_GET_EXTRN_ICS,
            'run_task_get_extrn_lbcs': RUN_TASK_GET_EXTRN_LBCS,
            'run_task_make_ics': RUN_TASK_MAKE_ICS,
            'run_task_make_lbcs': RUN_TASK_MAKE_LBCS,
            'run_task_run_fcst': RUN_TASK_RUN_FCST,
            'run_task_run_post': RUN_TASK_RUN_POST,
            'run_task_get_obs_ccpa': RUN_TASK_GET_OBS_CCPA,
            'run_task_get_obs_mrms': RUN_TASK_GET_OBS_MRMS,
            'run_task_get_obs_ndas': RUN_TASK_GET_OBS_NDAS,
            'run_task_vx_gridstat': RUN_TASK_VX_GRIDSTAT,
            'run_task_vx_pointstat': RUN_TASK_VX_POINTSTAT,
            'run_task_vx_ensgrid': RUN_TASK_VX_ENSGRID,
            'run_task_vx_enspoint': RUN_TASK_VX_ENSPOINT,
            #
            # Number of physical cores per node for the current machine.
            #
            'ncores_per_node': NCORES_PER_NODE,
            #
            # Directories and files.
            #
            'jobsdir': JOBSDIR,
            'logdir': LOGDIR,
            'scriptsdir': SCRIPTSDIR,
            'cycle_basedir': CYCLE_BASEDIR,
            'global_var_defns_fp': GLOBAL_VAR_DEFNS_FP,
            'load_modules_run_task_fp': LOAD_MODULES_RUN_TASK_FP,
            #
            # External model information for generating ICs and LBCs.
            #
            'extrn_mdl_name_ics': EXTRN_MDL_NAME_ICS,
            'extrn_mdl_name_lbcs': EXTRN_MDL_NAME_LBCS,
            #
            # Parameters that determine the set of cycles to run.
            #
            'date_first_cycl': date_to_str(DATE_FIRST_CYCL, format="%Y%m%d"),
            'date_last_cycl': date_to_str(DATE_LAST_CYCL, format="%Y%m%d"),
            'cdate_first_cycl': cdate_first_cycl,
            'cycl_hrs': cycl_hrs_str,
            'cycl_freq': f"{INCR_CYCL_FREQ:02d}:00:00",
            #
            # Forecast length (same for all cycles).
            #
            'fcst_len_hrs': FCST_LEN_HRS,
            #
            # Inline post
            #
            'write_dopost': WRITE_DOPOST,
            #
            # METPlus-specific information
            #
            'model': MODEL,
            'met_install_dir': MET_INSTALL_DIR,
            'met_bin_exec': MET_BIN_EXEC,
            'metplus_path': METPLUS_PATH,
            'vx_config_dir': VX_CONFIG_DIR,
            'metplus_conf': METPLUS_CONF,
            'met_config': MET_CONFIG,
            'ccpa_obs_dir': CCPA_OBS_DIR,
            'mrms_obs_dir': MRMS_OBS_DIR,
            'ndas_obs_dir': NDAS_OBS_DIR,
            #
            # Ensemble-related parameters.
            #
            'do_ensemble': DO_ENSEMBLE,
            'num_ens_members': NUM_ENS_MEMBERS,
            'ndigits_ensmem_names': f"{NDIGITS_ENSMEM_NAMES}",
            'ensmem_indx_name': ensmem_indx_name,
            'uscore_ensmem_name': uscore_ensmem_name,
            'slash_ensmem_subdir': slash_ensmem_subdir,
            #
            # Parameters associated with subhourly post-processed output
            #
            'sub_hourly_post': SUB_HOURLY_POST,
            'delta_min': DT_SUBHOURLY_POST_MNTS,
            'first_fv3_file_tstr': f"000:{time_str}"
        }
        # End of "settings" variable.
        settings_str = cfg_to_yaml_str(settings)

        print_info_msg(dedent(f'''
            The variable \"settings\" specifying values of the rococo XML variables
            has been set as follows:
            #-----------------------------------------------------------------------
            settings =\n\n''') + settings_str,
                       verbose=VERBOSE)

        #
        # Call the python script to generate the experiment's actual XML file
        # from the jinja template file.
        #
        try:
            fill_jinja_template([
                "-q", "-u", settings_str, "-t", template_xml_fp, "-o",
                WFLOW_XML_FP
            ])
        except:
            print_err_msg_exit(
                dedent(f'''
                Call to python script fill_jinja_template.py to create a rocoto workflow
                XML file from a template file failed.  Parameters passed to this script
                are:
                  Full path to template rocoto XML file:
                    template_xml_fp = \"{template_xml_fp}\"
                  Full path to output rocoto XML file:
                    WFLOW_XML_FP = \"{WFLOW_XML_FP}\"
                  Namelist settings specified on command line:\n
                    settings =\n\n''') + settings_str)
    #
    #-----------------------------------------------------------------------
    #
    # Create a symlink in the experiment directory that points to the workflow
    # (re)launch script.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(f'''
        Creating symlink in the experiment directory (EXPTDIR) that points to the
        workflow launch script (WFLOW_LAUNCH_SCRIPT_FP):
          EXPTDIR = \"{EXPTDIR}\"
          WFLOW_LAUNCH_SCRIPT_FP = \"{WFLOW_LAUNCH_SCRIPT_FP}\"''',
                   verbose=VERBOSE)

    create_symlink_to_file(WFLOW_LAUNCH_SCRIPT_FP,
                           os.path.join(EXPTDIR, WFLOW_LAUNCH_SCRIPT_FN),
                           False)
    #
    #-----------------------------------------------------------------------
    #
    # If USE_CRON_TO_RELAUNCH is set to TRUE, add a line to the user's cron
    # table to call the (re)launch script every CRON_RELAUNCH_INTVL_MNTS mi-
    # nutes.
    #
    #-----------------------------------------------------------------------
    #
    if USE_CRON_TO_RELAUNCH:
        add_crontab_line()
    #
    #-----------------------------------------------------------------------
    #
    # Create the FIXam directory under the experiment directory.  In NCO mode,
    # this will be a symlink to the directory specified in FIXgsm, while in
    # community mode, it will be an actual directory with files copied into
    # it from FIXgsm.
    #
    #-----------------------------------------------------------------------
    #
    # First, consider NCO mode.
    #
    if RUN_ENVIR == "nco":

        ln_vrfy(f'''-fsn "{FIXgsm}" "{FIXam}"''')
        #
        # Resolve the target directory that the FIXam symlink points to and check
        # that it exists.
        #
        try:
            path_resolved = os.path.realpath(FIXam)
        except:
            path_resolved = FIXam
        if not os.path.exists(path_resolved):
            print_err_msg_exit(f'''
            In order to be able to generate a forecast experiment in NCO mode (i.e.
            when RUN_ENVIR set to \"nco\"), the path specified by FIXam after resolving
            all symlinks (path_resolved) must be an existing directory (but in this
            case isn't):
              RUN_ENVIR = \"{RUN_ENVIR}\"
              FIXam = \"{FIXam}\"
              path_resolved = \"{path_resolved}\"
            Please ensure that path_resolved is an existing directory and then rerun
            the experiment generation script.''')
    #
    # Now consider community mode.
    #
    else:

        print_info_msg(f'''
        Copying fixed files from system directory (FIXgsm) to a subdirectory
        (FIXam) in the experiment directory:
          FIXgsm = \"{FIXgsm}\"
          FIXam = \"{FIXam}\"''',
                       verbose=VERBOSE)

        check_for_preexist_dir_file(FIXam, "delete")
        mkdir_vrfy("-p", FIXam)
        mkdir_vrfy("-p", os.path.join(FIXam, "fix_co2_proj"))

        num_files = len(FIXgsm_FILES_TO_COPY_TO_FIXam)
        for i in range(num_files):
            fn = f"{FIXgsm_FILES_TO_COPY_TO_FIXam[i]}"
            cp_vrfy(os.path.join(FIXgsm, fn), os.path.join(FIXam, fn))
    #
    #-----------------------------------------------------------------------
    #
    # Copy MERRA2 aerosol climatology data.
    #
    #-----------------------------------------------------------------------
    #
    if USE_MERRA_CLIMO:
        print_info_msg(f'''
            Copying MERRA2 aerosol climatology data files from system directory
            (FIXaer/FIXlut) to a subdirectory (FIXclim) in the experiment directory:
              FIXaer = \"{FIXaer}\"
              FIXlut = \"{FIXlut}\"
              FIXclim = \"{FIXclim}\"''',
                       verbose=VERBOSE)

        check_for_preexist_dir_file(FIXclim, "delete")
        mkdir_vrfy("-p", FIXclim)

        cp_vrfy(os.path.join(FIXaer, "merra2.aerclim*.nc"), FIXclim)
        cp_vrfy(os.path.join(FIXlut, "optics*.dat"), FIXclim)
    #
    #-----------------------------------------------------------------------
    #
    # Copy templates of various input files to the experiment directory.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(f'''
        Copying templates of various input files to the experiment directory...''',
                   verbose=VERBOSE)

    print_info_msg(f'''
        Copying the template data table file to the experiment directory...''',
                   verbose=VERBOSE)
    cp_vrfy(DATA_TABLE_TMPL_FP, DATA_TABLE_FP)

    print_info_msg(f'''
        Copying the template field table file to the experiment directory...''',
                   verbose=VERBOSE)
    cp_vrfy(FIELD_TABLE_TMPL_FP, FIELD_TABLE_FP)

    print_info_msg(f'''
        Copying the template NEMS configuration file to the experiment directory...''',
                   verbose=VERBOSE)
    cp_vrfy(NEMS_CONFIG_TMPL_FP, NEMS_CONFIG_FP)
    #
    # Copy the CCPP physics suite definition file from its location in the
    # clone of the FV3 code repository to the experiment directory (EXPT-
    # DIR).
    #
    print_info_msg(f'''
        Copying the CCPP physics suite definition XML file from its location in
        the forecast model directory sturcture to the experiment directory...''',
                   verbose=VERBOSE)
    cp_vrfy(CCPP_PHYS_SUITE_IN_CCPP_FP, CCPP_PHYS_SUITE_FP)
    #
    # Copy the field dictionary file from its location in the
    # clone of the FV3 code repository to the experiment directory (EXPT-
    # DIR).
    #
    print_info_msg(f'''
        Copying the field dictionary file from its location in the forecast
        model directory sturcture to the experiment directory...''',
                   verbose=VERBOSE)
    cp_vrfy(FIELD_DICT_IN_UWM_FP, FIELD_DICT_FP)
    #
    #-----------------------------------------------------------------------
    #
    # Set parameters in the FV3-LAM namelist file.
    #
    #-----------------------------------------------------------------------
    #
    print_info_msg(f'''
        Setting parameters in weather model's namelist file (FV3_NML_FP):
        FV3_NML_FP = \"{FV3_NML_FP}\"''')
    #
    # Set npx and npy, which are just NX plus 1 and NY plus 1, respectively.
    # These need to be set in the FV3-LAM Fortran namelist file.  They represent
    # the number of cell vertices in the x and y directions on the regional
    # grid.
    #
    npx = NX + 1
    npy = NY + 1
    #
    # For the physics suites that use RUC LSM, set the parameter kice to 9,
    # Otherwise, leave it unspecified (which means it gets set to the default
    # value in the forecast model).
    #
    # NOTE:
    # May want to remove kice from FV3.input.yml (and maybe input.nml.FV3).
    #
    kice = None
    if SDF_USES_RUC_LSM:
        kice = 9
    #
    # Set lsoil, which is the number of input soil levels provided in the
    # chgres_cube output NetCDF file.  This is the same as the parameter
    # nsoill_out in the namelist file for chgres_cube.  [On the other hand,
    # the parameter lsoil_lsm (not set here but set in input.nml.FV3 and/or
    # FV3.input.yml) is the number of soil levels that the LSM scheme in the
    # forecast model will run with.]  Here, we use the same approach to set
    # lsoil as the one used to set nsoill_out in exregional_make_ics.sh.
    # See that script for details.
    #
    # NOTE:
    # May want to remove lsoil from FV3.input.yml (and maybe input.nml.FV3).
    # Also, may want to set lsm here as well depending on SDF_USES_RUC_LSM.
    #
    lsoil = 4
    if ( EXTRN_MDL_NAME_ICS == "HRRR" or \
         EXTRN_MDL_NAME_ICS == "RAP" ) and \
       ( SDF_USES_RUC_LSM ):
        lsoil = 9
    #
    # Create a multiline variable that consists of a yaml-compliant string
    # specifying the values that the namelist variables that are physics-
    # suite-independent need to be set to.  Below, this variable will be
    # passed to a python script that will in turn set the values of these
    # variables in the namelist file.
    #
    # IMPORTANT:
    # If we want a namelist variable to be removed from the namelist file,
    # in the "settings" variable below, we need to set its value to the
    # string "null".  This is equivalent to setting its value to
    #    !!python/none
    # in the base namelist file specified by FV3_NML_BASE_SUITE_FP or the
    # suite-specific yaml settings file specified by FV3_NML_YAML_CONFIG_FP.
    #
    # It turns out that setting the variable to an empty string also works
    # to remove it from the namelist!  Which is better to use??
    #
    settings = {}
    settings['atmos_model_nml'] = {
        'blocksize': BLOCKSIZE,
        'ccpp_suite': CCPP_PHYS_SUITE
    }
    settings['fv_core_nml'] = {
        'target_lon': LON_CTR,
        'target_lat': LAT_CTR,
        'nrows_blend': HALO_BLEND,
        #
        # Question:
        # For a ESGgrid type grid, what should stretch_fac be set to?  This depends
        # on how the FV3 code uses the stretch_fac parameter in the namelist file.
        # Recall that for a ESGgrid, it gets set in the function set_gridparams_ESGgrid(.sh)
        # to something like 0.9999, but is it ok to set it to that here in the
        # FV3 namelist file?
        #
        'stretch_fac': STRETCH_FAC,
        'npx': npx,
        'npy': npy,
        'layout': [LAYOUT_X, LAYOUT_Y],
        'bc_update_interval': LBC_SPEC_INTVL_HRS
    }
    settings['gfs_physics_nml'] = {
        'kice': kice or None,
        'lsoil': lsoil or None,
        'do_shum': DO_SHUM,
        'do_sppt': DO_SPPT,
        'do_skeb': DO_SKEB,
        'do_spp': DO_SPP,
        'n_var_spp': N_VAR_SPP,
        'n_var_lndp': N_VAR_LNDP,
        'lndp_type': LNDP_TYPE,
        'fhcyc': FHCYC_LSM_SPP_OR_NOT
    }
    #
    # Add to "settings" the values of those namelist variables that specify
    # the paths to fixed files in the FIXam directory.  As above, these namelist
    # variables are physcs-suite-independent.
    #
    # Note that the array FV3_NML_VARNAME_TO_FIXam_FILES_MAPPING contains
    # the mapping between the namelist variables and the names of the files
    # in the FIXam directory.  Here, we loop through this array and process
    # each element to construct each line of "settings".
    #
    dummy_run_dir = os.path.join(EXPTDIR, "any_cyc")
    if DO_ENSEMBLE:
        dummy_run_dir = os.path.join(dummy_run_dir, "any_ensmem")

    regex_search = "^[ ]*([^| ]+)[ ]*[|][ ]*([^| ]+)[ ]*$"
    num_nml_vars = len(FV3_NML_VARNAME_TO_FIXam_FILES_MAPPING)
    namsfc_dict = {}
    for i in range(num_nml_vars):

        mapping = f"{FV3_NML_VARNAME_TO_FIXam_FILES_MAPPING[i]}"
        tup = find_pattern_in_str(regex_search, mapping)
        nml_var_name = tup[0]
        FIXam_fn = tup[1]

        fp = "\"\""
        if FIXam_fn:
            fp = os.path.join(FIXam, FIXam_fn)
            #
            # If not in NCO mode, for portability and brevity, change fp so that it
            # is a relative path (relative to any cycle directory immediately under
            # the experiment directory).
            #
            if RUN_ENVIR != "nco":
                fp = os.path.relpath(os.path.realpath(fp), start=dummy_run_dir)
    #
    # Add a line to the variable "settings" that specifies (in a yaml-compliant
    # format) the name of the current namelist variable and the value it should
    # be set to.
    #
        namsfc_dict[nml_var_name] = fp
    #
    # Add namsfc_dict to settings
    #
    settings['namsfc'] = namsfc_dict
    #
    # Use netCDF4 when running the North American 3-km domain due to file size.
    #
    if PREDEF_GRID_NAME == "RRFS_NA_3km":
        settings['fms2_io_nml'] = {'netcdf_default_format': 'netcdf4'}
    #
    # Add the relevant tendency-based stochastic physics namelist variables to
    # "settings" when running with SPPT, SHUM, or SKEB turned on. If running
    # with SPP or LSM SPP, set the "new_lscale" variable.  Otherwise only
    # include an empty "nam_stochy" stanza.
    #
    nam_stochy_dict = {}
    if DO_SPPT:
        nam_stochy_dict.update({
            'iseed_sppt': ISEED_SPPT,
            'new_lscale': NEW_LSCALE,
            'sppt': SPPT_MAG,
            'sppt_logit': SPPT_LOGIT,
            'sppt_lscale': SPPT_LSCALE,
            'sppt_sfclimit': SPPT_SFCLIMIT,
            'sppt_tau': SPPT_TSCALE,
            'spptint': SPPT_INT,
            'use_zmtnblck': USE_ZMTNBLCK
        })

    if DO_SHUM:
        nam_stochy_dict.update({
            'iseed_shum': ISEED_SHUM,
            'new_lscale': NEW_LSCALE,
            'shum': SHUM_MAG,
            'shum_lscale': SHUM_LSCALE,
            'shum_tau': SHUM_TSCALE,
            'shumint': SHUM_INT
        })

    if DO_SKEB:
        nam_stochy_dict.update({
            'iseed_skeb': ISEED_SKEB,
            'new_lscale': NEW_LSCALE,
            'skeb': SKEB_MAG,
            'skeb_lscale': SKEB_LSCALE,
            'skebnorm': SKEBNORM,
            'skeb_tau': SKEB_TSCALE,
            'skebint': SKEB_INT,
            'skeb_vdof': SKEB_VDOF
        })

    if DO_SPP or DO_LSM_SPP:
        nam_stochy_dict.update({'new_lscale': NEW_LSCALE})

    settings['nam_stochy'] = nam_stochy_dict
    #
    # Add the relevant SPP namelist variables to "settings" when running with
    # SPP turned on.  Otherwise only include an empty "nam_sppperts" stanza.
    #
    nam_sppperts_dict = {}
    if DO_SPP:
        nam_sppperts_dict = {
            'iseed_spp': ISEED_SPP,
            'spp_lscale': SPP_LSCALE,
            'spp_prt_list': SPP_MAG_LIST,
            'spp_sigtop1': SPP_SIGTOP1,
            'spp_sigtop2': SPP_SIGTOP2,
            'spp_stddev_cutoff': SPP_STDDEV_CUTOFF,
            'spp_tau': SPP_TSCALE,
            'spp_var_list': SPP_VAR_LIST
        }

    settings['nam_sppperts'] = nam_sppperts_dict
    #
    # Add the relevant LSM SPP namelist variables to "settings" when running with
    # LSM SPP turned on.
    #
    nam_sfcperts_dict = {}
    if DO_LSM_SPP:
        nam_sfcperts_dict = {
            'lndp_type': LNDP_TYPE,
            'lndp_model_type': LNDP_MODEL_TYPE,
            'lndp_tau': LSM_SPP_TSCALE,
            'lndp_lscale': LSM_SPP_LSCALE,
            'iseed_lndp': ISEED_LSM_SPP,
            'lndp_var_list': LSM_SPP_VAR_LIST,
            'lndp_prt_list': LSM_SPP_MAG_LIST
        }

    settings['nam_sfcperts'] = nam_sfcperts_dict

    settings_str = cfg_to_yaml_str(settings)

    print_info_msg(dedent(f'''
        The variable \"settings\" specifying values of the weather model's 
        namelist variables has been set as follows:
        
        settings =\n\n''') + settings_str,
                   verbose=VERBOSE)
    #
    #-----------------------------------------------------------------------
    #
    # Call the set_namelist.py script to create a new FV3 namelist file (full
    # path specified by FV3_NML_FP) using the file FV3_NML_BASE_SUITE_FP as
    # the base (i.e. starting) namelist file, with physics-suite-dependent
    # modifications to the base file specified in the yaml configuration file
    # FV3_NML_YAML_CONFIG_FP (for the physics suite specified by CCPP_PHYS_SUITE),
    # and with additional physics-suite-independent modificaitons specified
    # in the variable "settings" set above.
    #
    #-----------------------------------------------------------------------
    #
    try:
        set_namelist([
            "-q", "-n", FV3_NML_BASE_SUITE_FP, "-c", FV3_NML_YAML_CONFIG_FP,
            CCPP_PHYS_SUITE, "-u", settings_str, "-o", FV3_NML_FP
        ])
    except:
        print_err_msg_exit(
            dedent(f'''
            Call to python script set_namelist.py to generate an FV3 namelist file
            failed.  Parameters passed to this script are:
              Full path to base namelist file:
                FV3_NML_BASE_SUITE_FP = \"{FV3_NML_BASE_SUITE_FP}\"
              Full path to yaml configuration file for various physics suites:
                FV3_NML_YAML_CONFIG_FP = \"{FV3_NML_YAML_CONFIG_FP}\"
              Physics suite to extract from yaml configuration file:
                CCPP_PHYS_SUITE = \"{CCPP_PHYS_SUITE}\"
              Full path to output namelist file:
                FV3_NML_FP = \"{FV3_NML_FP}\"
              Namelist settings specified on command line:\n
                settings =\n\n''') + settings_str)
    #
    # If not running the MAKE_GRID_TN task (which implies the workflow will
    # use pregenerated grid files), set the namelist variables specifying
    # the paths to surface climatology files.  These files are located in
    # (or have symlinks that point to them) in the FIXLAM directory.
    #
    # Note that if running the MAKE_GRID_TN task, this action usually cannot
    # be performed here but must be performed in that task because the names
    # of the surface climatology files depend on the CRES parameter (which is
    # the C-resolution of the grid), and this parameter is in most workflow
    # configurations is not known until the grid is created.
    #
    if not RUN_TASK_MAKE_GRID:

        set_FV3nml_sfc_climo_filenames()
    #
    #-----------------------------------------------------------------------
    #
    # To have a record of how this experiment/workflow was generated, copy
    # the experiment/workflow configuration file to the experiment directo-
    # ry.
    #
    #-----------------------------------------------------------------------
    #
    cp_vrfy(os.path.join(USHDIR, EXPT_CONFIG_FN), EXPTDIR)
    #
    #-----------------------------------------------------------------------
    #
    # For convenience, print out the commands that need to be issued on the
    # command line in order to launch the workflow and to check its status.
    # Also, print out the line that should be placed in the user's cron table
    # in order for the workflow to be continually resubmitted.
    #
    #-----------------------------------------------------------------------
    #
    if WORKFLOW_MANAGER == "rocoto":
        wflow_db_fn = f"{os.path.splitext(WFLOW_XML_FN)[0]}.db"
        rocotorun_cmd = f"rocotorun -w {WFLOW_XML_FN} -d {wflow_db_fn} -v 10"
        rocotostat_cmd = f"rocotostat -w {WFLOW_XML_FN} -d {wflow_db_fn} -v 10"

    print_info_msg(f'''
        ========================================================================
        ========================================================================
        
        Experiment generation completed.  The experiment directory is:
        
          EXPTDIR=\"{EXPTDIR}\"
        
        ========================================================================
        ========================================================================
        ''')
    #
    #-----------------------------------------------------------------------
    #
    # If rocoto is required, print instructions on how to load and use it
    #
    #-----------------------------------------------------------------------
    #
    if WORKFLOW_MANAGER == "rocoto":

        print_info_msg(f'''
        To launch the workflow, first ensure that you have a compatible version
        of rocoto available. For most pre-configured platforms, rocoto can be
        loaded via a module:
        
          > module load rocoto
        
        For more details on rocoto, see the User's Guide.
        
        To launch the workflow, first ensure that you have a compatible version
        of rocoto loaded.  For example, to load version 1.3.1 of rocoto, use
        
          > module load rocoto/1.3.1
        
        (This version has been tested on hera; later versions may also work but
        have not been tested.)
        
        To launch the workflow, change location to the experiment directory
        (EXPTDIR) and issue the rocotrun command, as follows:
        
          > cd {EXPTDIR}
          > {rocotorun_cmd}
        
        To check on the status of the workflow, issue the rocotostat command
        (also from the experiment directory):
        
          > {rocotostat_cmd}
        
        Note that:
        
        1) The rocotorun command must be issued after the completion of each
           task in the workflow in order for the workflow to submit the next
           task(s) to the queue.
        
        2) In order for the output of the rocotostat command to be up-to-date,
           the rocotorun command must be issued immediately before issuing the
           rocotostat command.
        
        For automatic resubmission of the workflow (say every 3 minutes), the
        following line can be added to the user's crontab (use \"crontab -e\" to
        edit the cron table):
        
        */3 * * * * cd {EXPTDIR} && ./launch_FV3LAM_wflow.sh called_from_cron=\"TRUE\"
        ''')
    #
    # If necessary, run the NOMADS script to source external model data.
    #
    if NOMADS:
        print("Getting NOMADS online data")
        print(f"NOMADS_file_type= {NOMADS_file_type}")
        cd_vrfy(EXPTDIR)
        NOMADS_script = os.path.join(USHDIR, "NOMADS_get_extrn_mdl_files.h")
        run_command(
            f'''{NOMADS_script} {date_to_str(DATE_FIRST_CYCL,format="%Y%m%d")} \
                      {CYCL_HRS} {NOMADS_file_type} {FCST_LEN_HRS} {LBC_SPEC_INTVL_HRS}'''
        )
Ejemplo n.º 19
0
def set_thompson_mp_fix_files(ccpp_phys_suite_fp, thompson_mp_climo_fn):
    """ Function that first checks whether the Thompson
    microphysics parameterization is being called by the selected physics
    suite.  If not, it sets the output variable whose name is specified by
    output_varname_sdf_uses_thompson_mp to "FALSE" and exits.  If so, it 
    sets this variable to "TRUE" and modifies the workflow arrays
    FIXgsm_FILES_TO_COPY_TO_FIXam and CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING
    to ensure that fixed files needed by the Thompson microphysics
    parameterization are copied to the FIXam directory and that appropriate
    symlinks to these files are created in the run directories.

    Args:
        ccpp_phys_suite_fp: full path to CCPP physics suite
        thompson_mp_climo_fn: netcdf file for thompson microphysics
    Returns:
        boolean: sdf_uses_thompson_mp
    """

    print_input_args(locals())

    # import all environment variables
    import_vars()

    #
    #-----------------------------------------------------------------------
    #
    # Check the suite definition file to see whether the Thompson microphysics
    # parameterization is being used.
    #
    #-----------------------------------------------------------------------
    #
    tree = load_xml_file(ccpp_phys_suite_fp)
    sdf_uses_thompson_mp = has_tag_with_value(tree, "scheme", "mp_thompson")
    #
    #-----------------------------------------------------------------------
    #
    # If the Thompson microphysics parameterization is being used, then...
    #
    #-----------------------------------------------------------------------
    #
    if sdf_uses_thompson_mp:
    #
    #-----------------------------------------------------------------------
    #
    # Append the names of the fixed files needed by the Thompson microphysics
    # parameterization to the workflow array FIXgsm_FILES_TO_COPY_TO_FIXam, 
    # and append to the workflow array CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING 
    # the mappings between these files and the names of the corresponding 
    # symlinks that need to be created in the run directories.
    #
    #-----------------------------------------------------------------------
    #
        thompson_mp_fix_files=[
          "CCN_ACTIVATE.BIN",
          "freezeH2O.dat",
          "qr_acr_qg.dat",
          "qr_acr_qs.dat",
          "qr_acr_qgV2.dat",
          "qr_acr_qsV2.dat"
        ]
    
        if (EXTRN_MDL_NAME_ICS  != "HRRR" and EXTRN_MDL_NAME_ICS  != "RAP") or \
           (EXTRN_MDL_NAME_LBCS != "HRRR" and EXTRN_MDL_NAME_LBCS != "RAP"):
          thompson_mp_fix_files.append(thompson_mp_climo_fn)
    
        FIXgsm_FILES_TO_COPY_TO_FIXam.extend(thompson_mp_fix_files)
    
        for fix_file in thompson_mp_fix_files:
          mapping=f"{fix_file} | {fix_file}"
          CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING.append(mapping)
    
        msg=dedent(f'''
            Since the Thompson microphysics parameterization is being used by this 
            physics suite (CCPP_PHYS_SUITE), the names of the fixed files needed by
            this scheme have been appended to the array FIXgsm_FILES_TO_COPY_TO_FIXam, 
            and the mappings between these files and the symlinks that need to be 
            created in the cycle directories have been appended to the array
            CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING.  After these modifications, the 
            values of these parameters are as follows:
            
            ''')
        msg+=dedent(f'''
                CCPP_PHYS_SUITE = \"{CCPP_PHYS_SUITE}\"
            
                FIXgsm_FILES_TO_COPY_TO_FIXam = {list_to_str(FIXgsm_FILES_TO_COPY_TO_FIXam)}
            ''')
        msg+=dedent(f'''
                CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING = {list_to_str(CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING)}
            ''')
        print_info_msg(msg)

        EXPORTS = [ "CYCLEDIR_LINKS_TO_FIXam_FILES_MAPPING", "FIXgsm_FILES_TO_COPY_TO_FIXam" ]
        export_vars(env_vars=EXPORTS)

    return sdf_uses_thompson_mp