UserScript: Calculate the Difficulty Index

model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index.conf

Scientific Objective

This use case calls the UserScript wrapper to run a user provided script that calculates the difficulty index for wind speed. This use case allows for the user to change a variety of variables needed to run the difficulty index (i.e. threshold start and units) so that the user can run the script at different thresholds without needing to alter the code. This script run by the use case uses METcalcpy to provide the difficulty index calculation and METplotpy to provide the plotting capability.

The difficulty index was developed by the Naval Research Lab (NRL). The overall aim of the difficulty index is to graphically represent the expected difficulty of a decision based on a set of forecasts (ensemble) of, e.g., wind speed as a function of space and time. There are two basic factors that can make a decision difficult. The first factor is the proximity of the ensemble mean forecast to a decision threshold, e.g. 34 knot winds. If the ensemble mean is either much lower or much higher than the threshold, the decision is easier; if it is closer to the threshold, the decision is harder. The second factor is the forecast precision, or ensemble spread. The greater the spread around the ensemble mean, the more likely it is that there will be ensemble members both above and below the decision threshold, making the decision harder. (A third factor that we will not address here is undiagnosed systematic error, which adds uncertainty in a similar way to ensemble spread.) The challenge is combining these factors into a continuous function that allows the user to assess relative risk.

Additional details on the computation of the Difficulty Index can be found in the METcalcpy documentation and more information on plotting difficulty index can be found in the METplotpy documentation.

Version Added

METplus version 4.1

Datasets

Forecast: NOAA Global Ensemble Forecast System (GEFS)

Observation: None

Climatology: None

Location: All of the input data required for this use case can be found in a sample data tarball. Each use case category will have one or more sample data tarballs. It is only necessary to download the tarball with the use case’s dataset and not the entire collection of sample data. Click here to access the METplus releases page and download sample data for the appropriate release: https://github.com/dtcenter/METplus/releases This tarball should be unpacked into the directory that you will set the value of INPUT_BASE. See Running METplus section for more information.

METplus Components

This use case runs the UserScript wrapper tool to run a user provided script, in this case, wind_difficulty_index.py.

METplus Workflow

Beginning time (INIT_BEG): 2020120812

End time (INIT_END): 2020120812

Increment between beginning and end times (INIT_INCREMENT): 12H

Sequence of forecast leads to process (LEAD_SEQ): None

This use case loops by process which means that each tool is run for all times before moving to the next tool. The tool order is as follows:

UserScript

This example loops by initialization time (with begin, end, and increment as specified in the METplus UserScript_fcstGEFS_Difficulty_Index.conf file).

1 initialization time will be run over 1 lead time:

Init: 20201208_12Z

Forecast lead: 60

Since the data file used only contains a single lead time, the lead time is implied and not configured anywhere in the use case configuration file.

METplus Configuration

METplus first loads all of the configuration files found in parm/metplus_config, then it loads any configuration files passed to METplus via the command line, i.e. parm/use_cases/model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index.conf

[config]

# Documentation for this use case can be found at
# https://metplus.readthedocs.io/en/latest/generated/model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index.html

# For additional information, please see the METplus Users Guide.
# https://metplus.readthedocs.io/en/latest/Users_Guide

###
# Processes to run
# https://metplus.readthedocs.io/en/latest/Users_Guide/systemconfiguration.html#process-list
###

PROCESS_LIST = UserScript


###
# Time Info
# LOOP_BY options are INIT, VALID, RETRO, and REALTIME
# If set to INIT or RETRO:
#   INIT_TIME_FMT, INIT_BEG, INIT_END, and INIT_INCREMENT must also be set
# If set to VALID or REALTIME:
#   VALID_TIME_FMT, VALID_BEG, VALID_END, and VALID_INCREMENT must also be set
# LEAD_SEQ is the list of forecast leads to process
# https://metplus.readthedocs.io/en/latest/Users_Guide/systemconfiguration.html#timing-control
###

LOOP_BY = INIT
INIT_TIME_FMT = %Y%m%d%H
INIT_BEG = 2020120812
INIT_END = 2020120812
INIT_INCREMENT = 12H

LEAD_SEQ = 

USER_SCRIPT_CUSTOM_LOOP_LIST = nc

USER_SCRIPT_RUNTIME_FREQ = RUN_ONCE_FOR_EACH


###
# File I/O
# https://metplus.readthedocs.io/en/latest/Users_Guide/systemconfiguration.html#directory-and-filename-template-info
###

USER_SCRIPT_INPUT_DIR = {INPUT_BASE}/model_applications/medium_range/diff_index
USER_SCRIPT_INPUT_TEMPLATE = {USER_SCRIPT_INPUT_DIR}/wndspd_GEFS_NorthPac_5dy_30mem_{init?fmt=%Y%m%d%H}.npz

USER_SCRIPT_OUTPUT_DIR = {OUTPUT_BASE}/model_applications/medium_range/diff_index


###
# UserScript Settings
# https://metplus.readthedocs.io/en/latest/Users_Guide/wrappers.html#userscript
###

