diff --git a/.github/jobs/free_disk_space.sh b/.github/jobs/free_disk_space.sh index 9c9902a602..fc6c6bad0b 100755 --- a/.github/jobs/free_disk_space.sh +++ b/.github/jobs/free_disk_space.sh @@ -10,6 +10,41 @@ sudo rm -rf /opt/ghc sudo rm -rf "/usr/local/share/boost" sudo rm -rf "$AGENT_TOOLSDIRECTORY" +### +# more cleanup taken from https://github.com/apache/flink/blob/master/tools/azure-pipelines/free_disk_space.sh +### + +echo "==============================================================================" +echo "Freeing up disk space on CI system" +echo "==============================================================================" + +echo "Listing 100 largest packages" +dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100 +df -h +echo "Removing large packages" +sudo apt-get remove -y '^dotnet-.*' +sudo apt-get remove -y '^llvm-.*' +sudo apt-get remove -y 'php.*' +sudo apt-get remove -y '^mongodb-.*' +sudo apt-get remove -y '^mysql-.*' +sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri +sudo apt-get autoremove -y +sudo apt-get clean +df -h +echo "Removing large directories" + +sudo rm -rf /usr/share/dotnet/ +sudo rm -rf /usr/local/graalvm/ +sudo rm -rf /usr/local/.ghcup/ +sudo rm -rf /usr/local/share/powershell +sudo rm -rf /usr/local/share/chromium +sudo rm -rf /usr/local/lib/android +sudo rm -rf /usr/local/lib/node_modules + +### +# end cleanup from azure script +### + printf "\nChecking disk usage after cleanup" df -h diff --git a/.github/jobs/setup_and_run_use_cases.py b/.github/jobs/setup_and_run_use_cases.py index d65cf247c5..a2e55f8fa0 100755 --- a/.github/jobs/setup_and_run_use_cases.py +++ b/.github/jobs/setup_and_run_use_cases.py @@ -89,6 +89,16 @@ def main(): # print list of existing docker images commands.append('docker images') + # remove docker image after creating run env or prune untagged images + commands.append(f'docker image rm dtcenter/metplus-dev:{branch_name} -f') + commands.append('docker image prune -f') + run_commands(commands) + + commands = [] + + # list docker images again after removal + commands.append('docker images') + # start interactive container in the background commands.append( f"docker run -d --rm -it -e GITHUB_WORKSPACE " diff --git a/.github/parm/use_case_groups.json b/.github/parm/use_case_groups.json index 4bf11ac758..5684f4f2cd 100644 --- a/.github/parm/use_case_groups.json +++ b/.github/parm/use_case_groups.json @@ -89,6 +89,11 @@ "index_list": "9", "run": false }, + { + "category": "marine_and_cryosphere", + "index_list": "10", + "run": false + }, { "category": "medium_range", "index_list": "0", @@ -259,6 +264,11 @@ "index_list": "13", "run": false }, + { + "category": "short_range", + "index_list": "14", + "run": false + }, { "category": "space_weather", "index_list": "0-1", diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 6f8c9b3d45..9a492b652e 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,8 +16,8 @@ jobs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies @@ -27,12 +27,12 @@ jobs: python -m pip install -r docs/requirements.txt - name: Build Documentation run: ./.github/jobs/build_documentation.sh - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: METplus_documentation path: artifact/documentation - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: documentation_warnings.log diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e08aac81e4..f5ac63a66d 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -36,19 +36,18 @@ on: workflow_dispatch: inputs: + force_met_image: + description: 'MET DockerHub repo to force run to use, e.g. met:11.1.0 or met-dev:feature_XYZ_name-PR. Leave this blank to determine repo automatically.' repository: - description: 'Repository that triggered workflow' - required: true + description: 'Repository that triggered workflow (used by external repo triggering)' sha: - description: 'Commit hash that triggered the event' - required: true + description: 'Commit hash that triggered the event (used by external repo triggering)' ref: - description: 'Branch that triggered event' - required: true + description: 'Branch that triggered event (used by external repo triggering)' actor: - description: 'User that triggered the event' + description: 'User that triggered the event (used by external repo triggering)' pusher_email: - description: 'Email address of user who triggered push event' + description: 'Email address of user who triggered push event (used by external repo triggering)' jobs: @@ -69,7 +68,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set job controls id: job_status run: .github/jobs/set_job_controls.sh @@ -93,17 +92,19 @@ jobs: needs: job_control if: ${{ needs.job_control.outputs.run_get_image == 'true' && needs.job_control.outputs.run_some_tests == 'true' }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' + - name: Free disk space + run: .github/jobs/free_disk_space.sh - name: Get METplus Image run: .github/jobs/docker_setup.sh env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} EXTERNAL_TRIGGER: ${{ needs.job_control.outputs.external_trigger }} - #SET_MET_IMAGE: met:11.0.0 + SET_MET_IMAGE: ${{ github.event.inputs.force_met_image }} update_data_volumes: name: Docker Setup - Update Data Volumes @@ -131,8 +132,8 @@ jobs: needs: [job_control] if: ${{ needs.job_control.outputs.run_unit_tests == 'true' }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' cache: 'pip' @@ -166,7 +167,7 @@ jobs: matrix: ${{fromJson(needs.job_control.outputs.matrix)}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Free disk space run: .github/jobs/free_disk_space.sh @@ -209,36 +210,63 @@ jobs: run: .github/jobs/copy_output_to_artifact.sh ${{ steps.get-artifact-name.outputs.artifact_name }} - name: Upload output data artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() && steps.run_tests.conclusion != 'skipped' }} with: name: ${{ steps.get-artifact-name.outputs.artifact_name }} path: artifact/${{ steps.get-artifact-name.outputs.artifact_name }} - name: Upload error logs artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() && steps.save-errors.outputs.upload_error_logs }} with: - name: error_logs + name: error_logs-${{ steps.get-artifact-name.outputs.artifact_name }} path: artifact/error_logs if-no-files-found: ignore - name: Upload difference data artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() && steps.run-diff.outputs.upload_diff == 'true' }} with: name: diff-${{ steps.get-artifact-name.outputs.artifact_name }} path: artifact/diff-${{ steps.get-artifact-name.outputs.artifact_name }} if-no-files-found: ignore + merge_error_logs: + name: Merge Error Logs + runs-on: ubuntu-latest + needs: use_case_tests + if: ${{ always() && needs.use_case_tests.result == 'failure' }} + steps: + - uses: actions/checkout@v4 + - name: Check for error logs + id: check-for-error-logs + run: | + json_data=$(curl -s -H "Authorization: Bearer ${{ github.token }}" \ + "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs") + error_log_jobs=($(echo "$json_data" | jq -r '.jobs[] | select(.name | startswith("Use Case Tests")) | .steps[] | select(.name | startswith("Save error logs")) | select(.conclusion | startswith("success"))')) + # save output variable to merge error logs if any error logs were created + if [ ! -z "${error_log_jobs}" ]; then + echo "has_error_logs=true" >> $GITHUB_OUTPUT + else + echo "has_error_logs=false" >> $GITHUB_OUTPUT + fi + - name: Merge Artifacts + if: ${{ always() && steps.check-for-error-logs.outputs.has_error_logs == 'true' }} + uses: actions/upload-artifact/merge@v4 + with: + name: error_logs + pattern: error_logs-* + delete-merged: true + create_output_data_volumes: name: Create Output Docker Data Volumes runs-on: ubuntu-latest needs: [use_case_tests] if: ${{ needs.job_control.outputs.run_save_truth_data == 'true' }} steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 - run: .github/jobs/create_output_data_volumes.sh env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} diff --git a/.github/workflows/update_truth.yml b/.github/workflows/update_truth.yml index cf9734e449..4b8c310dd7 100644 --- a/.github/workflows/update_truth.yml +++ b/.github/workflows/update_truth.yml @@ -26,7 +26,7 @@ jobs: fi echo ERROR: Branch is $branch_name - must be develop or match main_vX.Y exit 1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 name: Checkout repository with: fetch-depth: 0 diff --git a/docs/Contributors_Guide/documentation.rst b/docs/Contributors_Guide/documentation.rst index c60ed1f65d..a340774bd9 100644 --- a/docs/Contributors_Guide/documentation.rst +++ b/docs/Contributors_Guide/documentation.rst @@ -172,6 +172,25 @@ See `Sphinx code blocks `_ for more information. +Code in a Paragraph +^^^^^^^^^^^^^^^^^^^ + +To refer to code within a paragraph (inline), use +`Inline code highlighting `_. + +For example: + + .. code-block:: none + + To compile MET with GRIB2 support, use the :code:`--enable-grib2` + option when configuring MET. + +Results in the following displayed text: + +To compile MET with GRIB2 support, use the :code:`--enable-grib2` +option when configuring MET. + + Bold ---- @@ -288,7 +307,7 @@ resulting in the following displayed text: An example of the usage of footnotes in reStructured text can be seen in the `METplus docs/index.rst file `_ -and displayed in ReadTheDocs `here `_. +and displayed in Read The Docs `here `_. See `Sphinx footnotes `_ @@ -559,8 +578,8 @@ See for more information. -Linking to METplus Use Case -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Linking to METplus Use Cases +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Linking to METplus Use Cases must be done with an external web link. Since the HTML file is generated from a Python script, the `:ref:` role can’t be used. @@ -750,6 +769,33 @@ The table will be displayed in the following way: * - Row 2, column 1 - Row 2, column 2 +Line Breaks in List Tables +"""""""""""""""""""""""""" + +In some instances, the text in a column of a table needs to wrap to keep the +text readable. To create a line break use :code:`:raw-html:`
``. See +Column Number 37-38 in the first column of +`Table 13.8 Format information for SSVAR `_ +as an example. The raw RST for that cell is: + +.. code-block:: none + + FBAR_NCL, :raw-html:`
` FBAR_NCU + +Using CSV Files to Create Tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It can be easier to create a table in a spreadsheet than through RST syntax. +A CSV (comma-separated values) file can be referenced in the RST file +using the "csv-table" directive. + +For more information, see +`CSV Files `_ +and +`CSV Table `_. + +As of 2023, using CSV files to create tables hasn't been used in the METplus +documentation. Converting an Existing Table into a List Table ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -890,6 +936,87 @@ Below is the resulting displayed table: - Hayashi - 19 +Grid Tables +^^^^^^^^^^^ + +Grid tables are created by "drawing" the table structure with various symbols, +via grid-like "ASCII art". Creating grid tables can be cumbersome to create, +however the +`Grid Tables `_ +documentation indicates that the +`Emacs table mode `_ allows for easier editing +of grid tables. + +An example of the use of a grid table can be found in the MET Installation Guide under the +dropdown title **IF THE USER ALREADY HAS THE LIBRARY DEPENDENCIES INSTALLED** in +the section +`External Library Handling in compile_MET_all.sh `_. + +To force a grid table to use a line break inside of a cell so that the text will +wrap, insert an empty line between the text to be wrapped. The raw RST for this table +looks like: + +.. code-block:: none + + +-------------------+--------------------------------+------------------------------+ + | **Feature** | **Configuration Option** | **Environment Variables** | + +===================+================================+==============================+ + | *Always* | | MET_BUFRLIB, | + | | | | + | *Required* | | BUFRLIB_NAME, | + | | | | + | | | MET_PROJ, | + | | | | + | | | MET_HDF5, | + | | | | + | | | MET_NETCDF, | + | | | | + | | | MET_GSL | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_GRIB2CLIB, | + | | | | + | GRIB2 | :code:`--enable-grib2` | MET_GRIB2CINC, | + | | | | + | Support | | GRIB2CLIB_NAME, | + | | | | + | | | LIB_JASPER, | + | | | | + | | | LIB_PNG, | + | | | | + | | | LIB_Z | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_PYTHON_BIN_EXE, | + | | | | + | Python | :code:`--enable-python` | MET_PYTHON_CC, | + | | | | + | Support | | MET_PYTHON_LD | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_ATLAS, | + | | | | + | Unstructured Grid | :code:`--enable-ugrid` | MET_ECKIT | + | | | | + | Support | | | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_HDF | + | | | | + | LIDAR2NC | :code:`--enable-lidar2nc` | | + | | | | + | Support | | | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_HDF, | + | | | | + | MODIS | :code:`--enable-modis` | MET_HDFEOS | + | | | | + | Support | | | + +-------------------+--------------------------------+------------------------------+ + | *Optional* | :code:`--enable-all` or | MET_CAIRO, | + | | | | + | MODE Graphics | :code:`--enable-mode_graphics` | MET_FREETYPE | + | | | | + | Support | | | + +-------------------+--------------------------------+------------------------------+ + + PrettyTable ^^^^^^^^^^^ @@ -1033,18 +1160,37 @@ Troubleshooting Testing RST Formatting in an Online Editor ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -It can be time consuming to wait for ReadTheDocs to build. To quickly test how -something will display formatting, this -`Online Sphinx Editor `_ -can be used. Please note that in order to use this editor, users must have -access to the machine **seneca** and the UCAR VPN. This is not a secure -website (note that it begins with "http" and not "https"). A user must be -logged into the UCAR VPN to use this editor. If the editor is not displayed, -please try using a different browser. - -Thank you to -`livesphinx `_ -for providing our team with the code to run this Online Sphinx Editor. +It can be time consuming to wait for Read the Docs to build. To quickly test how +the RST will be displayed, use the +`Online Sphinx Editor `_. +Click on 'Execute' to view the rendered documentation in the 'Result' window. + +WARNING: Duplicate explicit target name +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It may be necessary or desirable to have two links with the same name, +for example, when referring to a METplus Use Case. However, using links +with the same name can result in the "WARNING: Duplicate explicit target name". + +To resolve this warning, first, determine if it is possible and makes sense +to distinguish the name of the link, and change the name of the link. If it +is best to use the same link name, +`anonymous hyperlinks `__ +can be used. Anonymous hyperlink references are specified with two underscores +instead of one. For example, instead of the typical link syntax + +.. code-block:: none + + `Link text `_ + +use two underscores at the end of the formatting, like this: + +.. code-block:: none + + `Link text `__ + +See `anonymous hyperlinks `__ +for more information. Escape Characters ^^^^^^^^^^^^^^^^^ diff --git a/docs/Users_Guide/glossary.rst b/docs/Users_Guide/glossary.rst index 6a3d717e5b..671e6cc037 100644 --- a/docs/Users_Guide/glossary.rst +++ b/docs/Users_Guide/glossary.rst @@ -7397,6 +7397,16 @@ METplus Configuration Glossary | *Used by:* TCPairs + TC_PAIRS_CONSENSUS_DIAG_REQUIRED + Specify the value for the nth 'consensus.diag_required' in the MET configuration file for TCPairs. + + | *Used by:* TCPairs + + TC_PAIRS_CONSENSUS_DIAG_MIN_REQ + Specify the value for the nth 'consensus.diag_min_req' in the MET configuration file for TCPairs. + + | *Used by:* TCPairs + TC_PAIRS_CONSENSUS_WRITE_MEMBERS Specify the value for the nth 'consensus.write_members' in the MET configuration file for TCPairs. @@ -11511,3 +11521,93 @@ METplus Configuration Glossary Specify the value for 'regrid.censor_val' in the MET configuration file for WaveletStat. | *Used by:* WaveletStat + + POINT_STAT_LAND_MASK_FLAG + Specify the value for 'land_mask.flag' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_FILE_NAME + Specify the value for 'land_mask.file_name' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_FIELD_NAME + Specify the value for 'land_mask.field.name' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_FIELD_LEVEL + Specify the value for 'land_mask.field.level' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_REGRID_METHOD + Specify the value for 'land_mask.regrid.method' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_REGRID_WIDTH + Specify the value for 'land_mask.regrid.width' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_LAND_MASK_THRESH + Specify the value for 'land_mask.thresh' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_FLAG + Specify the value for 'topo_mask.flag' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_FILE_NAME + Specify the value for 'topo_mask.file_name' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_FIELD_NAME + Specify the value for 'topo_mask.field.name' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_FIELD_LEVEL + Specify the value for 'topo_mask.field.level' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_REGRID_METHOD + Specify the value for 'topo_mask.regrid.method' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_REGRID_WIDTH + Specify the value for 'topo_mask.regrid.width' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_USE_OBS_THRESH + Specify the value for 'topo_mask.use_obs_thresh' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH + Specify the value for 'topo_mask.interp_fcst_thresh' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_DUPLICATE_FLAG + Specify the value for 'duplicate_flag' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_OBS_SUMMARY + Specify the value for 'obs_summary' in the MET configuration file for PointStat. + + | *Used by:* PointStat + + POINT_STAT_OBS_PERC_VALUE + Specify the value for 'obs_perc_value' in the MET configuration file for PointStat. + + | *Used by:* PointStat diff --git a/docs/Users_Guide/overview.rst b/docs/Users_Guide/overview.rst index 1d8e543abe..509394c58f 100644 --- a/docs/Users_Guide/overview.rst +++ b/docs/Users_Guide/overview.rst @@ -434,7 +434,37 @@ METplus Components Python Requirements Feature Relative by Lead using Multiple User-Defined Fields (pygrib) - <../generated/model_applications/medium_range/TCStat_SeriesAnalysis_fcstGFS_obsGFS_FeatureRelative_SeriesByLead_PyEmbed_Multiple_Diagnostics.html>`_ + <../generated/model_applications/medium_range/TCStat_SeriesAnalysis_fcstGFS_obsGFS_FeatureRelative_SeriesByLead_PyEmbed_Multiple_Diagnostics.html>`_ :raw-html:`
` + `GridStat: + Cloud Fractions Using GFS + and ERA5 Data + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.html>`_ :raw-html:`
` + `GridStat: + Cloud Height with Neighborhood + and Probabilities + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstMPAS_obsERA5_cloudBaseHgt.html>`_ :raw-html:`
` + `GridStat: + Cloud Pressure and + Temperature Heights + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstGFS_obsSATCORPS_cloudTopPressAndTemp.html>`_ :raw-html:`
` + `GridStat: + Cloud Fractions Using GFS + and MERRA2 Data + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.html>`_ :raw-html:`
` + `GridStat: + Cloud Fractions Using MPAS + and SatCORPS Data + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.html>`_ :raw-html:`
` + `GridStat: + Cloud Fractions Using MPAS + and MERRA2 Data + (pygrib) + <../generated/model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.html>`_ * - pylab - - METplus wrappers diff --git a/docs/Users_Guide/wrappers.rst b/docs/Users_Guide/wrappers.rst index 380c7e8673..321ca160c6 100644 --- a/docs/Users_Guide/wrappers.rst +++ b/docs/Users_Guide/wrappers.rst @@ -6286,8 +6286,26 @@ Configuration | :term:`POINT_STAT_CLIMO_CDF_CENTER_BINS` | :term:`POINT_STAT_CLIMO_CDF_WRITE_BINS` | :term:`POINT_STAT_CLIMO_CDF_DIRECT_PROB` +| :term:`POINT_STAT_LAND_MASK_FLAG` +| :term:`POINT_STAT_LAND_MASK_FILE_NAME` +| :term:`POINT_STAT_LAND_MASK_FIELD_NAME` +| :term:`POINT_STAT_LAND_MASK_FIELD_LEVEL` +| :term:`POINT_STAT_LAND_MASK_REGRID_METHOD` +| :term:`POINT_STAT_LAND_MASK_REGRID_WIDTH` +| :term:`POINT_STAT_LAND_MASK_THRESH` +| :term:`POINT_STAT_TOPO_MASK_FLAG` +| :term:`POINT_STAT_TOPO_MASK_FILE_NAME` +| :term:`POINT_STAT_TOPO_MASK_FIELD_NAME` +| :term:`POINT_STAT_TOPO_MASK_FIELD_LEVEL` +| :term:`POINT_STAT_TOPO_MASK_REGRID_METHOD` +| :term:`POINT_STAT_TOPO_MASK_REGRID_WIDTH` +| :term:`POINT_STAT_TOPO_MASK_USE_OBS_THRESH` +| :term:`POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH` | :term:`POINT_STAT_OBS_QUALITY_INC` | :term:`POINT_STAT_OBS_QUALITY_EXC` +| :term:`POINT_STAT_DUPLICATE_FLAG` +| :term:`POINT_STAT_OBS_SUMMARY` +| :term:`POINT_STAT_OBS_PERC_VALUE` | :term:`POINT_STAT_OUTPUT_FLAG_FHO` | :term:`POINT_STAT_OUTPUT_FLAG_CTC` | :term:`POINT_STAT_OUTPUT_FLAG_CTS` @@ -6589,6 +6607,42 @@ ${METPLUS_OBS_QUALITY_EXC} * - :term:`POINT_STAT_OBS_QUALITY_EXC` - obs_quality_exc +${METPLUS_DUPLICATE_FLAG} +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 5 5 + :header-rows: 1 + + * - METplus Config(s) + - MET Config File + * - :term:`POINT_STAT_DUPLICATE_FLAG` + - duplicate_flag + +${METPLUS_OBS_SUMMARY} +^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 5 5 + :header-rows: 1 + + * - METplus Config(s) + - MET Config File + * - :term:`POINT_STAT_OBS_SUMMARY` + - obs_summary + +${METPLUS_OBS_PERC_VALUE} +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 5 5 + :header-rows: 1 + + * - METplus Config(s) + - MET Config File + * - :term:`POINT_STAT_OBS_PERC_VALUE` + - obs_perc_value + ${METPLUS_MESSAGE_TYPE_GROUP_MAP} """"""""""""""""""""""""""""""""" @@ -6679,6 +6733,56 @@ ${METPLUS_CLIMO_CDF_DICT} * - :term:`POINT_STAT_CLIMO_CDF_DIRECT_PROB` - climo_cdf.direct_prob +${METPLUS_LAND_MASK_DICT} +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 5 5 + :header-rows: 1 + + * - METplus Config(s) + - MET Config File + * - :term:`POINT_STAT_LAND_MASK_FLAG` + - land_mask.flag + * - :term:`POINT_STAT_LAND_MASK_FILE_NAME` + - land_mask.file_name + * - :term:`POINT_STAT_LAND_MASK_FIELD_NAME` + - land_mask.field.name + * - :term:`POINT_STAT_LAND_MASK_FIELD_LEVEL` + - land_mask.field.level + * - :term:`POINT_STAT_LAND_MASK_REGRID_METHOD` + - land_mask.regrid.method + * - :term:`POINT_STAT_LAND_MASK_REGRID_WIDTH` + - land_mask.regrid.width + * - :term:`POINT_STAT_LAND_MASK_THRESH` + - land_mask.thresh + +${METPLUS_TOPO_MASK_DICT} +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 5 5 + :header-rows: 1 + + * - METplus Config(s) + - MET Config File + * - :term:`POINT_STAT_TOPO_MASK_FLAG` + - topo_mask.flag + * - :term:`POINT_STAT_TOPO_MASK_FILE_NAME` + - topo_mask.file_name + * - :term:`POINT_STAT_TOPO_MASK_FIELD_NAME` + - topo_mask.field.name + * - :term:`POINT_STAT_TOPO_MASK_FIELD_LEVEL` + - topo_mask.field.level + * - :term:`POINT_STAT_TOPO_MASK_REGRID_METHOD` + - topo_mask.regrid.method + * - :term:`POINT_STAT_TOPO_MASK_REGRID_WIDTH` + - topo_mask.regrid.width + * - :term:`POINT_STAT_TOPO_MASK_USE_OBS_THRESH` + - topo_mask.use_obs_thresh + * - :term:`POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH` + - topo_mask.interp_fcst_thresh + ${METPLUS_OBS_WINDOW_DICT} """""""""""""""""""""""""" @@ -9425,6 +9529,8 @@ METplus Configuration | :term:`TC_PAIRS_CONSENSUS_MEMBERS` | :term:`TC_PAIRS_CONSENSUS_REQUIRED` | :term:`TC_PAIRS_CONSENSUS_MIN_REQ` +| :term:`TC_PAIRS_CONSENSUS_DIAG_REQUIRED` +| :term:`TC_PAIRS_CONSENSUS_DIAG_MIN_REQ` | :term:`TC_PAIRS_CONSENSUS_WRITE_MEMBERS` | :term:`TC_PAIRS_SKIP_LEAD_SEQ` | :term:`TC_PAIRS_RUN_ONCE` @@ -9717,6 +9823,10 @@ ${METPLUS_CONSENSUS_LIST} - consensus.required * - :term:`TC_PAIRS_CONSENSUS_MIN_REQ` - consensus.min_req + * - :term:`TC_PAIRS_CONSENSUS_DIAG_REQUIRED` + - consensus.diag_required + * - :term:`TC_PAIRS_CONSENSUS_DIAG_MIN_REQ` + - consensus.diag_min_req * - :term:`TC_PAIRS_CONSENSUS_WRITE_MEMBERS` - consensus.write_members diff --git a/docs/_static/clouds-GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.png b/docs/_static/clouds-GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.png new file mode 100644 index 0000000000..9615270bc0 Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.png differ diff --git a/docs/_static/clouds-GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.png b/docs/_static/clouds-GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.png new file mode 100644 index 0000000000..021d3b0ba5 Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.png differ diff --git a/docs/_static/clouds-GridStat_fcstGFS_obsSATCORPS_cloudTopPressAndTemp.png b/docs/_static/clouds-GridStat_fcstGFS_obsSATCORPS_cloudTopPressAndTemp.png new file mode 100644 index 0000000000..66c92fbbd2 Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstGFS_obsSATCORPS_cloudTopPressAndTemp.png differ diff --git a/docs/_static/clouds-GridStat_fcstMPAS_obsERA5_cloudBaseHgt.png b/docs/_static/clouds-GridStat_fcstMPAS_obsERA5_cloudBaseHgt.png new file mode 100644 index 0000000000..074aac3ced Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstMPAS_obsERA5_cloudBaseHgt.png differ diff --git a/docs/_static/clouds-GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.png b/docs/_static/clouds-GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.png new file mode 100644 index 0000000000..df38e95181 Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.png differ diff --git a/docs/_static/clouds-GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.png b/docs/_static/clouds-GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.png new file mode 100644 index 0000000000..3857ae574d Binary files /dev/null and b/docs/_static/clouds-GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.png differ diff --git a/docs/_static/marine_and_cryosphere-PointStat_fcstGFS_obsJASON3_satelliteAltimetry.png b/docs/_static/marine_and_cryosphere-PointStat_fcstGFS_obsJASON3_satelliteAltimetry.png new file mode 100644 index 0000000000..20d8052089 Binary files /dev/null and b/docs/_static/marine_and_cryosphere-PointStat_fcstGFS_obsJASON3_satelliteAltimetry.png differ diff --git a/docs/_static/short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png b/docs/_static/short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png new file mode 100644 index 0000000000..3dfa948d3c Binary files /dev/null and b/docs/_static/short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png differ diff --git a/docs/requirements.txt b/docs/requirements.txt index 6bbf0dbad2..4ae7110210 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ sphinx==5.3.0 -pillow==10.0.1 +pillow==10.2.0 sphinx-gallery==0.14.0 sphinx-rtd-theme==1.3.0 sphinx-design==0.3.0 diff --git a/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.py b/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.py index b6bf9a50e5..854e15a7a9 100644 --- a/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.py +++ b/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.py @@ -1,6 +1,6 @@ """ -GridStat: Cloud Fractions with Neighborhood and Probabilities -============================================================= +GridStat: Cloud Fractions Using GFS and ERA5 Data +================================================= model_applications/clouds/GridStat_fcstGFS_obsERA5_lowAndTotalCloudFrac.conf diff --git a/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.py b/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.py index 586bd25634..b8ab2fc8ce 100644 --- a/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.py +++ b/docs/use_cases/model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.py @@ -1,6 +1,6 @@ """ -GridStat: Cloud Fractions with Neighborhood and Probabilities -============================================================= +GridStat: Cloud Fractions Using GFS and MERRA2 Data +=================================================== model_applications/clouds/GridStat_fcstGFS_obsMERRA2_lowAndTotalCloudFrac.conf diff --git a/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.py b/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.py index 8214f7217a..139ed51c50 100644 --- a/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.py +++ b/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.py @@ -1,6 +1,6 @@ """ -GridStat: Cloud Fractions with Neighborhood and Probabilities -============================================================= +GridStat: Cloud Fractions Using MPAS and MERRA2 Data +==================================================== model_applications/clouds/GridStat_fcstMPAS_obsMERRA2_lowAndTotalCloudFrac.conf diff --git a/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.py b/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.py index a8c79302a5..37ffab119d 100644 --- a/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.py +++ b/docs/use_cases/model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.py @@ -1,6 +1,6 @@ """ -GridStat: Cloud Fractions with Neighborhood and Probabilities -============================================================= +GridStat: Cloud Fractions Using MPAS and SatCORPS Data +====================================================== model_applications/clouds/GridStat_fcstMPAS_obsSATCORPS_lowAndTotalCloudFrac.conf diff --git a/docs/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.py b/docs/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.py new file mode 100644 index 0000000000..af2ec6a88b --- /dev/null +++ b/docs/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.py @@ -0,0 +1,132 @@ +""" +PointStat: read in satellite data and verify wind speeds or wave heights +======================================================================== + +model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf + +""" +############################################################################## +# Scientific Objective +# -------------------- +# +# Satellite data provides a wealth of information, especially over vast water bodies (eg. oceans) +# where traditional observation methods are sparse or unavailable. This use case shows how a satellite +# dataset can be used as observations to verify against a model forecast. While the use case is set +# up to verify using JASON-3 data, the Python script called on via Python Embedding is capabile +# of processing SARAL and Sentinel-6a datasets as well. + +############################################################################## +# Datasets +# -------- +# +# | **Forecast:** GFS forecast data (wind speed and sig. wave hgt) +# +# | **Observations:** JASON-3 satellite data +# +# | **Location:** All of the input data required for this use case can be found in the met_test sample data tarball. Click here to 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 calls Python Embedding during PointStat, which is the only tool used. +# + +############################################################################## +# METplus Workflow +# ---------------- +# +# PointStat kicks off a Python script execution, which reads in the file name, variable field of interest, and type of file (JASON, SARAL, or SENTINEL). +# After these points are passed back to PointStat as the point observation dataset, they are compared to gridded forecast data. +# CTC and CTS line types are output, which can be adjusted for additional wind speeds/ wave heights. +# The use case processes the following run time: +# +# | **Valid:** 2024-01-02 12Z 12hr lead +# | + +############################################################################## +# 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 +# parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf +# +# .. highlight:: bash +# .. literalinclude:: ../../../../parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf + +############################################################################## +# MET Configuration +# --------------------- +# +# METplus sets environment variables based on user settings in the METplus configuration file. +# See :ref:`How METplus controls MET config file settings` for more details. +# +# **YOU SHOULD NOT SET ANY OF THESE ENVIRONMENT VARIABLES YOURSELF! THEY WILL BE OVERWRITTEN BY METPLUS WHEN IT CALLS THE MET TOOLS!** +# +# If there is a setting in the MET configuration file that is currently not supported by METplus you'd like to control, please refer to: +# :ref:`Overriding Unsupported MET config file settings` +# +# .. note:: See the :ref:`PointStat MET Configuration` section of the User's Guide for more information on the environment variables used in the file below: +# +# .. highlight:: bash +# .. literalinclude:: ../../../../parm/met_config/PointStatConfig_wrapped + +############################################################################## +# Python Embedding +# ---------------- +# +# This use case calls the read_satData.py script to read and pass to PointStat the user-requested variable. +# The script needs 3 inputs in the following order: an input file, a variable field to extract, +# and where the data came from, passed as JASON (JASON-3), SARAL, or SENTINEL (Sentinel-6a). +# The location of the code is parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/read_satData.py +# +# .. highlight:: bash +# .. literalinclude:: ../../../../parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/read_satData.py + + +############################################################################## +# 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/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf /path/to/user_system.conf +# +# See :ref:`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 model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry (relative to **OUTPUT_BASE**) +# and will contain the following files: +# +# * point_stat_swh_120000L_20240102_120000V.stat +# * point_stat_wind_120000L_20240102_120000V.stat + +############################################################################## +# Keywords +# -------- +# +# .. note:: +# +# * PointStatToolUseCase +# * PythonEmbeddingFileUseCase +# * GRIB2FileUseCase +# * MarineAndCryosphereAppUseCase +# +# Navigate to the :ref:`quick-search` page to discover other similar use cases. +# +# +# +# sphinx_gallery_thumbnail_path = '_static/marine_and_cryosphere-PointStat_fcstGFS_obsJASON3_satelliteAltimetry.png' + diff --git a/docs/use_cases/model_applications/s2s/UserScript_fcstS2S_obsERAI_CrossSpectra.py b/docs/use_cases/model_applications/s2s/UserScript_fcstS2S_obsERAI_CrossSpectra.py index 8733d24c41..855321191d 100644 --- a/docs/use_cases/model_applications/s2s/UserScript_fcstS2S_obsERAI_CrossSpectra.py +++ b/docs/use_cases/model_applications/s2s/UserScript_fcstS2S_obsERAI_CrossSpectra.py @@ -1,5 +1,5 @@ """ -UserScript: Compue Cross Spectra and make a plot +UserScript: Compute Cross Spectra and Make a Plot ======================================================================== model_applications/ diff --git a/docs/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot_ecnt_spread_skill.py b/docs/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot_ecnt_spread_skill.py new file mode 100755 index 0000000000..c48e6c6cec --- /dev/null +++ b/docs/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot_ecnt_spread_skill.py @@ -0,0 +1,255 @@ +""" +UserScript: Reformat MET .stat ECNT data, calculate aggregation statistics, and generate a spread skill plot +====================================================================================================================== + +model_applications/ +short_range/ +UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot_ecnt_spread_skill.py + +""" + +################################################################################# +# Scientific Objective +# -------------------- +# +# This use case illustrates how to use MET .stat output (using the ECNT linetype data) to generate a +# spread skill plot using a subset of the METplus Analysis Tools (METdataio, METcalcpy, +# and METplotpy). The METdataio METreformat module extracts the ECNT linetype data and +# performs reformatting, the METcalcpy agg-stat module performs aggregation, and the +# METplotpy line plot is used to generate the spread skill plot. +# + +################################################################################# +# Datasets +# -------- +# +# * Forecast dataset: RRFS GEFS (Rapid Refresh Forecast System Global Ensemble Forecast System) +# * Observation dataset: None +# +# **Input**: MET .stat files from MET ensemble-stat tool for RRFS for 20220506 +# +# **Location**: All the input data required for this use case can be found in the met_test sample data tarball +# (**sample_data-short_range.tgz**). +# +# Click here to see the METplus releases page and download sample data for the appropriate +# release: https://github.com/dtcenter/METplus/releases +# +# See `Running METplus `_ +# section for more information. +# +# **This tarball should be unpacked into the directory corresponding to the value of INPUT_BASE** in the +# `User Configuration File `_ +# section. +# + +############################################################################# +# External Dependencies +# --------------------- +# You will need to use the version of Python that is required for the METplus version +# in use. Refer to the Installation section of the User's Guide for basic Python requirements: +# https://metplus.readthedocs.io/en/latest/Users_Guide/installation.html +# +# The METplus Analysis tools: METdataio, METcalcpy, and METplotpy have the additional third-party +# Python package requirements. The version numbers are found in the requirements.txt file found at the +# top-level directory of each repository. +# +# * lxml +# * pandas +# * pyyaml +# * numpy +# * netcdf4 +# * xarray +# * scipy +# * metpy +# * pint +# * python-dateutil +# * kaleido (python-kaleido) +# * plotly +# * matplotlib + + + +############################################################################## +# METplus Components +# ------------------ +# +# This use case runs the UserScript wrapper tool to run a user provided script, +# in this case, reformat_ecnt_linetype.py, agg_stat_ecnt.py and plot_spread_skill.py. +# It also requires the METdataio, METcalcpy and METplotpy source code to reformat the MET .stat output, +# perform aggregation, and generate the plot. Clone the METdataio repository +# (https://github.com/dtcenter/METdataio), +# METcalcpy repository (https://github.com/dtcenter/METcalcpy, and the METplotpy +# repository (https://github.com/dtcenter/METplotpy) under the same base directory as the +# METPLUS_BASE directory so that the METdataio, METcalcpy, and METplotpy directories are under the +# same base directory (i.e. if the METPLUS_BASE directory is /home/username/working/METplus, +# then clone the METdataio, METcalcpy and METplotpy source code into the /home/username/working directory) +# +# Clone the METdataio, METcalcpy, and METplotpy source code from their repositories under a base directory. The +# repositories are located: +# +# * https://github.com/dtcenter/METdataio +# * https://github.com/dtcenter/METcalcpy +# * https://github.com/dtcenter/METplotpy +# +# +# +# Define the OUTPUT_BASE, INPUT_BASE, and MET_INSTALL_DIR settings in the user +# configuration file. For instructions on how to set up the user configuration file, refer to the `User ConfigurationFile +# `_ section. +# + + +############################################################################## +# METplus Workflow +# ---------------- +# +# This use case reads in the MET .stat output that contains the ECNT linetype (from +# the MET ensemble-stat tool). The .stat output *MUST* reside under one directory. +# If .stat files are spread among multiple directories, these must be consolidated under a +# single directory. +# The use case loops over three processes: reformatting, aggregating, and plotting. +# + + +############################################################################## +# METplus Configuration +# --------------------- +# +# METplus first loads all the configuration files found in parm/metplus_config, +# then it loads any configuration files passed to METplus via the command line +# with the -c option, i.e. -c parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf +# +# .. highlight:: bash +# .. literalinclude:: ../../../../parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf +# + +############################################################################# +# MET Configuration +# --------------------- +# +# There are no MET tools used in this use case. The use case uses MET .stat +# output as input for the reformatting step. +# + +############################################################################## +# Python Embedding +# ---------------- +# +# There is no python embedding in this use case +# + +############################################################################## +# Python Scripts +# ---------------- +# This use case uses Python scripts to invoke the METdataio reformatter, the METcalcpy aggregator, and the METplotpy +# line plot. +# +# The following Python script (from METdataio) is used to reformat the MET .stat ECNT linetype data +# into a format that can be used by the aggregating script. +# +# .. highlight:: python +# .. literalinclude:: ../../../../parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt_linetype.py +# +# This Python script (from METcalcpy) is used to calculate aggregation statistics for the ECNT linetype. +# +# .. highlight:: python +# .. literalinclude:: ../../../../parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.py +# +# Finally,this Python script (from METplotpy) is used to generate a spread-skill plot using the METplotypy line plot code. +# +# .. highlight:: python +# .. literalinclude:: ../../../../parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.py +# + +############################################################################## +# Running METplus +# --------------- +# +# This use case can be run two ways: +# +# 1) Passing in UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf, +# then a user-specific system configuration file:: +# +# run_metplus.py -c /path/to/METplus/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf -c /path/to/user_system.conf +# +# 2) Modifying the configurations in parm/metplus_config, then passing in UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf:: +# +# run_metplus.py -c /path/to/METplus/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf +# +# The former method is recommended. Whether you add them to a user-specific configuration file or modify the metplus_config files, the following variables must be set correctly: +# +# * **INPUT_BASE** - Path to directory where sample data tarballs are unpacked (See Datasets section to obtain tarballs). This is not required to run METplus, but it is required to run the examples in parm/use_cases +# * **OUTPUT_BASE** - Path where METplus output will be written. This must be in a location where you have write permissions +# * **MET_INSTALL_DIR** - Path to location where MET is installed locally +# +# and for the [exe] section, you will need to define the location of NON-MET executables. +# If the executable is in the user's path, METplus will find it from the name. +# If the executable is not in the path, specify the full path to the executable here (i.e. RM = /bin/rm) +# The following executables are required for performing series analysis use cases: +# +# Example User Configuration File:: +# +# [config] +# INPUT_BASE = /path/to/sample/input/data +# OUTPUT_BASE = /path/to/output/dir +# MET_INSTALL_DIR = /path/to/met-X.Y +# +# +# [exe] +# RM = /path/to/rm +# CUT = /path/to/cut +# TR = /path/to/tr +# NCAP2 = /path/to/ncap2 +# CONVERT = /path/to/convert +# NCDUMP = /path/to/ncdump +# + +############################################################################## +# Expected Output +# ---------------- +# +# A successful run will output the following both to the screen and to the logfile, one for the +# reformat, aggregate, and plot steps of the use case:: +# +# INFO: METplus has successfully finished running. +# +# +# **Reformat Output** +# +# The reformatted ensemble-stat ECNT linetype data should exist in the location specified in the user +# configuration file (OUTPUT_BASE). Verify that the ensemble_stat_ecnt.data file exists. The file now has all +# the statistics under the stat_name and stat_value columns, all ECNT statistic columns labelled with their +# corresponding names (e.g. crps, crpss, rmse, etc.) and confidence level values under the +# following columns: stat_btcl and stat_btcu +# +# +# **Aggregation Output** +# +# The METcalcpy agg_stat module is used to calculate aggregated statistics and confidence intervals for each +# series (line) point. +# +# **Plot Output** +# +# A spread-skill plot of temperature for the RMSE, SPREAD_PLUS_OERR, and a ratio line of SPREAD_PLUS_OERR/RMSE is +# created and found in the output location specified in the user configuration file (OUTPUT_BASE). The plot is named +# short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png +# +# + +############################################################################## +# Keywords +# -------- +# +# .. note:: +# +# * UserScriptUseCase +# * ShortRangeAppUseCase +# * METdataioUseCase +# * METcalcpyUseCase +# * METplotpyUseCase +# +# Navigate to the :ref:`quick-search` page to discover other similar use cases. +# +# +# +# sphinx_gallery_thumbnail_path = '_static/short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png' diff --git a/internal/tests/pytests/conftest.py b/internal/tests/pytests/conftest.py index 4e3f94c6bb..2b7e7dfbab 100644 --- a/internal/tests/pytests/conftest.py +++ b/internal/tests/pytests/conftest.py @@ -82,8 +82,9 @@ def metplus_config(request): function arguments and set a variable called config to metplus_config, e.g. config = metplus_config. - This fixture also replaces config.logger with a MagicMock object. This - allows tests to assert the logger was called with a specific message. + This fixture wraps config.logger with a MagicMock object, allowing tests to + assert the logger was called with a specific message. Logged output is still + written to ${METPLUS_TEST_OUTPUT_BASE}/test_output/{RUN_ID}/logs/metplus.log e.g. def test_example(metplus_config): @@ -100,7 +101,7 @@ def test_example(metplus_config): # Set mock logger old_logger = config.logger - config.logger = mock.MagicMock() + config.logger = mock.MagicMock(wraps=old_logger) yield config diff --git a/internal/tests/pytests/wrappers/point_stat/test_point_stat_wrapper.py b/internal/tests/pytests/wrappers/point_stat/test_point_stat_wrapper.py index 26e1294c58..a12441f85a 100755 --- a/internal/tests/pytests/wrappers/point_stat/test_point_stat_wrapper.py +++ b/internal/tests/pytests/wrappers/point_stat/test_point_stat_wrapper.py @@ -544,6 +544,63 @@ def test_met_dictionary_in_var_options(metplus_config): 'sid = ["one", "two"];' 'llpnt = [{ name = "LAT30TO40"; lat_thresh = >=30&&<=40; lon_thresh = NA; }, { name = "BOX"; lat_thresh = >=20&&<=40; lon_thresh = >=-110&&<=-90; }];}' )}), + # land_mask dictionary + ({'POINT_STAT_LAND_MASK_FLAG': 'false', }, + {'METPLUS_LAND_MASK_DICT': 'land_mask = {flag = FALSE;}'}), + ({'POINT_STAT_LAND_MASK_FILE_NAME': '/some/file/path.nc', }, + {'METPLUS_LAND_MASK_DICT': 'land_mask = {file_name = ["/some/file/path.nc"];}'}), + ({'POINT_STAT_LAND_MASK_FIELD_NAME': 'LAND', + 'POINT_STAT_LAND_MASK_FIELD_LEVEL': 'L0'}, + {'METPLUS_LAND_MASK_DICT': 'land_mask = {field = {name = "LAND";level = "L0";}}'}), + ({'POINT_STAT_LAND_MASK_REGRID_METHOD': 'NEAREST', + 'POINT_STAT_LAND_MASK_REGRID_WIDTH': '1'}, + {'METPLUS_LAND_MASK_DICT': 'land_mask = {regrid = {method = NEAREST;width = 1;}}'}), + ({'POINT_STAT_LAND_MASK_THRESH': 'eq1', }, + {'METPLUS_LAND_MASK_DICT': 'land_mask = {thresh = eq1;}'}), + ({'POINT_STAT_LAND_MASK_FLAG': 'false', + 'POINT_STAT_LAND_MASK_FILE_NAME': '/some/file/path.nc', + 'POINT_STAT_LAND_MASK_FIELD_NAME': 'LAND', + 'POINT_STAT_LAND_MASK_FIELD_LEVEL': 'L0', + 'POINT_STAT_LAND_MASK_REGRID_METHOD': 'NEAREST', + 'POINT_STAT_LAND_MASK_REGRID_WIDTH': '1', + 'POINT_STAT_LAND_MASK_THRESH': 'eq1', + }, + {'METPLUS_LAND_MASK_DICT': ('land_mask = {flag = FALSE;file_name = ["/some/file/path.nc"];' + 'field = {name = "LAND";level = "L0";}' + 'regrid = {method = NEAREST;width = 1;}thresh = eq1;}')}), + # topo_mask dictionary + ({'POINT_STAT_TOPO_MASK_FLAG': 'false', }, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {flag = FALSE;}'}), + ({'POINT_STAT_TOPO_MASK_FILE_NAME': '/some/file/path.nc', }, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {file_name = ["/some/file/path.nc"];}'}), + ({'POINT_STAT_TOPO_MASK_FIELD_NAME': 'TOPO', + 'POINT_STAT_TOPO_MASK_FIELD_LEVEL': 'L0'}, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {field = {name = "TOPO";level = "L0";}}'}), + ({'POINT_STAT_TOPO_MASK_REGRID_METHOD': 'NEAREST', + 'POINT_STAT_TOPO_MASK_REGRID_WIDTH': '1'}, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {regrid = {method = NEAREST;width = 1;}}'}), + ({'POINT_STAT_TOPO_MASK_USE_OBS_THRESH': 'ge-100&&le100', }, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {use_obs_thresh = ge-100&&le100;}'}), + ({'POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH': 'ge-50&&le50', }, + {'METPLUS_TOPO_MASK_DICT': 'topo_mask = {interp_fcst_thresh = ge-50&&le50;}'}), + ({'POINT_STAT_TOPO_MASK_FLAG': 'false', + 'POINT_STAT_TOPO_MASK_FILE_NAME': '/some/file/path.nc', + 'POINT_STAT_TOPO_MASK_FIELD_NAME': 'TOPO', + 'POINT_STAT_TOPO_MASK_FIELD_LEVEL': 'L0', + 'POINT_STAT_TOPO_MASK_REGRID_METHOD': 'NEAREST', + 'POINT_STAT_TOPO_MASK_REGRID_WIDTH': '1', + 'POINT_STAT_TOPO_MASK_USE_OBS_THRESH': 'ge-100&&le100', + 'POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH': 'ge-50&&le50' + }, + {'METPLUS_TOPO_MASK_DICT': ('topo_mask = {flag = FALSE;file_name = ["/some/file/path.nc"];' + 'field = {name = "TOPO";level = "L0";}regrid = {method = NEAREST;width = 1;}' + 'use_obs_thresh = ge-100&&le100;interp_fcst_thresh = ge-50&&le50;}')}), + ({'POINT_STAT_DUPLICATE_FLAG': 'NONE', }, + {'METPLUS_DUPLICATE_FLAG': 'duplicate_flag = NONE;'}), + ({'POINT_STAT_OBS_SUMMARY': 'NONE', }, + {'METPLUS_OBS_SUMMARY': 'obs_summary = NONE;'}), + ({'POINT_STAT_OBS_PERC_VALUE': '50', }, + {'METPLUS_OBS_PERC_VALUE': 'obs_perc_value = 50;'}), ] ) diff --git a/internal/tests/pytests/wrappers/tc_pairs/test_tc_pairs_wrapper.py b/internal/tests/pytests/wrappers/tc_pairs/test_tc_pairs_wrapper.py index 0edb935b29..c19aa85daa 100644 --- a/internal/tests/pytests/wrappers/tc_pairs/test_tc_pairs_wrapper.py +++ b/internal/tests/pytests/wrappers/tc_pairs/test_tc_pairs_wrapper.py @@ -369,29 +369,41 @@ def test_tc_pairs_storm_id_lists(metplus_config, config_overrides, 'TC_PAIRS_CONSENSUS1_MEMBERS': 'member1a, member1b', 'TC_PAIRS_CONSENSUS1_REQUIRED': 'true, false', 'TC_PAIRS_CONSENSUS1_MIN_REQ': '1', + 'TC_PAIRS_CONSENSUS1_DIAG_REQUIRED': 'false, true', + 'TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ': '2', 'TC_PAIRS_CONSENSUS1_WRITE_MEMBERS': 'false',}, {'METPLUS_CONSENSUS_LIST': ( 'consensus = [{name = "name1";members = ["member1a", "member1b"];' - 'required = [true, false];min_req = 1;write_members = FALSE;}];' + 'required = [true, false];min_req = 1;' + 'diag_required = [false, true];diag_min_req = 2;' + 'write_members = FALSE;}];' )}), # 14: consensus 2 dictionaries ('INIT', {'TC_PAIRS_CONSENSUS1_NAME': 'name1', 'TC_PAIRS_CONSENSUS1_MEMBERS': 'member1a, member1b', 'TC_PAIRS_CONSENSUS1_REQUIRED': 'true, false', 'TC_PAIRS_CONSENSUS1_MIN_REQ': '1', + 'TC_PAIRS_CONSENSUS1_DIAG_REQUIRED': 'false, true', + 'TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ': '2', 'TC_PAIRS_CONSENSUS1_WRITE_MEMBERS': 'false', 'TC_PAIRS_CONSENSUS2_NAME': 'name2', 'TC_PAIRS_CONSENSUS2_MEMBERS': 'member2a, member2b', 'TC_PAIRS_CONSENSUS2_REQUIRED': 'false, true', 'TC_PAIRS_CONSENSUS2_MIN_REQ': '2', + 'TC_PAIRS_CONSENSUS2_DIAG_REQUIRED': 'true, false', + 'TC_PAIRS_CONSENSUS2_DIAG_MIN_REQ': '3', 'TC_PAIRS_CONSENSUS2_WRITE_MEMBERS': 'true', }, {'METPLUS_CONSENSUS_LIST': ( 'consensus = [' '{name = "name1";members = ["member1a", "member1b"];' - 'required = [true, false];min_req = 1;write_members = FALSE;},' + 'required = [true, false];min_req = 1;' + 'diag_required = [false, true];diag_min_req = 2;' + 'write_members = FALSE;},' '{name = "name2";members = ["member2a", "member2b"];' - 'required = [false, true];min_req = 2;write_members = TRUE;}];' + 'required = [false, true];min_req = 2;' + 'diag_required = [true, false];diag_min_req = 3;' + 'write_members = TRUE;}];' )}), # 15: valid_exc ('INIT', {'TC_PAIRS_VALID_EXCLUDE': '20141031_14'}, @@ -455,29 +467,41 @@ def test_tc_pairs_storm_id_lists(metplus_config, config_overrides, 'TC_PAIRS_CONSENSUS1_MEMBERS': 'member1a, member1b', 'TC_PAIRS_CONSENSUS1_REQUIRED': 'true, false', 'TC_PAIRS_CONSENSUS1_MIN_REQ': '1', + 'TC_PAIRS_CONSENSUS1_DIAG_REQUIRED': 'false, true', + 'TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ': '2', 'TC_PAIRS_CONSENSUS1_WRITE_MEMBERS': 'false',}, {'METPLUS_CONSENSUS_LIST': ( 'consensus = [{name = "name1";members = ["member1a", "member1b"];' - 'required = [true, false];min_req = 1;write_members = FALSE;}];' + 'required = [true, false];min_req = 1;' + 'diag_required = [false, true];diag_min_req = 2;' + 'write_members = FALSE;}];' )}), # 35: consensus 2 dictionaries ('VALID', {'TC_PAIRS_CONSENSUS1_NAME': 'name1', 'TC_PAIRS_CONSENSUS1_MEMBERS': 'member1a, member1b', 'TC_PAIRS_CONSENSUS1_REQUIRED': 'true, false', 'TC_PAIRS_CONSENSUS1_MIN_REQ': '1', + 'TC_PAIRS_CONSENSUS1_DIAG_REQUIRED': 'false, true', + 'TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ': '2', 'TC_PAIRS_CONSENSUS1_WRITE_MEMBERS': 'false', 'TC_PAIRS_CONSENSUS2_NAME': 'name2', 'TC_PAIRS_CONSENSUS2_MEMBERS': 'member2a, member2b', 'TC_PAIRS_CONSENSUS2_REQUIRED': 'false, true', 'TC_PAIRS_CONSENSUS2_MIN_REQ': '2', + 'TC_PAIRS_CONSENSUS2_DIAG_REQUIRED': 'true, false', + 'TC_PAIRS_CONSENSUS2_DIAG_MIN_REQ': '3', 'TC_PAIRS_CONSENSUS2_WRITE_MEMBERS': 'true', }, {'METPLUS_CONSENSUS_LIST': ( 'consensus = [' '{name = "name1";members = ["member1a", "member1b"];' - 'required = [true, false];min_req = 1;write_members = FALSE;},' + 'required = [true, false];min_req = 1;' + 'diag_required = [false, true];diag_min_req = 2;' + 'write_members = FALSE;},' '{name = "name2";members = ["member2a", "member2b"];' - 'required = [false, true];min_req = 2;write_members = TRUE;}];' + 'required = [false, true];min_req = 2;' + 'diag_required = [true, false];diag_min_req = 3;' + 'write_members = TRUE;}];' )}), # 36: valid_exc ('VALID', {'TC_PAIRS_VALID_EXCLUDE': '20141031_14'}, diff --git a/internal/tests/use_cases/all_use_cases.txt b/internal/tests/use_cases/all_use_cases.txt index 68f29e3873..658f73d3dd 100644 --- a/internal/tests/use_cases/all_use_cases.txt +++ b/internal/tests/use_cases/all_use_cases.txt @@ -103,6 +103,7 @@ Category: marine_and_cryosphere #X::GridStat_fcstRTOFS_obsGHRSST_climWOA_sst::model_applications/marine_and_cryosphere/GridStat_fcstRTOFS_obsGHRSST_climWOA_sst.conf:: icecover_env, py_embed 8::PointStat_fcstRTOFS_obsARGO_climoWOA23_temp::model_applications/marine_and_cryosphere/PointStat_fcstRTOFS_obsARGO_climoWOA23_temp.conf:: py_embed 9::PointStat_fcstGFS_obsASCAT_satelliteWinds::model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsASCAT_satelliteWinds.conf:: icecover_env, py_embed +10::PointStat_fcstGFS_obsJASON3_satelliteAltimetry::model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf:: py_embed_base_env, py_embed Category: medium_range 0::PointStat_fcstGFS_obsNAM_Sfc_MultiField_PrepBufr:: model_applications/medium_range/PointStat_fcstGFS_obsNAM_Sfc_MultiField_PrepBufr.conf @@ -174,6 +175,7 @@ Category: short_range 11::UserScript_fcstFV3_fcstOnly_PhysicsTendency_VerticalProfile::model_applications/short_range/UserScript_fcstFV3_fcstOnly_PhysicsTendency_VerticalProfile.conf:: metplotpy_env 12::UserScript_fcstFV3_fcstOnly_PhysicsTendency_VerticalCrossSection::model_applications/short_range/UserScript_fcstFV3_fcstOnly_PhysicsTendency_VerticalCrossSection.conf:: metplotpy_env 13::MODEMultivar_fcstHRRR_obsMRMS_HRRRanl::model_applications/short_range/MODEMultivar_fcstHRRR_obsMRMS_HRRRanl.conf +14::UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot::model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf:: metdataio, metcalcpy, metplotpy,mp_analysis_env Category: space_weather diff --git a/metplus/wrappers/point_stat_wrapper.py b/metplus/wrappers/point_stat_wrapper.py index e150ab4a25..d8809c7225 100755 --- a/metplus/wrappers/point_stat_wrapper.py +++ b/metplus/wrappers/point_stat_wrapper.py @@ -30,12 +30,17 @@ class PointStatWrapper(CompareGriddedWrapper): 'METPLUS_FCST_FIELD', 'METPLUS_OBS_FIELD', 'METPLUS_MESSAGE_TYPE', + 'METPLUS_LAND_MASK_DICT', + 'METPLUS_TOPO_MASK_DICT', 'METPLUS_OBS_WINDOW_DICT', 'METPLUS_MASK_DICT', 'METPLUS_OUTPUT_PREFIX', 'METPLUS_CLIMO_CDF_DICT', 'METPLUS_OBS_QUALITY_INC', 'METPLUS_OBS_QUALITY_EXC', + 'METPLUS_DUPLICATE_FLAG', + 'METPLUS_OBS_SUMMARY', + 'METPLUS_OBS_PERC_VALUE', 'METPLUS_OUTPUT_FLAG_DICT', 'METPLUS_INTERP_DICT', 'METPLUS_CLIMO_MEAN_DICT', @@ -111,26 +116,19 @@ def create_c_dict(self): """ c_dict = super().create_c_dict() c_dict['VERBOSITY'] = ( - self.config.getstr('config', - 'LOG_POINT_STAT_VERBOSITY', - c_dict['VERBOSITY']) + self.config.getstr('config', 'LOG_POINT_STAT_VERBOSITY', + c_dict['VERBOSITY']) ) c_dict['ALLOW_MULTIPLE_FILES'] = True c_dict['OFFSETS'] = getlistint( - self.config.getstr('config', - 'POINT_STAT_OFFSETS', - '0') + self.config.getstr('config', 'POINT_STAT_OFFSETS', '0') ) c_dict['FCST_INPUT_TEMPLATE'] = ( - self.config.getraw('filename_templates', - 'FCST_POINT_STAT_INPUT_TEMPLATE', - '') + self.config.getraw('config', 'FCST_POINT_STAT_INPUT_TEMPLATE', '') ) c_dict['OBS_INPUT_TEMPLATE'] = ( - self.config.getraw('filename_templates', - 'OBS_POINT_STAT_INPUT_TEMPLATE', - '') + self.config.getraw('config', 'OBS_POINT_STAT_INPUT_TEMPLATE', '') ) c_dict['FCST_INPUT_DATATYPE'] = ( @@ -141,11 +139,11 @@ def create_c_dict(self): ) c_dict['FCST_INPUT_DIR'] = ( - self.config.getdir('FCST_POINT_STAT_INPUT_DIR','') + self.config.getdir('FCST_POINT_STAT_INPUT_DIR', '') ) c_dict['OBS_INPUT_DIR'] = ( - self.config.getdir('OBS_POINT_STAT_INPUT_DIR','') + self.config.getdir('OBS_POINT_STAT_INPUT_DIR', '') ) c_dict['OUTPUT_DIR'] = ( @@ -153,8 +151,7 @@ def create_c_dict(self): ) c_dict['OUTPUT_TEMPLATE'] = ( - self.config.getraw('config', - 'POINT_STAT_OUTPUT_TEMPLATE') + self.config.getraw('config', 'POINT_STAT_OUTPUT_TEMPLATE') ) # get climatology config variables @@ -169,8 +166,7 @@ def create_c_dict(self): self.add_met_config(name='message_type', data_type='list') - self.add_met_config(name='file_type', - data_type='string', + self.add_met_config(name='file_type', data_type='string', env_var_name='FCST_FILE_TYPE', metplus_configs=['POINT_STAT_FCST_FILE_TYPE', 'FCST_POINT_STAT_FILE_TYPE', @@ -178,8 +174,7 @@ def create_c_dict(self): extra_args={'remove_quotes': True, 'uppercase': True}) - self.add_met_config(name='file_type', - data_type='string', + self.add_met_config(name='file_type', data_type='string', env_var_name='OBS_FILE_TYPE', metplus_configs=['POINT_STAT_OBS_FILE_TYPE', 'OBS_POINT_STAT_FILE_TYPE', @@ -189,6 +184,35 @@ def create_c_dict(self): self.handle_climo_cdf_dict() + self.add_met_config_dict('land_mask', { + 'flag': 'bool', + 'file_name': 'list', + 'field': ('dict', None, { + 'name': 'string', + 'level': 'string', + }), + 'regrid': ('dict', None, { + 'method': ('string', 'remove_quotes'), + 'width': 'int', + }), + 'thresh': 'thresh', + }) + + self.add_met_config_dict('topo_mask', { + 'flag': 'bool', + 'file_name': 'list', + 'field': ('dict', None, { + 'name': 'string', + 'level': 'string', + }), + 'regrid': ('dict', None, { + 'method': ('string', 'remove_quotes'), + 'width': 'int', + }), + 'use_obs_thresh': 'thresh', + 'interp_fcst_thresh': 'thresh', + }) + c_dict['OBS_VALID_BEG'] = ( self.config.getraw('config', 'POINT_STAT_OBS_VALID_BEG', '') ) @@ -197,55 +221,49 @@ def create_c_dict(self): ) c_dict['FCST_PROB_THRESH'] = ( - self.config.getstr('config', - 'FCST_POINT_STAT_PROB_THRESH', '==0.1') + self.config.getstr('config', 'FCST_POINT_STAT_PROB_THRESH', '==0.1') ) c_dict['OBS_PROB_THRESH'] = ( - self.config.getstr('config', - 'OBS_POINT_STAT_PROB_THRESH', '==0.1') + self.config.getstr('config', 'OBS_POINT_STAT_PROB_THRESH', '==0.1') ) c_dict['ONCE_PER_FIELD'] = ( - self.config.getbool('config', - 'POINT_STAT_ONCE_PER_FIELD', - False) + self.config.getbool('config', 'POINT_STAT_ONCE_PER_FIELD', False) ) - self.add_met_config(name='obs_quality_inc', - data_type='list', + self.add_met_config(name='obs_quality_inc', data_type='list', metplus_configs=['POINT_STAT_OBS_QUALITY_INC', 'POINT_STAT_OBS_QUALITY_INCLUDE', 'POINT_STAT_OBS_QUALITY']) - self.add_met_config(name='obs_quality_exc', - data_type='list', + self.add_met_config(name='obs_quality_exc', data_type='list', metplus_configs=['POINT_STAT_OBS_QUALITY_EXC', 'POINT_STAT_OBS_QUALITY_EXCLUDE']) + self.add_met_config(name='duplicate_flag', data_type='string', + extra_args={'remove_quotes': True, 'uppercase': True}) + self.add_met_config(name='obs_summary', data_type='string', + extra_args={'remove_quotes': True, 'uppercase': True}) + + self.add_met_config(name='obs_perc_value', data_type='int') + self.handle_flags('output') self.handle_interp_dict() self.add_met_config( - name='time_interp_method', - data_type='string', + name='time_interp_method', data_type='string', env_var_name='CLIMO_MEAN_TIME_INTERP_METHOD', metplus_configs=['POINT_STAT_CLIMO_MEAN_TIME_INTERP_METHOD'], - extra_args={'remove_quotes': True, - 'uppercase': True, - }, + extra_args={'remove_quotes': True, 'uppercase': True}, ) self.add_met_config( - name='time_interp_method', - data_type='string', + name='time_interp_method', data_type='string', env_var_name='CLIMO_STDEV_TIME_INTERP_METHOD', metplus_configs=['POINT_STAT_CLIMO_STDEV_TIME_INTERP_METHOD'], - extra_args={'remove_quotes': True, - 'uppercase': True, - }, + extra_args={'remove_quotes': True, 'uppercase': True}, ) - self.add_met_config(name='hss_ec_value', - data_type='float', + self.add_met_config(name='hss_ec_value', data_type='float', metplus_configs=['POINT_STAT_HSS_EC_VALUE']) self.add_met_config_dict('hira', { @@ -257,12 +275,10 @@ def create_c_dict(self): 'prob_cat_thresh': ('list', 'remove_quotes'), }) - self.add_met_config(name='message_type_group_map', - data_type='list', + self.add_met_config(name='message_type_group_map', data_type='list', extra_args={'remove_quotes': True}) - self.add_met_config(name='seeps_p1_thresh', - data_type='string', + self.add_met_config(name='seeps_p1_thresh', data_type='string', extra_args={'remove_quotes': True}) if not c_dict['FCST_INPUT_TEMPLATE']: diff --git a/metplus/wrappers/tc_pairs_wrapper.py b/metplus/wrappers/tc_pairs_wrapper.py index 6df53af8f0..f1b726e3f7 100755 --- a/metplus/wrappers/tc_pairs_wrapper.py +++ b/metplus/wrappers/tc_pairs_wrapper.py @@ -411,6 +411,8 @@ def _handle_consensus(self): 'members': 'list', 'required': ('list', 'remove_quotes'), 'min_req': 'int', + 'diag_required': ('list', 'remove_quotes'), + 'diag_min_req': 'int', 'write_members': 'bool', } return_code = add_met_config_dict_list(config=self.config, diff --git a/parm/met_config/PointStatConfig_wrapped b/parm/met_config/PointStatConfig_wrapped index fdba7135f6..b551a4f2fc 100644 --- a/parm/met_config/PointStatConfig_wrapped +++ b/parm/met_config/PointStatConfig_wrapped @@ -74,9 +74,14 @@ ${METPLUS_OBS_QUALITY_INC} //obs_quality_exc = ${METPLUS_OBS_QUALITY_EXC} -duplicate_flag = NONE; -obs_summary = NONE; -obs_perc_value = 50; +//duplicate_flag = +${METPLUS_DUPLICATE_FLAG} + +//obs_summary = +${METPLUS_OBS_SUMMARY} + +//obs_perc_value = +${METPLUS_OBS_PERC_VALUE} // // Mapping of message type group name to comma-separated list of values. @@ -104,6 +109,29 @@ ${METPLUS_CLIMO_CDF_DICT} //////////////////////////////////////////////////////////////////////////////// +// +// Land/Sea mask +// For LANDSF message types, only use forecast grid points where land = TRUE. +// For WATERSF message types, only use forecast grid points where land = FALSE. +// land_mask.flag may be set separately in each "obs.field" entry. +// +//land_mask = { +${METPLUS_LAND_MASK_DICT} + +// +// Topography +// For SURFACE message types, only use observations where the topo - station +// elevation difference meets the use_obs_thresh threshold. +// For the observations kept, when interpolating forecast data to the +// observation location, only use forecast grid points where the topo - station +// difference meets the interp_fcst_thresh threshold. +// topo_mask.flag may be set separately in each "obs.field" entry. +// +//topo_mask = { +${METPLUS_TOPO_MASK_DICT} + +//////////////////////////////////////////////////////////////////////////////// + // // Point observation time window // diff --git a/parm/use_cases/met_tool_wrapper/PointStat/PointStat.conf b/parm/use_cases/met_tool_wrapper/PointStat/PointStat.conf index 3c6de6eb1f..b5a493b787 100644 --- a/parm/use_cases/met_tool_wrapper/PointStat/PointStat.conf +++ b/parm/use_cases/met_tool_wrapper/PointStat/PointStat.conf @@ -98,6 +98,10 @@ POINT_STAT_CONFIG_FILE ={PARM_BASE}/met_config/PointStatConfig_wrapped #POINT_STAT_OBS_QUALITY_INC = 1, 2, 3 #POINT_STAT_OBS_QUALITY_EXC = +#POINT_STAT_DUPLICATE_FLAG = +#POINT_STAT_OBS_SUMMARY = +#POINT_STAT_OBS_PERC_VALUE = + POINT_STAT_CLIMO_MEAN_TIME_INTERP_METHOD = NEAREST #POINT_STAT_CLIMO_STDEV_TIME_INTERP_METHOD = @@ -134,6 +138,23 @@ POINT_STAT_OUTPUT_FLAG_VL1L2 = STAT #POINT_STAT_CLIMO_CDF_WRITE_BINS = True #POINT_STAT_CLIMO_CDF_DIRECT_PROB = +#POINT_STAT_LAND_MASK_FLAG = +#POINT_STAT_LAND_MASK_FILE_NAME = +#POINT_STAT_LAND_MASK_FIELD_NAME = +#POINT_STAT_LAND_MASK_FIELD_LEVEL = +#POINT_STAT_LAND_MASK_REGRID_METHOD = +#POINT_STAT_LAND_MASK_REGRID_WIDTH = +#POINT_STAT_LAND_MASK_THRESH = + +#POINT_STAT_TOPO_MASK_FLAG = +#POINT_STAT_TOPO_MASK_FILE_NAME = +#POINT_STAT_TOPO_MASK_FIELD_NAME = +#POINT_STAT_TOPO_MASK_FIELD_LEVEL = +#POINT_STAT_TOPO_MASK_REGRID_METHOD = +#POINT_STAT_TOPO_MASK_REGRID_WIDTH = +#POINT_STAT_TOPO_MASK_USE_OBS_THRESH = +#POINT_STAT_TOPO_MASK_INTERP_FCST_THRESH = + #POINT_STAT_HSS_EC_VALUE = #POINT_STAT_SEEPS_P1_THRESH = diff --git a/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_extra_tropical.conf b/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_extra_tropical.conf index e2db247609..e5a0bcf1eb 100644 --- a/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_extra_tropical.conf +++ b/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_extra_tropical.conf @@ -103,6 +103,8 @@ TC_PAIRS_MISSING_VAL = -9999 #TC_PAIRS_CONSENSUS1_MEMBERS = #TC_PAIRS_CONSENSUS1_REQUIRED = #TC_PAIRS_CONSENSUS1_MIN_REQ = +#TC_PAIRS_CONSENSUS1_DIAG_REQUIRED = +#TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ = #TC_PAIRS_CONSENSUS1_WRITE_MEMBERS = #TC_PAIRS_CHECK_DUP = diff --git a/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_tropical.conf b/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_tropical.conf index 2df99931c2..e68b5d45f3 100644 --- a/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_tropical.conf +++ b/parm/use_cases/met_tool_wrapper/TCPairs/TCPairs_tropical.conf @@ -96,6 +96,8 @@ TC_PAIRS_DLAND_FILE = MET_BASE/tc_data/dland_global_tenth_degree.nc #TC_PAIRS_CONSENSUS1_MEMBERS = #TC_PAIRS_CONSENSUS1_REQUIRED = #TC_PAIRS_CONSENSUS1_MIN_REQ = +#TC_PAIRS_CONSENSUS1_DIAG_REQUIRED = +#TC_PAIRS_CONSENSUS1_DIAG_MIN_REQ = #TC_PAIRS_CONSENSUS1_WRITE_MEMBERS = #TC_PAIRS_CHECK_DUP = diff --git a/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsASCAT_satelliteWinds.conf b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsASCAT_satelliteWinds.conf index 5b5963fadc..ec5710730c 100644 --- a/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsASCAT_satelliteWinds.conf +++ b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsASCAT_satelliteWinds.conf @@ -108,4 +108,4 @@ POINT_STAT_MASK_SID = POINT_STAT_MESSAGE_TYPE = SATWND -POINT_STAT_MET_CONFIG_OVERRIDES = land_mask = { flag = TRUE;} +POINT_STAT_LAND_MASK_FLAG = TRUE diff --git a/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf new file mode 100644 index 0000000000..8f830a1049 --- /dev/null +++ b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.conf @@ -0,0 +1,119 @@ +[config] + +# Documentation for this use case can be found at +# https://metplus.readthedocs.io/en/latest/generated/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry.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 = PointStat, PointStat(wind) + + +### +# 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 = VALID +VALID_TIME_FMT = %Y%m%d_%H +VALID_BEG = 20240102_12 +VALID_END = 20240102_12 +VALID_INCREMENT = 1d + +LEAD_SEQ = 12 + + +### +# File I/O +# https://metplus.readthedocs.io/en/latest/Users_Guide/systemconfiguration.html#directory-and-filename-template-info +### +CONFIG_DIR = {PARM_BASE}/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry + +FCST_POINT_STAT_INPUT_DIR = {INPUT_BASE}/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry +FCST_POINT_STAT_INPUT_TEMPLATE = gfswave.t00z.global.0p25.f{lead?fmt=%3H}.grib2 + + +OBS_POINT_STAT_INPUT_DIR = +OBS_POINT_STAT_INPUT_TEMPLATE = PYTHON_NUMPY= {CONFIG_DIR}/read_satData.py {INPUT_BASE}/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/JA3_GPSOPR_2PfS362_215_{valid?fmt=%Y%m%d}_102102_{valid?fmt=%Y%m%d}_121958.nc:swh_ocean:JASON + +POINT_STAT_OUTPUT_DIR = {OUTPUT_BASE}/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry +POINT_STAT_OUTPUT_PREFIX = swh + +POINT_STAT_CLIMO_MEAN_INPUT_DIR = +POINT_STAT_CLIMO_MEAN_INPUT_TEMPLATE = + +POINT_STAT_CLIMO_STDEV_INPUT_DIR = +POINT_STAT_CLIMO_STDEV_INPUT_TEMPLATE = + + +### +# Field Info +# https://metplus.readthedocs.io/en/latest/Users_Guide/systemconfiguration.html#field-info +### + +POINT_STAT_ONCE_PER_FIELD = False + + +FCST_VAR1_NAME = WVHGT +FCST_VAR1_LEVELS = Z0 + +OBS_VAR1_NAME = swh_ocean +OBS_VAR1_LEVELS = Z0 + +### +# PointStat Settings +# https://metplus.readthedocs.io/en/latest/Users_Guide/wrappers.html#pointstat +### + +POINT_STAT_CONFIG_FILE ={PARM_BASE}/met_config/PointStatConfig_wrapped + +POINT_STAT_CLIMO_MEAN_TIME_INTERP_METHOD = NEAREST + +POINT_STAT_INTERP_TYPE_METHOD = BILIN +POINT_STAT_INTERP_TYPE_WIDTH = 2 + +POINT_STAT_OUTPUT_FLAG_CNT = STAT +POINT_STAT_OUTPUT_FLAG_SL1L2 = + +OBS_POINT_STAT_WINDOW_BEGIN = -1800 +OBS_POINT_STAT_WINDOW_END = 1800 + +POINT_STAT_OFFSETS = 0 + +MODEL = GFS + +POINT_STAT_DESC = NA +OBTYPE = + +POINT_STAT_REGRID_TO_GRID = NONE +POINT_STAT_REGRID_METHOD = BILIN +POINT_STAT_REGRID_WIDTH = 2 + +POINT_STAT_MESSAGE_TYPE = WDSATR + +[wind] + +OBS_POINT_STAT_INPUT_TEMPLATE = PYTHON_NUMPY= {CONFIG_DIR}/read_satData.py {INPUT_BASE}/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/JA3_GPSOPR_2PfS362_215_{valid?fmt=%Y%m%d}_102102_{valid?fmt=%Y%m%d}_121958.nc:wind_speed_alt:JASON + +FCST_VAR1_NAME = WIND +FCST_VAR1_LEVELS = Z0 + +OBS_VAR1_NAME = wind_speed_alt +OBS_VAR1_LEVELS = Z0 + +POINT_STAT_OUTPUT_PREFIX = wind + +POINT_STAT_OUTPUT_FLAG_CNT = +POINT_STAT_OUTPUT_FLAG_SL1L2 = STAT + diff --git a/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/read_satData.py b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/read_satData.py new file mode 100644 index 0000000000..0614efa4a2 --- /dev/null +++ b/parm/use_cases/model_applications/marine_and_cryosphere/PointStat_fcstGFS_obsJASON3_satelliteAltimetry/read_satData.py @@ -0,0 +1,103 @@ +#This script is designed to ingest one of three types of satellite netCDF files: JASON-3, SARAL, and Sentinel-6a. +#It also grabs the lat, lon, and time information and puts it into a list of lists, the accepted format of MET. +#Currently the script can accept any variable in the netCDFs; however, it's important to note that +#JASON-3 and Sentinel-6a data use groups and non-groups for data organization, which could cause an issue if the requested +#variable is outside of the hardcoded group. The original intent of the use case was for wind speeds and significant wave heights. +#FOR FUTURE REFERENCE +#JASON-3 is swh_ocean, SARAL is swh, and Sentinel-6a is swh_ocean + +from netCDF4 import Dataset +import sys +import numpy as np +import datetime as dt +import xarray as xr +import pandas as pd +#from met.point import convert_point_data + +#Users are responsible for passing the following arguements at runtime: +##input file +##Varible field to read in +##type of file (JASON, SARAL, or SENTINEL) +if len(sys.argv[1].split(':')) == 3: + try: + input_file,field_name,file_type = sys.argv[1].split(':') + + except: + print("input directory may not be set correctly, dates may not be in correct format. Please recheck") + sys.exit() + + if file_type == 'JASON' or file_type == 'SENTINEL': + #need to check if the variable is in the data_01 group or data_01/ku group + try: + ds = xr.open_dataset(input_file, group="/data_01/ku") + du = xr.open_dataset(input_file, group="/data_01") + obs_hold = ds[field_name] + except KeyError: + ds = xr.open_dataset(input_file, group="/data_01") + du = xr.open_dataset(input_file, group="/data_01") + obs_hold = ds[field_name] + obs = obs_hold.values + latitude = np.array(du.latitude.values) + longitude = np.array(du.longitude.values) + time = np.array(du.time.values) + + #convert times to MET readable + new_time = [] + for i in range(len(time)): + new_time.append(pd.to_datetime(str(time[i])).strftime("%Y%m%d_%H%M%S")) + + elif file_type == 'SARAL': + f_in = Dataset(input_file,'r') + obs = np.array(f_in[field_name][:]) + latitude = np.array(f_in['lat'][:]) + longitude = np.array(f_in['lon'][:]) + time = np.array(f_in['time'][:]) + + #adjust times to MET time + new_time = [] + for i in range(len(time)): + new_time.append(dt.datetime(2000,1,1) + dt.timedelta(seconds=int(time[i]))) + new_time[i] = new_time[i].strftime("%Y%m%d_%H%M%S") + + else: + print('file type '+file_type+' not supported. Please use JASON, SARAL, or SENTINEL') + sys.exit() + + #Currently, all station IDs are assigned the same value. If this is not the desired behavior it will require + #additional coding + sid = np.full(len(latitude),"1") + + #get arrays into lists, then create a list of lists + #this also requires creating the last arrays of typ, elv, var, lvl, hgt, and qc + typ = np.full(len(latitude), 'WDSATR').tolist() + elv = np.zeros(len(latitude),dtype=int).tolist() + var = np.full(len(latitude), field_name).tolist() + lvl = np.full(len(latitude),1013.25).tolist() + #adding additional check; if 'wind' appears in the variable name, it's assumed + #to be a wind speed and gets a height of 10m; otherwise its a height of 0 + if field_name.rfind('wind') != -1: + hgt = np.full(len(latitude),0,dtype=int).tolist() + else: + hgt = np.full(len(latitude),0,dtype=int).tolist() + qc = np.full(len(latitude),'NA').tolist() + + #if the data has nans, this replaces them with -9999 (default bad value in MET) + #obs = np.nan_to_num(obs, nan=-9999) + + sid = sid.tolist() + vld = new_time + lat = latitude.tolist() + lon = longitude.tolist() + obs = obs.tolist() + l_tuple = list(zip(typ,sid,vld,lat,lon,elv,var,lvl,hgt,qc,obs)) + point_data = [list(ele) for ele in l_tuple] + #met_point_data = convert_point_data(point_data) + + print("Data Length:\t" + repr(len(point_data))) + print("Data Type:\t" + repr(type(point_data))) + +#if the incorrect number of args are passed, the system will print out the usage statement and end +else: + print("Run Command:\n\n read_satData.py /path/to/input/input_file:variable_field_name:file_type\n\nCommands notes:\nIf only certain variable fields return errors, the field may not be supported or outisde of the expected netCDF group. Additional coding changes may be required.\nfile names currently supported: JASON SARAL SENTINEL\nCurrent Message_type is hard-coded to WDSATR\n") + sys.exit() + diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf new file mode 100755 index 0000000000..6ee8473174 --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf @@ -0,0 +1,67 @@ +[config] + +# Documentation for this use case can be found at +# https://metplus.readthedocs.io/en/latest/generated/model_applications/short-range/UserScript_fcstRRFS_obsOnly_Reformat_Aggregate_Plot.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(reformatter), UserScript(aggregate), UserScript(plotting) + +### +# 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 = VALID +VALID_TIME_FMT = %Y%m%d_%H%M%S +VALID_BEG = 20220506_000000 + +USER_SCRIPT_RUNTIME_FREQ = RUN_ONCE +LOOP_ORDER = processes + +### +# UserScript Settings +# https://metplus.readthedocs.io/en/latest/Users_Guide/wrappers.html#userscript +### + + +[user_env_vars] +REFORMAT_YAML_CONFIG_NAME = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt.yaml +AGGREGATE_YAML_CONFIG_NAME = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.yaml +PLOTTING_YAML_CONFIG_NAME = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.yaml +REFORMAT_INPUT_BASE = {INPUT_BASE}/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot +REFORMAT_OUTPUT_BASE = {OUTPUT_BASE}/reformatted +AGGREGATE_INPUT_BASE = {REFORMAT_OUTPUT_BASE} +AGGREGATE_OUTPUT_BASE = {OUTPUT_BASE}/aggregated +PLOT_INPUT_BASE = {AGGREGATE_OUTPUT_BASE} +PLOT_OUTPUT_BASE = {OUTPUT_BASE}/plot +METDATAIO_BASE = {METPLUS_BASE}/../METdataio +METCALCPY_BASE = {METPLUS_BASE}/../METcalcpy +METPLOTPY_BASE = {METPLUS_BASE}/../METplotpy +PYTHONPATH = {METDATAIO_BASE}:{METDATAIO_BASE}/METdbLoad:{METDATAIO_BASE}/METdbLoad/ush:{METDATAIO_BASE}/METreformat:{METCALCPY_BASE}:{METCALCPY_BASE}/metcalcpy:{METPLOTPY_BASE}:{METPLOTPY_BASE}/metplotpy/plots + + +[reformatter] +USER_SCRIPT_COMMAND = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt_linetype.py + +[aggregate] +USER_SCRIPT_COMMAND = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.py + +[plotting] +USER_SCRIPT_COMMAND = {PARM_BASE}/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.py + + + + diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.py b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.py new file mode 100755 index 0000000000..4766dd500e --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + + +import os +import time +import logging +import pandas as pd +import yaml +from metcalcpy.util import read_env_vars_in_config as readconfig +from metcalcpy.agg_stat import AggStat + +logger = logging.getLogger(__name__) + +def main(): + ''' + Read in the config file (with ENVIRONMENT variables defined in the + UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf). Invoke METcalcpy agg_stat module to + calculate the aggregation statistics, clean up the data so it is compatible for the METplotpy line plot + and write a tab-separated ASCII file. + + ''' + + start_agg_step = time.time() + + # Read in the YAML configuration file. Environment variables in + # the configuration file are supported. + try: + input_config_file = os.getenv("AGGREGATE_YAML_CONFIG_NAME", "aggregate_ecnt.yaml") + settings = readconfig.parse_config(input_config_file) + logger.info(settings) + except yaml.YAMLError as exc: + logger.error(exc) + + # Calculate the aggregation statistics using METcalcpy agg_stat + agg_begin = time.time() + try: + os.mkdir(os.getenv("AGGREGATE_OUTPUT_BASE")) + except OSError: + # Directory already exists, ignore error. + pass + + AGG_STAT = AggStat(settings) + AGG_STAT.calculate_stats_and_ci() + agg_finish = time.time() + time_for_aggregation = agg_finish - agg_begin + logger.info("Total time for calculating aggregation statistics (in seconds): {time_for_aggregation}") + + # Add a 'dummy' column (fcst_valid with the same values as fcst_lead) + # to the output data. The aggregation was based + # on the fcst_lead BUT the line plot requires a *second* time-related + # column (i.e. fcst_init_beg, fcst_valid, etc.) to identify unique + # points. In this case, the aggregated data already consists of + # unique points. If any other time column was used, this + # step would not be required. + output_file = settings['agg_stat_output'] + df = pd.read_csv(output_file, sep='\t') + df['fcst_valid'] = df['fcst_lead'] + df.to_csv(output_file, sep='\t') + + finish_agg = time.time() + total_agg_step = finish_agg - start_agg_step + logger.info("Total time for performing the aggregation step (in sec): {total_agg_step} ") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.yaml b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.yaml new file mode 100644 index 0000000000..4d94d41809 --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/aggregate_ecnt.yaml @@ -0,0 +1,41 @@ +agg_stat_input: !ENV '${AGGREGATE_INPUT_BASE}/ensemble_stat_ecnt.data' +agg_stat_output: !ENV '${AGGREGATE_OUTPUT_BASE}/ecnt_aggregated.data' +alpha: 0.05 +append_to_file: null +circular_block_bootstrap: True +derived_series_1: [] +derived_series_2: [] +event_equal: False +fcst_var_val_1: + TMP: + - ECNT_RMSE + - ECNT_SPREAD_PLUS_OERR +fcst_var_val_2: {} +indy_vals: +- '30000' +- '40000' +- '60000' +- '90000' +- '120000' +- '150000' +- '160000' +- '170000' +- '180000' +- '200000' +- '240000' +- '270000' + +indy_var: fcst_lead +line_type: ecnt +list_stat_1: + - ECNT_RMSE + - ECNT_SPREAD_PLUS_OERR +list_stat_2: [] +method: perc +num_iterations: 1 +num_threads: -1 +random_seed: null +series_val_1: + model: + - RRFS_GEFS_GF.SPP.SPPT +series_val_2: {} diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.py b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.py new file mode 100755 index 0000000000..d482ba5d4e --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + + +import os +from time import perf_counter +import logging +import yaml +import metcalcpy.util.read_env_vars_in_config as readconfig +from metplotpy.plots.line import line + +def main(): + + # Read in the YAML configuration file. Environment variables in + # the configuration file are supported. + try: + input_config_file = os.getenv("PLOTTING_YAML_CONFIG_NAME", "plot_spread_skill.yaml") + settings = readconfig.parse_config(input_config_file) + logging.info(settings) + except yaml.YAMLError as exc: + logging.error(exc) + + try: + start = perf_counter() + plot = line.Line(settings) + plot.save_to_file() + plot.write_html() + plot.write_output_file() + end = perf_counter() + execution_time = end - start + plot.line_logger.info(f"Finished creating line plot, execution time: {execution_time} seconds") + except ValueError as val_er: + print(val_er) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.yaml b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.yaml new file mode 100644 index 0000000000..2b1951bd82 --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/plot_spread_skill.yaml @@ -0,0 +1,281 @@ +alpha: 0.05 +box_avg: 'False' +box_boxwex: 0.2 +box_notch: 'False' +box_outline: 'True' +box_pts: 'False' +caption_align: 0.0 +caption_col: '#333333' +caption_offset: 3.0 +caption_size: 0.8 +caption_weight: 1 +cex: 1 + +colors: +# Colors for each line that will be generated. +# Accessible colors https://davidmathlogic.com/colorblind/#%23D81B60-%231E88E5-%23FFC107-%23004D40 +- '#D41159' # reddish +- '#4B0092' # purple-ish +- '#1Aff1A' # lime green-ish + + +con_series: +- 1 +- 1 +- 1 + +create_html: 'False' + +# Two derived series (lines) are supported (i.e. difference or ratio lines: DIFF or RATIO). +derived_series_1: + # Generate the ratio line of the ECNT spread_plus_oerr/rmse for temperature and the + # The first line is the top portion (left side) of the ratio, the second is the bottom (right side), and the + # third line indicates the type of derived line (i.e. RATIO or DIFF). This is a list of lists, so remember + # to have two '-' at the start of the first line ( - - 'model var stat') + # + # RRFS_GEFS_GF.SPP.SPPT model for temperature and the SPREAD_PLUS_OERR and RMSE statistics. + # +- - RRFS_GEFS_GF.SPP.SPPT TMP ECNT_SPREAD_PLUS_OERR + - RRFS_GEFS_GF.SPP.SPPT TMP ECNT_RMSE + - RATIO + +derived_series_2: [] + +dump_points_1: 'False' +dump_points_2: 'False' +event_equal: 'False' +fcst_var_val_1: + # Specify the variable name and the statistic(s) of interest. + # This will generate the line for the spread_plus_oerr values for TMP and a + # line for rmse values for TMP. + TMP: + - ECNT_SPREAD_PLUS_OERR + - ECNT_RMSE + +fcst_var_val_2: {} + +fixed_vars_vals_input: {} +grid_col: '#cccccc' +grid_lty: 3 +grid_lwd: 1 +grid_on: 'True' +grid_x: listX + +# Labels for the x-axis +indy_label: +- '3' +- '4' +- '6' +- '9' +- '12' +- '15' +- '16' +- '17' +- '18' +- '20' +- '24' +- '27' +- '30' +- '32' +- '33' +- '34' +- '36' + +# Stagger the points so overlapping points don't obscure one another +indy_stagger_1: 'True' +indy_stagger_2: 'True' + +# Values for the x-axis +indy_vals: +- '30000' +- '40000' +- '60000' +- '90000' +- '120000' +- '150000' +- '160000' +- '170000' +- '180000' +- '200000' +- '240000' +- '270000' +- '300000' +- '320000' +- '330000' +- '340000' +- '360000' + +# Specify the variable corresponding to the x-axis +indy_var: fcst_lead + +legend_box: o +legend_inset: + x: 0.0 + y: -0.25 +legend_ncol: -1 +legend_size: 0.8 +line_type: None + +# Explicitly set only the statistics for the derived series line +# (without the accompanying variable name) +list_stat_1: + - ECNT_SPREAD_PLUS_OERR + - ECNT_RMSE +list_stat_2: [] + +mar: +- 8 +- 4 +- 5 +- 4 +method: bca +mgp: +- 1 +- 1 +- 0 +num_iterations: 1 +num_threads: -1 +plot_caption: Caption +plot_ci: +- std +- std +- std + +# Turn on/off displaying the lines +plot_disp: +- 'True' +- 'True' +- 'True' + + +plot_filename: !ENV '${PLOT_OUTPUT_BASE}/short-range_UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.png' +plot_height: 8.5 +plot_res: 72 +plot_stat: median +plot_type: png16m +plot_units: in +plot_width: 11.0 + +# Optional, uncomment and set to directory to store the .points1 file +# that is used by METviewer (created when dump_points_1 is set to True) +# if dump_points_1 is True and this is uncommented, the points1 file +# will be saved in the default location (i.e. where the input data file is stored). +#points_path: /path/to + +random_seed: null + +# Define the style of the line +series_line_style: +- '-' # solid +- '--' # dashes +- ':' # dots + + + +series_line_width: +- 1 +- 1 +- 1 + +series_order: +# The order in which to apply settings for line width, line style, colors, symbols, etc. +# A convenience for experimenting with which color/line style/symbol combination works best for +# a particular line. +- 1 +- 2 +- 3 + + +series_symbols: +- . # circle +- "s" # square +- ^ # triangle up + + +series_type: +# b=lines + markers, l=lines only p=markers only +- b +- b +- b + +series_val_1: + # value of interest, in this case, the model, RRFS_GEFS_GF.SPP.SPPT + model: + - 'RRFS_GEFS_GF.SPP.SPPT' +series_val_2: {} + +show_nstats: 'False' +show_signif: +- 'False' +- 'False' +- 'False' + + +stat_input: !ENV '${PLOT_INPUT_BASE}/ecnt_aggregated.data' +sync_yaxes: 'False' +title: Spread skill plot with ratio line from MET ensemble-stat data (aggregation statistics) +title_align: 0.5 +title_offset: -2 +title_size: 1.4 +title_weight: 2.0 +# Use empty list to use automatically generated legend labels +user_legend: [] +# comment above and uncomment below to use user-specified legend labels +#user_legend: ['ECNT SPREAD_PLUS_OERR for RRFS_GEFS_GF.SPP.SPPT TMP ', 'ECNT RMSE for for RRFS_GEFS_GF.SPP.SPPT TMP', 'RATIO(RRFS_GEFS_GF.SPP.SPPT TMP SPREAD_PLUS_OERR/RMSE)'] +variance_inflation_factor: 'False' +vert_plot: 'False' +x2lab_align: 0.5 +x2lab_offset: -0.5 +x2lab_size: 0.8 +x2lab_weight: 1 +x2tlab_horiz: 0.5 +x2tlab_orient: 1 +x2tlab_perp: 1 +x2tlab_size: 0.8 +xaxis: fcst lead (hr) +xaxis_reverse: 'False' +xlab_align: 0.5 +xlab_offset: 2 +xlab_size: 1 +xlab_weight: 1 +xlim: [] +xtlab_decim: 0 +xtlab_horiz: 0.5 +xtlab_orient: 1 +xtlab_perp: -0.75 +xtlab_size: 1 +y2lab_align: 0.5 +y2lab_offset: 1 +y2lab_size: 1 +y2lab_weight: 1 +y2lim: [] +y2tlab_horiz: 0.5 +y2tlab_orient: 1 +y2tlab_perp: 1 +y2tlab_size: 1.0 +yaxis_1: Statistic value (or ratio of statistic values) +yaxis_2: '' +ylab_align: 0.5 +ylab_offset: -2 +ylab_size: 1 +ylab_weight: 1 +ylim: [] +ytlab_horiz: 0.5 +ytlab_orient: 1 +ytlab_perp: 0.5 +ytlab_size: 1 + +show_legend: +# 0 for don't show, 1 to show + - 1 + - 1 + - 1 + + +# Default logging is ERROR and to stdout. Omit the log_filename and log_level setting name and value if +# to use default settings. +log_filename: !ENV '${PLOT_OUTPUT_BASE}/spread_skill_ecnt.log' +log_level: INFO + +# 0-value on the x-axis is to the far left. +start_from_zero: False diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt.yaml b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt.yaml new file mode 100755 index 0000000000..a60bbdac21 --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt.yaml @@ -0,0 +1,23 @@ +# The REFORMAT_OUTPUT_BASE and REFORMAT_INPUT_BASE are defined in the +# UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot.conf file +# + +# Indicates whether the .stat file input contains aggregated statistics (i.e. output from MET stat-analysis +# has been applied to MET .stat output from point-stat, grid-stat, or ensemble-stat, or previously aggregated +# via METcalcpy agg_stat). In this example, the input data is not aggregated. +# Set this to False, ensuring the appropriate format for METcalcpy agg_stat. +input_stats_aggregated: False +output_dir: !ENV '${REFORMAT_OUTPUT_BASE}' +output_filename: ensemble_stat_ecnt.data +line_type: ECNT +input_data_dir: !ENV '${REFORMAT_INPUT_BASE}' + +# !!! DO NOT modify the settings for the log_directory and log_filename.!!! +# The METdataio METreformat code is expecting values for these settings even though the log +# messages are in the METplus logfile. +log_directory: !ENV '${REFORMAT_OUTPUT_BASE}' +log_filename: 'ecnt_reformat.log' + +# Log levels are: INFO, DEBUG, WARNING, ERROR. +# Set to INFO for most verbose, ERROR for least verbose. +log_level: DEBUG diff --git a/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt_linetype.py b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt_linetype.py new file mode 100755 index 0000000000..239458c1f2 --- /dev/null +++ b/parm/use_cases/model_applications/short_range/UserScript_fcstRRFS_fcstOnly_Reformat_Aggregate_Plot/reformat_ecnt_linetype.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + + +import os +import time +import logging + +from METdbLoad.ush.read_data_files import ReadDataFiles +from METdbLoad.ush.read_load_xml import XmlLoadFile +from METreformat.write_stat_ascii import WriteStatAscii +from metcalcpy.util import read_env_vars_in_config as readconfig + + +logger = logging.getLogger(__name__) + +def main(): + + # Read in the YAML configuration file. Environment variables in + # the configuration file are supported. + input_config_file = os.getenv("REFORMAT_YAML_CONFIG_NAME", "reformat_ecnt.yaml") + settings = readconfig.parse_config(input_config_file) + logging.info(settings) + + + # Replacing the need for an XML specification file, pass in the XMLLoadFile and + # ReadDataFile parameters + rdf_obj: ReadDataFiles = ReadDataFiles() + xml_loadfile_obj: XmlLoadFile = XmlLoadFile(None) + + # Retrieve all the filenames in the data_dir specified in the YAML config file + load_files = xml_loadfile_obj.filenames_from_template(settings['input_data_dir'], + {}) + + flags = xml_loadfile_obj.flags + line_types = xml_loadfile_obj.line_types + beg_read_data = time.perf_counter() + rdf_obj.read_data(flags, load_files, line_types) + end_read_data = time.perf_counter() + time_to_read = end_read_data - beg_read_data + logger.info("Time to read input .stat data files using METdbLoad: {time_to_read}") + file_df = rdf_obj.stat_data + + # Check if the output file already exists, if so, delete it to avoid + # appending output from subsequent runs into the same file. + existing_output_file = os.path.join(settings['output_dir'], settings['output_filename']) + logger.info("Checking if {existing_output_file} already exists") + if os.path.exists(existing_output_file): + logger.info("Removing existing output file {existing_output_file}") + os.remove(existing_output_file) + + # Write stat file in ASCII format + stat_lines_obj: WriteStatAscii = WriteStatAscii(settings) + # stat_lines_obj.write_stat_ascii(file_df, parms, logger) + stat_lines_obj.write_stat_ascii(file_df, settings) + + +if __name__ == "__main__": + main()