From 78c53ce729043ae55827be2c6c557c73801ed2ea Mon Sep 17 00:00:00 2001 From: James Frost Date: Mon, 27 Feb 2023 09:35:37 +0000 Subject: [PATCH] Move METplus tasks out of command line repository They don't really map that well onto a CLI usecase, and we forced it it would be a very thin wrapper around run-metplus.py, which is probably not worth having. METplus based tasks should instead be run directly by whatever orchestration layer sits above the CSET CLI. --- src/CSET/__init__.py | 9 - src/CSET/tasks/demo_pointstat/.gitignore | 3 - .../demo_pointstat/Pointstat_MET_UKV.conf | 150 ------- .../demo_pointstat/Pointstat_METplus_UKV.conf | 166 -------- .../demo_pointstat/Pointstat_UKV_Areas.conf | 20 - src/CSET/tasks/demo_pointstat/README.md | 12 - .../demo_pointstat/run_metplus.sh.example | 32 -- .../user_system_local.conf.example | 22 - src/CSET/tasks/demo_pointstat/verpy/config.py | 105 ----- .../demo_pointstat/verpy/plot_met_stats.py | 294 ------------- .../demo_pointstat/verpy/verification.py | 385 ------------------ 11 files changed, 1198 deletions(-) delete mode 100644 src/CSET/tasks/demo_pointstat/.gitignore delete mode 100644 src/CSET/tasks/demo_pointstat/Pointstat_MET_UKV.conf delete mode 100644 src/CSET/tasks/demo_pointstat/Pointstat_METplus_UKV.conf delete mode 100644 src/CSET/tasks/demo_pointstat/Pointstat_UKV_Areas.conf delete mode 100644 src/CSET/tasks/demo_pointstat/README.md delete mode 100644 src/CSET/tasks/demo_pointstat/run_metplus.sh.example delete mode 100644 src/CSET/tasks/demo_pointstat/user_system_local.conf.example delete mode 100644 src/CSET/tasks/demo_pointstat/verpy/config.py delete mode 100755 src/CSET/tasks/demo_pointstat/verpy/plot_met_stats.py delete mode 100644 src/CSET/tasks/demo_pointstat/verpy/verification.py diff --git a/src/CSET/__init__.py b/src/CSET/__init__.py index b63bffa9a..c8e69a929 100644 --- a/src/CSET/__init__.py +++ b/src/CSET/__init__.py @@ -46,11 +46,6 @@ def main(): "recipe_file", type=Path, help="recipe file to execute" ) parser_operators.set_defaults(func=run_operators) - - # Run task - parser_task = subparsers.add_parser("task", help="run a MET task") - parser_task.set_defaults(func=run_task) - args = parser.parse_args() # Logging verbosity if args.verbose >= 2: @@ -68,7 +63,3 @@ def run_operators(args): from .operators._internal import execute_recipe execute_recipe(args.recipe_file, args.input_file, args.output_file) - - -def run_task(args): - raise NotImplementedError diff --git a/src/CSET/tasks/demo_pointstat/.gitignore b/src/CSET/tasks/demo_pointstat/.gitignore deleted file mode 100644 index 912469d06..000000000 --- a/src/CSET/tasks/demo_pointstat/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -user_system_local.conf -run_metplus.sh -verpy/CSET_Example diff --git a/src/CSET/tasks/demo_pointstat/Pointstat_MET_UKV.conf b/src/CSET/tasks/demo_pointstat/Pointstat_MET_UKV.conf deleted file mode 100644 index 4cc55ef17..000000000 --- a/src/CSET/tasks/demo_pointstat/Pointstat_MET_UKV.conf +++ /dev/null @@ -1,150 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Point-Stat configuration file. -// -// For additional information, see the MET_BASE/config/README file. -// -//////////////////////////////////////////////////////////////////////////////// - - -// Output model name to be written -//model = -${METPLUS_MODEL} - -// Output description to be written -// May be set separately in each "obs.field" entry -//desc = -${METPLUS_DESC} - -//////////////////////////////////////////////////////////////////////////////// - -// Verification grid -//regrid = { -${METPLUS_REGRID_DICT} - -//////////////////////////////////////////////////////////////////////////////// - -// May be set separately in each "field" entry - -censor_thresh = []; -censor_val = []; -cat_thresh = [ NA ]; -cnt_thresh = [ NA ]; -cnt_logic = UNION; -wind_thresh = [ NA ]; -wind_logic = UNION; -eclv_points = 0.05; -rank_corr_flag = FALSE; - -// Forecast and observation fields to be verified -fcst = { - ${METPLUS_FCST_FIELD} -} - -obs = { - ${METPLUS_OBS_FIELD} -} -//////////////////////////////////////////////////////////////////////////////// - - -// Point observation filtering options -// May be set separately in each "obs.field" entry -//message_type = -${METPLUS_MESSAGE_TYPE} -sid_exc = []; -//obs_quality = -${METPLUS_OBS_QUALITY_INC} -${METPLUS_OBS_QUALITY_EXC} -duplicate_flag = NONE; -//obs_summary = NONE; -obs_summary = NEAREST; -obs_perc_value = 50; - -// Mapping of message type group name to comma-separated list of values. -message_type_group_map = [ - { key = "SURFACE"; val = "11600,10101,10102"; }, - { key = "UPPERAIR"; val = "50101,50102"; }, -// { key = "ANYAIR"; val = "AIRCAR,AIRCFT"; }, -// { key = "ANYSFC"; val = "ADPSFC,SFCSHP,ADPUPA,PROFLR,MSONET"; }, -// { key = "ONLYSF"; val = "ADPSFC,SFCSHP"; }, - { key = "LANDSF"; val = ""; }, - { key = "WATERSF"; val = ""; } -]; - -//////////////////////////////////////////////////////////////////////////////// - -// Climatology data -//climo_mean = { -${METPLUS_CLIMO_MEAN_DICT} - - -//climo_stdev = { -${METPLUS_CLIMO_STDEV_DICT} - -// May be set separately in each "obs.field" entry -//climo_cdf = { -${METPLUS_CLIMO_CDF_DICT} - -//////////////////////////////////////////////////////////////////////////////// - -// Point observation time window -//obs_window = { -${METPLUS_OBS_WINDOW_DICT} - -//////////////////////////////////////////////////////////////////////////////// - -// Verification masking regions -mask = { - ${METPLUS_MASK_GRID} - ${METPLUS_MASK_POLY} - ${METPLUS_MASK_SID} - ${METPLUS_MASK_LLPNT} -} - -//////////////////////////////////////////////////////////////////////////////// - -// Confidence interval settings -ci_alpha = [ 0.05 ]; - -boot = { - interval = PCTILE; - rep_prop = 1.0; - n_rep = 0; - rng = "mt19937"; - seed = ""; -} - -//////////////////////////////////////////////////////////////////////////////// - -// Interpolation methods -//interp = { -${METPLUS_INTERP_DICT} - -//////////////////////////////////////////////////////////////////////////////// - -// HiRA verification method -hira = { - flag = TRUE; - width = [ 3 ]; - vld_thresh = 1.0; - cov_thresh = [ ==0.1 ]; - shape = SQUARE; - prob_cat_thresh = [ <271.15, <273.15, <275.15, <298.15, <300.15, <303.15 ]; -} - -//////////////////////////////////////////////////////////////////////////////// - -// Statistical output types -//output_flag = { -${METPLUS_OUTPUT_FLAG_DICT} - -//////////////////////////////////////////////////////////////////////////////// - -tmp_dir = "/tmp"; -//output_prefix = -${METPLUS_OUTPUT_PREFIX} -//version = "V10.0.0"; - -//////////////////////////////////////////////////////////////////////////////// - -${METPLUS_MET_CONFIG_OVERRIDES} diff --git a/src/CSET/tasks/demo_pointstat/Pointstat_METplus_UKV.conf b/src/CSET/tasks/demo_pointstat/Pointstat_METplus_UKV.conf deleted file mode 100644 index 2ead7c71f..000000000 --- a/src/CSET/tasks/demo_pointstat/Pointstat_METplus_UKV.conf +++ /dev/null @@ -1,166 +0,0 @@ -[config] - -# List of applications to run - only PointStat for this case -PROCESS_LIST = PointStat - -# time looping - 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 -LOOP_BY = INIT - -# Format of VALID_BEG and VALID_END using % items -# %Y = 4 digit year, %m = 2 digit month, %d = 2 digit day, etc. -# see www.strftime.org for more information -# %Y%m%d%H expands to YYYYMMDDHH -INIT_TIME_FMT = %Y%m%d%H - -# Start time for METplus run - must match INIT_TIME_FMT -INIT_BEG = {TIMESTRING} - -# End time for METplus run - must match INIT_TIME_FMT -INIT_END = {TIMESTRING} - -# Increment between METplus runs (in seconds if no units are specified) -# Must be >= 60 seconds -INIT_INCREMENT = 60M - -# List of forecast leads to process for each run time (init or valid) -# In hours if units are not specified -# If unset, defaults to 0 (don't loop through forecast leads) -LEAD_SEQ = begin_end_incr(0,120,1) - -# Order of loops to process data - Options are times, processes -# Not relevant if only one item is in the PROCESS_LIST -# times = run all wrappers in the PROCESS_LIST for a single run time, then -# increment the run time and run all wrappers again until all times have -# been evaluated. -# processes = run the first wrapper in the PROCESS_LIST for all times -# specified, then repeat for the next item in the PROCESS_LIST until all -# wrappers have been run -#LOOP_ORDER = processes - -# Verbosity of MET output - overrides LOG_VERBOSITY for PointStat only -LOG_POINT_STAT_VERBOSITY = 2 - -# Location of MET config file to pass to GridStat -# References PARM_BASE which is the location of the parm directory corresponding -# to the ush directory of the master_metplus.py script that is called -# or the value of the environment variable METPLUS_PARM_BASE if set -POINT_STAT_CONFIG_FILE = {ADD_CONFIG} - -# Time relative to each input file's valid time (in seconds if no units are -# specified) for data within the file to be considered valid. Values are set in -# the 'obs_window' dictionary in the PointStat config file -OBS_POINT_STAT_WINDOW_BEGIN = -1800 -OBS_POINT_STAT_WINDOW_END = 1800 - -# Optional list of offsets to look for point observation data -POINT_STAT_OFFSETS = 0 - -# Model/fcst and obs name, e.g. GFS, NAM, GDAS, etc. -MODEL = METO_UKV -OBTYPE = METO - -# fields to compare -# Note: If FCST_VAR_* is set, then a corresponding OBS_VAR_* variable must -# be set To use one variables for both forecast and observation data, set -# BOTH_VAR_* instead - -FCST_VAR1_NAME = air_temperature -# This is temporary - only works with the full forecast_period set -FCST_VAR1_LEVELS = "({lead?fmt=%H},*,*)" -FCST_VAR1_OPTIONS = set_attr_name="T2M"; set_attr_level="Z0" -OBS_VAR1_NAME = TMP -OBS_VAR1_OPTIONS = set_attr_name="T2M" -OBS_VAR1_LEVELS = Z0 - -# Regrid to specified grid. Indicate NONE if no regridding, or the grid id -# (e.g. G212) -POINT_STAT_REGRID_TO_GRID = NONE - -POINT_STAT_OUTPUT_PREFIX = {MODEL}_G000 - -# sets the -obs_valid_beg command line argument (optional) -# not used for this example -#POINT_STAT_OBS_VALID_BEG = {valid?fmt=%Y%m%d_%H} - -# sets the -obs_valid_end command line argument (optional) -# not used for this example -#POINT_STAT_OBS_VALID_END = {valid?fmt=%Y%m%d_%H} - -# Verification Masking regions -# Indicate which grid and polygon masking region, if applicable -POINT_STAT_GRID = - - -# Message types, if all message types are to be returned, leave this empty, -# otherwise indicate the message types of interest. -POINT_STAT_MESSAGE_TYPE = SURFACE - -# Variables and levels as specified in the field dictionary of the MET -# point_stat configuration file. Specify as FCST_VARn_NAME, FCST_VARn_LEVELS, -# (optional) FCST_VARn_OPTION - -# set to True to run PointStat once for each name/level combination -# set to False to run PointStat once per run time including all fields -POINT_STAT_ONCE_PER_FIELD = False - - -# End of [config] section and start of [dir] section -[dir] -FCST_POINT_STAT_INPUT_DIR = {INPUT_BASE_FORECAST} -OBS_POINT_STAT_INPUT_DIR = {INPUT_BASE_OBSERVATION} - -# directory containing climatology mean input to PointStat -# Not used in this example -POINT_STAT_CLIMO_MEAN_INPUT_DIR = - -# directory containing climatology mean input to PointStat -# Not used in this example -POINT_STAT_CLIMO_STDEV_INPUT_DIR = - - -POINT_STAT_OUTPUT_DIR = {OUTPUT_BASE_RES} - - -# Select the type of output file(s) you wish to create -#POINT_STAT_OUTPUT_FLAG_FHO = -#POINT_STAT_OUTPUT_FLAG_CTC = -#POINT_STAT_OUTPUT_FLAG_CTS = -#POINT_STAT_OUTPUT_FLAG_MCTC = -#POINT_STAT_OUTPUT_FLAG_MCTS = -POINT_STAT_OUTPUT_FLAG_CNT = BOTH -POINT_STAT_OUTPUT_FLAG_SL1L2 = BOTH -#POINT_STAT_OUTPUT_FLAG_SAL1L2 = -POINT_STAT_OUTPUT_FLAG_VL1L2 = NONE -#POINT_STAT_OUTPUT_FLAG_VAL1L2 = -POINT_STAT_OUTPUT_FLAG_VCNT = NONE -#POINT_STAT_OUTPUT_FLAG_PCT = -#POINT_STAT_OUTPUT_FLAG_PSTD = -#POINT_STAT_OUTPUT_FLAG_PJC = -#POINT_STAT_OUTPUT_FLAG_PRC = -#POINT_STAT_OUTPUT_FLAG_ECNT = -#POINT_STAT_OUTPUT_FLAG_RPS = -#POINT_STAT_OUTPUT_FLAG_ECLV = -POINT_STAT_OUTPUT_FLAG_MPR = BOTH - -# End of [dir] section and start of [filename_templates] section -[filename_templates] - -# Template to look for forecast input to PointStat relative to FCST_POINT_STAT_INPUT_DIR -FCST_POINT_STAT_INPUT_TEMPLATE = out.nc - -# Template to look for observation input to PointStat relative to OBS_POINT_STAT_INPUT_DIR -OBS_POINT_STAT_INPUT_TEMPLATE = lndsyn_{valid?fmt=%Y%m%d%H}.nc - -# Template to look for climatology input to PointStat relative to POINT_STAT_CLIMO_MEAN_INPUT_DIR -# Not used in this example -POINT_STAT_CLIMO_MEAN_INPUT_TEMPLATE = - -# Template to look for climatology input to PointStat relative to POINT_STAT_CLIMO_STDEV_INPUT_DIR -# Not used in this example -POINT_STAT_CLIMO_STDEV_INPUT_TEMPLATE = - -POINT_STAT_OUTPUT_TEMPLATE = Output diff --git a/src/CSET/tasks/demo_pointstat/Pointstat_UKV_Areas.conf b/src/CSET/tasks/demo_pointstat/Pointstat_UKV_Areas.conf deleted file mode 100644 index aa0d5aaa8..000000000 --- a/src/CSET/tasks/demo_pointstat/Pointstat_UKV_Areas.conf +++ /dev/null @@ -1,20 +0,0 @@ -[config] -# Selection of qc flag entries to include in the output. Leave blank to include -# everything. Needs to directly match what is stored in obs file (include -# leading zeros if present) -POINT_STAT_OBS_QUALITY_INC = -POINT_STAT_OBS_QUALITY_EXC = - -# List of full path to poly masking files. NOTE: Only short lists of poly files -# work (those that fit on one line), a long list will result in an environment -# variable that is too long, resulting in an error. For long lists of poly -# masking files (i.e. all the mask files in the NCEP_mask directory), define -# these in the MET point_stat configuration file. -POINT_STAT_MASK_POLY = {MET_AREA_DIR}/poly_umukv.nc -POINT_STAT_MASK_SID = {MET_STN_DIR}/2011.stns, - {MET_STN_DIR}/2014.stns, - {MET_STN_DIR}/2015.stns, - {MET_STN_DIR}/2020.stns, - {MET_STN_DIR}/2024.stns, - {MET_STN_DIR}/2103.stns -POINT_STAT_MASK_LLPNT = diff --git a/src/CSET/tasks/demo_pointstat/README.md b/src/CSET/tasks/demo_pointstat/README.md deleted file mode 100644 index 7c76c5c61..000000000 --- a/src/CSET/tasks/demo_pointstat/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# METplus Pointstat - -You need to make a copy of the `user_system_local.conf.example` file with the -`.example` extension removed, and fill in the configuration options to suite -your local environment. Similarly the `run_metplus.sh.example` file also needs -its extension removed and the contained variables replaced. - -Once these steps are done the stats can be generated with METplus. - -```bash -./run_metplus.sh -``` diff --git a/src/CSET/tasks/demo_pointstat/run_metplus.sh.example b/src/CSET/tasks/demo_pointstat/run_metplus.sh.example deleted file mode 100644 index 42a9d354f..000000000 --- a/src/CSET/tasks/demo_pointstat/run_metplus.sh.example +++ /dev/null @@ -1,32 +0,0 @@ -#! /usr/bin/env bash - -# Copyright 2022 Met Office and contributors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Setup environment. -# module load scitools/production-os45-1 - -# Add METplus and libraries to PATH. -export PATH=${PATH}:/path/to/METplus/ush:/path/to/MET/bin -export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/path/to/MET/lib -# Leave blank to skip VerPy plotting. -export VERPY_LOCATION= -# Cycle start time. -export ROSE_TASK_CYCLE_TIME="20220921T0300Z" -export DATESTAMP="2022092103" - -run_metplus.py -c Pointstat_METplus_UKV.conf -c Pointstat_UKV_Areas.conf -c user_system_local.conf -cd verpy -# Replace the source flag here with your value of OUTPUT_BASE_RES/Output -python3 plot_met_stats.py --plot_dir ${PWD} --start 2022092103 --end 2022092603 --source /output/path/results/Output --case_studies True --expids METO_UKV --model_names 'UKV - MET' diff --git a/src/CSET/tasks/demo_pointstat/user_system_local.conf.example b/src/CSET/tasks/demo_pointstat/user_system_local.conf.example deleted file mode 100644 index 1dcef79bf..000000000 --- a/src/CSET/tasks/demo_pointstat/user_system_local.conf.example +++ /dev/null @@ -1,22 +0,0 @@ -[dir] -# Input/Output locations -INPUT_BASE_FORECAST = /path/to/forecast/data/ -INPUT_BASE_OBSERVATION = /path/to/obs/data/ -OUTPUT_BASE = /output/path -OUTPUT_BASE_RES = /output/path/results - -# MET install locations -MET_INSTALL_DIR = /path/to/MET/MET_Dev/ -MET_AREA_DIR = /path/to/MET/AreaMasks -MET_STN_DIR = /path/to/MET/StationLists - -# TIMESTRING is linked to whattime in rose-app.conf, linked to the suite.rc so -# that the time formats in the suite.rc and the METplus' PointStat.conf match -# up. Any {ENV[]} variable is defined within [env] section of the rose-app.conf - -TIMESTRING = {ENV[DATESTAMP]} -# FCAST_LEADS = {ENV[FCASTRANGE]} -FCAST_LEADS = - -# Additional config files required to run Pointstat -ADD_CONFIG = {ENV[PWD]}/Pointstat_MET_UKV.conf diff --git a/src/CSET/tasks/demo_pointstat/verpy/config.py b/src/CSET/tasks/demo_pointstat/verpy/config.py deleted file mode 100644 index 8ee4f3290..000000000 --- a/src/CSET/tasks/demo_pointstat/verpy/config.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2022 Met Office and contributors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Set up choices of CSET verification configuration, e.g. thresholds etc. Should -be aligned with operational verification and configuration from METplus apps -""" - -# Settings below here are relevant for the verification part of CSET - -# VerPy default settings, first Scale Series plots. Defaults chosen are suitable -# for use over the UK. SS prefix => scale statistics options (fractional -# statistics, e.g. Fractions Skill Score). For fractional statistics, set the -# thresholds and neighbourhood scale sizes required. -SS_SCALES = [1, 5, 25, 51, 101] -SS_THRESH = [0.5, 1.0, 4.0, 8.0, 16.0, 32.0, 64.0] -SS_FREQS = ["20%", "10%", "5%"] - -# GS prefix => options for general statistics plots ('traditional' verification) -# For categorical parameters, set lists of thresholds. -GS_LINESTYLES = ["-", "--", "-.", ":"] -GS_THRESH_WIND_CON = [-5.0, -2.5, 0, 2.5, 5.0] -GS_THRESH_WIND = ["> 5.0", "> 10.0", "> 13.0", "> 17.5"] -GS_THRESH_CLOUD_FRAC = ["> 0.3125", "> 0.5625", "> 0.8125"] -GS_THRESH_CLOUD_BASE = ["<= 100.0", "<= 300.00", "<= 500.00", "<= 1000.0", "<= 1500.0"] -GS_THRESH_VIS = ["<= 200.0", "<= 1000.0", "<= 4000.0", "<= 5000.0"] -GS_THRESH_PRECIP = [ - "> 0.1999", - "> 0.4999", - "> 0.9999", - "> 3.9999", - "> 7.9999", - "> 15.9999", -] - -# HIRA prefix => HIgh Resolution Assessment Framework options -HIRA_SCALES = [1, 3, 7, 11] -HIRA_LS = ["-", "--", "-.", ":"] -HIRA_THRESH_WIND = [ - ">= 2.06", - ">= 3.60", - ">= 5.65", - ">= 8.74", - ">= 11.31", - ">= 14.39", - ">= 17.48", - ">= 21.07", - ">= 24.67", - ">= 60.", -] -HIRA_THRESH_CLOUDFRAC = [ - ">=0.0625", - ">=0.1875", - ">=0.3125", - ">=0.4375", - ">=0.5625", - ">=0.6875", - ">=0.8125", - ">=0.9375", -] -HIRA_THRESH_CLOUDBASE = [ - "<50.0", - "<100.0", - "<200.0", - "<500.0", - "<1000.0", - "<1500.0", - "<2000.0", - "<2500.0", - "<5000.0", -] -HIRA_THRESH_VIS = [ - "<50", - "<100", - "<200", - "<500", - "<1000", - "<2000", - "<3000", - "<4000", - "<5000", - "<7500", -] -HIRA_THRESH_PRECIP = [ - ">=0.25", - ">=0.5", - ">=1", - ">=2", - ">=4", - ">=8", - ">=16", - ">=32", - ">=64", -] diff --git a/src/CSET/tasks/demo_pointstat/verpy/plot_met_stats.py b/src/CSET/tasks/demo_pointstat/verpy/plot_met_stats.py deleted file mode 100755 index 740b10fba..000000000 --- a/src/CSET/tasks/demo_pointstat/verpy/plot_met_stats.py +++ /dev/null @@ -1,294 +0,0 @@ -# Copyright 2022 Met Office and contributors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -VerPy_location = os.getenv("VERPY_LOCATION") -if not VerPy_location: - sys.exit(1) -sys.path.append(VerPy_location) - -# Replace prelude() function if required -import errno -import datetime -import warnings -import argparse -from VerPy.job import run -from VerPy.plottheme import get_theme -import VerPy.abv2data - - -def mkdir_p(path): - """Makes a directory, mimicking mkdir -p behaviour.""" - try: - os.makedirs(path) - except OSError as exc: - if exc.errno == errno.EEXIST: - pass - else: - raise - - -def str2bool(string): - """ - Converts the string "True" or "true" to boolean True, otherwise returns \ - False. - """ - return string.lower() == "true" - - -def parse_args(): - """Parses and returns command line arguments.""" - parser = argparse.ArgumentParser( - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="Plotting basic verification statistics", - ) - - # Required arguments - parser.add_argument( - "--plot_dir", type=str, help="Output plot directory", dest="plot_dir" - ) - parser.add_argument( - "--start", - type=lambda d: datetime.datetime.strptime(d, "%Y%m%d%H%M"), - help=("Start date for verification statistics " "format YYYYmmddHHMM"), - dest="start", - ) - parser.add_argument( - "--end", - type=lambda d: datetime.datetime.strptime(d, "%Y%m%d%H%M"), - help=("End date for verification statistics " "format YYYYmmddHHMM"), - dest="end", - ) - parser.add_argument( - "--source", - type=str, - help="List of directories containing ARD files", - dest="source", - ) - parser.add_argument( - "--expids", type=str, help=("Experiment IDs used in the ", " ARD file names.") - ) - parser.add_argument( - "--model_names", type=str, help="Names for models", dest="model_names" - ) - parser.add_argument( - "--case_studies", - type=str2bool, - default=True, - help=( - "Set to True to ensure only appropriate plot types " - " for case studies are created." - ), - dest="cs", - ) - - # Optional arguments - parser.add_argument( - "--areas", - default=2015, - type=int, - help="Areas to plot verification statistics over", - dest="areas", - ) - parser.add_argument( - "--vts", - nargs="+", - default=range(0, 2400, 100), - type=int, - help="Valid times to plot statistics at.", - dest="vts", - ) - parser.add_argument( - "--fcrs", - nargs="+", - type=int, - default=range(0, 12000, 100), - help="Desired forecast lead times to use for plotting.", - dest="fcrs", - ) - parser.add_argument( - "--diurnal_fcrs", - nargs="+", - default=range(300, 12100, 300), - type=int, - help="Forecast lead times for producing diurnal plots", - dest="diurnal_fcrs", - ) - - # Parse the arguments - args = parser.parse_args() - - # Checking input argument values - - return args - - -def get_surf_components(base_options): - """ - Generates a list of components based on the options provided. - A 'component' is an options dictionary (to be passed to :func:`VerPy.job.run`). - - A user-provided dictionary should provide the following keys: - - * start - * end - * expids - * source - - Any other keys (e.g. verbosity) will also be used, but these 4 must - exist. - - :param base_options: A dictionary containing common options between - components - :return list: A list of component tuples - """ - # Validate user-options - if not isinstance(base_options, dict): - raise Exception(f"Expecting dictionary argument, got {type(base_options)}.") - for key in ("start", "end", "expids", "source"): - if key not in base_options: - raise Exception(f'Expecting key "{key}".') - - components = [ - # Temperature - { - "stat": [2, 7051, 7752, 7053], - "type": "cnt", - "truth": 10000, - "param": (16, 1, -1.0), - "jobid": "Temp", - }, - ] - - # Add common options - for comp in components: - comp.update(base_options) - - return components - - -def main(): - """ - Plot standard (traditional) verification statistics from the - supplied Area Results Database (ARD) file. - - This module uses the supplied configuration to produce timeseries, forecast range series - and diurnal cycle plots of traditional verification metrics for both continuous - and categorical meteorological variables. - For example, Mean Error, Root Mean Square Error, Equitable Threat Score (and other - contingency table derived metrics) - """ - now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - print(f"{now} Starting general statistics plots...") - - # Parse command line arguments - args = parse_args() - - outputdir = args.plot_dir - start = args.start - end = args.end - source = list(filter(None, args.source.split(","))) - print(source) - expids = list(filter(None, args.expids.split(","))) - print(expids) - names = list(filter(None, args.model_names.split(","))) - print(names) - cs = args.cs - - # Change output types for case study/trial - if cs: - outputs = ["fcrdiurnal", "fcrseries"] - else: - outputs = ["fcrdiurnal", "fcrseries", "timeseries"] - - # First continuous statistics - stat_opts = { - "system": "MET", - "start": start, - "end": end, - "plotopts": ["1x1", "skip empty"], - "expids": expids, - "source": source, - "names": names, - "area": args.areas, - "vts": args.vts, - "fcrs": args.fcrs, - "confopts": "stderr", - "dataopts": "equalize", - } - - # Creating plottheme which can cope with up to 120 lead times (worst case UKV scenario) - theme = get_theme("split") - theme["palette"]["markers"] = theme["palette"]["markers"] * 10 - theme["palette"]["markersize"] = theme["palette"]["markersize"] * 10 - theme["palette"]["linestyles"] = theme["palette"]["linestyles"] * 10 - theme["palette"]["linewidths"] = theme["palette"]["linewidths"] * 10 - - for output in outputs: - print(f"{now} Continuous statistics, output type {output}.") - - statsdir = os.path.join(outputdir, "CSET_Example") - # Can this be improved/replaced now? - mkdir_p(statsdir) - - # Surface components: construct dictionary and then plot - components = get_surf_components(stat_opts) - for comp in components: - comp["output"] = output - - if output == "timeseries": - for comp in components: - comp["jobid"] += "TS" - comp["diffopts"] = ["True", "split diff"] - comp["plotopts"] = ["1x2", "skip empty"] - if "TCA" in comp["param"]: - comp["fcrs"] = args.cloud_fcrs - if cs: - comp["compare-on-plot"] = "fcrs" - comp["plottheme"] = theme - - if output == "fcrseries": - for comp in components: - comp["jobid"] += "FCR" - comp["diffopts"] = ["True", "split diff"] - comp["plotopts"] = ["2x2", "skip empty"] - if "Wind" in comp["param"]: - comp["diffopts"] = "True" - - if output == "fcrdiurnal": - for comp in components: - comp["jobid"] += "Diurnal" - comp["fcrs"] = args.diurnal_fcrs - comp["plottheme"] = theme - - if output == "diurnal": - for comp in components: - comp["jobid"] += "Diurnal" - comp["compare-on-plot"] = "fcrs" - comp["plottheme"] = theme - - try: - run(statsdir, components) - except VerPy.abv2data.ABV2DataError as e: - warnings.warn(e) - - # Set up links for web display - print(f"{now} Finished plot_stats!") - print(f"View at {outputdir:s}/CSET_Example") - - -if __name__ == "__main__": - main() diff --git a/src/CSET/tasks/demo_pointstat/verpy/verification.py b/src/CSET/tasks/demo_pointstat/verpy/verification.py deleted file mode 100644 index 30a2c6ddd..000000000 --- a/src/CSET/tasks/demo_pointstat/verpy/verification.py +++ /dev/null @@ -1,385 +0,0 @@ -# Copyright 2022 Met Office and contributors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Contains functions for use with VerPy plotting scripts. -""" -import copy -import numpy -import config -from VerPy.case import Case -from VerPy.data import Data -from VerPy.options import Options -from VerPy.parameter import ParameterDict - - -def comps_to_data(summary_data, case_num): - """ - Convert component cases from each subjob into a single case. - The first case is used to set up the dimensions. - """ - - params = [ - ParameterDict(name=k, unit=None, set_other=False) for k in summary_data.keys() - ] - base_key = summary_data.keys()[0] - base = summary_data[base_key][0] - for s in base.stats: - s["agg"] = numpy.mean # Do a simple mean - if "scales" in base.dims: - dat = Data( - nan_vals=True, - params=params, - scales=base.scales, - stats=base.stats, - fcrs=base.fcrs, - dates=base.dates, - ) - else: - dat = Data( - nan_vals=True, - params=params, - scales=base.scales, - stats=base.stats, - fcrs=base.fcrs, - dates=base.dates, - ) - for k, v in summary_data.items(): - dat_ = v[case_num] - param = ParameterDict(name=k, unit=None, set_other=False) - for fcr in base.fcrs: - if "scales" in base.dims: - for scale in base.scales: - new_ind = dat.index({"params": param, "fcrs": fcr, "scales": scale}) - old_ind = dat_.index({"fcrs": fcr, "scales": scale}) - if old_ind is not None: - dat.vals[new_ind] = numpy.squeeze(dat_.vals[old_ind]) - else: - new_ind = dat.index({"params": param, "fcrs": fcr}) - old_ind = dat_.index({"fcrs": fcr}) - if old_ind is not None: - dat.vals[new_ind] = numpy.squeeze(dat_.vals[old_ind]) - - return dat - - -def get_hira_data(expid, source, comp, n_ens, vts, fcrs): - """ - Extracts HiRA data from the Area Results Databases, given the source - files and components passed in, looping over the required - neighbourhood scales. A 'component' is an options dictionary - (to be passed to :func:`VerPy.job.run`). - - Assumes the number of bins is 10 for a deterministic model, otherwise the - number of bins is set to (1 + number_of_ensemble_members). - - :param expid: String containing the experiment id used in the ARD files. - :param source: String containing the location of the ARD files. - :param comp: A dictionary containing common options between - components - :returns VerPy.data.Data: A VerPy.data.Data object. - """ - if n_ens == 1: - n_bins = 10 - else: - n_bins = 1 + n_ens - - for i, scale in enumerate(config.HIRA_SCALES): - opts = { - "jobid": "HiRAExtract", - "expids": "{}{:02}".format(expid, scale), - "source": source, - "times": vts, - "fcrs": fcrs, - } - - opts.update(comp) - # Set ensopts for CRPS according to scale - if comp["stat"] == 40813: - opts["ensopts"] = 1 + n_ens * scale**2 - else: - opts["ensopts"] = n_bins - opts["probbins"] = [(x + 0.5) / n_bins for x in range(0, n_bins)] - opts = Options(opts) - case = Case(opts) - case.load_data() - - case.data.expand_dims("scales", scale) - if hasattr(case.data, "_base_data"): - del case.data._base_data - if i == 0: - dat = copy.deepcopy(case.data) - else: - dat.concatenate(case.data) - - return dat - - -def get_pers_data(expid, source, comp, n_ens, vts): - """ - Gets the relevant persistence data out of the Area Results Database file. - - Assumes the number of bins is 10 for a deterministic model, otherwise the - number of bins is set to (1 + number_of_ensemble_members). - - :param expid: String containing the experiment id used in the ARD files. - :param source: String containing the location of the ARD files. - :param comp: A dictionary containing common options between - components - :returns VerPy.data.Data: A VerPy.data.Data object. - """ - - if n_ens == 1: - n_bins = 10 - else: - n_bins = 1 + n_ens - - scale = config.HIRA_SCALES[0] - - opts = { - "jobid": "HiRAExtractPers", - "expids": "{}{:02}_P".format(expid, scale), - "source": source, - "times": vts, - "fcrs": 0, - } - - opts.update(comp) - # Set ensopts for CRPS according to scale - if comp["stat"] == 40813: - opts["ensopts"] = 2 - else: - opts["ensopts"] = n_bins - opts["probbins"] = [(x + 0.5) / n_bins for x in range(0, n_bins)] - opts = Options(opts) - case = Case(opts) - case.load_data() - - case.data.expand_dims("scales", scale) - if hasattr(case.data, "_base_data"): - del case.data._base_data - - return case.data - - -def get_hira_components(base_options, use_srews=True): - """ - Generates a list of hira components based on the options provided. - A 'component' is an options dictionary (to be passed to :func:`VerPy.job.run`). - - A user-provided dictionary should provide the following keys: - - * start - * end - - Any other keys (e.g. verbosity) will also be used, but these 7 must - exist. - - :param base_options: A dictionary containing common options between - components - :return list: A list of component tuples - """ - - # Validate user-options - if not isinstance(base_options, dict): - raise Exception( - "Expecting dictionary argument, got {}.".format(type(base_options)) - ) - for key in ("start", "end"): - if key not in base_options: - raise Exception('Expecting key "{}".'.format(key)) - - components = [ - { - "param": (16, 1, -1.0), - "thresh": [0], - "stat": 40813, - "type": "categorical", - "jobid": "TempCRPS", - "truth": 10000, - }, - ] - - # Add common options - for comp in components: - comp.update(base_options) - - return components - - -def get_hira_bias_components(base_options, use_srews=True): - """ - Generates a list of bias components based on the options provided. - A 'component' is an options dictionary (to be passed to :func:`VerPy.job.run`). - - A user-provided dictionary should provide the following keys: - - * start - * end - - Any other keys (e.g. verbosity) will also be used, but these 7 must - exist. - - :param base_options: A dictionary containing common options between - components - :return list: A list of component tuples - """ - - # Validate user-options - if not isinstance(base_options, dict): - raise Exception( - "Expecting dictionary argument, got {}.".format(type(base_options)) - ) - for key in ("start", "end"): - if key not in base_options: - raise Exception('Expecting key "{}".'.format(key)) - - components = [ - { - "param": (16, 1, -1.0), - "stat": 7051, - "type": "continuous", - "jobid": "Temp", - "truth": 10000, - }, - ] - - # Add common options - for comp in components: - comp.update(base_options) - - return components - - -def convert_stats(stats): - """ - If skill scores are required, convert the input statistic object from - an error score. - """ - new_stats = [] - for s in stats: - new = copy.copy(s) - new["orientation"] = "positive" - new["range"] = (float("-inf"), 1) - new["name"] = s["name"].replace("Score", "Skill Score") - new_stats.append(new) - - return new_stats - - -def replicate(dat, fcrs): - """ - Replicates the T+0 persistence data for all forecast lead times - in the main data object. - """ - new = copy.copy(dat.vals) - axis = dat.get_val_axis("fcrs") - dat.fcrs[0] = fcrs[0] - for i, fcr in enumerate(fcrs[1:]): - dat.vals = numpy.concatenate((dat.vals, new), axis=axis) - dat.fcrs.append(fcr) - - -def get_profile_components(base_options, levels): - """ - Generates a list of profile components based on the options provided. - A 'component' is an options dictionary (to be passed to :func:`VerPy.job.run`). - - A user-provided dictionary should provide the following keys: - - * start - * end - * expids - * source - * fcrs - - Any other keys (e.g. verbosity) will also be used, but these 5 must - exist. - - :param base_options: A dictionary containing common options between - components - :return list: A list of component tuples - """ - - # Validate user-options - if not isinstance(base_options, dict): - raise Exception( - "Expecting dictionary argument, got {}.".format(type(base_options)) - ) - for key in ("start", "end", "expids", "source", "fcrs"): - if key not in base_options: - raise Exception('Expecting key "{}".'.format(key)) - - components = [ - # Temperature - { - "stat": [7051, 7752], - "type": "continuous", - "truth": 50000, - "param": (16, 8, levels), - "jobid": "Temp", - }, - ] - - # Add common options - for comp in components: - comp.update(base_options) - - return components - - -def get_surf_components(base_options): - """ - Generates a list of components based on the options provided. - A 'component' is an options dictionary (to be passed to :func:`VerPy.job.run`). - - A user-provided dictionary should provide the following keys: - - * start - * end - * expids - * source - - Any other keys (e.g. verbosity) will also be used, but these 4 must - exist. - - :param base_options: A dictionary containing common options between - components - :return list: A list of component tuples - """ - # Validate user-options - if not isinstance(base_options, dict): - raise Exception( - "Expecting dictionary argument, got {}.".format(type(base_options)) - ) - for key in ("start", "end", "expids", "source"): - if key not in base_options: - raise Exception('Expecting key "{}".'.format(key)) - - components = [ - # Temperature - { - "stat": [2, 7051, 7752, 7053], - "type": "cnt", - "truth": 10000, - "param": (16, 1, -1.0), - "jobid": "Temp", - }, - ] - - # Add common options - for comp in components: - comp.update(base_options) - - return components