diff --git a/docs/users_guide/analysis_tasks.rst b/docs/users_guide/analysis_tasks.rst index ae18168ec..439ecb049 100644 --- a/docs/users_guide/analysis_tasks.rst +++ b/docs/users_guide/analysis_tasks.rst @@ -37,6 +37,7 @@ Analysis Tasks tasks/woceTransects tasks/soseTransects tasks/woaTransects + tasks/antshipTransects tasks/geojsonTransects tasks/oceanRegionalProfiles tasks/regionalTSDiagrams diff --git a/docs/users_guide/tasks/antshipTransects.rst b/docs/users_guide/tasks/antshipTransects.rst new file mode 100644 index 000000000..a8694bbca --- /dev/null +++ b/docs/users_guide/tasks/antshipTransects.rst @@ -0,0 +1,239 @@ +.. _task_antshipTransects: + +antshipTransects +============= + +An analysis task for interpolating MPAS fields to transects from ship-based observations around Antarctica and comparing them. + +Component and Tags:: + + component: ocean + tags: climatology, transect, publicObs + +Configuration Options +--------------------- + +The following configuration options are available for this task:: + + [antshipTransects] + [antshipTransects] + ## options related to plotting model ship CTD transects. + + # Times for comparison times (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, + # Nov, Dec, JFM, AMJ, JAS, OND, ANN) + seasons = ['JFM'] + #seasons = ['ANN', 'Jan', 'JFM', 'JAS'] + + # The approximate horizontal resolution (in km) of each transect. Latitude/ + # longitude between observation points will be subsampled at this interval. + # Use 'obs' to indicate no subsampling. Use 'mpas' to indicate plotting of + # model data on the native grid, in which case comparison with observations + # will take place on the observation grid. + #horizontalResolution = mpas + #horizontalResolution = obs + horizontalResolution = 5 + + # Horizontal bounds of the plot (in km), or an empty list for automatic bounds + # The bounds are a 2-element list of the minimum and maximum distance along the + # transect. Note: A21=Drake Passage; A23=South Atlantic; A12=Prime Meridian + horizontalBounds = {'Belling-BelgicaTrough1-2007': [], + 'Belling-BelgicaTrough2-2007': [], + 'Belling-Cross1-2007': [], + 'Belling-Cross2-2007': [], + 'Belling-Cross3-2007': [], + 'Belling-EltaninBay-Along-2007': [], + 'Belling-GeorgeVI-AlongW1-2007': [], + 'Belling-GeorgeVI-AlongW2-2007': [], + 'Amundsen-AcrossPIG1-2020': [], + 'Amundsen-AcrossPIG2-2020': [], + 'Amundsen-AcrossPIG3-2020': [], + 'Amundsen-E-Dodson1-1994': [], + 'Amundsen-E-Dodson2-1994': [], + 'Amundsen-E-PIG-2020': [], + 'Amundsen-E-ThwaitesCrosson-1994': [], + 'Amundsen-W-PIG-1994': [], + 'Amundsen-W-PIG-2009': [], + 'Amundsen-W-PIG-2014': [], + 'Amundsen-W-PIG-2019': [], + 'Ross-C1-2004': [], + 'Ross-C2-2004': [], + 'Ross-C3-2004': [], + 'Ross-C4-2004': [], + 'Ross-C5-2004': [], + 'Ross-C6-2004': [], + 'Ross-C7-2004': [], + 'Ross-Shelf1-1994': [], + 'Amery-AlongFront-2002': [], + 'Amery-AlongFront-2006': [], + 'Amery-Lon73.5-1992': [], + 'Amery-Lon73.5-2000': [], + 'Amery-Lon75.5-1992': [], + 'Amery-Lon75.5-2008': [], + 'DML-RoiB-E-2008': [], + 'DML-RoiB-E-2019': [], + 'DML-RoiB-C-1996': [], + 'DML-RoiB-C-2006': [], + 'DML-RoiB-W-2003': [], + 'DML-Munin-Lon18-2003': [], + 'DML-Fimbul-E-Lon6.25-1991': [], + 'DML-Fimbul-C-Lon0-1992': [], + 'DML-Fimbul-C-Lon0-1996': [], + 'DML-Fimbul-C-Lon0-2008': [], + 'Weddell-RiiserLarsen-C1-1995': [], + 'Weddell-RiiserLarsen-C2-1985': [], + 'Weddell-RiiserLarsen-C2-2009': [], + 'Weddell-RiiserLarsen-C2-2010': [], + 'Weddell-RiiserLarsen-C3-1985': [], + 'Weddell-RiiserLarsen-C4-1985': [], + 'Weddell-RiiserLarsen-C5-1985': [], + 'Weddell-RiiserLarsen-C6-1987': [], + 'Weddell-StancombBrunt-1990': [], + 'Weddell-Filchner-C1-2009': [], + 'Weddell-Filchner-C2-2017': [], + 'Weddell-Filchner-C3-1995': [], + 'Weddell-Filchner-C4-1977': [], + 'Weddell-Filchner-C4-1990': [], + 'Weddell-Filchner-C5-1978': [], + 'Weddell-FilchnerTrough-1995': [], + 'Weddell-FilchnerTrough-1999': [], + 'Weddell-Ronne-C1-1999': [], + 'Weddell-FRISAlongFront-1995': [], + 'Weddell-FRISAlongFront-2018': [], + 'Weddell-Larsen-C1-1992': [], + 'Weddell-Larsen-C2-1992': [], + 'Weddell-Larsen-AlongBreak-1992': []} + + # The name of the vertical comparison grid. Valid values are 'mpas' for the + # MPAS vertical grid, 'obs' to use the locations of observations or + # any other name if the vertical grid is defined by 'verticalComparisonGrid'. + # If horizontalResolution is 'mpas', model data (both main and control) will be + # plotted on the MPAS vertical grid, regardless of the comparison grid. + #verticalComparisonGridName = mpas + verticalComparisonGridName = obs + #verticalComparisonGridName = uniform_0_to_4000m_at_10m + + # The vertical comparison grid if 'verticalComparisonGridName' is not 'mpas' or + # 'obs'. This should be numpy array of (typically negative) elevations (in m). + # The first and last entries are used as axis bounds for 'mpas' and 'obs' + # vertical comparison grids + #verticalComparisonGrid = numpy.linspace(0, -1100, 111) + + # A range for the y axis (if any) + verticalBounds = [] + + # The minimum weight of a destination cell after remapping. Any cell with + # weights lower than this threshold will therefore be masked out. + renormalizationThreshold = 0.01 + + + [antshipTemperatureTransects] + ## options related to plotting FRIS transects of potential temperature + + # colormap for model/observations + colormapNameResult = RdYlBu_r + # whether the colormap is indexed or continuous + colormapTypeResult = indexed + # color indices into colormapName for filled contours + colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 24), int) + # colormap levels/values for contour boundaries + colorbarLevelsResult = np.arange(-2.2, 2.2 + 0.2, 0.2) + # colormap levels/values for ticks (defaults to same as levels) + colorbarTicksResult = np.arange(-2.0, 2.0 + 0.4, 0.4) + + # Adding contour lines to the figure + contourLevelsResult = [-1.9] + contourThicknessResult = 0.5 + contourColorResult = black + + # colormap for differences + colormapNameDifference = balance + # whether the colormap is indexed or continuous + colormapTypeDifference = indexed + # color indices into colormapName for filled contours + colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 22), int) + # colormap levels/values for contour boundaries + colorbarLevelsDifference = np.arange(-1.0, 1.0 + 0.1, 0.1) + colorbarTicksDifference = np.arange(-1.0, 1.0 + 0.2, 0.2) + + + + [antshipSalinityTransects] + ## options related to plotting FRIS transects of salinity + + # colormap for model/observations + colormapNameResult = haline + # whether the colormap is indexed or continuous + colormapTypeResult = indexed + # color indices into colormapName for filled contours + colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 22), int) + # colormap levels/values for contour boundaries + colorbarLevelsResult = np.arange(34.0, 35.0 + 0.05, 0.05) + # colormap levels/values for ticks (defaults to same as levels) + colorbarTicksResult = np.arange(34.0, 35.0 + 0.1, 0.1) + # Adding contour lines to the figure + contourLevelsResult = [34.8] + contourThicknessResult = 0.5 + contourColorResult = black + + # colormap for differences + colormapNameDifference = balance + # whether the colormap is indexed or continuous + colormapTypeDifference = indexed + # color indices into colormapName for filled contours + colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 26), int) + # colormap levels/values for contour boundaries + colorbarLevelsDifference = np.arange(-0.6, 0.6 + 0.05, 0.05) + colorbarTicksDifference = np.arange(-0.6, 0.6 + 0.2, 0.2) + + + [antshipPotentialDensityTransects] + ## options related to plotting geojson transects of potential density + + # colormap for model/observations + colormapNameResult = Spectral_r + # whether the colormap is indexed or continuous + colormapTypeResult = indexed + # color indices into colormapName for filled contours + colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 22), int) + # colormap levels/values for contour boundaries + colorbarLevelsResult = np.arange(1027.1, 1028.1 + 0.05, 0.05) + # colormap levels/values for ticks (defaults to same as levels) + colorbarTicksResult = np.arange(1027.1, 1028.1 + 0.1, 0.1) + + # colormap for differences + colormapNameDifference = balance + # whether the colormap is indexed or continuous + colormapTypeDifference = indexed + # color indices into colormapName for filled contours + colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 26), int) + # colormap levels/values for contour boundaries + colorbarLevelsDifference = np.arange(-0.6, 0.6 + 0.05, 0.05) + colorbarTicksDifference = np.arange(-0.6, 0.6 + 0.2, 0.2) + + [antshipPotentialDensityContourTransects] + ## options related to plotting FRIS transects with potential density contours + + # Whether to plot the transect as a single contour plot, as opposed to separate + # panels for model, reference and difference. + compareAsContoursOnSinglePlot = True + + contourLevelsResult = [1027.2, 1027.4, 1027.6, 1027.7, 1027.8, 1027.85, 1027.9, 1027.95, 1028.0] + + +For details on these configuration options, see: + * :ref:`config_transects` + * :ref:`config_remapping` + * :ref:`config_colormaps` + * :ref:`config_seasons` + +Observations +------------ + +:ref:`antship` + +Example Result +-------------- + +.. image:: examples/antship_transect.png + :width: 500 px + :align: center diff --git a/docs/users_guide/tasks/examples/antship_transect.png b/docs/users_guide/tasks/examples/antship_transect.png new file mode 100644 index 000000000..636c913c2 Binary files /dev/null and b/docs/users_guide/tasks/examples/antship_transect.png differ diff --git a/mpas_analysis/__main__.py b/mpas_analysis/__main__.py index a6c137795..68da2161c 100755 --- a/mpas_analysis/__main__.py +++ b/mpas_analysis/__main__.py @@ -225,6 +225,9 @@ def build_analysis_list(config, controlConfig): analyses.append(ocean.WoceTransects(config, oceanClimatologyTasks['avg'], controlConfig)) + analyses.append(ocean.AntshipTransects(config, oceanClimatologyTasks['avg'], + controlConfig)) + analyses.append(ocean.SoseTransects(config, oceanClimatologyTasks['avg'], controlConfig)) diff --git a/mpas_analysis/default.cfg b/mpas_analysis/default.cfg index d11bd836c..2d647a168 100755 --- a/mpas_analysis/default.cfg +++ b/mpas_analysis/default.cfg @@ -520,6 +520,7 @@ argoSubdirectory = ARGO woa23Subdirectory = WOA23 schmidtkoSubdirectory = Schmidtko woceSubdirectory = WOCE +antshipSubdirectory = AntShip no3Subdirectory = BGC/NO3 po4Subdirectory = BGC/PO4 sio3Subdirectory = BGC/SiO3 @@ -2991,6 +2992,210 @@ compareAsContoursOnSinglePlot = True contourLevelsResult = [1027.2, 1027.4, 1027.6, 1027.7, 1027.8, 1027.85, 1027.9, 1027.95, 1028.0] +[antshipTransects] +## options related to plotting model ship CTD transects. + +# While all these are available for comparison (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, +# Nov, Dec, JFM, AMJ, JAS, OND, ANN), these data were collected in Antarctic summer, so for reasonable +# comparison JFM is recommended +seasons = [] +#seasons = ['JFM'] + +# The approximate horizontal resolution (in km) of each transect. Latitude/ +# longitude between observation points will be subsampled at this interval. +# Use 'obs' to indicate no subsampling. Use 'mpas' to indicate plotting of +# model data on the native grid, in which case comparison with observations +# will take place on the observation grid. +#horizontalResolution = mpas +#horizontalResolution = obs +horizontalResolution = 5 + +# Horizontal bounds of the plot (in km), or an empty list for automatic bounds +# The bounds are a 2-element list of the minimum and maximum distance along the +# transect. Note: A21=Drake Passage; A23=South Atlantic; A12=Prime Meridian +horizontalBounds = {'Belling-BelgicaTrough1-2007': [], + 'Belling-BelgicaTrough2-2007': [], + 'Belling-Cross1-2007': [], + 'Belling-Cross2-2007': [], + 'Belling-Cross3-2007': [], + 'Belling-EltaninBay-Along-2007': [], + 'Belling-GeorgeVI-AlongW1-2007': [], + 'Belling-GeorgeVI-AlongW2-2007': [], + 'Amundsen-AcrossPIG1-2020': [], + 'Amundsen-AcrossPIG2-2020': [], + 'Amundsen-AcrossPIG3-2020': [], + 'Amundsen-E-Dodson1-1994': [], + 'Amundsen-E-Dodson2-1994': [], + 'Amundsen-E-PIG-2020': [], + 'Amundsen-E-ThwaitesCrosson-1994': [], + 'Amundsen-W-PIG-1994': [], + 'Amundsen-W-PIG-2009': [], + 'Amundsen-W-PIG-2014': [], + 'Amundsen-W-PIG-2019': [], + 'Ross-C1-2004': [], + 'Ross-C2-2004': [], + 'Ross-C3-2004': [], + 'Ross-C4-2004': [], + 'Ross-C5-2004': [], + 'Ross-C6-2004': [], + 'Ross-C7-2004': [], + 'Ross-Shelf1-1994': [], + 'Amery-AlongFront-2002': [], + 'Amery-AlongFront-2006': [], + 'Amery-Lon73.5-1992': [], + 'Amery-Lon73.5-2000': [], + 'Amery-Lon75.5-1992': [], + 'Amery-Lon75.5-2008': [], + 'DML-RoiB-E-2008': [], + 'DML-RoiB-E-2019': [], + 'DML-RoiB-C-1996': [], + 'DML-RoiB-C-2006': [], + 'DML-RoiB-W-2003': [], + 'DML-Munin-Lon18-2003': [], + 'DML-Fimbul-E-Lon6.25-1991': [], + 'DML-Fimbul-C-Lon0-1992': [], + 'DML-Fimbul-C-Lon0-1996': [], + 'DML-Fimbul-C-Lon0-2008': [], + 'Weddell-RiiserLarsen-C1-1995': [], + 'Weddell-RiiserLarsen-C2-1985': [], + 'Weddell-RiiserLarsen-C2-2009': [], + 'Weddell-RiiserLarsen-C2-2010': [], + 'Weddell-RiiserLarsen-C3-1985': [], + 'Weddell-RiiserLarsen-C4-1985': [], + 'Weddell-RiiserLarsen-C5-1985': [], + 'Weddell-RiiserLarsen-C6-1987': [], + 'Weddell-StancombBrunt-1990': [], + 'Weddell-Filchner-C1-2009': [], + 'Weddell-Filchner-C2-2017': [], + 'Weddell-Filchner-C3-1995': [], + 'Weddell-Filchner-C4-1977': [], + 'Weddell-Filchner-C4-1990': [], + 'Weddell-Filchner-C5-1978': [], + 'Weddell-FilchnerTrough-1995': [], + 'Weddell-FilchnerTrough-1999': [], + 'Weddell-Ronne-C1-1999': [], + 'Weddell-FRISAlongFront-1995': [], + 'Weddell-FRISAlongFront-2018': [], + 'Weddell-Larsen-C1-1992': [], + 'Weddell-Larsen-C2-1992': [], + 'Weddell-Larsen-AlongBreak-1992': []} + +# The name of the vertical comparison grid. Valid values are 'mpas' for the +# MPAS vertical grid, 'obs' to use the locations of observations or +# any other name if the vertical grid is defined by 'verticalComparisonGrid'. +# If horizontalResolution is 'mpas', model data (both main and control) will be +# plotted on the MPAS vertical grid, regardless of the comparison grid. +#verticalComparisonGridName = mpas +verticalComparisonGridName = obs +#verticalComparisonGridName = uniform_0_to_4000m_at_10m + +# The vertical comparison grid if 'verticalComparisonGridName' is not 'mpas' or +# 'obs'. This should be numpy array of (typically negative) elevations (in m). +# The first and last entries are used as axis bounds for 'mpas' and 'obs' +# vertical comparison grids +#verticalComparisonGrid = numpy.linspace(0, -1100, 111) + +# A range for the y axis (if any) +verticalBounds = [] + +# The minimum weight of a destination cell after remapping. Any cell with +# weights lower than this threshold will therefore be masked out. +renormalizationThreshold = 0.01 + + +[antshipTemperatureTransects] +## options related to plotting FRIS transects of potential temperature + +# colormap for model/observations +colormapNameResult = RdYlBu_r +# whether the colormap is indexed or continuous +colormapTypeResult = indexed +# color indices into colormapName for filled contours +colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 24), int) +# colormap levels/values for contour boundaries +colorbarLevelsResult = np.arange(-2.2, 2.2 + 0.2, 0.2) +# colormap levels/values for ticks (defaults to same as levels) +colorbarTicksResult = np.arange(-2.0, 2.0 + 0.4, 0.4) + +# Adding contour lines to the figure +contourLevelsResult = [-1.9] +contourThicknessResult = 0.5 +contourColorResult = black + +# colormap for differences +colormapNameDifference = balance +# whether the colormap is indexed or continuous +colormapTypeDifference = indexed +# color indices into colormapName for filled contours +colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 22), int) +# colormap levels/values for contour boundaries +colorbarLevelsDifference = np.arange(-1.0, 1.0 + 0.1, 0.1) +colorbarTicksDifference = np.arange(-1.0, 1.0 + 0.2, 0.2) + + + +[antshipSalinityTransects] +## options related to plotting FRIS transects of salinity + +# colormap for model/observations +colormapNameResult = haline +# whether the colormap is indexed or continuous +colormapTypeResult = indexed +# color indices into colormapName for filled contours +colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 22), int) +# colormap levels/values for contour boundaries +colorbarLevelsResult = np.arange(34.0, 35.0 + 0.05, 0.05) +# colormap levels/values for ticks (defaults to same as levels) +colorbarTicksResult = np.arange(34.0, 35.0 + 0.1, 0.1) +# Adding contour lines to the figure +contourLevelsResult = [34.8] +contourThicknessResult = 0.5 +contourColorResult = black + +# colormap for differences +colormapNameDifference = balance +# whether the colormap is indexed or continuous +colormapTypeDifference = indexed +# color indices into colormapName for filled contours +colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 26), int) +# colormap levels/values for contour boundaries +colorbarLevelsDifference = np.arange(-0.6, 0.6 + 0.05, 0.05) +colorbarTicksDifference = np.arange(-0.6, 0.6 + 0.2, 0.2) + + +[antshipPotentialDensityTransects] +## options related to plotting geojson transects of potential density + +# colormap for model/observations +colormapNameResult = Spectral_r +# whether the colormap is indexed or continuous +colormapTypeResult = indexed +# color indices into colormapName for filled contours +colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 22), int) +# colormap levels/values for contour boundaries +colorbarLevelsResult = np.arange(1027.1, 1028.1 + 0.05, 0.05) +# colormap levels/values for ticks (defaults to same as levels) +colorbarTicksResult = np.arange(1027.1, 1028.1 + 0.1, 0.1) + +# colormap for differences +colormapNameDifference = balance +# whether the colormap is indexed or continuous +colormapTypeDifference = indexed +# color indices into colormapName for filled contours +colormapIndicesDifference = numpy.array(numpy.linspace(0, 255, 26), int) +# colormap levels/values for contour boundaries +colorbarLevelsDifference = np.arange(-0.6, 0.6 + 0.05, 0.05) +colorbarTicksDifference = np.arange(-0.6, 0.6 + 0.2, 0.2) + +[antshipPotentialDensityContourTransects] +## options related to plotting FRIS transects with potential density contours + +# Whether to plot the transect as a single contour plot, as opposed to separate +# panels for model, reference and difference. +compareAsContoursOnSinglePlot = True + +contourLevelsResult = [1027.2, 1027.4, 1027.6, 1027.7, 1027.8, 1027.85, 1027.9, 1027.95, 1028.0] + [geojsonTransects] ## options related to plotting model transects at points determined by a diff --git a/mpas_analysis/obs/observational_datasets.xml b/mpas_analysis/obs/observational_datasets.xml index 7851749b4..02dc004a5 100755 --- a/mpas_analysis/obs/observational_datasets.xml +++ b/mpas_analysis/obs/observational_datasets.xml @@ -1033,6 +1033,57 @@ + + + + AntShip sections + + + ocean + + + AntShip + ---- + Southern Ocean (90°S-45°S) conservative temperature and absolute salinity profiles compilation (OCEAN ICE D1.1) + + This profile compilation contains conservative temperature and absolute salinity profiles computed from ship CTD, Argo floats and seal-borne profilers in the Southern Ocean (90°S-45°S) since 1972, using the GSW toolbox. It provides with an opportunity to investigate the broad scale climatology of the Southern Ocean hydrography on and off the continental shelf and facilitate localized timeseries analysis of the variability across various timescales. + + + + - [OCEAN ICE D1.1](https://www.seanoe.org/data/00886/99787/) + + + "Public" data may be placed on-line. + + + [Zhou et al. (2024)](https://doi.org/10.17882/99787) + + + @techreport{Zhou2024, + author = {S. Zhou and P. Dutrieux and C. Giulivi}, + title = { Southern Ocean (90°S-45°S) conservative temperature and absolute salinity profiles compilation (OCEAN ICE D1.1)}, + year = 2024, + url = {https://doi.org/10.17882/99787}, + note = {Accessed Nov 2024} + } + + + + + (missing) + + + - antshipTransects + + + Ocean/AntShip + + + antship + + + + World Ocean Atlas v2 diff --git a/mpas_analysis/ocean/__init__.py b/mpas_analysis/ocean/__init__.py index e232f868d..841da2575 100644 --- a/mpas_analysis/ocean/__init__.py +++ b/mpas_analysis/ocean/__init__.py @@ -53,6 +53,7 @@ RegionalTSDiagrams from mpas_analysis.ocean.woce_transects import WoceTransects +from mpas_analysis.ocean.antship_transects import AntshipTransects from mpas_analysis.ocean.sose_transects import SoseTransects from mpas_analysis.ocean.woa_transects import WoaTransects from mpas_analysis.ocean.geojson_transects import GeojsonTransects diff --git a/mpas_analysis/ocean/antship_transects.py b/mpas_analysis/ocean/antship_transects.py new file mode 100644 index 000000000..d301761cb --- /dev/null +++ b/mpas_analysis/ocean/antship_transects.py @@ -0,0 +1,267 @@ +# This software is open source software available under the BSD-3 license. +# +# Copyright (c) 2022 Triad National Security, LLC. All rights reserved. +# Copyright (c) 2022 Lawrence Livermore National Security, LLC. All rights +# reserved. +# Copyright (c) 2022 UT-Battelle, LLC. All rights reserved. +# +# Additional copyright and license information can be found in the LICENSE file +# distributed with this code, or at +# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/main/LICENSE +from mpas_analysis.shared import AnalysisTask +from mpas_analysis.ocean.compute_transects_subtask import \ + ComputeTransectsSubtask, TransectsObservations + +from mpas_analysis.ocean.plot_transect_subtask import PlotTransectSubtask + +from mpas_analysis.shared.io.utility import build_obs_path + +from collections import OrderedDict + + +class AntshipTransects(AnalysisTask): + """ + Plot model output and compare it against Antarctic ship transects (https://doi.org/10.17882/99787) + """ + # Authors + # ------- + # Irena Vankova + + def __init__(self, config, mpasClimatologyTask, controlConfig=None): + """ + Construct the analysis task and adds it as a subtask of the + ``parentTask``. + + Parameters + ---------- + config : mpas_tools.config.MpasConfigParser + Configuration options + + mpasClimatologyTask : ``MpasClimatologyTask`` + The task that produced the climatology to be remapped and plotted + as a transect + + controlconfig : mpas_tools.config.MpasConfigParser, optional + Configuration options for a control run (if any) + """ + # Authors + # ------- + # Irena Vankova + + tags = ['transect', 'antarctic', 'publicObs'] + + # call the constructor from the base class (AnalysisTask) + super(AntshipTransects, self).__init__( + config=config, taskName='antshipTransects', + componentName='ocean', + tags=tags) + + sectionName = self.taskName + + seasons = config.getexpression(sectionName, 'seasons') + + horizontalResolution = config.get(sectionName, 'horizontalResolution') + + verticalComparisonGridName = config.get(sectionName, + 'verticalComparisonGridName') + + if verticalComparisonGridName in ['mpas', 'obs']: + verticalComparisonGrid = None + else: + verticalComparisonGrid = config.getexpression( + sectionName, 'verticalComparisonGrid', use_numpyfunc=True) + + verticalBounds = config.getexpression(sectionName, 'verticalBounds') + + horizontalBounds = config.getexpression( + sectionName, 'horizontalBounds') + + #observationsDirectory = build_obs_path( + # config, 'ocean', 'woceSubdirectory') + + observationsDirectory = build_obs_path( + config, 'ocean', 'antshipSubdirectory') + + origObsFileNames = \ + {'Belling-BelgicaTrough1-2007': 'Belling-BelgicaTrough1-2007.nc', + 'Belling-BelgicaTrough2-2007': 'Belling-BelgicaTrough2-2007.nc', + 'Belling-Cross1-2007': 'Belling-Cross1-2007.nc', + 'Belling-Cross2-2007': 'Belling-Cross2-2007.nc', + 'Belling-Cross3-2007': 'Belling-Cross3-2007.nc', + 'Belling-EltaninBay-Along-2007': 'Belling-EltaninBay-Along-2007.nc', + 'Belling-GeorgeVI-AlongW1-2007': 'Belling-GeorgeVI-AlongW1-2007.nc', + 'Belling-GeorgeVI-AlongW2-2007': 'Belling-GeorgeVI-AlongW2-2007.nc', + 'Amundsen-AcrossPIG1-2020': 'Amundsen-AcrossPIG1-2020.nc', + 'Amundsen-AcrossPIG2-2020': 'Amundsen-AcrossPIG2-2020.nc', + 'Amundsen-AcrossPIG3-2020': 'Amundsen-AcrossPIG3-2020.nc', + 'Amundsen-E-Dodson1-1994': 'Amundsen-E-Dodson1-1994.nc', + 'Amundsen-E-Dodson2-1994': 'Amundsen-E-Dodson2-1994.nc', + 'Amundsen-E-PIG-2020': 'Amundsen-E-PIG-2020.nc', + 'Amundsen-E-ThwaitesCrosson-1994': 'Amundsen-E-ThwaitesCrosson-1994.nc', + 'Amundsen-W-PIG-1994': 'Amundsen-W-PIG-1994.nc', + 'Amundsen-W-PIG-2009': 'Amundsen-W-PIG-2009.nc', + 'Amundsen-W-PIG-2014': 'Amundsen-W-PIG-2014.nc', + 'Amundsen-W-PIG-2019': 'Amundsen-W-PIG-2019.nc', + 'Ross-C1-2004': 'Ross-C1-2004.nc', + 'Ross-C2-2004': 'Ross-C2-2004.nc', + 'Ross-C3-2004': 'Ross-C3-2004.nc', + 'Ross-C4-2004': 'Ross-C4-2004.nc', + 'Ross-C5-2004': 'Ross-C5-2004.nc', + 'Ross-C6-2004': 'Ross-C6-2004.nc', + 'Ross-C7-2004': 'Ross-C7-2004.nc', + 'Ross-Shelf1-1994': 'Ross-Shelf1-1994.nc', + 'Amery-AlongFront-2002': 'Amery-AlongFront-2002.nc', + 'Amery-AlongFront-2006': 'Amery-AlongFront-2006.nc', + 'Amery-Lon73.5-1992': 'Amery-Lon73.5-1992.nc', + 'Amery-Lon73.5-2000': 'Amery-Lon73.5-2000.nc', + 'Amery-Lon75.5-1992': 'Amery-Lon75.5-1992.nc', + 'Amery-Lon75.5-2008': 'Amery-Lon75.5-2008.nc', + 'DML-RoiB-E-2008': 'DML-RoiB-E-2008.nc', + 'DML-RoiB-E-2019': 'DML-RoiB-E-2019.nc', + 'DML-RoiB-C-1996': 'DML-RoiB-C-1996.nc', + 'DML-RoiB-C-2006': 'DML-RoiB-C-2006.nc', + 'DML-RoiB-W-2003': 'DML-RoiB-W-2003.nc', + 'DML-Munin-Lon18-2003': 'DML-Munin-Lon18-2003.nc', + 'DML-Fimbul-E-Lon6.25-1991': 'DML-Fimbul-E-Lon6.25-1991.nc', + 'DML-Fimbul-C-Lon0-1992': 'DML-Fimbul-C-Lon0-1992.nc', + 'DML-Fimbul-C-Lon0-1996': 'DML-Fimbul-C-Lon0-1996.nc', + 'DML-Fimbul-C-Lon0-2008': 'DML-Fimbul-C-Lon0-2008.nc', + 'Weddell-RiiserLarsen-C1-1995': 'Weddell-RiiserLarsen-C1-1995.nc', + 'Weddell-RiiserLarsen-C2-1985': 'Weddell-RiiserLarsen-C2-1985.nc', + 'Weddell-RiiserLarsen-C2-2009': 'Weddell-RiiserLarsen-C2-2009.nc', + 'Weddell-RiiserLarsen-C2-2010': 'Weddell-RiiserLarsen-C2-2010.nc', + 'Weddell-RiiserLarsen-C3-1985': 'Weddell-RiiserLarsen-C3-1985.nc', + 'Weddell-RiiserLarsen-C4-1985': 'Weddell-RiiserLarsen-C4-1985.nc', + 'Weddell-RiiserLarsen-C5-1985': 'Weddell-RiiserLarsen-C5-1985.nc', + 'Weddell-RiiserLarsen-C6-1987': 'Weddell-RiiserLarsen-C6-1987.nc', + 'Weddell-StancombBrunt-1990': 'Weddell-StancombBrunt-1990.nc', + 'Weddell-Filchner-C1-2009': 'Weddell-Filchner-C1-2009.nc', + 'Weddell-Filchner-C2-2017': 'Weddell-Filchner-C2-2017.nc', + 'Weddell-Filchner-C3-1995': 'Weddell-Filchner-C3-1995.nc', + 'Weddell-Filchner-C4-1977': 'Weddell-Filchner-C4-1977.nc', + 'Weddell-Filchner-C4-1990': 'Weddell-Filchner-C4-1990.nc', + 'Weddell-Filchner-C5-1978': 'Weddell-Filchner-C5-1978.nc', + 'Weddell-FilchnerTrough-1995': 'Weddell-FilchnerTrough-1995.nc', + 'Weddell-FilchnerTrough-1999': 'Weddell-FilchnerTrough-1999.nc', + 'Weddell-Ronne-C1-1999': 'Weddell-Ronne-C1-1999.nc', + 'Weddell-FRISAlongFront-1995': 'Weddell-FRISAlongFront-1995.nc', + 'Weddell-FRISAlongFront-2018': 'Weddell-FRISAlongFront-2018.nc', + 'Weddell-Larsen-C1-1992': 'Weddell-Larsen-C1-1992.nc', + 'Weddell-Larsen-C2-1992': 'Weddell-Larsen-C2-1992.nc', + 'Weddell-Larsen-AlongBreak-1992': 'Weddell-Larsen-AlongBreak-1992.nc'} + + obsFileNames = {} + for transectName in horizontalBounds: + found = False + for name in origObsFileNames: + if transectName.startswith(name): + obsFileNames[transectName] = origObsFileNames[name] + found = True + break + if not found: + raise ValueError(f'Keys for horizontalBounds must start ' + f'with one of {list(origObsFileNames)}') + + for transectName in obsFileNames: + fileName = '{}/{}'.format(observationsDirectory, + obsFileNames[transectName]) + obsFileNames[transectName] = fileName + + fields = \ + {'temperature': + {'mpas': 'timeMonthly_avg_activeTracers_temperature', + 'obs': 'potentialTemperature', + 'titleName': 'Potential Temperature', + 'units': r'$\degree$C'}, + 'salinity': + {'mpas': 'timeMonthly_avg_activeTracers_salinity', + 'obs': 'salinity', + 'titleName': 'Salinity', + 'units': r'PSU'}, + 'potentialDensity': + {'mpas': 'timeMonthly_avg_potentialDensity', + 'obs': 'potentialDensity', + 'titleName': 'Potential Density', + 'units': r'kg m$^{-3}$'}, + 'potentialDensityContour': + {'mpas': 'timeMonthly_avg_potentialDensity', + 'obs': 'potentialDensity', + 'titleName': 'Potential Density Contours', + 'units': r'kg m$^{-3}$'}} + + transectCollectionName = 'AntShip_transects' + if horizontalResolution not in ['obs', 'mpas']: + transectCollectionName = \ + f'{transectCollectionName}_{horizontalResolution}km' + + transectsObservations = TransectsObservations(config, obsFileNames, + horizontalResolution, + transectCollectionName) + + computeTransectsSubtask = ComputeTransectsSubtask( + mpasClimatologyTask=mpasClimatologyTask, + parentTask=self, + climatologyName='AntShip', + transectCollectionName=transectCollectionName, + variableList=[field['mpas'] for field in fields.values()], + seasons=seasons, + obsDatasets=transectsObservations, + verticalComparisonGridName=verticalComparisonGridName, + verticalComparisonGrid=verticalComparisonGrid) + + plotObs = controlConfig is None + if plotObs: + + refTitleLabel = 'Observations (ship CTD)' + + diffTitleLabel = 'Model - Observations' + + else: + controlRunName = controlConfig.get('runs', 'mainRunName') + refTitleLabel = f'Control: {controlRunName}' + + diffTitleLabel = 'Main - Control' + + fieldNameDict = {'temperature': 'temperatureTransect', + 'salinity': 'salinityTransect', + 'potentialDensity': 'potentialDensityTransect', + 'potentialDensityContour': + 'potentialDensityContourTransect'} + + for fieldName in fields: + for transectName in obsFileNames: + for season in seasons: + outFileLabel = fieldNameDict[fieldName] + if plotObs: + refFieldName = fields[fieldName]['obs'] + else: + refFieldName = fields[fieldName]['mpas'] + + fieldNameUpper = fieldName[0].upper() + fieldName[1:] + titleName = fields[fieldName]['titleName'] + fieldNameInTitle = \ + f'{titleName} from {transectName.replace("_", " ")}' + + # make a new subtask for this season and comparison grid + subtask = PlotTransectSubtask( + self, season, transectName, fieldName, + computeTransectsSubtask, plotObs, controlConfig, + horizontalBounds[transectName]) + + subtask.set_plot_info( + outFileLabel=outFileLabel, + fieldNameInTitle=fieldNameInTitle, + mpasFieldName=fields[fieldName]['mpas'], + refFieldName=refFieldName, + refTitleLabel=refTitleLabel, + diffTitleLabel=diffTitleLabel, + unitsLabel=fields[fieldName]['units'], + imageCaption=f'{fieldNameInTitle} {season}', + galleryGroup='Antarctic Ship Transects', + groupSubtitle=None, + groupLink='antship', + galleryName=titleName, + configSectionName=f'antship{fieldNameUpper}Transects', + verticalBounds=verticalBounds) + + self.add_subtask(subtask) diff --git a/mpas_analysis/polar_regions.cfg b/mpas_analysis/polar_regions.cfg index 288cec1c1..a46d9fc30 100644 --- a/mpas_analysis/polar_regions.cfg +++ b/mpas_analysis/polar_regions.cfg @@ -315,6 +315,10 @@ colormapIndicesDifference = [0, 28, 57, 85, 113, 128, 128, 142, 170, 198, 227, colorbarLevelsDifference = [-0.5, -0.2, -0.1, -0.05, -0.02, 0, 0.02, 0.05, 0.1, 0.2, 0.5] +[antshipTransects] +## options related to plotting model ship CTD transects. + +seasons = ['JFM'] [oceanRegionalProfiles] ## options related to plotting vertical profiles of regional means (and diff --git a/mpas_analysis/shared/plot/vertical_section.py b/mpas_analysis/shared/plot/vertical_section.py index 4fe7a89e1..231023c09 100644 --- a/mpas_analysis/shared/plot/vertical_section.py +++ b/mpas_analysis/shared/plot/vertical_section.py @@ -895,9 +895,13 @@ def plot_vertical_section( # if any node of a triangle is masked, the triangle is masked triMask = np.amax(triMask, axis=1) unmaskedTriangulation = Triangulation(**triangulation_args) - mask_args = dict(triangulation_args) - mask_args['mask'] = triMask - maskedTriangulation = Triangulation(**mask_args) + anythingToPlot = not np.all(triMask) + if anythingToPlot: + mask_args = dict(triangulation_args) + mask_args['mask'] = triMask + maskedTriangulation = Triangulation(**mask_args) + else: + maskedTriangulation = None if contourComparisonField is not None: mask = contourComparisonField.notnull() triMask = np.logical_not(mask.values) @@ -925,37 +929,39 @@ def plot_vertical_section( plt.tricontourf(unmaskedTriangulation, zeroArray.values.ravel(), colors=invalidColor) - if not plotAsContours: - # display a heatmap of fieldArray - fieldMasked = field.where(mask, 0.0).values.ravel() + if maskedTriangulation is not None: + # there's something to plot + if not plotAsContours: + # display a heatmap of fieldArray + fieldMasked = field.where(mask, 0.0).values.ravel() - if colormapDict['levels'] is None: + if colormapDict['levels'] is None: - plotHandle = plt.tripcolor(maskedTriangulation, fieldMasked, - cmap=colormapDict['colormap'], - norm=colormapDict['norm'], - rasterized=True, shading='gouraud') - else: - plotHandle = plt.tricontourf(maskedTriangulation, fieldMasked, - cmap=colormapDict['colormap'], - norm=colormapDict['norm'], - levels=colormapDict['levels'], - extend='both') - - cbar = plt.colorbar(plotHandle, - orientation='vertical', - spacing='uniform', - aspect=9, - ticks=colormapDict['ticks']) + plotHandle = plt.tripcolor(maskedTriangulation, fieldMasked, + cmap=colormapDict['colormap'], + norm=colormapDict['norm'], + rasterized=True, shading='gouraud') + else: + plotHandle = plt.tricontourf(maskedTriangulation, fieldMasked, + cmap=colormapDict['colormap'], + norm=colormapDict['norm'], + levels=colormapDict['levels'], + extend='both') + + cbar = plt.colorbar(plotHandle, + orientation='vertical', + spacing='uniform', + aspect=9, + ticks=colormapDict['ticks']) - if colorbarLabel is not None: - cbar.set_label(colorbarLabel) + if colorbarLabel is not None: + cbar.set_label(colorbarLabel) - else: - # display a white heatmap to get a white background for non-land - zeroArray = xr.zeros_like(field) - plt.tricontourf(maskedTriangulation, zeroArray.values.ravel(), - colors='white') + else: + # display a white heatmap to get a white background for non-land + zeroArray = xr.zeros_like(field) + plt.tricontourf(maskedTriangulation, zeroArray.values.ravel(), + colors='white') ax = plt.gca() ax.set_facecolor(backgroundColor) @@ -975,8 +981,9 @@ def plot_vertical_section( fmt_string = None cs1 = None cs2 = None + plotLegend = False - if contourLevels is not None: + if contourLevels is not None and maskedTriangulation is not None: if len(contourLevels) == 0: # automatic calculation of contour levels contourLevels = None @@ -1009,11 +1016,11 @@ def plot_vertical_section( if labelContours: plt.clabel(cs2, fmt=fmt_string) - plotLegend = (((lineColor is not None and - comparisonContourLineColor is not None) or - (lineWidth is not None and - comparisonContourLineWidth is not None)) and - (plotAsContours and contourComparisonField is not None)) + plotLegend = (((lineColor is not None and + comparisonContourLineColor is not None) or + (lineWidth is not None and + comparisonContourLineWidth is not None)) and + (plotAsContours and contourComparisonField is not None)) if plotLegend: h1, _ = cs1.legend_elements() @@ -1149,7 +1156,13 @@ def _get_triangulation(x, y, mask): x = x.values.ravel() y = y.values.ravel() - maskedTriangulation = Triangulation(x=x, y=y, triangles=tris, mask=triMask) + anythingToPlot = not np.all(triMask) + if anythingToPlot: + maskedTriangulation = Triangulation(x=x, y=y, triangles=tris, + mask=triMask) + else: + maskedTriangulation = None + unmaskedTriangulation = Triangulation(x=x, y=y, triangles=tris) return maskedTriangulation, unmaskedTriangulation