USER_SCRIPT_COMMAND = {PARM_BASE}/use_cases/model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index/wind_difficulty_index.py


[user_env_vars]

# Difficulty index specific variables

DIFF_INDEX_INPUT_FILENAME = {USER_SCRIPT_INPUT_TEMPLATE}

DIFF_INDEX_THRESH_START = 10.0 

DIFF_INDEX_THRESH_END = 40.0

DIFF_INDEX_THRESH_STEP = 2.0

DIFF_INDEX_SAVE_THRESH_START = 20.0

DIFF_INDEX_SAVE_THRESH_STOP = 38.0

DIFF_INDEX_SAVE_THRESH_STEP = 2.0

DIFF_INDEX_UNITS = kn

DIFF_INDEX_FIG_FMT = png

DIFF_INDEX_FIG_BASENAME = {USER_SCRIPT_OUTPUT_DIR}/wndspd_GEFS_NorthPac_5dy_30mem_difficulty_index

MET Configuration

There are no MET tools used in this use case.

Python Embedding

This use case uses a Python embedding script to read input data.

parm/use_cases/model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index/wind_difficulty_index.py
#!/usr/bin/env python3

"""
Load fieldijn from npz file created with save_ensemble_data.py
helper function, compute ensemble mean and spread, compute
difficulty index for a set of thresholds, plot and save the results.
Author: Bill Campbell, NRL and Lindsay Blank, NCAR

Taken from original test_difficulty_index.py but replacing with METcalcpy and METplotpy.

"""
import os
import sys
import numpy as np
import matplotlib.pyplot as plt
from metcalcpy.calc_difficulty_index import forecast_difficulty as di
from metcalcpy.calc_difficulty_index import EPS
from metcalcpy.piecewise_linear import PiecewiseLinear as plin
import metplotpy.plots.difficulty_index.mycolormaps as mcmap
from metplotpy.plots.difficulty_index.plot_difficulty_index import plot_field


def load_data(filename):
    """Load ensemble data from file"""
    loaded = np.load(filename)
    lats, lons = (loaded['lats'], loaded['lons'])
    fieldijn = np.ma.masked_invalid(
        np.ma.masked_array(
            data=loaded['data']))

    return lats, lons, fieldijn


def compute_stats(field):
    """Compute mean and std dev"""
    mu = np.mean(field, axis=-1)
    sigma = np.std(field, axis=-1, ddof=1)

    return mu, sigma

def compute_wind_envelope():
    """
    Computes piecewise linear envelope for winds in knots.

    Returns
    -------
    Piecewise linear object

    """
    # Envelope for version 6.1, the default
    xunits = 'kn'
    A6_1_name = "A6_1"
    A6_1_left = 0.0
    A6_1_right = 0.0
    A6_1_xlist = [5.0, 28.0, 34.0, 50.0]
    A6_1_ylist = [0.0, 1.5, 1.5, 0.0]
    Aplin =\
            plin(A6_1_xlist, A6_1_ylist, xunits=xunits,
                    right=A6_1_right, left=A6_1_left, name=A6_1_name)

    return Aplin

def compute_difficulty_index(field, mu, sigma, thresholds, Aplin):
    """
    Compute difficulty index for an ensemble forecast given
    a set of thresholds, returning a dictionary of fields.
    """
    dij = {}
    for threshold in thresholds:
        dij[threshold] =\
            di(sigma, mu, threshold, field, Aplin=Aplin, sigma_over_mu_ref=EPS)

    return dij


def plot_difficulty_index(dij, lats, lons, thresholds, units):
    """
    Plot the difficulty index for a set of thresholds,
    returning a dictionary of figures
    """
    plt.close('all')
    myparams = {'figure.figsize': (8, 5),
                'figure.max_open_warning': 40}
    plt.rcParams.update(myparams)
    figs = {}
    cmap = mcmap.stoplight()
    for threshold in thresholds:
        if np.max(dij[threshold]) <= 1.0:
            vmax = 1.0
        else:
            vmax = 1.5
        figs[threshold] =\
            plot_field(dij[threshold],
                          lats, lons, vmin=0.0, vmax=vmax, cmap=cmap,
                          xlab='Longitude \u00b0E', ylab='Latitude',
                          clab='thresh={} {}'.format(threshold, units),
                          title='Forecast Decision Difficulty Index')

    return figs


def save_difficulty_figures(figs, save_thresh, units):
    """
    Save subset of difficulty index figures.
    """
    fig_fmt = os.environ.get('DIFF_INDEX_FIG_FMT')
    fig_basename = os.environ.get('DIFF_INDEX_FIG_BASENAME')

    # create output directory if it does not already exist
    output_dir = os.path.dirname(fig_basename)
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    for thresh in save_thresh:
        thresh_str = '{:.2f}'.format(thresh).replace('.', '_')
        fig_name = (fig_basename + thresh_str +
                    '_' + units + '.' + fig_fmt)
        print('Saving {}...\n'.format(fig_name))
        figs[thresh].savefig(fig_name, format=fig_fmt)


