Source code for mpas_analysis.ocean.climatology_map_waves

# This software is open source software available under the BSD-3 license.
#
# Copyright (c) 2020 Triad National Security, LLC. All rights reserved.
# Copyright (c) 2020 Lawrence Livermore National Security, LLC. All rights
# reserved.
# Copyright (c) 2020 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 __future__ import absolute_import, division, print_function, \
    unicode_literals

import xarray as xr
from pyremap import LatLonGridDescriptor

from mpas_analysis.shared import AnalysisTask

from mpas_analysis.shared.io.utility import build_obs_path

from mpas_analysis.shared.climatology import RemapMpasClimatologySubtask, \
    RemapObservedClimatologySubtask

from mpas_analysis.shared.plot import PlotClimatologyMapSubtask
from mpas_analysis.shared.interpolation.utility import add_periodic_lon

import datetime


[docs] class ClimatologyMapWaves(AnalysisTask): # {{{ """ An analysis task for the computation of wave climatologies """ # Authors # ------- # Steven Brus # Xylar Asay-Davis
[docs] def __init__(self, config, mpasClimatologyTask, regionMasksTask, controlConfig): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options mpasClimatologyTask : ``MpasClimatologyTask`` The task that produced the climatology to be remapped and plotted regionMasksTask : ``ComputeRegionMasks`` A task for computing region masks controlConfig : ``MpasAnalysisConfigParser`` Configuration options for a control run """ # Authors # ------- # Steven Brus # Xylar Asay-Davis fields = [{'prefix': 'significantWaveHeight', 'mpas': 'timeMonthly_avg_significantWaveHeight', 'units': r'm', 'titleName': 'Significant Wave Height', 'era5': 'swh', 'ss_cci': 'swh_mean'}, {'prefix': 'peakWavePeriod', 'mpas': 'timeMonthly_avg_peakWaveFrequency', 'units': r's', 'titleName': 'Peak Wave Period', 'era5': 'pp1d'}, {'prefix': 'iceFraction', 'mpas': 'timeMonthly_avg_iceFraction', 'units': r'', 'titleName': 'Ice Fraction'}, ] # call the constructor from the base class (AnalysisTask) super().__init__( config=config, taskName='climatologyMapWaves', componentName='ocean', tags=['climatology', 'horizontalMap', 'waves']) sectionName = self.taskName # read in what seasons we want to plot seasons = config.getexpression(sectionName, 'seasons') if len(seasons) == 0: raise ValueError(f'config section {sectionName} ' 'does not contain valid list of seasons') comparisonGridNames = config.getexpression(sectionName, 'comparisonGrids') if len(comparisonGridNames) == 0: raise ValueError(f'config section {sectionName} does ' 'not contain valid list of comparison grids') fieldList = config.getexpression(sectionName, 'fieldList') fieldsRequested = [field for field in fields if field['prefix'] in fieldList] variableListMpas = [field['mpas'] for field in fieldsRequested] variableListEraObs = [field['era5'] for field in fieldsRequested] variableListSscciObs = [field['ss_cci'] for field in fieldsRequested if 'ss_cci' in field] era5ObsStartYear = config.getint(sectionName, 'era5ObsStartYear') era5ObsEndYear = config.getint(sectionName, 'era5ObsEndYear') sscciObsStartYear = config.getint(sectionName, 'sscciObsStartYear') sscciObsEndYear = config.getint(sectionName, 'sscciObsEndYear') # the variableList variables will be added to # mpasClimatologyTask along with the seasons. remapClimatologySubtask = RemapMpasWavesClimatology( mpasClimatologyTask=mpasClimatologyTask, parentTask=self, climatologyName='wave', variableList=variableListMpas, comparisonGridNames=comparisonGridNames, seasons=seasons, iselValues=None) if controlConfig is None: observationsDirectory = build_obs_path( config, 'ocean', 'era5Subdirectory') obsFileName = f'{observationsDirectory}/ERA5_Monthly_1959-2021.nc' remapEraObservationsSubtask = RemapEra5ObservedWaveClimatology( parentTask=self, seasons=seasons, fileName=obsFileName, outFilePrefix='waveERA5', comparisonGridNames=comparisonGridNames, variableList=variableListEraObs, subtaskName='remapEraObservations') observationsDirectory = build_obs_path( config, 'ocean', 'sscciSubdirectory') obsFileName = f'{observationsDirectory}/SS_CCI_1991_2018.nc' remapSscciObservationsSubtask = RemapSscciObservedWaveClimatology( parentTask=self, seasons=seasons, fileName=obsFileName, outFilePrefix='waveSSCCI', comparisonGridNames=comparisonGridNames, variableList=variableListSscciObs, subtaskName='remapSscciObservations') else: remapObservationsSubtask = None for field in fieldsRequested: fieldPrefix = field['prefix'] upperFieldPrefix = fieldPrefix[0].upper() + fieldPrefix[1:] configSectionName = f'{self.taskName}{upperFieldPrefix}' if (field['prefix'] == 'significantWaveHeight') \ and (controlConfig is None): obs_names = ['era5', 'ss_cci'] else: obs_names = ['era5'] for obs in obs_names: if controlConfig is None: refFieldName = field[obs] diffTitleLabel = None if obs == 'era5': obs_type = 'Reanalysis' remapObservationsSubtask = \ remapEraObservationsSubtask refTitleLabel = f'{obs_type} ({obs.upper()})' \ f'{era5ObsStartYear}-{era5ObsEndYear}' elif obs == 'ss_cci': obs_type = 'Observations' remapObservationsSubtask = \ remapSscciObservationsSubtask refTitleLabel = f'{obs_type} ({obs.upper()})' \ f'{sscciObsStartYear}-{sscciObsEndYear}' galleryName = f"{field['titleName']} " \ f"({obs.upper()} {obs_type})" else: controlRunName = controlConfig.get('runs', 'mainRunName') refTitleLabel = f'{field["titleName"]}' \ f'(Control: {controlRunName})' diffTitleLabel = 'Main - Control' refFieldName = field['prefix'] galleryName = field['titleName'] outFileLabel = f'{fieldPrefix}Wave{obs}' for comparisonGridName in comparisonGridNames: for season in seasons: subtaskName = f'plot{upperFieldPrefix}_{season}_' \ f'{comparisonGridName}_{obs}' # make a new subtask for this # season and comparison grid subtask = PlotClimatologyMapSubtask( parentTask=self, season=season, comparisonGridName=comparisonGridName, remapMpasClimatologySubtask=remapClimatologySubtask, remapObsClimatologySubtask=remapObservationsSubtask, controlConfig=controlConfig, subtaskName=subtaskName) subtask.set_plot_info( outFileLabel=outFileLabel, fieldNameInTitle=field['titleName'], mpasFieldName=field['prefix'], refFieldName=refFieldName, refTitleLabel=refTitleLabel, diffTitleLabel=diffTitleLabel, unitsLabel=field['units'], imageCaption=field['titleName'], galleryGroup='Waves', groupSubtitle=None, groupLink='waves', galleryName=galleryName, configSectionName=configSectionName) self.add_subtask(subtask)
# }}} def setup_and_check(self): # {{{ """ Perform steps to set up the analysis and check for errors in the setup. """ # Authors # ------- # Steven Brus # Xylar Asay-Davis # first, call setup_and_check from the base class # (AnalysisTask), which will perform some common setup super().setup_and_check() if not self.namelist.getbool('config_use_active_wave'): raise ValueError('*** climatologyMapWaves requires ' 'config_use_active_wave\n' ' to be .true. Otherwise, no ' 'wave data is available \n' ' for plotting.')
# }}} # }}} class RemapMpasWavesClimatology(RemapMpasClimatologySubtask): # {{{ """ A subtask for reading and remapping wave climatology """ # Authors # ------- # Steven Brus, Xylar Asay-Davis def customize_masked_climatology(self, climatology, season): # {{{ """ Convert peak freqency to peak period The descriptor for the observation grid Parameters ---------- climatology : ``xarray.Dataset`` object the climatology data set season : str The name of the season to be masked Returns ------- climatology : ``xarray.Dataset`` object the modified climatology data set """ # Authors # ------- # Steven Brus # Xylar Asay-Davis if 'timeMonthly_avg_peakWaveFrequency' in climatology: climatology['peakWavePeriod'] = \ 1.0/climatology['timeMonthly_avg_peakWaveFrequency'] if 'timeMonthly_avg_significantWaveHeight' in climatology: climatology['significantWaveHeight'] = \ climatology['timeMonthly_avg_significantWaveHeight'] return climatology # }}} # }}} class RemapEra5ObservedWaveClimatology(RemapObservedClimatologySubtask): # {{{ """ A subtask for reading and remapping ERA5 wave observations """ # Authors # ------- # Steven Brus, Xylar Asay-Davis def __init__(self, parentTask, seasons, fileName, outFilePrefix, variableList, subtaskName, comparisonGridNames=['latlon']): # {{{ ''' Construct one analysis subtask for each plot (i.e. each season and comparison grid) and a subtask for computing climatologies. Parameters ---------- parentTask : ``AnalysisTask`` The parent (main) task for this subtask seasons : list of str A list of seasons (keys in ``constants.monthDictionary``) over which the climatology should be computed. fileName : str The name of the observation file outFilePrefix : str The prefix in front of output files and mapping files, typically the name of the field being remapped variableList : list List of observational variables to remap comparisonGridNames : list of str, optional The name(s) of the comparison grid to use for remapping. subtaskName : str, optional The name of the subtask ''' # Authors # ------- # Steven Brus self.variableList = variableList # call the constructor from the base class (AnalysisTask) super().__init__(parentTask, seasons, fileName, outFilePrefix, comparisonGridNames=comparisonGridNames, subtaskName=subtaskName) # }}} def get_observation_descriptor(self, fileName): # {{{ ''' get a MeshDescriptor for the observation grid Parameters ---------- fileName : str observation file name describing the source grid Returns ------- obsDescriptor : ``MeshDescriptor`` The descriptor for the observation grid ''' # Authors # ------- # Steven Brus, Xylar Asay-Davis # create a descriptor of the observation grid using the lat/lon # coordinates dsObs = self.build_observational_dataset(fileName) obsDescriptor = LatLonGridDescriptor.read(ds=dsObs, latVarName='latitude', lonVarName='longitude') return obsDescriptor # }}} def build_observational_dataset(self, fileName): # {{{ ''' read in the data sets for observations, and possibly rename some variables and dimensions Parameters ---------- fileName : str observation file name Returns ------- dsObs : ``xarray.Dataset`` The observational dataset ''' # Authors # ------- # Steven Brus, Xylar Asay-Davis sectionName = self.taskName climStartYear = self.config.getint(sectionName, 'era5ObsStartYear') climEndYear = self.config.getint(sectionName, 'era5ObsEndYear') timeStart = datetime.datetime(year=climStartYear, month=1, day=1) timeEnd = datetime.datetime(year=climEndYear, month=12, day=31) dsObs = xr.open_dataset(fileName) dsObs = dsObs.rename({'time': 'Time'}) dsObs = dsObs.sel(Time=slice(timeStart, timeEnd)) dsObs.coords['month'] = dsObs['Time.month'] dsObs.coords['year'] = dsObs['Time.year'] dsObs = dsObs[self.variableList] degrees = 'degree' in dsObs.longitude.units dsObs = add_periodic_lon(ds=dsObs, lonDim='longitude', degrees=degrees) return dsObs # }}} # }}} class RemapSscciObservedWaveClimatology(RemapObservedClimatologySubtask): # {{{ """ A subtask for reading and remapping SS_CCI wave observations """ # Authors # ------- # Steven Brus, Xylar Asay-Davis def __init__(self, parentTask, seasons, fileName, outFilePrefix, variableList, subtaskName, comparisonGridNames=['latlon']): # {{{ ''' Construct one analysis subtask for each plot (i.e. each season and comparison grid) and a subtask for computing climatologies. Parameters ---------- parentTask : ``AnalysisTask`` The parent (main) task for this subtask seasons : list of str A list of seasons (keys in ``constants.monthDictionary``) over which the climatology should be computed. fileName : str The name of the observation file outFilePrefix : str The prefix in front of output files and mapping files, typically the name of the field being remapped variableList : list List of observational variables to remap comparisonGridNames : list of str, optional The name(s) of the comparison grid to use for remapping. subtaskName : str, optional The name of the subtask ''' # Authors # ------- # Steven Brus, Xylar Asay-Davis self.variableList = variableList # call the constructor from the base class (AnalysisTask) super().__init__(parentTask, seasons, fileName, outFilePrefix, comparisonGridNames=comparisonGridNames, subtaskName=subtaskName) # }}} def get_observation_descriptor(self, fileName): # {{{ ''' get a MeshDescriptor for the observation grid Parameters ---------- fileName : str observation file name describing the source grid Returns ------- obsDescriptor : ``MeshDescriptor`` The descriptor for the observation grid ''' # Authors # ------- # Steven Brus, Xylar Asay-Davis # create a descriptor of the observation grid using the lat/lon # coordinates dsObs = self.build_observational_dataset(fileName) obsDescriptor = LatLonGridDescriptor.read(ds=dsObs, latVarName='lat', lonVarName='lon') return obsDescriptor # }}} def build_observational_dataset(self, fileName): # {{{ ''' read in the data sets for observations, and possibly rename some variables and dimensions Parameters ---------- fileName : str observation file name Returns ------- dsObs : ``xarray.Dataset`` The observational dataset ''' # Authors # ------- # Steven Brus, Xylar Asay-Davis sectionName = self.taskName climStartYear = self.config.getint(sectionName, 'sscciObsStartYear') climEndYear = self.config.getint(sectionName, 'sscciObsEndYear') timeStart = datetime.datetime(year=climStartYear, month=1, day=1) timeEnd = datetime.datetime(year=climEndYear, month=12, day=31) dsObs = xr.open_dataset(fileName) dsObs = dsObs.rename({'time': 'Time'}) dsObs = dsObs.sel(Time=slice(timeStart, timeEnd)) dsObs.coords['month'] = dsObs['Time.month'] dsObs.coords['year'] = dsObs['Time.year'] dsObs = dsObs[self.variableList] degrees = 'degree' in dsObs.lon.units dsObs = add_periodic_lon(ds=dsObs, lonDim='lon', degrees=degrees) return dsObs # }}} # }}} # vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python