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}'''
        )
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
Esempio n. 3
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}')
Esempio n. 4
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