def plot_statistics(mu, sigma, lats, lons, units='feet'):
    """Plot ensemble mean and spread, returning figure handles"""
    cmap = mcmap.spectral()
    mu_fig =\
        plot_field(mu, lats, lons, cmap=cmap, clab=units,
                      vmin=0.0, vmax=np.nanmax(mu),
                      xlab='Longitude \u00b0E',
                      ylab='Latitude',
                      title='Forecast Ensemble Mean')
    sigma_fig =\
        plot_field(sigma, lats, lons, cmap=cmap, clab=units,
                      vmin=0.0, vmax=np.nanmax(sigma),
                      xlab='Longitude \u00b0E',
                      ylab='Latitude',
                      title='Forecast Ensemble Std')

    return mu_fig, sigma_fig


def save_stats_figures(mu_fig, sigma_fig):
    """
    Save ensemble mean and spread figures.
    """

    fig_fmt = os.environ.get('DIFF_INDEX_FIG_FMT')
    fig_basename = os.environ.get('DIFF_INDEX_FIG_BASENAME')
    mu_name = fig_basename + 'mean.' + fig_fmt
    print('Saving {}...\n'.format(mu_name))
    mu_fig.savefig(mu_name, format=fig_fmt)
    sigma_name = fig_basename + 'std.' + fig_fmt
    print('Saving {}...\n'.format(sigma_name))
    sigma_fig.savefig(sigma_name, format=fig_fmt)


def main():
    """
    Load fieldijn from npz file created with NCEP_test.py
    helper function, compute ensemble mean and spread, compute
    difficulty index for a set of thresholds, plot and save the results.
    """

    filename = os.environ.get('DIFF_INDEX_INPUT_FILENAME')
    lats, lons, fieldijn = load_data(filename)
    # Convert m/s to knots
    units = os.environ.get('DIFF_INDEX_UNITS')
    mps2kn = 1.94384
    fieldijn = mps2kn * fieldijn
    # Ensemble mean, std dev
    muij, sigmaij = compute_stats(fieldijn)
    # Windspeed envelope
    Aplin = compute_wind_envelope()
    # Difficulty index for a set of thresholds
    #thresholds = np.arange(os.environ.get('DIFF_INDEX_THRESH_START'), os.environ.get('DIFF_INDEX_THRESH_END'), os.environ.get('DIFF_INDEX_THRESH_STEP'))
    start = float(os.environ.get('DIFF_INDEX_THRESH_START'))
    stop = float(os.environ.get('DIFF_INDEX_THRESH_END'))
    step = float(os.environ.get('DIFF_INDEX_THRESH_STEP'))
    thresholds = np.arange(start, stop, step)
    dij = compute_difficulty_index(fieldijn, muij, sigmaij, thresholds, Aplin=Aplin)
    # Plot and save difficulty index figures
    figs = plot_difficulty_index(dij, lats, lons, thresholds, units)
    save_start = float(os.environ.get('DIFF_INDEX_SAVE_THRESH_START'))
    save_stop = float(os.environ.get('DIFF_INDEX_SAVE_THRESH_STOP'))
    save_step = float(os.environ.get('DIFF_INDEX_SAVE_THRESH_STEP'))
    save_thresh = np.arange(save_start, save_stop, save_step)
    save_difficulty_figures(figs, save_thresh, units)
    # Plot and save ensemble mean, std_dev
    mu_fig, sigma_fig =\
        plot_statistics(muij, sigmaij, lats, lons, units=units)
    save_stats_figures(mu_fig, sigma_fig)


if __name__ == '__main__':
    main()

For more information on the basic requirements to utilize Python Embedding in METplus, please refer to the MET User’s Guide section on Python embedding.

User Scripting

[UPDATE CONTENT]

Running METplus

Pass the use case configuration file to the run_metplus.py script along with any user-specific system configuration files if desired:

run_metplus.py /path/to/METplus/parm/use_cases/model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index.conf /path/to/user_system.conf

See Running METplus for more information.

Expected Output

A successful run will output the following both to the screen and to the logfile:

INFO: METplus has successfully finished running.

Refer to the value set for OUTPUT_BASE to find where the output data was generated. Output for this use case will be found in a directory relative to OUTPUT_BASE. There should be a list of files that have the following format:

wndspd_GEFS_NorthPac_5dy_30mem_difficulty_indexTHRESH_00_kn.png

Where THRESH is a number between DIFF_INDEX_SAVE_THRESH_START and DIFF_INDEX_SAVE_THRESH_STOP which are defined in UserScript_fcstGEFS_Difficulty_Index.conf.

Keywords

Note

  • UserScriptUseCase

  • MediumRangeAppUseCase

  • NRLOrgUseCase

  • METcalcpyUseCase

  • METplotpyUseCase

Navigate to the METplus Quick Search for Use Cases page to discover other similar use cases.

sphinx_gallery_thumbnail_path = ‘_static/medium_range-UserScript_fcstGEFS_Difficulty_Index.png’

Gallery generated by Sphinx-Gallery