diff --git a/docs/api.rst b/docs/api.rst
index be61769f3..59bf6a56a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -57,6 +57,15 @@ Ocean tasks
TimeSeriesOHC
TimeSeriesSST
+.. currentmodule:: mpas_analysis.ocean.plot_climatology_map_subtask
+
+.. autosummary::
+ :toctree: generated/
+
+ PlotClimatologyMapSubtask
+ PlotClimatologyMapSubtask.set_plot_info
+
+
Sea ice tasks
-------------
@@ -69,10 +78,18 @@ Sea ice tasks
ClimatologyMapSeaIceThick
TimeSeriesSeaIce
+.. currentmodule:: mpas_analysis.sea_ice.plot_climatology_map_subtask
+
+.. autosummary::
+ :toctree: generated/
+
+ PlotClimatologyMapSubtask
+ PlotClimatologyMapSubtask.set_plot_info
+
Configuration
==============
-.. currentmodule:: mpas_analysis.configuration.MpasAnalysisConfigParser
+.. currentmodule:: mpas_analysis.configuration
.. autosummary::
:toctree: generated/
@@ -122,7 +139,6 @@ Climatology
get_comparison_descriptor
get_antarctic_stereographic_projection
get_remapper
- get_observation_climatology_file_names
compute_monthly_climatology
compute_climatology
add_years_months_days_in_month
@@ -134,6 +150,11 @@ Climatology
RemapMpasClimatologySubtask
RemapMpasClimatologySubtask.get_file_name
+ RemapObservedClimatologySubtask
+ RemapObservedClimatologySubtask.get_observation_descriptor
+ RemapObservedClimatologySubtask.build_observational_dataset
+ RemapObservedClimatologySubtask.get_file_name
+
Time Series
-----------
.. currentmodule:: mpas_analysis.shared.time_series
diff --git a/mpas_analysis/config.default b/mpas_analysis/config.default
index 05dfeb97d..a2e135ca5 100644
--- a/mpas_analysis/config.default
+++ b/mpas_analysis/config.default
@@ -158,10 +158,14 @@ startYear = 11
# the last year over which to average climatalogies
endYear = 20
-# The comparison grid resolution in degrees
+# The comparison lat/lon grid resolution in degrees
comparisonLatResolution = 0.5
comparisonLonResolution = 0.5
+# The comparison Antarctic polar stereographic grid size and resolution in km
+comparisonAntarcticStereoWidth = 6000.
+comparisonAntarcticStereoResolution = 10.
+
# interpolation order for model and observation results. Likely values are
# 'bilinear', 'neareststod' (nearest neighbor) or 'conserve'
mpasInterpolationMethod = bilinear
@@ -524,7 +528,7 @@ colorbarLevelsDifference = [-5, -3, -2, -1, 0, 1, 2, 3, 5]
# Nov, Dec, JFM, AMJ, JAS, OND, ANN)
seasons = ['JFM', 'JAS', 'ANN']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
[climatologyMapSSS]
@@ -549,7 +553,7 @@ colorbarLevelsDifference = [-3, -2, -1, -0.5, 0, 0.5, 1, 2, 3]
# Nov, Dec, JFM, AMJ, JAS, OND, ANN)
seasons = ['JFM', 'JAS', 'ANN']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
[climatologyMapMLD]
@@ -574,7 +578,7 @@ colorbarLevelsDifference = [-150, -80, -30, -10, 0, 10, 30, 80, 150]
# Nov, Dec, JFM, AMJ, JAS, OND, ANN)
seasons = ['JFM', 'JAS', 'ANN']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
[climatologyMapSeaIceConcNH]
@@ -600,7 +604,7 @@ colorbarLevelsDifference = [-1., -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8,
# observations are only available for these seasons)
seasons = ['JFM', 'JAS']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
# reference lat/lon for sea ice plots in the northern hemisphere
@@ -636,7 +640,7 @@ colorbarLevelsDifference = [-1., -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8,
# observations are only available for these seasons)
seasons = ['DJF', 'JJA']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
# reference lat/lon for sea ice plots in the northern hemisphere
@@ -672,7 +676,7 @@ colorbarLevelsDifference = [-3., -2.5, -2, -0.5, -0.1, 0, 0.1, 0.5, 2, 2.5, 3.]
# observations are only available for these seasons)
seasons = ['FM', 'ON']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
# reference lat/lon for sea ice plots in the northern hemisphere
@@ -708,7 +712,7 @@ colorbarLevelsDifference = [-3., -2.5, -2, -0.5, -0.1, 0, 0.1, 0.5, 2, 2.5, 3.]
# observations are only available for these seasons)
seasons = ['FM', 'ON']
-# comparison grid(s) ('lonlat', 'anatarctic') on which to plot analysis
+# comparison grid(s) ('latlon', 'antarctic') on which to plot analysis
comparisonGrids = ['latlon']
# reference lat/lon for sea ice plots in the northern hemisphere
diff --git a/mpas_analysis/configuration/__init__.py b/mpas_analysis/configuration/__init__.py
index c23b4d7fb..114e54cb5 100644
--- a/mpas_analysis/configuration/__init__.py
+++ b/mpas_analysis/configuration/__init__.py
@@ -1 +1 @@
-from .MpasAnalysisConfigParser import MpasAnalysisConfigParser
+from .mpas_analysis_config_parser import MpasAnalysisConfigParser
diff --git a/mpas_analysis/configuration/MpasAnalysisConfigParser.py b/mpas_analysis/configuration/mpas_analysis_config_parser.py
similarity index 100%
rename from mpas_analysis/configuration/MpasAnalysisConfigParser.py
rename to mpas_analysis/configuration/mpas_analysis_config_parser.py
diff --git a/mpas_analysis/ocean/__init__.py b/mpas_analysis/ocean/__init__.py
index 09ceff310..1ad3ab1b5 100644
--- a/mpas_analysis/ocean/__init__.py
+++ b/mpas_analysis/ocean/__init__.py
@@ -1,7 +1,9 @@
-from .climatology_map import ClimatologyMapSST, ClimatologyMapMLD, \
- ClimatologyMapSSS
+from .climatology_map_sst import ClimatologyMapSST
+from .climatology_map_mld import ClimatologyMapMLD
+from .climatology_map_sss import ClimatologyMapSSS
+
from .time_series_ohc import TimeSeriesOHC
from .time_series_sst import TimeSeriesSST
from .index_nino34 import IndexNino34
from .streamfunction_moc import StreamfunctionMOC
-from .meridional_heat_transport import MeridionalHeatTransport
\ No newline at end of file
+from .meridional_heat_transport import MeridionalHeatTransport
diff --git a/mpas_analysis/ocean/climatology_map.py b/mpas_analysis/ocean/climatology_map.py
deleted file mode 100644
index 9a15151c0..000000000
--- a/mpas_analysis/ocean/climatology_map.py
+++ /dev/null
@@ -1,729 +0,0 @@
-"""
-An analysis tasks for comparison of 2D model fields against observations.
-Currently supports sea surface temperature (sst), sea surface salinity
-(sss) and mixed layer depth (mld)
-
-Authors
--------
-Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
-"""
-
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-import xarray as xr
-import datetime
-import numpy as np
-import os
-
-from ..shared import AnalysisTask
-
-from ..shared.plot.plotting import plot_global_comparison, \
- setup_colormap
-from ..shared.constants import constants
-
-from ..shared.io.utility import build_config_full_path, make_directories
-from ..shared.html import write_image_xml
-
-from ..shared.climatology import get_comparison_descriptor, \
- get_remapper, get_observation_climatology_file_names, \
- compute_climatology, remap_and_write_climatology, \
- RemapMpasClimatologySubtask
-
-from ..shared.grid import LatLonGridDescriptor
-
-from ..shared.mpas_xarray import mpas_xarray
-
-from ..shared.interpolation import Remapper
-
-
-class ClimatologyMapOcean(AnalysisTask): # {{{
- """
- An analysis task for comparison of 2D model fields against observations.
-
- Attributes
- ----------
- fieldName : str
- A short name of the field being analyzed
-
- fieldNameInTitle : str
- An equivalent name of the field appropriate for figure titles
-
- mpasFieldName : str
- The name of the MPAS timeSeriesStatsMonthly variable to be analyzed
-
- iselValues : dict
- A dictionary of dimensions and indices (or ``None``) used to extract
- a slice of the MPAS field.
-
- obsFileName : str
- A file containing observtions from which to construct seasonal
- climatologies.
-
- obsFieldName : str
- A variable in the observations file to plot
-
- observationTitleLabel : str
- A label on the subplot showing observations
-
- outFileLabel : str
- A prefix for the resulting image file.
-
- unitsLabel : str
- The units for the field being plotted.
-
- galleryGroup : str
- In the generated website, the name of the group of analysis galleries
- to which this analysis belongs
-
- groupLink : str
- the link within the component webpage used to identify the gallery
- group (a short version of the gallery group name with no spaces or
- punctuation)
-
- groupSubtitle : str
- a subtitle for the gallery group
-
- galleryName : str, optional
- the name of the gallery (or possibly a subtitle for the gallery group
- if there is only one gallery in the group)
-
- remapClimatologySubtask : ``RemapMpasClimatologySubtask``
- The subtask that remaps the climatologies this task will plot.
- The ``remapClimatologySubtask`` is needed to determine the file names
- of the climatology output.
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
-
- def __init__(self, config, mpasClimatologyTask, taskName, tags): # {{{
- '''
- Construct one analysis subtask for each plot (i.e. each season and
- comparison grid) and a subtask for computing climatologies.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- taskName : str
- The name of the task, typically the same as the subclass name
- except starting with lowercase (e.g. 'climatologyMapSST' for class
- 'ClimatologyMapSST')
-
- tags : list of str
- Tags used to describe the task (e.g. 'climatology',
- 'horizontalMap'). These are used to determine which tasks are
- generated (e.g. 'all_transect' or 'no_climatology' in the
- 'generate' flags)
-
- Authors
- -------
- Xylar Asay-Davis
-
- '''
- # call the constructor from the base class (AnalysisTask)
- super(ClimatologyMapOcean, self).__init__(config=config,
- taskName=taskName,
- componentName='ocean',
- tags=tags)
-
- self.mpasClimatologyTask = mpasClimatologyTask
-
- sectionName = self.taskName
-
- # read in what seasons we want to plot
- seasons = config.getExpression(sectionName, 'seasons')
-
- if len(seasons) == 0:
- raise ValueError('config section {} does not contain valid list '
- 'of seasons'.format(sectionName))
-
- # comparisonGridNames = config.getExpression(sectionName,
- # 'comparisonGrids')
-
- # if len(comparisonGridNames) == 0:
- # raise ValueError('config section {} does not contain valid list '
- # 'of comparison grids'.format(sectionName))
-
- # the variable self.mpasFieldName will be added to mpasClimatologyTask
- # along with the seasons.
- self.remapClimatologySubtask = RemapMpasClimatologySubtask(
- mpasClimatologyTask=self.mpasClimatologyTask,
- parentTask=self,
- climatologyName=self.fieldName,
- variableList=[self.mpasFieldName],
- # comparisonGridNames=comparisonGridNames,
- seasons=seasons,
- iselValues=self.iselValues)
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- # call setup_and_check from the base class (AnalysisTask),
- # which will perform some common setup, including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar
- super(ClimatologyMapOcean, self).setup_and_check()
-
- config = self.config
- self.startYear = self.mpasClimatologyTask.startYear
- self.startDate = self.mpasClimatologyTask.startDate
- self.endYear = self.mpasClimatologyTask.endYear
- self.endDate = self.mpasClimatologyTask.endDate
-
- mainRunName = config.get('runs', 'mainRunName')
- seasons = config.getExpression(self.taskName, 'seasons')
-
- # we set up the remapper here because ESFM_RegridWeightGen seems to
- # have trouble if it runs in another process (or in several at once)
- self._setup_obs_remapper()
-
- self.xmlFileNames = []
- self.filePrefixes = {}
-
- for season in seasons:
- filePrefix = '{}_{}_{}_years{:04d}-{:04d}'.format(
- self.outFileLabel, mainRunName,
- season, self.startYear, self.endYear)
- self.xmlFileNames.append('{}/{}.xml'.format(self.plotsDirectory,
- filePrefix))
- self.filePrefixes[season] = filePrefix
-
- # make the mapping directory, because doing so within each process
- # seems to be giving ESMF_RegridWeightGen some trouble
- mappingSubdirectory = build_config_full_path(config, 'output',
- 'mappingSubdirectory')
- make_directories(mappingSubdirectory)
-
- # }}}
-
- def run_task(self): # {{{
- """
- Plots a comparison of ACME/MPAS output to SST, MLD or SSS observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
-
- self.logger.info("\nPlotting 2-d maps of {} climatologies...".format(
- self.fieldNameInTitle))
-
- # get local versions of member variables for convenience
- config = self.config
- fieldName = self.fieldName
-
- try:
- restartFileName = self.runStreams.readpath('restart')[0]
- except ValueError:
- raise IOError('No MPAS-O restart file found: need at least one '
- 'restart file for ocn_modelvsobs calculation')
-
- mainRunName = self.config.get('runs', 'mainRunName')
-
- seasons = config.getExpression(self.taskName, 'seasons')
-
- (colormapResult, colorbarLevelsResult) = setup_colormap(
- config, self.taskName, suffix='Result')
- (colormapDifference, colorbarLevelsDifference) = setup_colormap(
- config, self.taskName, suffix='Difference')
-
- dsRestart = xr.open_dataset(restartFileName)
- dsRestart = mpas_xarray.subset_variables(dsRestart, ['maxLevelCell'])
-
- dsObs = None
-
- # Interpolate and compute biases
- for season in seasons:
-
- monthValues = constants.monthDictionary[season]
-
- remappedFileName = self.remapClimatologySubtask.get_file_name(
- season=season, stage='remapped', comparisonGridName='latlon')
-
- remappedClimatology = xr.open_dataset(remappedFileName)
-
- modelOutput = \
- remappedClimatology[self.mpasFieldName].values
-
- lon = remappedClimatology['lon'].values
- lat = remappedClimatology['lat'].values
-
- lonTarg, latTarg = np.meshgrid(lon, lat)
-
- # now the observations
- (climatologyFileName, remappedFileName) = \
- get_observation_climatology_file_names(
- config=config, fieldName=fieldName, monthNames=season,
- componentName='ocean', remapper=self.obsRemapper)
-
- if not os.path.exists(remappedFileName):
-
- if dsObs is None:
- # load the observations the first time
- dsObs = self._build_observational_dataset()
-
- seasonalClimatology = compute_climatology(
- dsObs, monthValues, maskVaries=True)
-
- if self.obsRemapper is None:
- # no need to remap because the observations are on the
- # comparison grid already
- remappedClimatology = seasonalClimatology
- else:
- remappedClimatology = \
- remap_and_write_climatology(
- config, seasonalClimatology, climatologyFileName,
- remappedFileName, self.obsRemapper,
- logger=self.logger)
-
- else:
-
- remappedClimatology = xr.open_dataset(remappedFileName)
- observations = remappedClimatology[self.obsFieldName].values
-
- bias = modelOutput - observations
-
- filePrefix = self.filePrefixes[season]
- outFileName = '{}/{}.png'.format(self.plotsDirectory, filePrefix)
- title = '{} ({}, years {:04d}-{:04d})'.format(
- self.fieldNameInTitle, season, self.startYear,
- self.endYear)
- plot_global_comparison(config,
- lonTarg,
- latTarg,
- modelOutput,
- observations,
- bias,
- colormapResult,
- colorbarLevelsResult,
- colormapDifference,
- colorbarLevelsDifference,
- fileout=outFileName,
- title=title,
- modelTitle='{}'.format(mainRunName),
- obsTitle=self.observationTitleLabel,
- diffTitle='Model-Observations',
- cbarlabel=self.unitsLabel)
-
- caption = '{} {}'.format(season, self.imageCaption)
- write_image_xml(
- config,
- filePrefix,
- componentName='Ocean',
- componentSubdirectory='ocean',
- galleryGroup=self.galleryGroup,
- groupSubtitle=self.groupSubtitle,
- groupLink=self.groupLink,
- gallery=self.galleryName,
- thumbnailDescription=season,
- imageDescription=caption,
- imageCaption=caption)
-
- # }}}
-
- def _setup_obs_remapper(self): # {{{
- """
- Set up the remapper for remapping from the MPAS to the comparison
- grid.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- config = self.config
- fieldName = self.fieldName
-
- seasons = config.getExpression(self.taskName, 'seasons')
-
- # make reamppers
- comparisonDescriptor = get_comparison_descriptor(
- config=config, comparisonGridName='latlon')
- self.comparisonGridName = comparisonDescriptor.meshName
-
- obsDescriptor = LatLonGridDescriptor.read(fileName=self.obsFileName,
- latVarName='lat',
- lonVarName='lon')
-
- origObsRemapper = Remapper(comparisonDescriptor, obsDescriptor)
-
- season = seasons[0]
-
- # now the observations
- (climatologyFileName, remappedFileName) = \
- get_observation_climatology_file_names(
- config=config, fieldName=fieldName, monthNames=season,
- componentName='ocean', remapper=origObsRemapper)
-
- # make the remapper for the climatology
- self.obsRemapper = get_remapper(
- config=config, sourceDescriptor=obsDescriptor,
- comparisonDescriptor=comparisonDescriptor,
- mappingFilePrefix='map_obs_{}'.format(fieldName),
- method=config.get('oceanObservations',
- 'interpolationMethod'),
- logger=self.logger)
-
- # }}}
- # }}}
-
-
-class ClimatologyMapSST(ClimatologyMapOcean): # {{{
- """
- An analysis task for comparison of sea surface temperature (sst) against
- observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
- def __init__(self, config, mpasClimatologyTask): # {{{
- """
- Construct the analysis task.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- Authors
- -------
- Xylar Asay-Davis
- """
- self.fieldName = 'sst'
- self.fieldNameInTitle = 'SST'
- self.mpasFieldName = 'timeMonthly_avg_activeTracers_temperature'
- self.iselValues = {'nVertLevels': 0}
-
- # call the constructor from the base class (ClimatologyMapOcean)
- super(ClimatologyMapSST, self).__init__(
- config=config,
- mpasClimatologyTask=mpasClimatologyTask,
- taskName='climatologyMapSST',
- tags=['climatology', 'horizontalMap', self.fieldName])
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
-
- self.outFileLabel = 'sstHADOI'
-
- observationsDirectory = build_config_full_path(
- self.config, 'oceanObservations',
- '{}Subdirectory'.format(self.fieldName))
-
- self.obsFileName = \
- "{}/MODEL.SST.HAD187001-198110.OI198111-201203.nc".format(
- observationsDirectory)
-
- self.obsFieldName = 'SST'
-
- climStartYear = self.config.getint('oceanObservations',
- 'sstClimatologyStartYear')
- climEndYear = self.config.getint('oceanObservations',
- 'sstClimatologyEndYear')
-
- if climStartYear < 1925:
- period = 'pre-industrial'
- else:
- period = 'present-day'
-
- # Set appropriate figure labels for SST
- self.observationTitleLabel = \
- 'Observations (Hadley/OI, {} {:04d}-{:04d})'.format(period,
- climStartYear,
- climEndYear)
- self.unitsLabel = r'$^o$C'
-
- # variables for XML and webpages
- self.galleryGroup = 'Global Sea Surface Temperature'
- self.groupSubtitle = None
- self.groupLink = 'sst'
- self.galleryName = 'Observations: Hadley-NOAA-OI'
- self.imageCaption = 'Mean Sea Surface Temperature'
-
- # call setup_and_check from the base class (AnalysisTask),
- # which will perform some common setup, including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar
- super(ClimatologyMapSST, self).setup_and_check()
-
- # }}}
-
- def _build_observational_dataset(self): # {{{
- '''
- read in the data sets for observations, and possibly rename some
- variables and dimensions
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- climStartYear = self.config.getint('oceanObservations',
- 'sstClimatologyStartYear')
- climEndYear = self.config.getint('oceanObservations',
- 'sstClimatologyEndYear')
- timeStart = datetime.datetime(year=climStartYear, month=1, day=1)
- timeEnd = datetime.datetime(year=climEndYear, month=12, day=31)
-
- dsObs = xr.open_mfdataset(self.obsFileName)
- dsObs.rename({'time': 'Time'}, inplace=True)
- dsObs = dsObs.sel(Time=slice(timeStart, timeEnd))
- dsObs.coords['month'] = dsObs['Time.month']
- dsObs.coords['year'] = dsObs['Time.year']
-
- return dsObs # }}}
-
- # }}}
-
-
-class ClimatologyMapSSS(ClimatologyMapOcean): # {{{
- """
- An analysis task for comparison of sea surface salinity (sss) against
- observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
- def __init__(self, config, mpasClimatologyTask): # {{{
- """
- Construct the analysis task.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- Authors
- -------
- Xylar Asay-Davis
- """
- self.fieldName = 'sss'
- self.fieldNameInTitle = 'SSS'
- self.mpasFieldName = 'timeMonthly_avg_activeTracers_salinity'
- self.iselValues = {'nVertLevels': 0}
-
- # call the constructor from the base class (ClimatologyMapOcean)
- super(ClimatologyMapSSS, self).__init__(
- config=config,
- mpasClimatologyTask=mpasClimatologyTask,
- taskName='climatologyMapSSS',
- tags=['climatology', 'horizontalMap', self.fieldName])
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
-
- self.outFileLabel = 'sssAquarius'
-
- observationsDirectory = build_config_full_path(
- self.config, 'oceanObservations',
- '{}Subdirectory'.format(self.fieldName))
-
- self.obsFileName = \
- '{}/Aquarius_V3_SSS_Monthly.nc'.format(
- observationsDirectory)
-
- self.obsFieldName = 'SSS'
-
- self.observationTitleLabel = 'Observations (Aquarius, 2011-2014)'
- self.unitsLabel = 'PSU'
-
- # variables for XML and webpages
- self.galleryGroup = 'Global Sea Surface Salinity'
- self.groupSubtitle = None
- self.groupLink = 'sss'
- self.galleryName = 'Observations: Aquarius'
- self.imageCaption = 'Mean Sea Surface Salinity'
-
- # call setup_and_check from the base class (AnalysisTask),
- # which will perform some common setup, including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar
- super(ClimatologyMapSSS, self).setup_and_check()
-
- # }}}
-
- def _build_observational_dataset(self): # {{{
- '''
- read in the data sets for observations, and possibly rename some
- variables and dimensions
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- timeStart = datetime.datetime(2011, 8, 1)
- timeEnd = datetime.datetime(2014, 12, 31)
-
- dsObs = xr.open_mfdataset(self.obsFileName)
- dsObs.rename({'time': 'Time'}, inplace=True)
- dsObs = dsObs.sel(Time=slice(timeStart, timeEnd))
- dsObs.coords['month'] = dsObs['Time.month']
- dsObs.coords['year'] = dsObs['Time.year']
-
- return dsObs # }}}
-
- # }}}
-
-
-class ClimatologyMapMLD(ClimatologyMapOcean): # {{{
- """
- An analysis task for comparison of mixed layer depth (mld) against
- observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
- def __init__(self, config, mpasClimatologyTask): # {{{
- """
- Construct the analysis task.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- Authors
- -------
- Xylar Asay-Davis
- """
-
- self.fieldName = 'mld'
- self.fieldNameInTitle = 'MLD'
- self.mpasFieldName = 'timeMonthly_avg_dThreshMLD'
- self.iselValues = None
-
- # call the constructor from the base class (ClimatologyMapOcean)
- super(ClimatologyMapMLD, self).__init__(
- config=config,
- mpasClimatologyTask=mpasClimatologyTask,
- taskName='climatologyMapMLD',
- tags=['climatology', 'horizontalMap', self.fieldName])
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
-
- self.outFileLabel = 'mldHolteTalleyARGO'
-
- observationsDirectory = build_config_full_path(
- self.config, 'oceanObservations',
- '{}Subdirectory'.format(self.fieldName))
-
- self.obsFileName = \
- '{}/holtetalley_mld_climatology.nc'.format(
- observationsDirectory)
-
- self.obsFieldName = 'mld_dt_mean'
-
- # Set appropriate MLD figure labels
- self.observationTitleLabel = \
- 'Observations (HolteTalley density threshold MLD)'
- self.unitsLabel = 'm'
-
- # variables for XML and webpages
- self.galleryGroup = 'Global Mixed-Layer Depth'
- self.groupSubtitle = None
- self.groupLink = 'mld'
- self.galleryName = 'Observations: Holte-Talley ARGO'
- self.imageCaption = 'Mean Mixed-Layer Depth'
-
- # call setup_and_check from the base class (AnalysisTask),
- # which will perform some common setup, including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar
- super(ClimatologyMapMLD, self).setup_and_check()
-
- # }}}
-
- def _build_observational_dataset(self): # {{{
- '''
- read in the data sets for observations, and possibly rename some
- variables and dimensions
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- # Load MLD observational data
- dsObs = xr.open_mfdataset(self.obsFileName)
-
- # Increment month value to be consistent with the model output
- dsObs.iMONTH.values += 1
- # Rename the dimensions to be consistent with other obs. data sets
- dsObs.rename({'month': 'calmonth', 'lat': 'latCoord',
- 'lon': 'lonCoord'}, inplace=True)
- dsObs.rename({'iMONTH': 'Time', 'iLAT': 'lat', 'iLON': 'lon'},
- inplace=True)
-
- # set the coordinates now that the dimensions have the same names
- dsObs.coords['lat'] = dsObs['latCoord']
- dsObs.coords['lon'] = dsObs['lonCoord']
- dsObs.coords['Time'] = dsObs['calmonth']
- dsObs.coords['month'] = ('Time', np.array(dsObs['calmonth'], int))
-
- # no meaningful year since this is already a climatology
- dsObs.coords['year'] = ('Time', np.ones(dsObs.dims['Time'], int))
-
- dsObs = mpas_xarray.subset_variables(dsObs, [self.obsFieldName,
- 'month'])
-
- return dsObs # }}}
-
-# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/ocean/climatology_map_mld.py b/mpas_analysis/ocean/climatology_map_mld.py
new file mode 100644
index 000000000..89218be6c
--- /dev/null
+++ b/mpas_analysis/ocean/climatology_map_mld.py
@@ -0,0 +1,205 @@
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+import numpy as np
+
+from ..shared import AnalysisTask
+
+from ..shared.io.utility import build_config_full_path
+
+from ..shared.climatology import RemapMpasClimatologySubtask, \
+ RemapObservedClimatologySubtask
+
+from .plot_climatology_map_subtask import PlotClimatologyMapSubtask
+
+from ..shared.grid import LatLonGridDescriptor
+
+from ..shared.mpas_xarray import mpas_xarray
+
+
+class ClimatologyMapMLD(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of mixed layer depth (mld) against
+ observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+ def __init__(self, config, mpasClimatologyTask): # {{{
+ """
+ Construct the analysis task.
+
+ Parameters
+ ----------
+ config : instance of MpasAnalysisConfigParser
+ Contains configuration options
+
+ mpasClimatologyTask : ``MpasClimatologyTask``
+ The task that produced the climatology to be remapped and plotted
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ fieldName = 'mld'
+ # call the constructor from the base class (AnalysisTask)
+ super(ClimatologyMapMLD, self).__init__(
+ config=config, taskName='climatologyMapMLD',
+ componentName='ocean',
+ tags=['climatology', 'horizontalMap', fieldName])
+
+ sectionName = self.taskName
+
+ mpasFieldName = 'timeMonthly_avg_dThreshMLD'
+ iselValues = None
+
+ observationTitleLabel = \
+ 'Observations (HolteTalley density threshold MLD)'
+
+ observationsDirectory = build_config_full_path(
+ config, 'oceanObservations',
+ '{}Subdirectory'.format(fieldName))
+
+ obsFileName = \
+ "{}/holtetalley_mld_climatology.nc".format(observationsDirectory)
+ obsFieldName = 'mld'
+
+ # read in what seasons we want to plot
+ seasons = config.getExpression(sectionName, 'seasons')
+
+ if len(seasons) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of seasons'.format(sectionName))
+
+ comparisonGridNames = config.getExpression(sectionName,
+ 'comparisonGrids')
+
+ if len(comparisonGridNames) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of comparison grids'.format(sectionName))
+
+ # the variable 'timeMonthly_avg_dThreshMLD' will be added to
+ # mpasClimatologyTask along with the seasons.
+ remapClimatologySubtask = RemapMpasClimatologySubtask(
+ mpasClimatologyTask=mpasClimatologyTask,
+ parentTask=self,
+ climatologyName=fieldName,
+ variableList=[mpasFieldName],
+ comparisonGridNames=comparisonGridNames,
+ seasons=seasons,
+ iselValues=iselValues)
+
+ remapObservationsSubtask = RemapObservedMLDClimatology(
+ parentTask=self, seasons=seasons, fileName=obsFileName,
+ outFilePrefix=obsFieldName,
+ comparisonGridNames=comparisonGridNames)
+ self.add_subtask(remapObservationsSubtask)
+ for comparisonGridName in comparisonGridNames:
+ for season in seasons:
+ # make a new subtask for this season and comparison grid
+ subtask = PlotClimatologyMapSubtask(self, season,
+ comparisonGridName,
+ remapClimatologySubtask,
+ remapObservationsSubtask)
+
+ subtask.set_plot_info(
+ outFileLabel='mldHolteTalleyARGO',
+ fieldNameInTitle='MLD',
+ mpasFieldName=mpasFieldName,
+ obsFieldName=obsFieldName,
+ observationTitleLabel=observationTitleLabel,
+ unitsLabel=r'm',
+ imageCaption='Mean Mixed-Layer Depth',
+ galleryGroup='Mixed-Layer Depth',
+ groupSubtitle=None,
+ groupLink='mld',
+ galleryName='Observations: Holte-Talley ARGO')
+
+ self.add_subtask(subtask)
+ # }}}
+ # }}}
+
+
+class RemapObservedMLDClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping MLD observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # Load MLD observational data
+ dsObs = xr.open_dataset(fileName)
+
+ # Increment month value to be consistent with the model output
+ dsObs.iMONTH.values += 1
+ # Rename the dimensions to be consistent with other obs. data sets
+ dsObs.rename({'month': 'calmonth', 'lat': 'latCoord',
+ 'lon': 'lonCoord', 'mld_dt_mean': 'mld'}, inplace=True)
+ dsObs.rename({'iMONTH': 'Time', 'iLAT': 'lat', 'iLON': 'lon'},
+ inplace=True)
+
+ # set the coordinates now that the dimensions have the same names
+ dsObs.coords['lat'] = dsObs['latCoord']
+ dsObs.coords['lon'] = dsObs['lonCoord']
+ dsObs.coords['Time'] = dsObs['calmonth']
+ dsObs.coords['month'] = ('Time', np.array(dsObs['calmonth'], int))
+
+ # no meaningful year since this is already a climatology
+ dsObs.coords['year'] = ('Time', np.ones(dsObs.dims['Time'], int))
+
+ dsObs = mpas_xarray.subset_variables(dsObs, ['mld', 'month'])
+ return dsObs # }}}
+
+ # }}}
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/ocean/climatology_map_sss.py b/mpas_analysis/ocean/climatology_map_sss.py
new file mode 100644
index 000000000..168a3d59c
--- /dev/null
+++ b/mpas_analysis/ocean/climatology_map_sss.py
@@ -0,0 +1,192 @@
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+import datetime
+
+from ..shared import AnalysisTask
+
+from ..shared.io.utility import build_config_full_path
+
+from ..shared.climatology import RemapMpasClimatologySubtask, \
+ RemapObservedClimatologySubtask
+
+from .plot_climatology_map_subtask import PlotClimatologyMapSubtask
+
+from ..shared.grid import LatLonGridDescriptor
+
+
+class ClimatologyMapSSS(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of sea surface salinity (sss) against
+ observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+ def __init__(self, config, mpasClimatologyTask): # {{{
+ """
+ Construct the analysis task.
+
+ Parameters
+ ----------
+ config : instance of MpasAnalysisConfigParser
+ Contains configuration options
+
+ mpasClimatologyTask : ``MpasClimatologyTask``
+ The task that produced the climatology to be remapped and plotted
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ self.fieldName = 'sss'
+ # call the constructor from the base class (AnalysisTask)
+ super(ClimatologyMapSSS, self).__init__(
+ config=config, taskName='climatologyMapSSS',
+ componentName='ocean',
+ tags=['climatology', 'horizontalMap', self.fieldName])
+
+ mpasFieldName = 'timeMonthly_avg_activeTracers_salinity'
+ iselValues = {'nVertLevels': 0}
+
+ sectionName = self.taskName
+
+ observationTitleLabel = \
+ 'Observations (Aquarius, 2011-2014)'
+
+ observationsDirectory = build_config_full_path(
+ config, 'oceanObservations',
+ '{}Subdirectory'.format(self.fieldName))
+
+ obsFileName = \
+ "{}/Aquarius_V3_SSS_Monthly.nc".format(
+ observationsDirectory)
+ obsFieldName = 'sss'
+
+ # read in what seasons we want to plot
+ seasons = config.getExpression(sectionName, 'seasons')
+
+ if len(seasons) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of seasons'.format(sectionName))
+
+ comparisonGridNames = config.getExpression(sectionName,
+ 'comparisonGrids')
+
+ if len(comparisonGridNames) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of comparison grids'.format(sectionName))
+
+ # the variable self.mpasFieldName will be added to mpasClimatologyTask
+ # along with the seasons.
+ remapClimatologySubtask = RemapMpasClimatologySubtask(
+ mpasClimatologyTask=mpasClimatologyTask,
+ parentTask=self,
+ climatologyName=self.fieldName,
+ variableList=[mpasFieldName],
+ comparisonGridNames=comparisonGridNames,
+ seasons=seasons,
+ iselValues=iselValues)
+
+ remapObservationsSubtask = RemapObservedSSSClimatology(
+ parentTask=self, seasons=seasons, fileName=obsFileName,
+ outFilePrefix=obsFieldName,
+ comparisonGridNames=comparisonGridNames)
+ self.add_subtask(remapObservationsSubtask)
+ for comparisonGridName in comparisonGridNames:
+ for season in seasons:
+ # make a new subtask for this season and comparison grid
+ subtask = PlotClimatologyMapSubtask(self, season,
+ comparisonGridName,
+ remapClimatologySubtask,
+ remapObservationsSubtask)
+
+ subtask.set_plot_info(
+ outFileLabel='sssAquarius',
+ fieldNameInTitle='SSS',
+ mpasFieldName=mpasFieldName,
+ obsFieldName=obsFieldName,
+ observationTitleLabel=observationTitleLabel,
+ unitsLabel=r'PSU',
+ imageCaption='Mean Sea Surface Salinity',
+ galleryGroup='Sea Surface Salinity',
+ groupSubtitle=None,
+ groupLink='sss',
+ galleryName='Observations: Aquarius')
+
+ self.add_subtask(subtask)
+ # }}}
+ # }}}
+
+
+class RemapObservedSSSClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping SSS observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ timeStart = datetime.datetime(2011, 8, 1)
+ timeEnd = datetime.datetime(2014, 12, 31)
+
+ dsObs = xr.open_dataset(fileName)
+ dsObs.rename({'time': 'Time', 'SSS': 'sss'}, inplace=True)
+ dsObs = dsObs.sel(Time=slice(timeStart, timeEnd))
+ dsObs.coords['month'] = dsObs['Time.month']
+ dsObs.coords['year'] = dsObs['Time.year']
+
+ return dsObs # }}}
+
+ # }}}
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/ocean/climatology_map_sst.py b/mpas_analysis/ocean/climatology_map_sst.py
new file mode 100644
index 000000000..db4bcca5a
--- /dev/null
+++ b/mpas_analysis/ocean/climatology_map_sst.py
@@ -0,0 +1,208 @@
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+import datetime
+
+from ..shared import AnalysisTask
+
+from ..shared.io.utility import build_config_full_path
+
+from ..shared.climatology import RemapMpasClimatologySubtask, \
+ RemapObservedClimatologySubtask
+
+from .plot_climatology_map_subtask import PlotClimatologyMapSubtask
+
+from ..shared.grid import LatLonGridDescriptor
+
+
+class ClimatologyMapSST(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of sea surface temperature (sst) against
+ observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+ def __init__(self, config, mpasClimatologyTask): # {{{
+ """
+ Construct the analysis task.
+
+ Parameters
+ ----------
+ config : instance of MpasAnalysisConfigParser
+ Contains configuration options
+
+ mpasClimatologyTask : ``MpasClimatologyTask``
+ The task that produced the climatology to be remapped and plotted
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ self.fieldName = 'sst'
+ # call the constructor from the base class (AnalysisTask)
+ super(ClimatologyMapSST, self).__init__(
+ config=config, taskName='climatologyMapSST',
+ componentName='ocean',
+ tags=['climatology', 'horizontalMap', self.fieldName])
+
+ mpasFieldName = 'timeMonthly_avg_activeTracers_temperature'
+ iselValues = {'nVertLevels': 0}
+
+ sectionName = self.taskName
+
+ climStartYear = config.getint('oceanObservations',
+ 'sstClimatologyStartYear')
+ climEndYear = config.getint('oceanObservations',
+ 'sstClimatologyEndYear')
+
+ if climStartYear < 1925:
+ period = 'pre-industrial'
+ else:
+ period = 'present-day'
+
+ observationTitleLabel = \
+ 'Observations (Hadley/OI, {} {:04d}-{:04d})'.format(period,
+ climStartYear,
+ climEndYear)
+
+ observationsDirectory = build_config_full_path(
+ config, 'oceanObservations',
+ '{}Subdirectory'.format(self.fieldName))
+
+ obsFileName = \
+ "{}/MODEL.SST.HAD187001-198110.OI198111-201203.nc".format(
+ observationsDirectory)
+ obsFieldName = 'sst'
+
+ # read in what seasons we want to plot
+ seasons = config.getExpression(sectionName, 'seasons')
+
+ if len(seasons) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of seasons'.format(sectionName))
+
+ comparisonGridNames = config.getExpression(sectionName,
+ 'comparisonGrids')
+
+ if len(comparisonGridNames) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of comparison grids'.format(sectionName))
+
+ # the variable self.mpasFieldName will be added to mpasClimatologyTask
+ # along with the seasons.
+ remapClimatologySubtask = RemapMpasClimatologySubtask(
+ mpasClimatologyTask=mpasClimatologyTask,
+ parentTask=self,
+ climatologyName=self.fieldName,
+ variableList=[mpasFieldName],
+ comparisonGridNames=comparisonGridNames,
+ seasons=seasons,
+ iselValues=iselValues)
+
+ remapObservationsSubtask = RemapObservedSSTClimatology(
+ parentTask=self, seasons=seasons, fileName=obsFileName,
+ outFilePrefix=obsFieldName,
+ comparisonGridNames=comparisonGridNames)
+ self.add_subtask(remapObservationsSubtask)
+ for comparisonGridName in comparisonGridNames:
+ for season in seasons:
+ # make a new subtask for this season and comparison grid
+ subtask = PlotClimatologyMapSubtask(self, season,
+ comparisonGridName,
+ remapClimatologySubtask,
+ remapObservationsSubtask)
+
+ subtask.set_plot_info(
+ outFileLabel='sstHADOI',
+ fieldNameInTitle='SST',
+ mpasFieldName=mpasFieldName,
+ obsFieldName=obsFieldName,
+ observationTitleLabel=observationTitleLabel,
+ unitsLabel=r'$^o$C',
+ imageCaption='Mean Sea Surface Temperature',
+ galleryGroup='Sea Surface Temperature',
+ groupSubtitle=None,
+ groupLink='sst',
+ galleryName='Observations: Hadley-NOAA-OI')
+
+ self.add_subtask(subtask)
+ # }}}
+ # }}}
+
+
+class RemapObservedSSTClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping SST observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ climStartYear = self.config.getint('oceanObservations',
+ 'sstClimatologyStartYear')
+ climEndYear = self.config.getint('oceanObservations',
+ 'sstClimatologyEndYear')
+ timeStart = datetime.datetime(year=climStartYear, month=1, day=1)
+ timeEnd = datetime.datetime(year=climEndYear, month=12, day=31)
+
+ dsObs = xr.open_dataset(fileName)
+ dsObs.rename({'time': 'Time', 'SST': 'sst'}, inplace=True)
+ dsObs = dsObs.sel(Time=slice(timeStart, timeEnd))
+ dsObs.coords['month'] = dsObs['Time.month']
+ dsObs.coords['year'] = dsObs['Time.year']
+
+ return dsObs # }}}
+
+ # }}}
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/ocean/plot_climatology_map_subtask.py b/mpas_analysis/ocean/plot_climatology_map_subtask.py
new file mode 100644
index 000000000..ae79e1f21
--- /dev/null
+++ b/mpas_analysis/ocean/plot_climatology_map_subtask.py
@@ -0,0 +1,420 @@
+"""
+An analysis subtasks for plotting comparison of 2D model fields against
+observations.
+
+Authors
+-------
+Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+"""
+
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+import numpy as np
+
+from ..shared import AnalysisTask
+
+from ..shared.plot.plotting import plot_global_comparison, \
+ setup_colormap, plot_polar_projection_comparison
+
+from ..shared.html import write_image_xml
+
+from ..shared.grid import interp_extrap_corner
+
+
+class PlotClimatologyMapSubtask(AnalysisTask): # {{{
+ """
+ An analysis task for plotting 2D model fields against observations.
+
+ Attributes
+ ----------
+ season : str
+ A season (key in ``shared.constants.monthDictionary``) to be
+ plotted.
+
+ comparisonGridName : {'latlon', 'antarctic'}
+ The name of the comparison grid to plot.
+
+ remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask``
+ The subtask for remapping the MPAS climatology that this subtask
+ will plot
+
+ remapObsClimatologySubtask : ``RemapObservedClimatologySubtask``
+ The subtask for remapping the observational climatology that this
+ subtask will plot
+
+ outFileLabel : str
+ The prefix on each plot and associated XML file
+
+ fieldNameInTitle : str
+ The name of the field being plotted, as used in the plot title
+
+ mpasFieldName : str
+ The name of the variable in the MPAS timeSeriesStatsMonthly output
+
+ obsFieldName : str
+ The name of the variable to use from the observations file
+
+ observationTitleLabel : str
+ the title of the observations subplot
+
+ unitsLabel : str
+ the units of the plotted field, to be displayed on color bars
+
+ imageCaption : str
+ the caption when mousing over the plot or displaying it full
+ screen
+
+ galleryGroup : str
+ the name of the group of galleries in which this plot belongs
+
+ groupSubtitle : str
+ the subtitle of the group in which this plot belongs (or blank
+ if none)
+
+ groupLink : str
+ a short name (with no spaces) for the link to the gallery group
+
+ galleryName : str
+ the name of the gallery in which this plot belongs
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+
+ def __init__(self, parentTask, season, comparisonGridName,
+ remapMpasClimatologySubtask, remapObsClimatologySubtask):
+ # {{{
+ '''
+ 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 (master) task for this subtask
+
+ season : str
+ A season (key in ``shared.constants.monthDictionary``) to be
+ plotted.
+
+ comparisonGridName : {'latlon', 'antarctic'}
+ The name of the comparison grid to plot.
+
+ remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask``
+ The subtask for remapping the MPAS climatology that this subtask
+ will plot
+
+ remapObsClimatologySubtask : ``RemapObservedClimatologySubtask``
+ The subtask for remapping the observational climatology that this
+ subtask will plot
+
+ Authors
+ -------
+ Xylar Asay-Davis
+
+ '''
+
+ self.season = season
+ self.comparisonGridName = comparisonGridName
+ self.remapMpasClimatologySubtask = remapMpasClimatologySubtask
+ self.remapObsClimatologySubtask = remapObsClimatologySubtask
+ subtaskName = 'plot{}_{}'.format(season, comparisonGridName)
+ config = parentTask.config
+ taskName = parentTask.taskName
+ tags = parentTask.tags
+
+ # call the constructor from the base class (AnalysisTask)
+ super(PlotClimatologyMapSubtask, self).__init__(
+ config=config, taskName=taskName, subtaskName=subtaskName,
+ componentName='ocean', tags=tags)
+
+ # this task should not run until the remapping subtasks are done, since
+ # it relies on data from those subtasks
+ self.run_after(remapMpasClimatologySubtask)
+ self.run_after(remapObsClimatologySubtask)
+ # }}}
+
+ def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName,
+ obsFieldName, observationTitleLabel, unitsLabel,
+ imageCaption, galleryGroup, groupSubtitle, groupLink,
+ galleryName): # {{{
+ """
+ Store attributes related to plots, plot file names and HTML output.
+
+ Parameters
+ ----------
+ outFileLabel : str
+ The prefix on each plot and associated XML file
+
+ fieldNameInTitle : str
+ The name of the field being plotted, as used in the plot title
+
+ mpasFieldName : str
+ The name of the variable in the MPAS timeSeriesStatsMonthly output
+
+ obsFieldName : str
+ The name of the variable to use from the observations file
+
+ observationTitleLabel : str
+ the title of the observations subplot
+
+ unitsLabel : str
+ the units of the plotted field, to be displayed on color bars
+
+ imageCaption : str
+ the caption when mousing over the plot or displaying it full
+ screen
+
+ galleryGroup : str
+ the name of the group of galleries in which this plot belongs
+
+ groupSubtitle : str
+ the subtitle of the group in which this plot belongs (or blank
+ if none)
+
+ groupLink : str
+ a short name (with no spaces) for the link to the gallery group
+
+ galleryName : str
+ the name of the gallery in which this plot belongs
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ self.outFileLabel = outFileLabel
+ self.fieldNameInTitle = fieldNameInTitle
+ self.mpasFieldName = mpasFieldName
+ self.obsFieldName = obsFieldName
+ self.observationTitleLabel = observationTitleLabel
+ self.unitsLabel = unitsLabel
+
+ # xml/html related variables
+ self.imageCaption = imageCaption
+ self.galleryGroup = galleryGroup
+ self.groupSubtitle = groupSubtitle
+ self.groupLink = groupLink
+ self.galleryName = galleryName
+ # }}}
+
+ def setup_and_check(self): # {{{
+ """
+ Perform steps to set up the analysis and check for errors in the setup.
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ # first, call setup_and_check from the base class (AnalysisTask),
+ # which will perform some common setup, including storing:
+ # self.runDirectory , self.historyDirectory, self.plotsDirectory,
+ # self.namelist, self.runStreams, self.historyStreams,
+ # self.calendar
+ super(PlotClimatologyMapSubtask, self).setup_and_check()
+
+ config = self.config
+ self.startYear = config.getint('climatology', 'startYear')
+ self.endYear = config.getint('climatology', 'endYear')
+ self.startDate = config.get('climatology', 'startDate')
+ self.endDate = config.get('climatology', 'endDate')
+
+ mainRunName = config.get('runs', 'mainRunName')
+
+ self.xmlFileNames = []
+ self.filePrefixes = {}
+
+ if self.comparisonGridName == 'latlon':
+ self.filePrefix = '{}_{}_{}_years{:04d}-{:04d}'.format(
+ self.outFileLabel, mainRunName,
+ self.season, self.startYear, self.endYear)
+ else:
+ self.filePrefix = '{}_{}_{}_{}_years{:04d}-{:04d}'.format(
+ self.outFileLabel, self.comparisonGridName,
+ mainRunName, self.season, self.startYear, self.endYear)
+ self.xmlFileNames.append('{}/{}.xml'.format(self.plotsDirectory,
+ self.filePrefix))
+ # }}}
+
+ def run_task(self): # {{{
+ """
+ Plots a comparison of ACME/MPAS output to SST, MLD or SSS observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+
+ season = self.season
+ comparisonGridName = self.comparisonGridName
+ self.logger.info("\nPlotting 2-d maps of {} climatologies for {} on "
+ "the {} grid...".format(self.fieldNameInTitle,
+ season, comparisonGridName))
+
+ # first read the model climatology
+ remappedFileName = self.remapMpasClimatologySubtask.get_file_name(
+ season=season, stage='remapped',
+ comparisonGridName=comparisonGridName)
+
+ remappedModelClimatology = xr.open_dataset(remappedFileName)
+
+ # now the observations
+ remappedFileName = self.remapObsClimatologySubtask.get_file_name(
+ stage='remapped', season=season,
+ comparisonGridName=comparisonGridName)
+
+ remappedObsClimatology = xr.open_dataset(remappedFileName)
+
+ if self.comparisonGridName == 'latlon':
+ self._plot_latlon(remappedModelClimatology, remappedObsClimatology)
+ elif self.comparisonGridName == 'antarctic':
+ self._plot_antarctic(remappedModelClimatology,
+ remappedObsClimatology)
+ # }}}
+
+ def _plot_latlon(self, remappedModelClimatology, remappedObsClimatology):
+ # {{{
+ """ plotting a global lat-lon data set """
+
+ season = self.season
+ config = self.config
+ configSectionName = self.taskName
+
+ mainRunName = config.get('runs', 'mainRunName')
+
+ (colormapResult, colorbarLevelsResult) = setup_colormap(
+ config, configSectionName, suffix='Result')
+ (colormapDifference, colorbarLevelsDifference) = setup_colormap(
+ config, configSectionName, suffix='Difference')
+
+ modelOutput = \
+ remappedModelClimatology[self.mpasFieldName].values
+
+ lon = remappedModelClimatology['lon'].values
+ lat = remappedModelClimatology['lat'].values
+
+ lonTarg, latTarg = np.meshgrid(lon, lat)
+
+ observations = remappedObsClimatology[self.obsFieldName].values
+
+ bias = modelOutput - observations
+
+ filePrefix = self.filePrefix
+ outFileName = '{}/{}.png'.format(self.plotsDirectory, filePrefix)
+ title = '{} ({}, years {:04d}-{:04d})'.format(
+ self.fieldNameInTitle, season, self.startYear,
+ self.endYear)
+ plot_global_comparison(config,
+ lonTarg,
+ latTarg,
+ modelOutput,
+ observations,
+ bias,
+ colormapResult,
+ colorbarLevelsResult,
+ colormapDifference,
+ colorbarLevelsDifference,
+ fileout=outFileName,
+ title=title,
+ modelTitle='{}'.format(mainRunName),
+ obsTitle=self.observationTitleLabel,
+ diffTitle='Model-Observations',
+ cbarlabel=self.unitsLabel)
+
+ caption = '{} {}'.format(season, self.imageCaption)
+ write_image_xml(
+ config,
+ filePrefix,
+ componentName='Ocean',
+ componentSubdirectory='ocean',
+ galleryGroup='Global {}'.format(self.galleryGroup),
+ groupSubtitle=self.groupSubtitle,
+ groupLink=self.groupLink,
+ gallery=self.galleryName,
+ thumbnailDescription=season,
+ imageDescription=caption,
+ imageCaption=caption)
+
+ # }}}
+
+ def _plot_antarctic(self, remappedModelClimatology,
+ remappedObsClimatology): # {{{
+ """ plotting an Antarctic data set """
+
+ season = self.season
+ comparisonGridName = self.comparisonGridName
+ config = self.config
+ configSectionName = self.taskName
+
+ mainRunName = config.get('runs', 'mainRunName')
+
+ oceanMask = remappedModelClimatology['validMask'].values
+ self.landMask = np.ma.masked_array(
+ np.ones(oceanMask.shape),
+ mask=np.logical_not(np.isnan(oceanMask)))
+
+ modelOutput = \
+ remappedModelClimatology[self.mpasFieldName].values
+
+ observations = remappedObsClimatology[self.obsFieldName].values
+
+ bias = modelOutput - observations
+
+ x = interp_extrap_corner(remappedModelClimatology['x'].values)
+ y = interp_extrap_corner(remappedModelClimatology['y'].values)
+
+ filePrefix = self.filePrefix
+ outFileName = '{}/{}.png'.format(self.plotsDirectory, filePrefix)
+ title = '{} ({}, years {:04d}-{:04d})'.format(
+ self.fieldNameInTitle, season, self.startYear,
+ self.endYear)
+
+ if config.has_option(configSectionName, 'colormapIndicesResult'):
+ colorMapType = 'indexed'
+ elif config.has_option(configSectionName, 'normTypeResult'):
+ colorMapType = 'norm'
+ else:
+ raise ValueError('config section {} contains neither the info'
+ 'for an indexed color map nor for computing a '
+ 'norm'.format(configSectionName))
+
+ plot_polar_projection_comparison(
+ config,
+ x,
+ y,
+ self.landMask,
+ modelOutput,
+ observations,
+ bias,
+ fileout=outFileName,
+ colorMapSectionName=configSectionName,
+ colorMapType=colorMapType,
+ title=title,
+ modelTitle='{}'.format(mainRunName),
+ obsTitle=self.observationTitleLabel,
+ diffTitle='Model - Observations',
+ cbarlabel=self.unitsLabel)
+
+ upperGridName = comparisonGridName[0].upper() + comparisonGridName[1:]
+ caption = '{} {}'.format(season, self.imageCaption)
+ write_image_xml(
+ config,
+ filePrefix,
+ componentName='Ocean',
+ componentSubdirectory='ocean',
+ galleryGroup='{} {}'.format(upperGridName,
+ self.galleryGroup),
+ groupSubtitle=self.groupSubtitle,
+ groupLink=self.groupLink,
+ gallery=self.galleryName,
+ thumbnailDescription=season,
+ imageDescription=caption,
+ imageCaption=caption)
+
+ # }}}
+ # }}}
+
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/__init__.py b/mpas_analysis/sea_ice/__init__.py
index ff456b4bf..bc745d9d9 100644
--- a/mpas_analysis/sea_ice/__init__.py
+++ b/mpas_analysis/sea_ice/__init__.py
@@ -1,2 +1,3 @@
-from .climatology_map import ClimatologyMapSeaIceConc, ClimatologyMapSeaIceThick
-from .time_series import TimeSeriesSeaIce
\ No newline at end of file
+from .climatology_map_sea_ice_conc import ClimatologyMapSeaIceConc
+from .climatology_map_sea_ice_thick import ClimatologyMapSeaIceThick
+from .time_series import TimeSeriesSeaIce
diff --git a/mpas_analysis/sea_ice/climatology_map.py b/mpas_analysis/sea_ice/climatology_map.py
deleted file mode 100644
index 33a8672aa..000000000
--- a/mpas_analysis/sea_ice/climatology_map.py
+++ /dev/null
@@ -1,591 +0,0 @@
-
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-import os
-import os.path
-
-import numpy.ma as ma
-import numpy as np
-
-import xarray as xr
-
-from ..shared.climatology import get_comparison_descriptor, \
- get_remapper, get_observation_climatology_file_names, \
- remap_and_write_climatology, RemapMpasClimatologySubtask
-
-from ..shared.grid import LatLonGridDescriptor
-
-from ..shared.plot.plotting import plot_polar_comparison, \
- setup_colormap
-
-from ..shared.io.utility import build_config_full_path, make_directories
-from ..shared.io import write_netcdf
-from ..shared.html import write_image_xml
-
-from .sea_ice_analysis_task import SeaIceAnalysisTask
-
-
-class ClimatologyMapSeaIce(SeaIceAnalysisTask):
- """
- General comparison of 2-d model fields against data. Currently only
- supports sea ice concentration and sea ice thickness
-
- Attributes
- ----------
- remapClimatologySubtask : ``RemapMpasClimatologySubtask``
- The subtask that remaps the climatologies this task will plot.
- The ``remapClimatologySubtask`` is needed to determine the file names
- of the climatology output.
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
-
- Authors
- -------
- Xylar Asay-Davis, Milena Veneziani
- """
-
- def __init__(self, config, mpasClimatologyTask, taskName, tags): # {{{
- '''
- Construct one analysis subtask for each plot (i.e. each season and
- comparison grid) and a subtask for computing climatologies.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- taskName : str
- The name of the task, typically the same as the subclass name
- except starting with lowercase (e.g. 'climatologyMapSST' for class
- 'ClimatologyMapSST')
-
- tags : list of str
- Tags used to describe the task (e.g. 'climatology',
- 'horizontalMap'). These are used to determine which tasks are
- generated (e.g. 'all_transect' or 'no_climatology' in the
- 'generate' flags)
-
- Authors
- -------
- Xylar Asay-Davis
-
- '''
- # call the constructor from the base class (AnalysisTask)
- super(ClimatologyMapSeaIce, self).__init__(config=config,
- taskName=taskName,
- componentName='seaIce',
- tags=tags)
-
- self.mpasClimatologyTask = mpasClimatologyTask
-
- sectionName = self.taskName
-
- # read in what seasons we want to plot
- seasons = config.getExpression(sectionName, 'seasons')
-
- if len(seasons) == 0:
- raise ValueError('config section {} does not contain valid list '
- 'of seasons'.format(sectionName))
-
- # the variable self.mpasFieldName will be added to mpasClimatologyTask
- # along with the seasons.
- self.remapClimatologySubtask = RemapMpasClimatologySubtask(
- mpasClimatologyTask=self.mpasClimatologyTask,
- parentTask=self,
- climatologyName='{}{}'.format(self.fieldName, self.hemisphere),
- variableList=[self.mpasFieldName],
- seasons=seasons,
- iselValues=self.iselValues)
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- # first, call setup_and_check from the base class (SeaIceAnalysisTask),
- # which will perform some common setup
- super(ClimatologyMapSeaIce, self).setup_and_check()
-
- self.startYear = self.mpasClimatologyTask.startYear
- self.startDate = self.mpasClimatologyTask.startDate
- self.endYear = self.mpasClimatologyTask.endYear
- self.endDate = self.mpasClimatologyTask.endDate
-
- mainRunName = self.config.get('runs', 'mainRunName')
-
- # we set up the remapper here because ESFM_RegridWeightGen seems to
- # have trouble if it runs in another process (or in several at once)
- self._setup_obs_remapper()
-
- self.xmlFileNames = []
-
- for info in self.obsAndPlotInfo:
- season = info['season']
- outFileLabel = info['outFileLabel']
-
- filePrefix = '{}_{}_{}_years{:04d}-{:04d}'.format(
- outFileLabel, mainRunName,
- season, self.startYear, self.endYear)
- self.xmlFileNames.append('{}/{}.xml'.format(self.plotsDirectory,
- filePrefix))
- info['filePrefix'] = filePrefix
-
- # make the mapping directory, because doing so within each process
- # seems to be giving ESMF_RegridWeightGen some trouble
- mappingSubdirectory = \
- build_config_full_path(self.config, 'output',
- 'mappingSubdirectory')
- make_directories(mappingSubdirectory)
-
- # }}}
-
- def run_task(self): # {{{
- """
- Performs analysis of sea-ice properties by comparing with
- previous model results and/or observations.
-
- Authors
- -------
- Xylar Asay-Davis, Milena Veneziani
- """
-
- self.logger.info("\nPlotting 2-d maps of {} climatologies...".format(
- self.fieldNameInTitle))
-
- config = self.config
-
- mainRunName = config.get('runs', 'mainRunName')
- startYear = self.startYear
- endYear = self.endYear
-
- hemisphere = self.hemisphere
- sectionName = self.sectionName
- vertical = config.getboolean(sectionName, 'vertical')
- if hemisphere == 'NH':
- plotProjection = 'npstere'
- else:
- plotProjection = 'spstere'
-
- for info in self.obsAndPlotInfo:
- season = info['season']
-
- (colormapResult, colorbarLevelsResult) = setup_colormap(
- config, sectionName, suffix='Result')
- (colormapDifference, colorbarLevelsDifference) = setup_colormap(
- config, sectionName, suffix='Difference')
-
- referenceLongitude = config.getfloat(sectionName,
- 'referenceLongitude')
- minimumLatitude = config.getfloat(sectionName,
- 'minimumLatitude')
-
- fieldName = '{}{}'.format(self.mpasFieldName, hemisphere)
-
- remappedFileName = self.remapClimatologySubtask.get_file_name(
- season=season, stage='remapped', comparisonGridName='latlon')
- remappedClimatology = xr.open_dataset(remappedFileName)
-
- modelOutput = remappedClimatology[self.mpasFieldName].values
- if self.maskValue is not None:
- modelOutput = ma.masked_values(modelOutput, self.maskValue)
- lon = remappedClimatology['lon'].values
- lat = remappedClimatology['lat'].values
-
- lonTarg, latTarg = np.meshgrid(lon, lat)
-
- obsFileName = info['obsFileName']
-
- if not os.path.isfile(obsFileName):
- raise OSError('Obs file {} not found.'.format(
- obsFileName))
-
- fieldName = '{}{}'.format(self.obsFieldName, hemisphere)
-
- (obsClimatologyFileName, obsRemappedFileName) = \
- get_observation_climatology_file_names(
- config=config, fieldName=fieldName,
- monthNames=season, componentName=self.componentName,
- remapper=self.obsRemapper)
-
- if not os.path.exists(obsRemappedFileName):
-
- # load the observations the first time
- seasonalClimatology = self._build_observational_dataset(
- obsFileName)
- write_netcdf(seasonalClimatology, obsClimatologyFileName)
-
- if self.obsRemapper is None:
- # no need to remap because the observations are on the
- # comparison grid already
- remappedClimatology = seasonalClimatology
- else:
- remappedClimatology = \
- remap_and_write_climatology(
- config, seasonalClimatology,
- obsClimatologyFileName,
- obsRemappedFileName, self.obsRemapper,
- logger=self.logger)
-
- else:
-
- remappedClimatology = xr.open_dataset(obsRemappedFileName)
-
- observations = remappedClimatology[self.obsFieldName].values
- if self.maskValue is not None:
- observations = ma.masked_values(observations, self.maskValue)
-
- difference = modelOutput - observations
-
- startYear = self.startYear
- endYear = self.endYear
- observationTitleLabel = info['observationTitleLabel']
- filePrefix = info['filePrefix']
- title = '{} ({}, years {:04d}-{:04d})'.format(
- self.fieldNameInTitle, season, startYear, endYear)
- fileout = '{}/{}.png'.format(self.plotsDirectory, filePrefix)
- plot_polar_comparison(
- config,
- lonTarg,
- latTarg,
- modelOutput,
- observations,
- difference,
- colormapResult,
- colorbarLevelsResult,
- colormapDifference,
- colorbarLevelsDifference,
- title=title,
- fileout=fileout,
- plotProjection=plotProjection,
- latmin=minimumLatitude,
- lon0=referenceLongitude,
- modelTitle=mainRunName,
- obsTitle=observationTitleLabel,
- diffTitle='Model-Observations',
- cbarlabel=self.unitsLabel,
- vertical=vertical)
-
- galleryName = info['galleryName']
- imageDescription = info['imageDescription']
- imageCaption = info['imageCaption']
- write_image_xml(
- config,
- filePrefix,
- componentName='Sea Ice',
- componentSubdirectory='sea_ice',
- galleryGroup=self.galleryGroup,
- groupSubtitle=self.groupSubtitle,
- groupLink=self.groupLink,
- gallery=galleryName,
- thumbnailDescription=season,
- imageDescription=imageDescription,
- imageCaption=imageCaption)
- # }}}
-
- def _setup_obs_remapper(self): # {{{
- """
- Set up the remapper for remapping from the MPAS to the comparison
- grid.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- config = self.config
- hemisphere = self.hemisphere
-
- info = self.obsAndPlotInfo[0]
-
- fieldName = '{}{}'.format(self.mpasFieldName, hemisphere)
-
- obsFileName = info['obsFileName']
-
- obsDescriptor = LatLonGridDescriptor.read(fileName=obsFileName,
- latVarName='t_lat',
- lonVarName='t_lon')
-
- comparisonDescriptor = get_comparison_descriptor(
- config=config, comparisonGridName='latlon')
-
- fieldName = '{}{}'.format(self.obsFieldName, hemisphere)
- self.obsRemapper = get_remapper(
- config=config,
- sourceDescriptor=obsDescriptor,
- comparisonDescriptor=comparisonDescriptor,
- mappingFilePrefix='map_obs_{}'.format(fieldName),
- method=config.get('seaIceObservations',
- 'interpolationMethod'),
- logger=self.logger)
- # }}}
- # }}}
-
-
-class ClimatologyMapSeaIceConc(ClimatologyMapSeaIce): # {{{
- """
- An analysis task for comparison of sea ice concentration against
- observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
- def __init__(self, config, mpasClimatologyTask, hemisphere):
- # {{{
- """
- Construct the analysis task.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- hemisphere : {'NH', 'SH'}
- The hemisphere to plot
-
- Authors
- -------
- Xylar Asay-Davis
- """
- taskName = 'climatologyMapSeaIceConc{}'.format(hemisphere)
-
- self.sectionName = taskName
-
- self.fieldName = 'seaIceConc'
- self.hemisphere = hemisphere
- self.fieldNameInTitle = 'Sea ice concentration'
- self.seasons = config.getExpression(self.sectionName, 'seasons')
- self.observationPrefixes = config.getExpression(self.sectionName,
- 'observationPrefixes')
-
- self.mpasFieldName = 'timeMonthly_avg_iceAreaCell'
- self.iselValues = None
-
- tags = ['climatology', 'horizontalMap', self.fieldName]
-
- # call the constructor from the base class (AnalysisTask)
- super(ClimatologyMapSeaIceConc, self).__init__(
- config=config, mpasClimatologyTask=mpasClimatologyTask,
- taskName=taskName, tags=tags)
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- hemisphere = self.hemisphere
- if hemisphere == 'NH':
- hemisphereLong = 'Northern'
- else:
- hemisphereLong = 'Southern'
-
- self.obsAndPlotInfo = []
- for prefix in self.observationPrefixes:
- for season in self.seasons:
- localDict = {}
- localDict['season'] = season
- localDict['obsFileName'] = \
- build_config_full_path(
- self.config, 'seaIceObservations',
- 'concentration{}{}_{}'.format(prefix,
- hemisphere,
- season))
- localDict['observationTitleLabel'] = \
- 'Observations (SSM/I {})'.format(prefix)
- localDict['outFileLabel'] = \
- 'iceconc{}{}'.format(prefix, hemisphere)
-
- localDict['galleryName'] = \
- 'Observations: SSM/I {}'.format(prefix)
-
- localDict['imageDescription'] = \
- '{} Climatology Map of {}-Hemisphere Sea-Ice ' \
- 'Concentration'.format(season, hemisphereLong)
-
- localDict['imageCaption'] = \
- '{}.
Observations: SSM/I {}'.format(
- localDict['imageDescription'], prefix)
-
- self.obsAndPlotInfo.append(localDict)
-
- self.obsFieldName = 'AICE'
-
- self.unitsLabel = 'fraction'
-
- self.maskValue = None
-
- # variables for XML and webpages
- self.galleryGroup = '{}-Hemisphere Sea-Ice Concentration'.format(
- hemisphereLong)
- self.groupSubtitle = None
- self.groupLink = '{}_conc'.format(hemisphere.lower())
-
- # call setup_and_check from the base class
- # (ClimatologyMapSeaIce), which will perform some common setup,
- # including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar, self.namelistMap, self.streamMap, self.variableMap
- super(ClimatologyMapSeaIceConc, self).setup_and_check()
-
- # }}}
-
- def _build_observational_dataset(self, obsFileName): # {{{
- '''
- read in the data sets for observations, and possibly rename some
- variables and dimensions
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- dsObs = xr.open_mfdataset(obsFileName)
-
- return dsObs # }}}
-
- # }}}
-
-
-class ClimatologyMapSeaIceThick(ClimatologyMapSeaIce): # {{{
- """
- An analysis task for comparison of sea ice thickness against observations
-
- Authors
- -------
- Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
- """
- def __init__(self, config, mpasClimatologyTask, hemisphere):
- # {{{
- """
- Construct the analysis task.
-
- Parameters
- ----------
- config : instance of MpasAnalysisConfigParser
- Contains configuration options
-
- mpasClimatologyTask : ``MpasClimatologyTask``
- The task that produced the climatology to be remapped and plotted
-
- hemisphere : {'NH', 'SH'}
- The hemisphere to plot
-
- Authors
- -------
- Xylar Asay-Davis
- """
- taskName = 'climatologyMapSeaIceThick{}'.format(hemisphere)
-
- self.sectionName = taskName
-
- self.fieldName = 'seaIceThick'
- self.hemisphere = hemisphere
- self.fieldNameInTitle = 'Sea ice thickness'
- self.seasons = config.getExpression(self.sectionName, 'seasons')
-
- self.mpasFieldName = 'timeMonthly_avg_iceVolumeCell'
- self.iselValues = None
-
- tags = ['climatology', 'horizontalMap', self.fieldName]
-
- # call the constructor from the base class (AnalysisTask)
- super(ClimatologyMapSeaIceThick, self).__init__(
- config=config, mpasClimatologyTask=mpasClimatologyTask,
- taskName=taskName, tags=tags)
-
- # }}}
-
- def setup_and_check(self): # {{{
- """
- Perform steps to set up the analysis and check for errors in the setup.
-
- Authors
- -------
- Xylar Asay-Davis
- """
- hemisphere = self.hemisphere
-
- if hemisphere == 'NH':
- hemisphereLong = 'Northern'
- else:
- hemisphereLong = 'Southern'
-
- self.obsAndPlotInfo = []
- for season in self.seasons:
- localDict = {}
- localDict['season'] = season
- localDict['obsFileName'] = \
- build_config_full_path(
- self.config, 'seaIceObservations',
- 'thickness{}_{}'.format(hemisphere, season))
- localDict['observationTitleLabel'] = 'Observations (ICESat)'
- localDict['outFileLabel'] = 'icethick{}'.format(hemisphere)
-
- localDict['galleryName'] = 'Observations: ICESat'
-
- localDict['imageDescription'] = \
- '{} Climatology Map of {}-Hemisphere Sea-Ice ' \
- 'Thickness.'.format(season, hemisphereLong)
-
- localDict['imageCaption'] = localDict['imageDescription']
-
- self.obsAndPlotInfo.append(localDict)
-
- self.obsFieldName = 'HI'
-
- self.unitsLabel = 'm'
-
- self.maskValue = 0
-
- # variables for XML and webpages
- self.galleryGroup = '{}-Hemisphere Sea-Ice Thicknesss'.format(
- hemisphereLong)
- self.groupSubtitle = None
- self.groupLink = '{}_thick'.format(hemisphere.lower())
-
- # call setup_and_check from the base class
- # (ClimatologyMapSeaIce), which will perform some common setup,
- # including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar, self.namelistMap, self.streamMap, self.variableMap
- super(ClimatologyMapSeaIceThick, self).setup_and_check()
-
- # }}}
-
- def _build_observational_dataset(self, obsFileName): # {{{
- '''
- read in the data sets for observations, and possibly rename some
- variables and dimensions
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- dsObs = xr.open_mfdataset(obsFileName)
-
- return dsObs # }}}
-
- # }}}
-
-# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py b/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py
new file mode 100644
index 000000000..9f26dfc70
--- /dev/null
+++ b/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py
@@ -0,0 +1,215 @@
+
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+
+from ..shared import AnalysisTask
+
+from ..shared.climatology import RemapMpasClimatologySubtask, \
+ RemapObservedClimatologySubtask
+
+from .plot_climatology_map_subtask import PlotClimatologyMapSubtask
+
+from ..shared.io.utility import build_config_full_path
+
+from ..shared.grid import LatLonGridDescriptor
+
+
+class ClimatologyMapSeaIceConc(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of sea ice concentration against
+ observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+ def __init__(self, config, mpasClimatologyTask, hemisphere):
+ # {{{
+ """
+ Construct the analysis task.
+
+ Parameters
+ ----------
+ config : instance of MpasAnalysisConfigParser
+ Contains configuration options
+
+ mpasClimatologyTask : ``MpasClimatologyTask``
+ The task that produced the climatology to be remapped and plotted
+
+ hemisphere : {'NH', 'SH'}
+ The hemisphere to plot
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ taskName = 'climatologyMapSeaIceConc{}'.format(hemisphere)
+
+ self.fieldName = 'seaIceConc'
+ # call the constructor from the base class (AnalysisTask)
+ super(ClimatologyMapSeaIceConc, self).__init__(
+ config=config, taskName=taskName,
+ componentName='seaIce',
+ tags=['climatology', 'horizontalMap', self.fieldName])
+
+ mpasFieldName = 'timeMonthly_avg_iceAreaCell'
+ iselValues = None
+
+ sectionName = taskName
+
+ if hemisphere == 'NH':
+ hemisphereLong = 'Northern'
+ else:
+ hemisphereLong = 'Southern'
+
+ obsFieldName = 'seaIceConc'
+
+ # read in what seasons we want to plot
+ seasons = config.getExpression(sectionName, 'seasons')
+
+ if len(seasons) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of seasons'.format(sectionName))
+
+ comparisonGridNames = config.getExpression(sectionName,
+ 'comparisonGrids')
+
+ if len(comparisonGridNames) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of comparison grids'.format(sectionName))
+
+ # the variable self.mpasFieldName will be added to mpasClimatologyTask
+ # along with the seasons.
+ remapClimatologySubtask = RemapMpasClimatologySubtask(
+ mpasClimatologyTask=mpasClimatologyTask,
+ parentTask=self,
+ climatologyName='{}{}'.format(self.fieldName, hemisphere),
+ variableList=[mpasFieldName],
+ comparisonGridNames=comparisonGridNames,
+ seasons=seasons,
+ iselValues=iselValues)
+
+ observationPrefixes = config.getExpression(sectionName,
+ 'observationPrefixes')
+ for prefix in observationPrefixes:
+ for season in seasons:
+ observationTitleLabel = \
+ 'Observations (SSM/I {})'.format(prefix)
+
+ obsFileName = build_config_full_path(
+ config, 'seaIceObservations',
+ 'concentration{}{}_{}'.format(prefix,
+ hemisphere,
+ season))
+
+ remapObservationsSubtask = RemapObservedConcClimatology(
+ parentTask=self, seasons=[season],
+ fileName=obsFileName,
+ outFilePrefix='{}{}{}_{}'.format(obsFieldName, prefix,
+ hemisphere, season),
+ comparisonGridNames=comparisonGridNames,
+ subtaskName='remapObservations_{}{}'.format(prefix,
+ season))
+ self.add_subtask(remapObservationsSubtask)
+ for comparisonGridName in comparisonGridNames:
+
+ imageDescription = \
+ '{} Climatology Map of {}-Hemisphere Sea-Ice ' \
+ 'Concentration'.format(season, hemisphereLong)
+ imageCaption = '{}.
Observations: SSM/I {}'.format(
+ imageDescription, prefix)
+ galleryGroup = \
+ '{}-Hemisphere Sea-Ice Concentration'.format(
+ hemisphereLong)
+ # make a new subtask for this season and comparison grid
+ subtask = PlotClimatologyMapSubtask(
+ self, hemisphere, season, comparisonGridName,
+ remapClimatologySubtask, remapObservationsSubtask,
+ subtaskSuffix=prefix)
+
+ subtask.set_plot_info(
+ outFileLabel='iceconc{}{}'.format(prefix,
+ hemisphere),
+ fieldNameInTitle='Sea ice concentration',
+ mpasFieldName=mpasFieldName,
+ obsFieldName=obsFieldName,
+ observationTitleLabel=observationTitleLabel,
+ unitsLabel=r'fraction',
+ imageDescription=imageDescription,
+ imageCaption=imageCaption,
+ galleryGroup=galleryGroup,
+ groupSubtitle=None,
+ groupLink='{}_conc'.format(hemisphere.lower()),
+ galleryName='Observations: SSM/I {}'.format(
+ prefix))
+
+ self.add_subtask(subtask)
+ # }}}
+ # }}}
+
+
+class RemapObservedConcClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping sea ice concentration observations
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ latVarName='t_lat',
+ lonVarName='t_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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ dsObs = xr.open_dataset(fileName)
+ dsObs.rename({'AICE': 'seaIceConc'}, inplace=True)
+ return dsObs
+ # }}}
+ # }}}
+
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py b/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py
new file mode 100644
index 000000000..09719cef1
--- /dev/null
+++ b/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py
@@ -0,0 +1,204 @@
+
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import xarray as xr
+
+from ..shared import AnalysisTask
+
+from ..shared.climatology import RemapMpasClimatologySubtask, \
+ RemapObservedClimatologySubtask
+
+from .plot_climatology_map_subtask import PlotClimatologyMapSubtask
+
+from ..shared.io.utility import build_config_full_path
+
+from ..shared.grid import LatLonGridDescriptor
+
+
+class ClimatologyMapSeaIceThick(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of sea ice thickness against
+ observations
+
+ Authors
+ -------
+ Luke Van Roekel, Xylar Asay-Davis, Milena Veneziani
+ """
+ def __init__(self, config, mpasClimatologyTask, hemisphere):
+ # {{{
+ """
+ Construct the analysis task.
+
+ Parameters
+ ----------
+ config : instance of MpasAnalysisConfigParser
+ Contains configuration options
+
+ mpasClimatologyTask : ``MpasClimatologyTask``
+ The task that produced the climatology to be remapped and plotted
+
+ hemisphere : {'NH', 'SH'}
+ The hemisphere to plot
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ taskName = 'climatologyMapSeaIceThick{}'.format(hemisphere)
+
+ self.fieldName = 'seaIceThick'
+ # call the constructor from the base class (AnalysisTask)
+ super(ClimatologyMapSeaIceThick, self).__init__(
+ config=config, taskName=taskName,
+ componentName='seaIce',
+ tags=['climatology', 'horizontalMap', self.fieldName])
+
+ mpasFieldName = 'timeMonthly_avg_iceVolumeCell'
+ iselValues = None
+
+ sectionName = taskName
+
+ if hemisphere == 'NH':
+ hemisphereLong = 'Northern'
+ else:
+ hemisphereLong = 'Southern'
+
+ obsFieldName = 'seaIceThick'
+
+ # read in what seasons we want to plot
+ seasons = config.getExpression(sectionName, 'seasons')
+
+ if len(seasons) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of seasons'.format(sectionName))
+
+ comparisonGridNames = config.getExpression(sectionName,
+ 'comparisonGrids')
+
+ if len(comparisonGridNames) == 0:
+ raise ValueError('config section {} does not contain valid list '
+ 'of comparison grids'.format(sectionName))
+
+ # the variable self.mpasFieldName will be added to mpasClimatologyTask
+ # along with the seasons.
+ remapClimatologySubtask = RemapMpasClimatologySubtask(
+ mpasClimatologyTask=mpasClimatologyTask,
+ parentTask=self,
+ climatologyName='{}{}'.format(self.fieldName, hemisphere),
+ variableList=[mpasFieldName],
+ comparisonGridNames=comparisonGridNames,
+ seasons=seasons,
+ iselValues=iselValues)
+
+ for season in seasons:
+
+ obsFileName = build_config_full_path(
+ config, 'seaIceObservations',
+ 'thickness{}_{}'.format(hemisphere, season))
+
+ remapObservationsSubtask = RemapObservedThickClimatology(
+ parentTask=self, seasons=[season],
+ fileName=obsFileName,
+ outFilePrefix='{}{}_{}'.format(obsFieldName, hemisphere,
+ season),
+ comparisonGridNames=comparisonGridNames,
+ subtaskName='remapObservations{}'.format(season))
+ self.add_subtask(remapObservationsSubtask)
+ for comparisonGridName in comparisonGridNames:
+
+ imageDescription = \
+ '{} Climatology Map of {}-Hemisphere Sea-Ice ' \
+ 'Thickness.'.format(season, hemisphereLong)
+ imageCaption = imageDescription
+ galleryGroup = \
+ '{}-Hemisphere Sea-Ice Concentration'.format(
+ hemisphereLong)
+ # make a new subtask for this season and comparison grid
+ subtask = PlotClimatologyMapSubtask(
+ self, hemisphere, season, comparisonGridName,
+ remapClimatologySubtask, remapObservationsSubtask)
+
+ subtask.set_plot_info(
+ outFileLabel='icethick{}'.format(hemisphere),
+ fieldNameInTitle='Sea ice thickness',
+ mpasFieldName=mpasFieldName,
+ obsFieldName=obsFieldName,
+ observationTitleLabel='Observations (ICESat)',
+ unitsLabel=r'm',
+ imageDescription=imageDescription,
+ imageCaption=imageCaption,
+ galleryGroup=galleryGroup,
+ groupSubtitle=None,
+ groupLink='{}_thick'.format(hemisphere.lower()),
+ galleryName='Observations: ICESat',
+ maskValue=0)
+
+ self.add_subtask(subtask)
+ # }}}
+ # }}}
+
+
+class RemapObservedThickClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping sea ice thickness observations
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ latVarName='t_lat',
+ lonVarName='t_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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ dsObs = xr.open_dataset(fileName)
+ dsObs.rename({'HI': 'seaIceThick'}, inplace=True)
+ return dsObs
+ # }}}
+ # }}}
+
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/plot_climatology_map_subtask.py b/mpas_analysis/sea_ice/plot_climatology_map_subtask.py
new file mode 100644
index 000000000..acbf7fdbb
--- /dev/null
+++ b/mpas_analysis/sea_ice/plot_climatology_map_subtask.py
@@ -0,0 +1,359 @@
+
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import numpy.ma as ma
+import numpy as np
+
+import xarray as xr
+
+from ..shared import AnalysisTask
+
+from ..shared.plot.plotting import plot_polar_comparison, \
+ setup_colormap
+
+from ..shared.html import write_image_xml
+
+
+class PlotClimatologyMapSubtask(AnalysisTask): # {{{
+ """
+ An analysis task for plotting 2D model fields against observations.
+
+ Attributes
+ ----------
+ hemisphere : str
+ The hemisphere to plot
+
+ season : str
+ A season (key in ``shared.constants.monthDictionary``) to be
+ plotted.
+
+ comparisonGridName : {'latlon', 'antarctic'}
+ The name of the comparison grid to plot.
+
+ remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask``
+ The subtask for remapping the MPAS climatology that this subtask
+ will plot
+
+ remapObsClimatologySubtask : ``RemapObservedClimatologySubtask``
+ The subtask for remapping the observational climatology that this
+ subtask will plot
+
+ outFileLabel : str
+ The prefix on each plot and associated XML file
+
+ fieldNameInTitle : str
+ The name of the field being plotted, as used in the plot title
+
+ mpasFieldName : str
+ The name of the variable in the MPAS timeSeriesStatsMonthly output
+
+ obsFieldName : str
+ The name of the variable to use from the observations file
+
+ observationTitleLabel : str
+ the title of the observations subplot
+
+ unitsLabel : str
+ the units of the plotted field, to be displayed on color bars
+
+ imageCaption : str
+ the caption when mousing over the plot or displaying it full
+ screen
+
+ galleryGroup : str
+ the name of the group of galleries in which this plot belongs
+
+ groupSubtitle : str
+ the subtitle of the group in which this plot belongs (or blank
+ if none)
+
+ groupLink : str
+ a short name (with no spaces) for the link to the gallery group
+
+ galleryName : str
+ the name of the gallery in which this plot belongs
+
+ Authors
+ -------
+ Xylar Asay-Davis, Milena Veneziani
+ """
+
+ def __init__(self, parentTask, hemisphere, season, comparisonGridName,
+ remapMpasClimatologySubtask, remapObsClimatologySubtask,
+ subtaskSuffix=None): # {{{
+ '''
+ 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 (master) task for this subtask
+
+ hemisphere : str
+ The hemisphere to plot
+
+ season : str
+ A season (key in ``shared.constants.monthDictionary``) to be
+ plotted.
+
+ comparisonGridName : {'latlon'}
+ The name of the comparison grid to plot.
+
+ remapMpasClimatologySubtask : ``RemapMpasClimatologySubtask``
+ The subtask for remapping the MPAS climatology that this subtask
+ will plot
+
+ remapObsClimatologySubtask : ``RemapObservedClimatologySubtask``
+ The subtask for remapping the observational climatology that this
+ subtask will plot
+
+ subtaskSuffix : str, optional
+ A suffix on the subtask to ensure that it is unique (e.g. the
+ observations being plotted)
+
+ Authors
+ -------
+ Xylar Asay-Davis
+
+ '''
+
+ self.hemisphere = hemisphere
+ self.season = season
+ self.comparisonGridName = comparisonGridName
+ self.remapMpasClimatologySubtask = remapMpasClimatologySubtask
+ self.remapObsClimatologySubtask = remapObsClimatologySubtask
+
+ subtaskName = 'plot{}'.format(season)
+ if subtaskSuffix is not None:
+ subtaskName = '{}_{}'.format(subtaskName, subtaskSuffix)
+
+ config = parentTask.config
+ taskName = parentTask.taskName
+ tags = parentTask.tags
+
+ # call the constructor from the base class (AnalysisTask)
+ super(PlotClimatologyMapSubtask, self).__init__(
+ config=config, taskName=taskName, subtaskName=subtaskName,
+ componentName='ocean', tags=tags)
+
+ # this task should not run until the remapping subtasks are done, since
+ # it relies on data from those subtasks
+ self.run_after(remapMpasClimatologySubtask)
+ self.run_after(remapObsClimatologySubtask)
+ # }}}
+
+ def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName,
+ obsFieldName, observationTitleLabel, unitsLabel,
+ imageDescription, imageCaption, galleryGroup,
+ groupSubtitle, groupLink, galleryName, maskValue=None):
+ # {{{
+ """
+ Store attributes related to plots, plot file names and HTML output.
+
+ Parameters
+ ----------
+ outFileLabel : str
+ The prefix on each plot and associated XML file
+
+ fieldNameInTitle : str
+ The name of the field being plotted, as used in the plot title
+
+ mpasFieldName : str
+ The name of the variable in the MPAS timeSeriesStatsMonthly output
+
+ obsFieldName : str
+ The name of the variable to use from the observations file
+
+ observationTitleLabel : str
+ the title of the observations subplot
+
+ unitsLabel : str
+ the units of the plotted field, to be displayed on color bars
+
+ imageDescription : str
+ the caption when mousing over the plot
+
+ imageCaption : str
+ the caption when displaying the plot full screen
+
+ galleryGroup : str
+ the name of the group of galleries in which this plot belongs
+
+ groupSubtitle : str
+ the subtitle of the group in which this plot belongs (or blank
+ if none)
+
+ groupLink : str
+ a short name (with no spaces) for the link to the gallery group
+
+ galleryName : str
+ the name of the gallery in which this plot belongs
+
+ maskValue : float, optional
+ a value to mask out in plots
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ self.outFileLabel = outFileLabel
+ self.fieldNameInTitle = fieldNameInTitle
+ self.mpasFieldName = mpasFieldName
+ self.obsFieldName = obsFieldName
+ self.observationTitleLabel = observationTitleLabel
+ self.unitsLabel = unitsLabel
+
+ # xml/html related variables
+ self.imageDescription = imageDescription
+ self.imageCaption = imageCaption
+ self.galleryGroup = galleryGroup
+ self.groupSubtitle = groupSubtitle
+ self.groupLink = groupLink
+ self.galleryName = galleryName
+
+ self.maskValue = maskValue
+ # }}}
+
+ def setup_and_check(self): # {{{
+ """
+ Perform steps to set up the analysis and check for errors in the setup.
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ # first, call setup_and_check from the base class (AnalysisTask),
+ # which will perform some common setup
+ super(PlotClimatologyMapSubtask, self).setup_and_check()
+
+ config = self.config
+ self.startYear = config.getint('climatology', 'startYear')
+ self.endYear = config.getint('climatology', 'endYear')
+ self.startDate = config.get('climatology', 'startDate')
+ self.endDate = config.get('climatology', 'endDate')
+
+ mainRunName = config.get('runs', 'mainRunName')
+
+ self.xmlFileNames = []
+
+ self.filePrefix = '{}_{}_{}_years{:04d}-{:04d}'.format(
+ self.outFileLabel, mainRunName,
+ self.season, self.startYear, self.endYear)
+ self.xmlFileNames.append('{}/{}.xml'.format(self.plotsDirectory,
+ self.filePrefix))
+
+ # }}}
+
+ def run_task(self): # {{{
+ """
+ Performs analysis of sea-ice properties by comparing with
+ previous model results and/or observations.
+
+ Authors
+ -------
+ Xylar Asay-Davis, Milena Veneziani
+ """
+
+ config = self.config
+ season = self.season
+ comparisonGridName = self.comparisonGridName
+
+ self.logger.info("\nPlotting 2-d maps of {} climatologies for "
+ "{} from {} observations...".format(
+ self.fieldNameInTitle,
+ season, self.observationTitleLabel))
+
+ mainRunName = config.get('runs', 'mainRunName')
+ startYear = self.startYear
+ endYear = self.endYear
+
+ hemisphere = self.hemisphere
+ sectionName = self.taskName
+ vertical = config.getboolean(sectionName, 'vertical')
+ if hemisphere == 'NH':
+ plotProjection = 'npstere'
+ else:
+ plotProjection = 'spstere'
+
+ (colormapResult, colorbarLevelsResult) = setup_colormap(
+ config, sectionName, suffix='Result')
+ (colormapDifference, colorbarLevelsDifference) = setup_colormap(
+ config, sectionName, suffix='Difference')
+
+ referenceLongitude = config.getfloat(sectionName,
+ 'referenceLongitude')
+ minimumLatitude = config.getfloat(sectionName,
+ 'minimumLatitude')
+
+ remappedFileName = self.remapMpasClimatologySubtask.get_file_name(
+ season=season, stage='remapped',
+ comparisonGridName=comparisonGridName)
+ remappedClimatology = xr.open_dataset(remappedFileName)
+
+ modelOutput = remappedClimatology[self.mpasFieldName].values
+ if self.maskValue is not None:
+ modelOutput = ma.masked_values(modelOutput, self.maskValue)
+ lon = remappedClimatology['lon'].values
+ lat = remappedClimatology['lat'].values
+
+ lonTarg, latTarg = np.meshgrid(lon, lat)
+
+ remappedFileName = self.remapObsClimatologySubtask.get_file_name(
+ stage='remapped', season=season,
+ comparisonGridName=comparisonGridName)
+
+ remappedObsClimatology = xr.open_dataset(remappedFileName)
+
+ observations = remappedObsClimatology[self.obsFieldName].values
+ if self.maskValue is not None:
+ observations = ma.masked_values(observations, self.maskValue)
+
+ difference = modelOutput - observations
+
+ startYear = self.startYear
+ endYear = self.endYear
+ observationTitleLabel = self.observationTitleLabel
+ filePrefix = self.filePrefix
+ title = '{} ({}, years {:04d}-{:04d})'.format(
+ self.fieldNameInTitle, season, startYear, endYear)
+ fileout = '{}/{}.png'.format(self.plotsDirectory, filePrefix)
+ plot_polar_comparison(
+ config,
+ lonTarg,
+ latTarg,
+ modelOutput,
+ observations,
+ difference,
+ colormapResult,
+ colorbarLevelsResult,
+ colormapDifference,
+ colorbarLevelsDifference,
+ title=title,
+ fileout=fileout,
+ plotProjection=plotProjection,
+ latmin=minimumLatitude,
+ lon0=referenceLongitude,
+ modelTitle=mainRunName,
+ obsTitle=observationTitleLabel,
+ diffTitle='Model-Observations',
+ cbarlabel=self.unitsLabel,
+ vertical=vertical)
+
+ write_image_xml(
+ config,
+ filePrefix,
+ componentName='Sea Ice',
+ componentSubdirectory='sea_ice',
+ galleryGroup=self.galleryGroup,
+ groupSubtitle=self.groupSubtitle,
+ groupLink=self.groupLink,
+ gallery=self.galleryName,
+ thumbnailDescription=season,
+ imageDescription=self.imageDescription,
+ imageCaption=self.imageCaption)
+ # }}}
+
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/sea_ice_analysis_task.py b/mpas_analysis/sea_ice/sea_ice_analysis_task.py
deleted file mode 100644
index 6d05af1c1..000000000
--- a/mpas_analysis/sea_ice/sea_ice_analysis_task.py
+++ /dev/null
@@ -1,75 +0,0 @@
-
-from __future__ import absolute_import, division, print_function, \
- unicode_literals
-
-from ..shared.analysis_task import AnalysisTask
-
-from ..shared.io import StreamsFile
-from ..shared.io.utility import build_config_full_path
-
-from ..shared.timekeeping.utility import get_simulation_start_time
-
-
-class SeaIceAnalysisTask(AnalysisTask): # {{{
- '''
- A base class for sea-ice analysis tasks that implements common setup
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- def setup_and_check(self): # {{{
- '''
- Perform steps to set up common to sea ice analysis, including
- finding the simulation start time and an appropriate restart file
- (from MPAS-SeaIce if possible, but from MPAS-Ocean as a fallback)
-
- Authors
- -------
- Xylar Asay-Davis
- '''
-
- # first, call setup_and_check from the base class (AnalysisTask),
- # which will perform some common setup, including storing:
- # self.runDirectory , self.historyDirectory, self.plotsDirectory,
- # self.namelist, self.runStreams, self.historyStreams,
- # self.calendar
- super(SeaIceAnalysisTask, self).setup_and_check()
-
- try:
- self.simulationStartTime = get_simulation_start_time(
- self.runStreams)
- except IOError:
- # try the ocean stream instead
- runDirectory = build_config_full_path(self.config, 'input',
- 'runSubdirectory')
- oceanStreamsFileName = build_config_full_path(
- self.config, 'input', 'oceanStreamsFileName')
- oceanStreams = StreamsFile(oceanStreamsFileName,
- streamsdir=runDirectory)
- self.simulationStartTime = get_simulation_start_time(oceanStreams)
-
- try:
- self.restartFileName = self.runStreams.readpath('restart')[0]
- except ValueError:
- # get an ocean restart file, since no sea-ice restart exists
- try:
- runDirectory = build_config_full_path(self.config, 'input',
- 'runSubdirectory')
- oceanStreamsFileName = build_config_full_path(
- self.config, 'input', 'oceanStreamsFileName')
- oceanStreams = StreamsFile(oceanStreamsFileName,
- streamsdir=runDirectory)
- self.restartFileName = oceanStreams.readpath('restart')[0]
- except ValueError:
- raise IOError('No MPAS-O or MPAS-Seaice restart file found: '
- 'need at least one restart file \n'
- 'for sea ice analysis tasks')
-
- # }}}
-
-
-# }}}
-
-# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/sea_ice/time_series.py b/mpas_analysis/sea_ice/time_series.py
index 5685438a4..d045daed1 100644
--- a/mpas_analysis/sea_ice/time_series.py
+++ b/mpas_analysis/sea_ice/time_series.py
@@ -4,7 +4,7 @@
import xarray as xr
-from .sea_ice_analysis_task import SeaIceAnalysisTask
+from ..shared import AnalysisTask
from ..shared.plot.plotting import timeseries_analysis_plot, \
timeseries_analysis_plot_polar
@@ -13,7 +13,7 @@
make_directories
from ..shared.timekeeping.utility import date_to_days, days_to_datetime, \
- datetime_to_days
+ datetime_to_days, get_simulation_start_time
from ..shared.timekeeping.MpasRelativeDelta import MpasRelativeDelta
from ..shared.generalized_reader import open_multifile_dataset
@@ -23,7 +23,7 @@
from ..shared.html import write_image_xml
-class TimeSeriesSeaIce(SeaIceAnalysisTask):
+class TimeSeriesSeaIce(AnalysisTask):
"""
Performs analysis of time series of sea-ice properties.
@@ -54,7 +54,7 @@ def __init__(self, config, mpasTimeSeriesTask): # {{{
-------
Xylar Asay-Davis
"""
- # first, call the constructor from the base class (SeaIceAnalysisTask)
+ # first, call the constructor from the base class (AnalysisTask)
super(TimeSeriesSeaIce, self).__init__(
config=config,
taskName='timeSeriesSeaIceAreaVol',
@@ -80,7 +80,7 @@ def setup_and_check(self): # {{{
-------
Xylar Asay-Davis
"""
- # first, call setup_and_check from the base class (SeaIceAnalysisTask),
+ # first, call setup_and_check from the base class (AnalysisTask),
# which will perform some common setup, including storing:
# self.runDirectory , self.historyDirectory, self.plotsDirectory,
# self.namelist, self.runStreams, self.historyStreams,
@@ -102,6 +102,31 @@ def setup_and_check(self): # {{{
check_path_exists(config.get('seaIcePreprocessedReference',
'baseDirectory'))
+ # get a list of timeSeriesStatsMonthly output files from the streams
+ # file, reading only those that are between the start and end dates
+ streamName = 'timeSeriesStatsMonthlyOutput'
+ self.startDate = config.get('timeSeries', 'startDate')
+ self.endDate = config.get('timeSeries', 'endDate')
+ self.inputFiles = \
+ self.historyStreams.readpath(streamName,
+ startDate=self.startDate,
+ endDate=self.endDate,
+ calendar=self.calendar)
+
+ if len(self.inputFiles) == 0:
+ raise IOError('No files were found in stream {} between {} and '
+ '{}.'.format(streamName, self.startDate,
+ self.endDate))
+
+ self.simulationStartTime = get_simulation_start_time(self.runStreams)
+
+ try:
+ self.restartFileName = self.runStreams.readpath('restart')[0]
+ except ValueError:
+ raise IOError('No MPAS-SeaIce restart file found: need at least '
+ 'one restart file to perform remapping of '
+ 'climatologies.')
+
# these are redundant for now. Later cleanup is needed where these
# file names are reused in run()
self.xmlFileNames = []
@@ -284,8 +309,8 @@ def run_task(self): # {{{
if compareWithObservations:
key = (hemisphere, 'iceArea')
- title[key] = '{}\nSSM/I observations, annual cycle (blue)'.format(
- title[key])
+ title[key] = '{}\nSSM/I observations, annual cycle ' \
+ '(blue)'.format(title[key])
if hemisphere == 'NH':
key = (hemisphere, 'iceVolume')
title[key] = \
@@ -440,9 +465,8 @@ def run_task(self): # {{{
'{}/{}.{}_polar.png'.format(self.plotsDirectory,
mainRunName,
variableName)
- title = \
- '{}, NH (black), SH (blue)\n{}'.format(plotTitles[variableName],
- mainRunName)
+ title = '{}, NH (black), SH (blue)\n' \
+ '{}'.format(plotTitles[variableName], mainRunName)
varList = [plotVars[('NH', variableName)],
plotVars[('SH', variableName)]]
timeseries_analysis_plot(config, varList,
diff --git a/mpas_analysis/shared/climatology/__init__.py b/mpas_analysis/shared/climatology/__init__.py
index a6fe39aa3..279616e8d 100644
--- a/mpas_analysis/shared/climatology/__init__.py
+++ b/mpas_analysis/shared/climatology/__init__.py
@@ -1,9 +1,9 @@
from .climatology import get_remapper, \
- get_observation_climatology_file_names, \
compute_monthly_climatology, compute_climatology, \
add_years_months_days_in_month, remap_and_write_climatology
from .mpas_climatology_task import MpasClimatologyTask
from .remap_mpas_climatology_subtask import RemapMpasClimatologySubtask
+from .remap_observed_climatology_subtask import RemapObservedClimatologySubtask
from .comparison_descriptors import get_comparison_descriptor, \
get_antarctic_stereographic_projection
diff --git a/mpas_analysis/shared/climatology/climatology.py b/mpas_analysis/shared/climatology/climatology.py
index d160a03de..9601e3f39 100644
--- a/mpas_analysis/shared/climatology/climatology.py
+++ b/mpas_analysis/shared/climatology/climatology.py
@@ -166,9 +166,7 @@ def get_observation_climatology_file_names(config, fieldName, monthNames,
make_directories(climatologyDirectory)
- if not _matches_comparison(remapper.sourceDescriptor,
- remapper.destinationDescriptor):
- make_directories(remappedDirectory)
+ make_directories(remappedDirectory)
return (climatologyFileName, remappedFileName) # }}}
@@ -336,7 +334,7 @@ def add_years_months_days_in_month(ds, calendar=None): # {{{
'will be computed with\n'
'month durations ignoring leap years.')
- daysInMonth = numpy.array([constants.daysInMonth[month-1] for
+ daysInMonth = numpy.array([constants.daysInMonth[int(month)-1] for
month in ds.month.values], float)
ds.coords['daysInMonth'] = ('Time', daysInMonth)
@@ -393,6 +391,11 @@ def remap_and_write_climatology(config, climatologyDataSet,
"""
useNcremap = config.getboolean('climatology', 'useNcremap')
+ if (isinstance(remapper.sourceDescriptor, ProjectionGridDescriptor) or
+ isinstance(remapper.destinationDescriptor, ProjectionGridDescriptor)):
+ # ncremap doesn't support projection grids
+ useNcremap = False
+
if remapper.mappingFileName is None:
# no remapping is needed
remappedClimatology = climatologyDataSet
diff --git a/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py b/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py
index 09785f56a..9fca4e893 100644
--- a/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py
+++ b/mpas_analysis/shared/climatology/remap_mpas_climatology_subtask.py
@@ -3,6 +3,7 @@
unicode_literals
import xarray as xr
+import numpy
import os
from ..analysis_task import AnalysisTask
@@ -89,7 +90,7 @@ def __init__(self, mpasClimatologyTask, parentTask, climatologyName,
to be computed or ['none'] (not ``None``) if only monthly
climatologies are needed.
- comparisonGridNames : list of {'latlon', 'antarctic'}, optinal
+ comparisonGridNames : list of {'latlon', 'antarctic'}, optional
The name(s) of the comparison grid to use for remapping.
iselValues : dict, optional
@@ -390,6 +391,10 @@ def _mask_climatologies(self, season, dsMask): # {{{
# slice
climatology = climatology.isel(**iselValues)
+ # add valid mask as a variable, useful for remapping later
+ climatology['validMask'] = \
+ xr.DataArray(numpy.ones(climatology.dims['nCells']),
+ dims=['nCells'])
# mask the data set
for variableName in self.variableList:
climatology[variableName] = \
@@ -431,8 +436,8 @@ def _remap(self, inFileName, outFileName, remapper, comparisonGridName):
useNcremap = self.config.getboolean('climatology', 'useNcremap')
- if comparisonGridName == 'antarctic':
- # ncremap doesn't support polar stereographic grids
+ if comparisonGridName != 'latlon':
+ # ncremap doesn't support grids other than lat/lon
useNcremap = False
renormalizationThreshold = self.config.getfloat(
diff --git a/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py b/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py
new file mode 100644
index 000000000..ac4c14af6
--- /dev/null
+++ b/mpas_analysis/shared/climatology/remap_observed_climatology_subtask.py
@@ -0,0 +1,346 @@
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import os
+import os.path
+import xarray as xr
+
+from ..analysis_task import AnalysisTask
+
+from ..constants import constants
+
+from ..io.utility import build_config_full_path, make_directories
+from ..io import write_netcdf
+
+from .climatology import get_remapper, remap_and_write_climatology, \
+ compute_climatology
+
+from .comparison_descriptors import get_comparison_descriptor
+
+
+class RemapObservedClimatologySubtask(AnalysisTask): # {{{
+ """
+ An analysis task for comparison of 2D model fields against observations.
+
+ Attributes
+ ----------
+ 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
+
+ comparisonGridNames : list of {'latlon', 'antarctic'}
+ The name(s) of the comparison grid to use for remapping.
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ def __init__(self, parentTask, seasons, fileName, outFilePrefix,
+ comparisonGridNames=['latlon'],
+ subtaskName='remapObservations'):
+ # {{{
+ '''
+ 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 (master) 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
+
+ comparisonGridNames : list of {'latlon', 'antarctic'}, optional
+ The name(s) of the comparison grid to use for remapping.
+
+ subtaskName : str, optional
+ The name of the subtask
+
+ Authors
+ -------
+ Xylar Asay-Davis
+
+ '''
+
+ self.seasons = seasons
+ self.fileName = fileName
+ self.outFilePrefix = outFilePrefix
+ self.comparisonGridNames = comparisonGridNames
+
+ config = parentTask.config
+ taskName = parentTask.taskName
+ tags = parentTask.tags
+ componentName = parentTask.componentName
+
+ # call the constructor from the base class (AnalysisTask)
+ super(RemapObservedClimatologySubtask, self).__init__(
+ config=config, taskName=taskName, subtaskName=subtaskName,
+ componentName=componentName, tags=tags)
+ # }}}
+
+ def setup_and_check(self): # {{{
+ """
+ Perform steps to set up the analysis and check for errors in the setup.
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ # call setup_and_check from the base class (AnalysisTask),
+ # which will perform some common setup, including storing:
+ # self.runDirectory , self.historyDirectory, self.plotsDirectory,
+ # self.namelist, self.runStreams, self.historyStreams,
+ # self.calendar
+ super(RemapObservedClimatologySubtask, self).setup_and_check()
+
+ # we set up the remappers here because ESFM_RegridWeightGen seems to
+ # have trouble if it runs in another process (or in several at once)
+ self._setup_remappers(self.fileName)
+
+ # build the observational data set and write it out to a file, to
+ # be read back in during the run_task() phase
+ obsFileName = self.get_file_name(stage='original')
+ if not os.path.exists(obsFileName):
+ ds = self.build_observational_dataset(self.fileName)
+ write_netcdf(ds, obsFileName)
+
+ # }}}
+
+ def run_task(self): # {{{
+ """
+ Performs remapping of obsrevations to the comparsion grid
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ config = self.config
+
+ obsFileName = self.get_file_name(stage='original')
+ if not os.path.isfile(obsFileName):
+ raise OSError('Obs file {} not found.'.format(
+ obsFileName))
+
+ for comparisonGridName in self.comparisonGridNames:
+ for season in self.seasons:
+
+ remappedFileName = self.get_file_name(
+ stage='remapped',
+ season=season,
+ comparisonGridName=comparisonGridName)
+
+ if not os.path.exists(remappedFileName):
+
+ ds = xr.open_dataset(obsFileName)
+
+ climatologyFileName = self.get_file_name(
+ stage='climatology',
+ season=season,
+ comparisonGridName=comparisonGridName)
+ if 'month' in ds.variables.keys() and \
+ 'year' in ds.variables.keys():
+ # this data set is not yet a climatology, so compute
+ # the climatology
+ monthValues = constants.monthDictionary[season]
+ seasonalClimatology = compute_climatology(
+ ds, monthValues, maskVaries=True)
+ else:
+ # We don't have month or year arrays to compute a
+ # climatology so assume this already is one
+ seasonalClimatology = ds
+
+ write_netcdf(seasonalClimatology, climatologyFileName)
+
+ remapper = self.remappers[comparisonGridName]
+
+ if remapper.mappingFileName is None:
+ # no need to remap because the observations are on the
+ # comparison grid already
+ os.symlink(climatologyFileName, remappedFileName)
+ else:
+ remap_and_write_climatology(
+ config, seasonalClimatology,
+ climatologyFileName,
+ remappedFileName, remapper,
+ logger=self.logger)
+
+ # }}}
+
+ def get_observation_descriptor(self, fileName): # {{{
+ '''
+ get a MeshDescriptor for the observation grid. A subclass derived from
+ this class must override this method to create the appropriate
+ descriptor
+
+ Parameters
+ ----------
+ fileName : str
+ observation file name describing the source grid
+
+ Returns
+ -------
+ obsDescriptor : ``MeshDescriptor``
+ The descriptor for the observation grid
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ '''
+
+ return None # }}}
+
+ def build_observational_dataset(self, fileName): # {{{
+ '''
+ read in the data sets for observations, and possibly rename some
+ variables and dimensions. A subclass derived from this class must
+ override this method to create the appropriate data set
+
+ Parameters
+ ----------
+ fileName : str
+ observation file name
+
+ Returns
+ -------
+ dsObs : ``xarray.Dataset``
+ The observational dataset
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ '''
+
+ return None # }}}
+
+ # }}}
+
+ def get_file_name(self, stage, season=None, comparisonGridName=None):
+ # {{{
+ """
+ Given config options, the name of a field and a string identifying the
+ months in a seasonal climatology, returns the full path for MPAS
+ climatology files before and after remapping.
+
+ Parameters
+ ----------
+ stage : {'original', 'climatology', 'remapped'}
+ The stage of the masking and remapping process
+
+ season : str, optional
+ One of the seasons in ``constants.monthDictionary``
+
+ comparisonGridName : {'latlon', 'antarctic'}, optional
+ The name of the comparison grid to use for remapping.
+
+ Returns
+ -------
+ fileName : str
+ The path to the climatology file for the specified season.
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ config = self.config
+ obsSection = '{}Observations'.format(self.componentName)
+ if comparisonGridName is None:
+ # just needed for getting the obs. grid name, so doesn't matter
+ # which comparison grid
+ remapper = self.remappers[self.comparisonGridNames[0]]
+ else:
+ remapper = self.remappers[comparisonGridName]
+
+ obsGridName = remapper.sourceDescriptor.meshName
+
+ outFilePrefix = self.outFilePrefix
+
+ if stage in ['original', 'climatology']:
+ climatologyDirectory = build_config_full_path(
+ config=config, section='output',
+ relativePathOption='climatologySubdirectory',
+ relativePathSection=obsSection)
+
+ make_directories(climatologyDirectory)
+
+ if stage == 'original':
+ fileName = '{}/{}_{}.nc'.format(
+ climatologyDirectory, outFilePrefix, obsGridName)
+ else:
+ fileName = '{}/{}_{}_{}.nc'.format(
+ climatologyDirectory, outFilePrefix, obsGridName, season)
+
+ elif stage == 'remapped':
+ remappedDirectory = build_config_full_path(
+ config=config, section='output',
+ relativePathOption='remappedClimSubdirectory',
+ relativePathSection=obsSection)
+
+ make_directories(remappedDirectory)
+
+ comparisonGridName = remapper.destinationDescriptor.meshName
+ fileName = '{}/{}_{}_to_{}_{}.nc'.format(
+ remappedDirectory, outFilePrefix, obsGridName,
+ comparisonGridName, season)
+
+ else:
+ raise ValueError('Unknown stage {}'.format(stage))
+
+ return fileName # }}}
+
+ def _setup_remappers(self, fileName): # {{{
+ """
+ Set up the remappers for remapping from observations to the comparison
+ grids.
+
+ Parameters
+ ----------
+ fileName : str
+ The name of the observation file used to determine the source grid
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+ config = self.config
+
+ sectionName = '{}Observations'.format(self.componentName)
+
+ obsDescriptor = self.get_observation_descriptor(fileName)
+
+ outFilePrefix = self.outFilePrefix
+ self.remappers = {}
+ for comparisonGridName in self.comparisonGridNames:
+
+ comparisonDescriptor = get_comparison_descriptor(
+ config, comparisonGridName=comparisonGridName)
+
+ self.remappers[comparisonGridName] = get_remapper(
+ config=config,
+ sourceDescriptor=obsDescriptor,
+ comparisonDescriptor=comparisonDescriptor,
+ mappingFilePrefix='map_obs_{}'.format(outFilePrefix),
+ method=config.get(sectionName,
+ 'interpolationMethod'),
+ logger=self.logger)
+ # }}}
+ # }}}
+
+# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/shared/grid/__init__.py b/mpas_analysis/shared/grid/__init__.py
index 7fae57fb3..beffc841f 100644
--- a/mpas_analysis/shared/grid/__init__.py
+++ b/mpas_analysis/shared/grid/__init__.py
@@ -1,2 +1,2 @@
from .grid import MpasMeshDescriptor, LatLonGridDescriptor, \
- ProjectionGridDescriptor
+ ProjectionGridDescriptor, interp_extrap_corner
diff --git a/mpas_analysis/shared/grid/grid.py b/mpas_analysis/shared/grid/grid.py
index 81e46abfd..ba7c4096d 100644
--- a/mpas_analysis/shared/grid/grid.py
+++ b/mpas_analysis/shared/grid/grid.py
@@ -189,7 +189,7 @@ def to_scrip(self, scripFileName): # {{{
inFile.close()
outFile.close() # }}}
-# }}}
+ # }}}
class LatLonGridDescriptor(MeshDescriptor): # {{{
@@ -259,8 +259,8 @@ def read(cls, fileName=None, ds=None, latVarName='lat',
ds[lonVarName].dims[0])
# interp/extrap corners
- descriptor.lonCorner = _interp_extrap_corner(descriptor.lon)
- descriptor.latCorner = _interp_extrap_corner(descriptor.lat)
+ descriptor.lonCorner = interp_extrap_corner(descriptor.lon)
+ descriptor.latCorner = interp_extrap_corner(descriptor.lat)
if 'history' in ds.attrs:
descriptor.history = '\n'.join([ds.attrs['history'],
@@ -368,6 +368,7 @@ def _set_coords(self, latVarName, lonVarName, latDimName,
if self.meshName is None:
self.meshName = '{}x{}{}'.format(abs(dLat), abs(dLon), units)
# }}}
+ # }}}
class ProjectionGridDescriptor(MeshDescriptor): # {{{
@@ -446,8 +447,8 @@ def read(cls, projection, fileName, meshName=None, xVarName='x',
ds[yVarName].dims[0])
# interp/extrap corners
- descriptor.xCorner = _interp_extrap_corner(descriptor.x)
- descriptor.yCorner = _interp_extrap_corner(descriptor.y)
+ descriptor.xCorner = interp_extrap_corner(descriptor.x)
+ descriptor.yCorner = interp_extrap_corner(descriptor.y)
# Update history attribute of netCDF file
if 'history' in ds.attrs:
@@ -487,8 +488,8 @@ def create(cls, projection, x, y, meshName): # {{{
descriptor._set_coords('x', 'y', 'x', 'y')
# interp/extrap corners
- descriptor.xCorner = _interp_extrap_corner(descriptor.x)
- descriptor.yCorner = _interp_extrap_corner(descriptor.y)
+ descriptor.xCorner = interp_extrap_corner(descriptor.x)
+ descriptor.yCorner = interp_extrap_corner(descriptor.y)
descriptor.history = sys.argv[:]
return descriptor # }}}
@@ -587,6 +588,19 @@ def _set_coords(self, xVarName, yVarName, xDimName, yDimName): # {{{
self.dimSize = [len(self.x), len(self.y)]
# }}}
+ # }}}
+
+
+def interp_extrap_corner(inField): # {{{
+ '''Interpolate/extrapolate a 1D field from grid centers to grid corners'''
+
+ outField = numpy.zeros(len(inField)+1)
+ outField[1:-1] = 0.5*(inField[0:-1] + inField[1:])
+ # extrapolate the ends
+ outField[0] = 1.5*inField[0] - 0.5*inField[1]
+ outField[-1] = 1.5*inField[-1] - 0.5*inField[-2]
+ return outField # }}}
+
def _create_scrip(outFile, grid_size, grid_corners, grid_rank, units,
meshName): # {{{
@@ -645,17 +659,6 @@ def _create_scrip(outFile, grid_size, grid_corners, grid_rank, units,
# }}}
-def _interp_extrap_corner(inField): # {{{
- '''Interpolate/extrapolate a 1D field from grid centers to grid corners'''
-
- outField = numpy.zeros(len(inField)+1)
- outField[1:-1] = 0.5*(inField[0:-1] + inField[1:])
- # extrapolate the ends
- outField[0] = 1.5*inField[0] - 0.5*inField[1]
- outField[-1] = 1.5*inField[-1] - 0.5*inField[-2]
- return outField # }}}
-
-
def _unwrap_corners(inField):
'''Turn a 2D array of corners into an array of rectangular mesh elements'''
outField = numpy.zeros(((inField.shape[0]-1)*(inField.shape[1]-1), 4))
diff --git a/mpas_analysis/shared/plot/plotting.py b/mpas_analysis/shared/plot/plotting.py
index 3c6d141fd..a71edf5c0 100644
--- a/mpas_analysis/shared/plot/plotting.py
+++ b/mpas_analysis/shared/plot/plotting.py
@@ -423,7 +423,7 @@ def plot_global_comparison(
diffTitle='Model-Observations',
cbarlabel='units',
titleFontSize=None,
- figsize=(8, 12),
+ figsize=(8, 13),
dpi=None):
"""
@@ -558,13 +558,203 @@ def plot_global_comparison(
plt.close()
-def _date_tick(days, pos, calendar='gregorian', includeMonth=True):
- days = np.maximum(days, 0.)
- date = days_to_datetime(days, calendar)
- if includeMonth:
- return '{:04d}-{:02d}'.format(date.year, date.month)
+def plot_polar_projection_comparison(
+ config,
+ x,
+ y,
+ landMask,
+ modelArray,
+ obsArray,
+ diffArray,
+ fileout,
+ colorMapSectionName,
+ colorMapType='norm',
+ title=None,
+ modelTitle='Model',
+ obsTitle='Observations',
+ diffTitle='Model-Observations',
+ cbarlabel='units',
+ titleFontSize=None,
+ figsize=None,
+ dpi=None,
+ vertical=False):
+
+ """
+ Plots a data set as a longitude/latitude map.
+
+ Parameters
+ ----------
+ config : instance of ConfigParser
+ the configuration, containing a [plot] section with options that
+ control plotting
+
+ x, y : numpy ndarrays
+ 1D x and y arrays defining the projection grid
+
+ landMask : numpy ndarrays
+ model and observational data sets
+
+ modelArray, obsArray : numpy ndarrays
+ model and observational data sets
+
+ diffArray : float array
+ difference between modelArray and obsArray
+
+ fileout : str
+ the file name to be written
+
+ colorMapSectionName : str
+ section name in ``config`` where color map info can be found.
+
+ colorMapType : {'norm', 'indexed'}, optional
+ The type of color map, either a matplotlib norm or indices into a color
+ map.
+
+ If 'norm', the following options must be defined for suffixes
+ ``Result`` and ``Difference``:
+ ``colormapName``, ``normType``,
+ ``normArgs``, ``colorbarTicks``
+
+ If 'indexed', these options are required:
+ ``colormapName``, ``colormapIndices``,
+ ``colorbarLevels``
+
+ The colorbar for each panel will be constructed from these options
+
+
+ title : str, optional
+ the subtitle of the plot
+
+ plotProjection : str, optional
+ Basemap projection for the plot
+
+ modelTitle : str, optional
+ title of the model panel
+
+ obsTitle : str, optional
+ title of the observations panel
+
+ diffTitle : str, optional
+ title of the difference (bias) panel
+
+ cbarlabel : str, optional
+ label on the colorbar
+
+ titleFontSize : int, optional
+ size of the title font
+
+ figsize : tuple of float, optional
+ the size of the figure in inches. If ``None``, the figure size is
+ ``(8, 22)`` if ``vertical == True`` and ``(22, 8)`` otherwise.
+
+ dpi : int, optional
+ the number of dots per inch of the figure, taken from section ``plot``
+ option ``dpi`` in the config file by default
+
+ vertical : bool, optional
+ whether the subplots should be stacked vertically rather than
+ horizontally
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ def plot_panel(ax, title, array, cmap, norm, ticks):
+ plt.title(title, y=1.06, **axis_font)
+
+ mesh = plt.pcolormesh(x, y, array, cmap=cmap, norm=norm)
+
+ plt.pcolormesh(x, y, landMask, cmap=landColorMap)
+ plt.contour(xCenter, yCenter, landMask.mask, (0.5,), colors='k',
+ linewidths=0.5)
+
+ # create an axes on the right side of ax. The width of cax will be 5%
+ # of ax and the padding between cax and ax will be fixed at 0.05 inch.
+ divider = make_axes_locatable(ax)
+ cax = divider.append_axes("right", size="5%", pad=0.05)
+
+ cbar = plt.colorbar(mesh, cax=cax)
+ cbar.set_label(cbarlabel)
+ if ticks is not None:
+ cbar.set_ticks(ticks)
+ cbar.set_ticklabels(['{}'.format(tick) for tick in ticks])
+
+ ax.axis('off')
+ ax.set_aspect('equal')
+ ax.autoscale(tight=True)
+
+ # set up figure
+ if dpi is None:
+ dpi = config.getint('plot', 'dpi')
+
+ if vertical:
+ if figsize is None:
+ figsize = (8, 22)
+ subplots = [311, 312, 313]
else:
- return '{:04d}'.format(date.year)
+ if figsize is None:
+ figsize = (22, 7.5)
+ subplots = [131, 132, 133]
+
+ if colorMapType == 'norm':
+ (cmapModelObs, normModelObs) = _setup_colormap_and_norm(
+ config, colorMapSectionName, suffix='Result')
+ (cmapDiff, normDiff) = _setup_colormap_and_norm(
+ config, colorMapSectionName, suffix='Difference')
+
+ colorbarTicksResult = config.getExpression(colorMapSectionName,
+ 'colorbarTicksResult')
+ colorbarTicksDifference = config.getExpression(
+ colorMapSectionName, 'colorbarTicksDifference')
+ elif colorMapType == 'indexed':
+
+ (cmapModelObs, colorbarTicksResult) = setup_colormap(
+ config, colorMapSectionName, suffix='Result')
+ (cmapDiff, colorbarTicksDifference) = setup_colormap(
+ config, colorMapSectionName, suffix='Difference')
+
+ normModelObs = cols.BoundaryNorm(colorbarTicksResult, cmapModelObs.N)
+ normDiff = cols.BoundaryNorm(colorbarTicksDifference, cmapDiff.N)
+ else:
+ raise ValueError('colorMapType must be one of {norm, indexed}')
+
+ # set up figure
+ fig = plt.figure(figsize=figsize, dpi=dpi)
+ if (title is not None):
+ if titleFontSize is None:
+ titleFontSize = config.get('plot', 'titleFontSize')
+ title_font = {'size': titleFontSize,
+ 'color': config.get('plot', 'titleFontColor'),
+ 'weight': config.get('plot', 'titleFontWeight')}
+ fig.suptitle(title, y=0.95, **title_font)
+ axis_font = {'size': config.get('plot', 'axisFontSize')}
+
+ # set up land colormap
+ colorList = [(0.8, 0.8, 0.8), (0.8, 0.8, 0.8)]
+ landColorMap = cols.LinearSegmentedColormap.from_list('land', colorList)
+
+ # locations of centers for land contour
+ xCenter = 0.5*(x[1:] + x[0:-1])
+ yCenter = 0.5*(y[1:] + y[0:-1])
+
+ ax = plt.subplot(subplots[0])
+ plot_panel(ax, modelTitle, modelArray, cmapModelObs, normModelObs,
+ colorbarTicksResult)
+
+ ax = plt.subplot(subplots[1])
+ plot_panel(ax, obsTitle, obsArray, cmapModelObs, normModelObs,
+ colorbarTicksResult)
+
+ ax = plt.subplot(subplots[2])
+ plot_panel(ax, diffTitle, diffArray, cmapDiff, normDiff,
+ colorbarTicksDifference)
+
+ if (fileout is not None):
+ plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
+
+ if not config.getboolean('plot', 'displayToScreen'):
+ plt.close()
def plot_1D(config, xArrays, fieldArrays, errArrays,
@@ -905,6 +1095,8 @@ def setup_colormap(config, configSectionName, suffix=''):
Xylar Asay-Davis, Milena Veneziani, Greg Streletz
'''
+ _register_custom_colormaps()
+
colormap = plt.get_cmap(config.get(configSectionName,
'colormapName{}'.format(suffix)))
@@ -1022,5 +1214,183 @@ def plot_xtick_format(plt, calendar, minDays, maxDays, maxXTicks):
plt.autoscale(enable=True, axis='x', tight=True)
+def _setup_colormap_and_norm(config, configSectionName, suffix=''):
+
+ '''
+ Set up a colormap from the registry
+
+ Parameters
+ ----------
+ config : instance of ConfigParser
+ the configuration, containing a [plot] section with options that
+ control plotting
+
+ configSectionName : str
+ name of config section
+
+ suffix: str, optional
+ suffix of colormap related options
+
+ Returns
+ -------
+ colormap : srt
+ new colormap
+
+ norm : ``SymLogNorm`` object
+ the norm used to normalize the colormap
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ '''
+
+ _register_custom_colormaps()
+
+ colormap = plt.get_cmap(config.get(configSectionName,
+ 'colormapName{}'.format(suffix)))
+
+ normType = config.get(configSectionName, 'normType{}'.format(suffix))
+
+ kwargs = config.getExpression(configSectionName,
+ 'normArgs{}'.format(suffix))
+
+ if normType == 'symLog':
+ norm = cols.SymLogNorm(**kwargs)
+ elif normType == 'linear':
+ norm = cols.Normalize(**kwargs)
+ else:
+ raise ValueError('Unsupported norm type {} in section {}'.format(
+ normType, configSectionName))
+
+ return (colormap, norm)
+
+
+def _date_tick(days, pos, calendar='gregorian', includeMonth=True):
+ days = np.maximum(days, 0.)
+ date = days_to_datetime(days, calendar)
+ if includeMonth:
+ return '{:04d}-{:02d}'.format(date.year, date.month)
+ else:
+ return '{:04d}'.format(date.year)
+
+
+def _register_custom_colormaps():
+ name = 'ferret'
+ backgroundColor = (0.9, 0.9, 0.9)
+
+ red = np.array([[0, 0.6],
+ [0.15, 1],
+ [0.35, 1],
+ [0.65, 0],
+ [0.8, 0],
+ [1, 0.75]])
+
+ green = np.array([[0, 0],
+ [0.1, 0],
+ [0.35, 1],
+ [1, 0]])
+
+ blue = np.array([[0, 0],
+ [0.5, 0],
+ [0.9, 0.9],
+ [1, 0.9]])
+
+ colorCount = 21
+ colorList = np.ones((colorCount, 4), float)
+ colorList[:, 0] = np.interp(np.linspace(0, 1, colorCount),
+ red[:, 0], red[:, 1])
+ colorList[:, 1] = np.interp(np.linspace(0, 1, colorCount),
+ green[:, 0], green[:, 1])
+ colorList[:, 2] = np.interp(np.linspace(0, 1, colorCount),
+ blue[:, 0], blue[:, 1])
+ colorList = colorList[::-1, :]
+
+ colorMap = cols.LinearSegmentedColormap.from_list(
+ name, colorList, N=255)
+
+ colorMap.set_bad(backgroundColor)
+ plt.register_cmap(name, colorMap)
+
+ name = 'erdc_iceFire_H'
+
+ colorArray = np.array([
+ [-1, 4.05432e-07, 0, 5.90122e-06],
+ [-0.87451, 0, 0.120401, 0.302675],
+ [-0.74902, 0, 0.216583, 0.524574],
+ [-0.623529, 0.0552475, 0.345025, 0.6595],
+ [-0.498039, 0.128047, 0.492588, 0.720288],
+ [-0.372549, 0.188955, 0.641309, 0.792092],
+ [-0.247059, 0.327673, 0.784935, 0.873434],
+ [-0.121569, 0.60824, 0.892164, 0.935547],
+ [0.00392157, 0.881371, 0.912178, 0.818099],
+ [0.129412, 0.951407, 0.835621, 0.449279],
+ [0.254902, 0.904481, 0.690489, 0],
+ [0.380392, 0.85407, 0.510864, 0],
+ [0.505882, 0.777093, 0.33018, 0.00088199],
+ [0.631373, 0.672862, 0.139087, 0.00269398],
+ [0.756863, 0.508815, 0, 0],
+ [0.882353, 0.299417, 0.000366289, 0.000547829],
+ [1, 0.0157519, 0.00332021, 4.55569e-08]], float)
+
+ colorCount = 255
+ colorList = np.ones((colorCount, 4), float)
+ x = colorArray[:, 0]
+ for cIndex in range(3):
+ colorList[:, cIndex] = np.interp(
+ np.linspace(-1., 1., colorCount),
+ x, colorArray[:, cIndex+1])
+
+ colorMap = cols.LinearSegmentedColormap.from_list(
+ name, colorList, N=255)
+
+ plt.register_cmap(name, colorMap)
+
+ name = 'erdc_iceFire_L'
+
+ colorArray = np.array([
+ [-1, 0.870485, 0.913768, 0.832905],
+ [-0.87451, 0.586919, 0.887865, 0.934003],
+ [-0.74902, 0.31583, 0.776442, 0.867858],
+ [-0.623529, 0.18302, 0.632034, 0.787722],
+ [-0.498039, 0.117909, 0.484134, 0.713825],
+ [-0.372549, 0.0507239, 0.335979, 0.654741],
+ [-0.247059, 0, 0.209874, 0.511832],
+ [-0.121569, 0, 0.114689, 0.28935],
+ [0.00392157, 0.0157519, 0.00332021, 4.55569e-08],
+ [0.129412, 0.312914, 0, 0],
+ [0.254902, 0.520865, 0, 0],
+ [0.380392, 0.680105, 0.15255, 0.0025996],
+ [0.505882, 0.785109, 0.339479, 0.000797922],
+ [0.631373, 0.857354, 0.522494, 0],
+ [0.756863, 0.910974, 0.699774, 0],
+ [0.882353, 0.951921, 0.842817, 0.478545],
+ [1, 0.881371, 0.912178, 0.818099]], float)
+
+ colorCount = 255
+ colorList = np.ones((colorCount, 4), float)
+ x = colorArray[:, 0]
+ for cIndex in range(3):
+ colorList[:, cIndex] = np.interp(
+ np.linspace(-1., 1., colorCount),
+ x, colorArray[:, cIndex+1])
+
+ colorMap = cols.LinearSegmentedColormap.from_list(
+ name, colorList, N=255)
+
+ plt.register_cmap(name, colorMap)
+
+ name = 'BuOr'
+ colors1 = plt.cm.PuOr(np.linspace(0., 1, 256))
+ colors2 = plt.cm.RdBu(np.linspace(0, 1, 256))
+
+ # combine them and build a new colormap, just the orange from the first
+ # and the blue from the second
+ colorList = np.vstack((colors1[0:128, :], colors2[128:256, :]))
+ # reverse the order
+ colorList = colorList[::-1, :]
+ colorMap = cols.LinearSegmentedColormap.from_list(name, colorList)
+
+ plt.register_cmap(name, colorMap)
+
# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python
diff --git a/mpas_analysis/test/test_analysis_task.py b/mpas_analysis/test/test_analysis_task.py
index c0cfa9c99..e6eeab82e 100644
--- a/mpas_analysis/test/test_analysis_task.py
+++ b/mpas_analysis/test/test_analysis_task.py
@@ -10,8 +10,7 @@
import pytest
from mpas_analysis.test import TestCase
from mpas_analysis.shared.analysis_task import AnalysisTask
-from mpas_analysis.configuration.MpasAnalysisConfigParser \
- import MpasAnalysisConfigParser
+from mpas_analysis.configuration import MpasAnalysisConfigParser
class TestAnalysisTask(TestCase):
diff --git a/mpas_analysis/test/test_climatology.py b/mpas_analysis/test/test_climatology.py
index ce7948441..087ccd7ca 100644
--- a/mpas_analysis/test/test_climatology.py
+++ b/mpas_analysis/test/test_climatology.py
@@ -18,11 +18,9 @@
from mpas_analysis.test import TestCase, loaddatadir
from mpas_analysis.shared.generalized_reader.generalized_reader \
import open_multifile_dataset
-from mpas_analysis.configuration.MpasAnalysisConfigParser \
- import MpasAnalysisConfigParser
+from mpas_analysis.configuration import MpasAnalysisConfigParser
from mpas_analysis.shared.climatology import \
get_comparison_descriptor, get_remapper, \
- get_observation_climatology_file_names, \
add_years_months_days_in_month, compute_climatology, \
compute_monthly_climatology
from mpas_analysis.shared.grid import MpasMeshDescriptor, LatLonGridDescriptor
@@ -195,26 +193,6 @@ def test_get_observations_remapper(self):
shutil.copyfile(defaultMappingFileName,
explicitMappingFileName)
- def test_get_observation_climatology_file_names(self):
- config = self.setup_config()
- fieldName = 'sst'
- monthNames = 'JFM'
- componentName = 'ocean'
-
- remapper = self.setup_obs_remapper(config, fieldName)
-
- (climatologyFileName, remappedFileName) = \
- get_observation_climatology_file_names(
- config, fieldName, monthNames, componentName, remapper)
- expectedClimatologyFileName = '{}/clim/obs/sst_1.0x1.0degree_' \
- 'JFM.nc'.format(self.test_dir)
- self.assertEqual(climatologyFileName, expectedClimatologyFileName)
-
- expectedRemappedFileName = '{}/clim/obs/remap/sst_1.0x1.0degree_' \
- 'to_0.5x0.5degree_' \
- 'JFM.nc'.format(self.test_dir)
- self.assertEqual(remappedFileName, expectedRemappedFileName)
-
def test_compute_climatology(self):
config = self.setup_config()
calendar = 'gregorian_noleap'
diff --git a/mpas_analysis/test/test_generalized_reader.py b/mpas_analysis/test/test_generalized_reader.py
index d7d24b4db..1bc362831 100644
--- a/mpas_analysis/test/test_generalized_reader.py
+++ b/mpas_analysis/test/test_generalized_reader.py
@@ -12,8 +12,7 @@
from mpas_analysis.test import TestCase, loaddatadir
from mpas_analysis.shared.generalized_reader.generalized_reader \
import open_multifile_dataset
-from mpas_analysis.configuration.MpasAnalysisConfigParser \
- import MpasAnalysisConfigParser
+from mpas_analysis.configuration import MpasAnalysisConfigParser
@pytest.mark.usefixtures("loaddatadir")
diff --git a/mpas_analysis/test/test_interpolate.py b/mpas_analysis/test/test_interpolate.py
index 5e6088232..0b44d0a42 100644
--- a/mpas_analysis/test/test_interpolate.py
+++ b/mpas_analysis/test/test_interpolate.py
@@ -20,8 +20,7 @@
from mpas_analysis.shared.grid import MpasMeshDescriptor, \
LatLonGridDescriptor, ProjectionGridDescriptor
from mpas_analysis.test import TestCase, loaddatadir
-from mpas_analysis.configuration.MpasAnalysisConfigParser \
- import MpasAnalysisConfigParser
+from mpas_analysis.configuration import MpasAnalysisConfigParser
@pytest.mark.usefixtures('loaddatadir')
diff --git a/mpas_analysis/test/test_mpas_config_parser.py b/mpas_analysis/test/test_mpas_config_parser.py
index b27eaba1b..38944f287 100644
--- a/mpas_analysis/test/test_mpas_config_parser.py
+++ b/mpas_analysis/test/test_mpas_config_parser.py
@@ -12,8 +12,7 @@
import six
from six.moves import configparser
from mpas_analysis.test import TestCase, loaddatadir
-from mpas_analysis.configuration.MpasAnalysisConfigParser \
- import MpasAnalysisConfigParser
+from mpas_analysis.configuration import MpasAnalysisConfigParser
from . import requires_numpy
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask.py b/mpas_analysis/test/test_remap_obs_clim_subtask.py
new file mode 100644
index 000000000..d0e0e55dc
--- /dev/null
+++ b/mpas_analysis/test/test_remap_obs_clim_subtask.py
@@ -0,0 +1,169 @@
+"""
+Unit test infrastructure for MpasClimatologyTask.
+
+Xylar Asay-Davis
+"""
+
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+import pytest
+import tempfile
+import shutil
+import os
+import xarray
+
+from mpas_analysis.test import TestCase, loaddatadir
+from mpas_analysis.configuration import MpasAnalysisConfigParser
+from mpas_analysis.shared.climatology import RemapObservedClimatologySubtask
+from mpas_analysis.shared import AnalysisTask
+from mpas_analysis.shared.io.utility import build_config_full_path, \
+ make_directories
+from mpas_analysis.shared.grid import LatLonGridDescriptor
+
+
+class RemapObservedMLDClimatology(RemapObservedClimatologySubtask): # {{{
+ """
+ A subtask for reading and remapping MLD observations
+
+ Authors
+ -------
+ Xylar Asay-Davis
+ """
+
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # create a descriptor of the observation grid using the lat/lon
+ # coordinates
+ obsDescriptor = LatLonGridDescriptor.read(fileName=fileName,
+ 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
+ -------
+ Xylar Asay-Davis
+ '''
+
+ # Load MLD observational data
+ dsObs = xarray.open_dataset(fileName)
+
+ dsObs.coords['month'] = dsObs['month']
+ dsObs.coords['year'] = dsObs['year']
+
+ return dsObs # }}}
+
+ # }}}
+
+
+@pytest.mark.usefixtures("loaddatadir")
+class TestRemapObsClimSubtask(TestCase):
+ def setUp(self):
+ # Create a temporary directory
+ self.test_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ # Remove the directory after the test
+ shutil.rmtree(self.test_dir)
+
+ def setup_config(self):
+ configPath = self.datadir.join('config.remap_obs')
+ config = MpasAnalysisConfigParser()
+ config.read(str(configPath))
+ config.set('input', 'baseDirectory', str(self.datadir))
+ config.set('oceanObservations', 'baseDirectory', str(self.datadir))
+ config.set('output', 'baseDirectory', str(self.test_dir))
+ return config
+
+ def setup_subtask(self):
+ config = self.setup_config()
+ parent = AnalysisTask(config, 'taskName', 'ocean')
+ obsFileName = '{}/mld_4.0x4.0degree.nc'.format(str(self.datadir))
+ remapObsTask = RemapObservedMLDClimatology(
+ parentTask=parent,
+ seasons=['ANN'], fileName=obsFileName,
+ outFilePrefix='mld',
+ comparisonGridNames=['latlon', 'antarctic'])
+
+ remapObsTask.setup_and_check()
+ return remapObsTask
+
+ def test_subtask_run_analysis(self):
+ remapSubtask = self.setup_subtask()
+
+ config = remapSubtask.config
+ logsDirectory = build_config_full_path(config, 'output',
+ 'logsSubdirectory')
+ make_directories(logsDirectory)
+ make_directories('{}/configs/'.format(logsDirectory))
+
+ remapSubtask.run(writeLogFile=False)
+
+ for comparisonGridName in remapSubtask.comparisonGridNames:
+ for season in remapSubtask.seasons:
+ for stage in ['original', 'climatology', 'remapped']:
+ fileName = remapSubtask.get_file_name(
+ season=season, stage=stage,
+ comparisonGridName=comparisonGridName)
+ assert(os.path.exists(fileName))
+
+ def test_subtask_get_file_name(self):
+ remapSubtask = self.setup_subtask()
+
+ for comparisonGridName in ['latlon', 'antarctic', None]:
+ fileName = remapSubtask.get_file_name(
+ stage='original', comparisonGridName=comparisonGridName)
+ assert(fileName == '{}/clim/obs/mld_4.0x4.0degree.nc'.format(
+ str(self.test_dir)))
+
+ stage = 'climatology'
+ for comparisonGridName in ['latlon', 'antarctic', None]:
+ fileName = remapSubtask.get_file_name(stage=stage,
+ season='JFM',
+ comparisonGridName='latlon')
+ assert(fileName == '{}/clim/obs/mld_4.0x4.0degree_JFM.nc'.format(
+ str(self.test_dir)))
+
+ stage = 'remapped'
+ fileName = remapSubtask.get_file_name(stage=stage, season='JFM',
+ comparisonGridName='latlon')
+ assert(fileName == '{}/clim/obs/remapped/mld_4.0x4.0degree_to_'
+ '0.5x0.5degree_JFM.nc'.format(str(self.test_dir)))
+
+ fileName = remapSubtask.get_file_name(stage=stage, season='JFM',
+ comparisonGridName='antarctic')
+ assert(fileName == '{}/clim/obs/remapped/mld_4.0x4.0degree_to_'
+ '6000.0x6000.0km_10.0km_Antarctic_stereo_JFM.nc'.format(
+ str(self.test_dir)))
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask/config.remap_obs b/mpas_analysis/test/test_remap_obs_clim_subtask/config.remap_obs
new file mode 100644
index 000000000..1e6c43e8f
--- /dev/null
+++ b/mpas_analysis/test/test_remap_obs_clim_subtask/config.remap_obs
@@ -0,0 +1,28 @@
+[input]
+baseDirectory = /dir/for/model/output
+runSubdirectory = .
+oceanHistorySubdirectory = .
+oceanNamelistFileName = mpas-o_in
+oceanStreamsFileName = streams.ocean
+
+[output]
+baseDirectory = /dir/for/analysis/output
+plotsSubdirectory = plots
+logsSubdirectory = logs
+mappingSubdirectory = mapping
+
+[climatology]
+startYear = 2
+endYear = 2
+comparisonLatResolution = 0.5
+comparisonLonResolution = 0.5
+comparisonAntarcticStereoWidth = 6000.
+comparisonAntarcticStereoResolution = 10.
+useNcremap = True
+renormalizationThreshold = 0.01
+
+[oceanObservations]
+baseDirectory = /dir/to/ocean/observations
+interpolationMethod = bilinear
+climatologySubdirectory = clim/obs
+remappedClimSubdirectory = clim/obs/remapped
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask/mld_4.0x4.0degree.nc b/mpas_analysis/test/test_remap_obs_clim_subtask/mld_4.0x4.0degree.nc
new file mode 100644
index 000000000..e463860b0
Binary files /dev/null and b/mpas_analysis/test/test_remap_obs_clim_subtask/mld_4.0x4.0degree.nc differ
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask/mpas-o_in b/mpas_analysis/test/test_remap_obs_clim_subtask/mpas-o_in
new file mode 100644
index 000000000..0d8ba89a3
--- /dev/null
+++ b/mpas_analysis/test/test_remap_obs_clim_subtask/mpas-o_in
@@ -0,0 +1,1092 @@
+&run_modes
+ config_ocean_run_mode = 'forward'
+/
+&time_management
+ config_calendar_type = 'gregorian_noleap'
+ config_do_restart = .true.
+ config_restart_timestamp_name = 'rpointer.ocn'
+ config_start_time = 'file'
+/
+&io
+ config_pio_num_iotasks = 0
+ config_pio_stride = 1
+ config_write_output_on_startup = .true.
+/
+&decomposition
+ config_block_decomp_file_prefix = '/project/projectdirs/acme/inputdata/ocn/mpas-o/oQU240/mpas-o.graph.info.151209.part.'
+ config_explicit_proc_decomp = .false.
+ config_num_halos = 3
+ config_number_of_blocks = 0
+ config_proc_decomp_file_prefix = 'graph.info.part.'
+/
+&init_setup
+ config_expand_sphere = .false.
+ config_init_configuration = 'none'
+ config_realistic_coriolis_parameter = .false.
+ config_vert_levels = -1
+ config_vertical_grid = 'uniform'
+ config_write_cull_cell_mask = .true.
+/
+&cvtgenerator
+ config_1dcvtgenerator_dzseed = 1.2
+ config_1dcvtgenerator_stretch1 = 1.0770
+ config_1dcvtgenerator_stretch2 = 1.0275
+/
+&init_ssh_and_landicepressure
+ config_iterative_init_variable = 'landIcePressure'
+/
+&time_integration
+ config_dt = '01:00:00'
+ config_time_integrator = 'split_explicit'
+/
+&ale_vertical_grid
+ config_dzdk_positive = .false.
+ config_max_thickness_factor = 6.0
+ config_min_thickness = 1.0
+ config_use_min_max_thickness = .false.
+ config_vert_coord_movement = 'uniform_stretching'
+/
+&ale_frequency_filtered_thickness
+ config_highfreqthick_del2 = 100.0
+ config_highfreqthick_restore_time = 30.0
+ config_thickness_filter_timescale = 5.0
+ config_use_freq_filtered_thickness = .false.
+ config_use_highfreqthick_del2 = .false.
+ config_use_highfreqthick_restore = .false.
+/
+&partial_bottom_cells
+ config_alter_ics_for_pbcs = .false.
+ config_min_pbc_fraction = 0.10
+ config_pbc_alteration_type = 'full_cell'
+/
+&hmix
+ config_apvm_scale_factor = 0.0
+ config_hmix_scalewithmesh = .false.
+ config_maxmeshdensity = -1.0
+/
+&hmix_del2
+ config_mom_del2 = 10.0
+ config_tracer_del2 = 10.0
+ config_use_mom_del2 = .false.
+ config_use_tracer_del2 = .false.
+/
+&hmix_del4
+ config_mom_del4 = 2.0e14
+ config_mom_del4_div_factor = 1.0
+ config_tracer_del4 = 0.0
+ config_use_mom_del4 = .true.
+ config_use_tracer_del4 = .false.
+/
+&hmix_leith
+ config_leith_dx = 15000.0
+ config_leith_parameter = 1.0
+ config_leith_visc2_max = 2.5e3
+ config_use_leith_del2 = .false.
+/
+&mesoscale_eddy_parameterization
+ config_gravwavespeed_trunc = 0.3
+ config_max_relative_slope = 0.01
+ config_redi_bottom_layer_tapering_depth = 0.0
+ config_redi_kappa = 0.0
+ config_redi_surface_layer_tapering_extent = 0.0
+ config_standardgm_tracer_kappa = 600.0
+ config_use_redi_bottom_layer_tapering = .false.
+ config_use_redi_surface_layer_tapering = .false.
+ config_use_standardgm = .true.
+/
+&hmix_del2_tensor
+ config_mom_del2_tensor = 10.0
+ config_use_mom_del2_tensor = .false.
+/
+&hmix_del4_tensor
+ config_mom_del4_tensor = 5.0e13
+ config_use_mom_del4_tensor = .false.
+/
+&rayleigh_damping
+ config_rayleigh_damping_coeff = 0.0
+ config_rayleigh_friction = .false.
+/
+&vmix
+ config_convective_diff = 1.0
+ config_convective_visc = 1.0
+/
+&vmix_const
+ config_use_const_diff = .false.
+ config_use_const_visc = .false.
+ config_vert_diff = 1.0e-5
+ config_vert_visc = 1.0e-4
+/
+&vmix_rich
+ config_bkrd_vert_diff = 1.0e-5
+ config_bkrd_vert_visc = 1.0e-4
+ config_rich_mix = 0.005
+ config_use_rich_diff = .false.
+ config_use_rich_visc = .false.
+/
+&vmix_tanh
+ config_max_diff_tanh = 2.5e-2
+ config_max_visc_tanh = 2.5e-1
+ config_min_diff_tanh = 1.0e-5
+ config_min_visc_tanh = 1.0e-4
+ config_use_tanh_diff = .false.
+ config_use_tanh_visc = .false.
+ config_zmid_tanh = -100
+ config_zwidth_tanh = 100
+/
+&cvmix
+ config_cvmix_background_diffusion = 1.0e-5
+ config_cvmix_background_viscosity = 1.0e-4
+ config_cvmix_convective_basedonbvf = .true.
+ config_cvmix_convective_diffusion = 1.0
+ config_cvmix_convective_triggerbvf = 0.0
+ config_cvmix_convective_viscosity = 1.0
+ config_cvmix_kpp_boundary_layer_depth = 30.0
+ config_cvmix_kpp_criticalbulkrichardsonnumber = 0.25
+ config_cvmix_kpp_ekmanobl = .false.
+ config_cvmix_kpp_interpolationomltype = 'quadratic'
+ config_cvmix_kpp_matching = 'SimpleShapes'
+ config_cvmix_kpp_monobobl = .false.
+ config_cvmix_kpp_stop_obl_search = 100.0
+ config_cvmix_kpp_surface_layer_averaging = 5.0
+ config_cvmix_kpp_surface_layer_extent = 0.1
+ config_cvmix_kpp_use_enhanced_diff = .true.
+ config_cvmix_num_ri_smooth_loops = 2
+ config_cvmix_prandtl_number = 1.0
+ config_cvmix_shear_kpp_exp = 3
+ config_cvmix_shear_kpp_nu_zero = 0.005
+ config_cvmix_shear_kpp_ri_zero = 0.7
+ config_cvmix_shear_mixing_scheme = 'KPP'
+ config_cvmix_shear_pp_alpha = 5.0
+ config_cvmix_shear_pp_exp = 2.0
+ config_cvmix_shear_pp_nu_zero = 0.005
+ config_use_cvmix = .true.
+ config_use_cvmix_background = .true.
+ config_use_cvmix_convection = .true.
+ config_use_cvmix_double_diffusion = .false.
+ config_use_cvmix_fixed_boundary_layer = .false.
+ config_use_cvmix_kpp = .true.
+ config_use_cvmix_shear = .true.
+ config_use_cvmix_tidal_mixing = .false.
+ configure_cvmix_kpp_minimum_obl_under_sea_ice = 10.0
+/
+&forcing
+ config_flux_attenuation_coefficient = 0.001
+ config_flux_attenuation_coefficient_runoff = 10.0
+ config_use_bulk_thickness_flux = .true.
+ config_use_bulk_wind_stress = .true.
+/
+&coupling
+ config_ssh_grad_relax_timescale = 86400.0
+/
+&shortwaveradiation
+ config_forcing_restart_file = 'Restart_forcing_time_stamp'
+ config_jerlov_water_type = 3
+ config_surface_buoyancy_depth = 1
+ config_sw_absorption_type = 'jerlov'
+/
+&frazil_ice
+ config_frazil_fractional_thickness_limit = 0.1
+ config_frazil_heat_of_fusion = 3.337e5
+ config_frazil_ice_density = 1000.0
+ config_frazil_in_open_ocean = .true.
+ config_frazil_land_ice_reference_salinity = 0.0
+ config_frazil_maximum_depth = 100.0
+ config_frazil_maximum_freezing_temperature = 0.0
+ config_frazil_sea_ice_reference_salinity = 4.0
+ config_frazil_under_land_ice = .true.
+ config_frazil_use_surface_pressure = .false.
+ config_specific_heat_sea_water = 3.996e3
+ config_use_frazil_ice_formation = .true.
+/
+&land_ice_fluxes
+ config_land_ice_flux_attenuation_coefficient = 10.0
+ config_land_ice_flux_boundarylayerneighborweight = 0.0
+ config_land_ice_flux_boundarylayerthickness = 10.0
+ config_land_ice_flux_cp_ice = 2.009e3
+ config_land_ice_flux_formulation = 'Jenkins'
+ config_land_ice_flux_isomip_gammat = 1e-4
+ config_land_ice_flux_jenkins_heat_transfer_coefficient = 0.011
+ config_land_ice_flux_jenkins_salt_transfer_coefficient = 3.1e-4
+ config_land_ice_flux_mode = 'off'
+ config_land_ice_flux_rho_ice = 918
+ config_land_ice_flux_rms_tidal_velocity = 5e-2
+ config_land_ice_flux_topdragcoeff = 2.5e-3
+ config_land_ice_flux_usehollandjenkinsadvdiff = .false.
+/
+&advection
+ config_coef_3rd_order = 0.25
+ config_horiz_tracer_adv_order = 3
+ config_monotonic = .true.
+ config_vert_tracer_adv = 'stencil'
+ config_vert_tracer_adv_order = 3
+/
+&bottom_drag
+ config_bottom_drag_coeff = 1.0e-3
+/
+&ocean_constants
+ config_density0 = 1026.0
+/
+&pressure_gradient
+ config_common_level_weight = 0.5
+ config_pressure_gradient_type = 'Jacobian_from_TS'
+/
+&eos
+ config_eos_type = 'jm'
+ config_land_ice_cavity_freezing_temperature_coeff_0 = 6.22e-2
+ config_land_ice_cavity_freezing_temperature_coeff_p = -7.43e-8
+ config_land_ice_cavity_freezing_temperature_coeff_ps = -1.74e-10
+ config_land_ice_cavity_freezing_temperature_coeff_s = -5.63e-2
+ config_land_ice_cavity_freezing_temperature_reference_pressure = 0.0
+ config_open_ocean_freezing_temperature_coeff_0 = -1.8
+ config_open_ocean_freezing_temperature_coeff_p = 0.0
+ config_open_ocean_freezing_temperature_coeff_ps = 0.0
+ config_open_ocean_freezing_temperature_coeff_s = 0.0
+ config_open_ocean_freezing_temperature_reference_pressure = 0.0
+/
+&eos_linear
+ config_eos_linear_alpha = 0.2
+ config_eos_linear_beta = 0.8
+ config_eos_linear_densityref = 1000.0
+ config_eos_linear_sref = 35.0
+ config_eos_linear_tref = 5.0
+/
+&split_explicit_ts
+ config_btr_dt = '0000_00:03:00'
+ config_btr_gam1_velwt1 = 0.5
+ config_btr_gam2_sshwt1 = 1.0
+ config_btr_gam3_velwt2 = 1.0
+ config_btr_solve_ssh2 = .false.
+ config_btr_subcycle_loop_factor = 2
+ config_n_bcl_iter_beg = 1
+ config_n_bcl_iter_end = 2
+ config_n_bcl_iter_mid = 2
+ config_n_btr_cor_iter = 2
+ config_n_ts_iter = 2
+ config_vel_correction = .true.
+/
+&testing
+ config_conduct_tests = .false.
+ config_tensor_test_function = 'sph_uCosCos'
+ config_test_tensors = .false.
+/
+&debug
+ config_check_ssh_consistency = .true.
+ config_check_tracer_monotonicity = .false.
+ config_check_zlevel_consistency = .false.
+ config_disable_redi_horizontal_term1 = .false.
+ config_disable_redi_horizontal_term2 = .false.
+ config_disable_redi_horizontal_term3 = .false.
+ config_disable_redi_k33 = .false.
+ config_disable_thick_all_tend = .false.
+ config_disable_thick_hadv = .false.
+ config_disable_thick_sflux = .false.
+ config_disable_thick_vadv = .false.
+ config_disable_tr_adv = .false.
+ config_disable_tr_all_tend = .false.
+ config_disable_tr_hmix = .false.
+ config_disable_tr_nonlocalflux = .false.
+ config_disable_tr_sflux = .false.
+ config_disable_tr_vmix = .false.
+ config_disable_vel_all_tend = .false.
+ config_disable_vel_coriolis = .false.
+ config_disable_vel_hmix = .false.
+ config_disable_vel_pgrad = .false.
+ config_disable_vel_surface_stress = .false.
+ config_disable_vel_vadv = .false.
+ config_disable_vel_vmix = .false.
+ config_filter_btr_mode = .false.
+ config_include_ke_vertex = .false.
+ config_prescribe_thickness = .false.
+ config_prescribe_velocity = .false.
+ config_read_nearest_restart = .false.
+/
+&constrain_haney_number
+ config_rx1_horiz_smooth_open_ocean_cells = 20
+ config_rx1_horiz_smooth_weight = 1.0
+ config_rx1_init_inner_weight = 0.1
+ config_rx1_inner_iter_count = 10
+ config_rx1_max = 5.0
+ config_rx1_min_layer_thickness = 1.0
+ config_rx1_min_levels = 3
+ config_rx1_outer_iter_count = 20
+ config_rx1_slope_weight = 1e-1
+ config_rx1_vert_smooth_weight = 1.0
+ config_rx1_zstar_weight = 1.0
+ config_use_rx1_constraint = .false.
+/
+&baroclinic_channel
+ config_baroclinic_channel_bottom_depth = 1000.0
+ config_baroclinic_channel_bottom_temperature = 10.1
+ config_baroclinic_channel_coriolis_parameter = -1.2e-4
+ config_baroclinic_channel_gradient_width_dist = 40e3
+ config_baroclinic_channel_gradient_width_frac = 0.08
+ config_baroclinic_channel_salinity = 35.0
+ config_baroclinic_channel_surface_temperature = 13.1
+ config_baroclinic_channel_temperature_difference = 1.2
+ config_baroclinic_channel_use_distances = .false.
+ config_baroclinic_channel_vert_levels = 20
+/
+&lock_exchange
+ config_lock_exchange_bottom_depth = 20.0
+ config_lock_exchange_cold_temperature = 5.0
+ config_lock_exchange_direction = 'y'
+ config_lock_exchange_isopycnal_min_thickness = 0.01
+ config_lock_exchange_layer_type = 'z-level'
+ config_lock_exchange_salinity = 35.0
+ config_lock_exchange_vert_levels = 20
+ config_lock_exchange_warm_temperature = 30.0
+/
+&internal_waves
+ config_internal_waves_amplitude_width_dist = 50e3
+ config_internal_waves_amplitude_width_frac = 0.33
+ config_internal_waves_bottom_depth = 500.0
+ config_internal_waves_bottom_temperature = 10.1
+ config_internal_waves_isopycnal_displacement = 125.0
+ config_internal_waves_layer_type = 'z-level'
+ config_internal_waves_salinity = 35.0
+ config_internal_waves_surface_temperature = 20.1
+ config_internal_waves_temperature_difference = 2.0
+ config_internal_waves_use_distances = false
+ config_internal_waves_vert_levels = 20
+/
+&overflow
+ config_overflow_bottom_depth = 2000.0
+ config_overflow_domain_temperature = 20.0
+ config_overflow_isopycnal_min_thickness = 0.01
+ config_overflow_layer_type = 'z-level'
+ config_overflow_plug_temperature = 10.0
+ config_overflow_plug_width_dist = 20e3
+ config_overflow_plug_width_frac = 0.10
+ config_overflow_ridge_depth = 500.0
+ config_overflow_salinity = 35.0
+ config_overflow_slope_center_dist = 40e3
+ config_overflow_slope_center_frac = 0.20
+ config_overflow_slope_width_dist = 7e3
+ config_overflow_slope_width_frac = 0.05
+ config_overflow_use_distances = false
+ config_overflow_vert_levels = 100
+/
+&global_ocean
+ config_global_ocean_chlorophyll_varname = 'none'
+ config_global_ocean_clearsky_varname = 'none'
+ config_global_ocean_cull_inland_seas = .true.
+ config_global_ocean_deepen_critical_passages = .true.
+ config_global_ocean_depress_by_land_ice = .false.
+ config_global_ocean_depth_conversion_factor = 1.0
+ config_global_ocean_depth_dimname = 'none'
+ config_global_ocean_depth_file = 'none'
+ config_global_ocean_depth_varname = 'none'
+ config_global_ocean_ecosys_depth_conversion_factor = 1.0
+ config_global_ocean_ecosys_depth_varname = 'none'
+ config_global_ocean_ecosys_file = 'unknown'
+ config_global_ocean_ecosys_forcing_file = 'unknown'
+ config_global_ocean_ecosys_forcing_time_dimname = 'none'
+ config_global_ocean_ecosys_lat_varname = 'none'
+ config_global_ocean_ecosys_latlon_degrees = .true.
+ config_global_ocean_ecosys_lon_varname = 'none'
+ config_global_ocean_ecosys_method = 'bilinear_interpolation'
+ config_global_ocean_ecosys_ndepth_dimname = 'none'
+ config_global_ocean_ecosys_nlat_dimname = 'none'
+ config_global_ocean_ecosys_nlon_dimname = 'none'
+ config_global_ocean_ecosys_vert_levels = -1
+ config_global_ocean_interior_restore_rate = 1.0e-7
+ config_global_ocean_land_ice_topo_draft_varname = 'none'
+ config_global_ocean_land_ice_topo_file = 'none'
+ config_global_ocean_land_ice_topo_grounded_frac_varname = 'none'
+ config_global_ocean_land_ice_topo_ice_frac_varname = 'none'
+ config_global_ocean_land_ice_topo_lat_varname = 'none'
+ config_global_ocean_land_ice_topo_latlon_degrees = .true.
+ config_global_ocean_land_ice_topo_lon_varname = 'none'
+ config_global_ocean_land_ice_topo_nlat_dimname = 'none'
+ config_global_ocean_land_ice_topo_nlon_dimname = 'none'
+ config_global_ocean_land_ice_topo_thickness_varname = 'none'
+ config_global_ocean_minimum_depth = 15
+ config_global_ocean_piston_velocity = 5.0e-5
+ config_global_ocean_salinity_file = 'none'
+ config_global_ocean_salinity_varname = 'none'
+ config_global_ocean_smooth_ecosys_iterations = 0
+ config_global_ocean_smooth_topography = .true.
+ config_global_ocean_smooth_ts_iterations = 0
+ config_global_ocean_swdata_file = 'none'
+ config_global_ocean_swdata_lat_varname = 'none'
+ config_global_ocean_swdata_latlon_degrees = .true.
+ config_global_ocean_swdata_lon_varname = 'none'
+ config_global_ocean_swdata_method = 'bilinear_interpolation'
+ config_global_ocean_swdata_nlat_dimname = 'none'
+ config_global_ocean_swdata_nlon_dimname = 'none'
+ config_global_ocean_temperature_file = 'none'
+ config_global_ocean_temperature_varname = 'none'
+ config_global_ocean_topography_file = 'none'
+ config_global_ocean_topography_has_ocean_frac = .false.
+ config_global_ocean_topography_lat_varname = 'none'
+ config_global_ocean_topography_latlon_degrees = .true.
+ config_global_ocean_topography_lon_varname = 'none'
+ config_global_ocean_topography_method = 'bilinear_interpolation'
+ config_global_ocean_topography_nlat_dimname = 'none'
+ config_global_ocean_topography_nlon_dimname = 'none'
+ config_global_ocean_topography_ocean_frac_varname = 'none'
+ config_global_ocean_topography_varname = 'none'
+ config_global_ocean_tracer_depth_conversion_factor = 1.0
+ config_global_ocean_tracer_depth_varname = 'none'
+ config_global_ocean_tracer_lat_varname = 'none'
+ config_global_ocean_tracer_latlon_degrees = .true.
+ config_global_ocean_tracer_lon_varname = 'none'
+ config_global_ocean_tracer_method = 'bilinear_interpolation'
+ config_global_ocean_tracer_ndepth_dimname = 'none'
+ config_global_ocean_tracer_nlat_dimname = 'none'
+ config_global_ocean_tracer_nlon_dimname = 'none'
+ config_global_ocean_tracer_vert_levels = -1
+ config_global_ocean_windstress_conversion_factor = 1
+ config_global_ocean_windstress_file = 'none'
+ config_global_ocean_windstress_lat_varname = 'none'
+ config_global_ocean_windstress_latlon_degrees = .true.
+ config_global_ocean_windstress_lon_varname = 'none'
+ config_global_ocean_windstress_meridional_varname = 'none'
+ config_global_ocean_windstress_method = 'bilinear_interpolation'
+ config_global_ocean_windstress_nlat_dimname = 'none'
+ config_global_ocean_windstress_nlon_dimname = 'none'
+ config_global_ocean_windstress_zonal_varname = 'none'
+ config_global_ocean_zenithangle_varname = 'none'
+/
+&cvmix_wswsbf
+ config_cvmix_wswsbf_bottom_depth = 400.0
+ config_cvmix_wswsbf_coriolis_parameter = 1.0e-4
+ config_cvmix_wswsbf_evaporation_flux = 0.0
+ config_cvmix_wswsbf_interior_salinity_restoring_rate = 1.0e-6
+ config_cvmix_wswsbf_interior_temperature_restoring_rate = 1.0e-6
+ config_cvmix_wswsbf_latent_heat_flux = 0.0
+ config_cvmix_wswsbf_max_windstress = 0.10
+ config_cvmix_wswsbf_mixed_layer_depth_salinity = 0.0
+ config_cvmix_wswsbf_mixed_layer_depth_temperature = 0.0
+ config_cvmix_wswsbf_mixed_layer_salinity_change = 0.0
+ config_cvmix_wswsbf_mixed_layer_temperature_change = 0.0
+ config_cvmix_wswsbf_rain_flux = 0.0
+ config_cvmix_wswsbf_salinity_gradient = 0.0
+ config_cvmix_wswsbf_salinity_gradient_mixed_layer = 0.0
+ config_cvmix_wswsbf_salinity_piston_velocity = 4.0e-6
+ config_cvmix_wswsbf_sensible_heat_flux = 0.0
+ config_cvmix_wswsbf_shortwave_heat_flux = 0.0
+ config_cvmix_wswsbf_surface_restoring_salinity = 35.0
+ config_cvmix_wswsbf_surface_restoring_temperature = 15.0
+ config_cvmix_wswsbf_surface_salinity = 35.0
+ config_cvmix_wswsbf_surface_temperature = 15.0
+ config_cvmix_wswsbf_temperature_gradient = 0.01
+ config_cvmix_wswsbf_temperature_gradient_mixed_layer = 0.0
+ config_cvmix_wswsbf_temperature_piston_velocity = 4.0e-6
+ config_cvmix_wswsbf_vert_levels = 100
+ config_cvmix_wswsbf_vertical_grid = 'uniform'
+/
+&iso
+ config_iso_acc_wind = 0.2
+ config_iso_asf_wind = -0.05
+ config_iso_cont_slope_flag = .true.
+ config_iso_depression_center_lon = 60
+ config_iso_depression_depth = 800
+ config_iso_depression_flag = .true.
+ config_iso_depression_north_lat = -65
+ config_iso_depression_south_lat = -72
+ config_iso_depression_width = 480000
+ config_iso_embayment_center_lat = -71
+ config_iso_embayment_center_lon = 60
+ config_iso_embayment_depth = 2000
+ config_iso_embayment_flag = .true.
+ config_iso_embayment_radius = 500000
+ config_iso_heat_flux_lat_mn = -53
+ config_iso_heat_flux_lat_sm = -65
+ config_iso_heat_flux_lat_ss = -70
+ config_iso_heat_flux_middle = 10
+ config_iso_heat_flux_north = -5
+ config_iso_heat_flux_region1 = -5
+ config_iso_heat_flux_region1_flag = false
+ config_iso_heat_flux_region1_radius = 300000
+ config_iso_heat_flux_region2 = -5
+ config_iso_heat_flux_region2_flag = false
+ config_iso_heat_flux_region2_radius = 240000
+ config_iso_heat_flux_south = -5
+ config_iso_initial_temp_h0 = 1200
+ config_iso_initial_temp_h1 = 500
+ config_iso_initial_temp_latn = -50
+ config_iso_initial_temp_lats = -75
+ config_iso_initial_temp_mt = 0.000075
+ config_iso_initial_temp_t1 = 3.5
+ config_iso_initial_temp_t2 = 4.0
+ config_iso_main_channel_depth = 4000.0
+ config_iso_max_cont_slope = 0.01
+ config_iso_north_wall_lat = -50
+ config_iso_plateau_center_lat = -58
+ config_iso_plateau_center_lon = 300
+ config_iso_plateau_flag = .true.
+ config_iso_plateau_height = 2000
+ config_iso_plateau_radius = 200000
+ config_iso_plateau_slope_width = 1000000
+ config_iso_region1_center_lat = -75
+ config_iso_region1_center_lon = 60
+ config_iso_region2_center_lat = -71
+ config_iso_region2_center_lon = 150
+ config_iso_region3_center_lat = -71
+ config_iso_region3_center_lon = 240
+ config_iso_region4_center_lat = -71
+ config_iso_region4_center_lon = 330
+ config_iso_ridge_center_lon = 180
+ config_iso_ridge_flag = .true.
+ config_iso_ridge_height = 2000.0
+ config_iso_ridge_width = 2000000
+ config_iso_salinity = 35.0
+ config_iso_shelf_depth = 500
+ config_iso_shelf_flag = .true.
+ config_iso_shelf_width = 120000
+ config_iso_south_wall_lat = -70
+ config_iso_surface_temperature_piston_velocity = 5.787e-5
+ config_iso_temperature_restore_lcx1 = 600000
+ config_iso_temperature_restore_lcx2 = 600000
+ config_iso_temperature_restore_lcx3 = 600000
+ config_iso_temperature_restore_lcx4 = 600000
+ config_iso_temperature_restore_lcy1 = 600000
+ config_iso_temperature_restore_lcy2 = 250000
+ config_iso_temperature_restore_lcy3 = 250000
+ config_iso_temperature_restore_lcy4 = 250000
+ config_iso_temperature_restore_region1_flag = .true.
+ config_iso_temperature_restore_region2_flag = .true.
+ config_iso_temperature_restore_region3_flag = .true.
+ config_iso_temperature_restore_region4_flag = .true.
+ config_iso_temperature_restore_t1 = -1
+ config_iso_temperature_restore_t2 = -1
+ config_iso_temperature_restore_t3 = -1
+ config_iso_temperature_restore_t4 = -1
+ config_iso_temperature_sponge_h1 = 1000
+ config_iso_temperature_sponge_l1 = 120000
+ config_iso_temperature_sponge_t1 = 10
+ config_iso_temperature_sponge_tau1 = 10.0
+ config_iso_vert_levels = 100
+ config_iso_wind_stress_max = 0.01
+ config_iso_wind_trans = -65
+/
+&soma
+ config_soma_bottom_depth = 2500.0
+ config_soma_center_latitude = 35.0
+ config_soma_center_longitude = 0.0
+ config_soma_density_difference = 4.0
+ config_soma_density_difference_linear = 0.05
+ config_soma_domain_width = 1.25e6
+ config_soma_phi = 0.1
+ config_soma_ref_density = 1000.0
+ config_soma_restoring_temp_piston_vel = 1.0e-5
+ config_soma_shelf_depth = 100.0
+ config_soma_shelf_width = -0.4
+ config_soma_surface_salinity = 33.0
+ config_soma_surface_temp_restoring_at_center_latitude = 7.5
+ config_soma_surface_temp_restoring_latitude_gradient = 0.5
+ config_soma_surface_temperature = 20.0
+ config_soma_thermocline_depth = 300.0
+ config_soma_use_surface_temp_restoring = false
+ config_soma_vert_levels = 100
+/
+&ziso
+ config_ziso_add_easterly_wind_stress_asf = false
+ config_ziso_antarctic_shelf_front_width = 600000
+ config_ziso_bottom_depth = 2500.0
+ config_ziso_coriolis_gradient = 1e-11
+ config_ziso_frazil_enable = false
+ config_ziso_frazil_temperature_anomaly = -3.0
+ config_ziso_initial_temp_h1 = 300.0
+ config_ziso_initial_temp_mt = 7.5e-5
+ config_ziso_initial_temp_t1 = 6.0
+ config_ziso_initial_temp_t2 = 3.6
+ config_ziso_mean_restoring_temp = 3.0
+ config_ziso_meridional_extent = 2.0e6
+ config_ziso_reference_coriolis = -1e-4
+ config_ziso_restoring_sponge_l = 8.0e4
+ config_ziso_restoring_temp_dev_ta = 2.0
+ config_ziso_restoring_temp_dev_tb = 2.0
+ config_ziso_restoring_temp_piston_vel = 1.93e-5
+ config_ziso_restoring_temp_tau = 30.0
+ config_ziso_restoring_temp_ze = 1250.0
+ config_ziso_shelf_depth = 500.0
+ config_ziso_slope_center_position = 5.0e5
+ config_ziso_slope_half_width = 1.0e5
+ config_ziso_use_slopping_bathymetry = false
+ config_ziso_vert_levels = 100
+ config_ziso_wind_stress_max = 0.2
+ config_ziso_wind_stress_shelf_front_max = -0.05
+ config_ziso_wind_transition_position = 800000.0
+ config_ziso_zonal_extent = 1.0e6
+/
+&sub_ice_shelf_2d
+ config_sub_ice_shelf_2d_bottom_depth = 2000.0
+ config_sub_ice_shelf_2d_bottom_salinity = 34.7
+ config_sub_ice_shelf_2d_cavity_thickness = 25.0
+ config_sub_ice_shelf_2d_edge_width = 15.0e3
+ config_sub_ice_shelf_2d_slope_height = 500.0
+ config_sub_ice_shelf_2d_surface_salinity = 34.5
+ config_sub_ice_shelf_2d_temperature = 1.0
+ config_sub_ice_shelf_2d_vert_levels = 20
+ config_sub_ice_shelf_2d_y1 = 30.0e3
+ config_sub_ice_shelf_2d_y2 = 60.0e3
+/
+&periodic_planar
+ config_periodic_planar_bottom_depth = 2500.0
+ config_periodic_planar_velocity_strength = 1.0
+ config_periodic_planar_vert_levels = 100
+/
+&ecosys_column
+ config_ecosys_column_bottom_depth = 6000.0
+ config_ecosys_column_ecosys_filename = 'unknown'
+ config_ecosys_column_ts_filename = 'unknown'
+ config_ecosys_column_vert_levels = 100
+ config_ecosys_column_vertical_grid = '100layerACMEv1'
+/
+&sea_mount
+ config_sea_mount_bottom_depth = 5000.0
+ config_sea_mount_coriolis_parameter = -1.0e-4
+ config_sea_mount_density_alpha = 0.2
+ config_sea_mount_density_coef_exp = 1028
+ config_sea_mount_density_coef_linear = 1024
+ config_sea_mount_density_depth_exp = 500
+ config_sea_mount_density_depth_linear = 4500
+ config_sea_mount_density_gradient_exp = 3.0
+ config_sea_mount_density_gradient_linear = 0.1
+ config_sea_mount_density_ref = 1028
+ config_sea_mount_density_tref = 5.0
+ config_sea_mount_height = 4500.0
+ config_sea_mount_layer_type = 'sigma'
+ config_sea_mount_radius = 10.0e3
+ config_sea_mount_salinity = 35.0
+ config_sea_mount_stratification_type = 'exponential'
+ config_sea_mount_vert_levels = 10
+ config_sea_mount_width = 40.0e3
+/
+&isomip
+ config_isomip_bottom_depth = -900.0
+ config_isomip_coriolis_parameter = -1.4e-4
+ config_isomip_eastern_boundary = 500e3
+ config_isomip_ice_fraction1 = 1.0
+ config_isomip_ice_fraction2 = 1.0
+ config_isomip_ice_fraction3 = 1.0
+ config_isomip_northern_boundary = 1000e3
+ config_isomip_restoring_salinity = 34.4
+ config_isomip_restoring_temperature = -1.9
+ config_isomip_salinity = 34.4
+ config_isomip_salinity_piston_velocity = 1.157e-5
+ config_isomip_southern_boundary = 0.0
+ config_isomip_temperature = -1.9
+ config_isomip_temperature_piston_velocity = 1.157e-5
+ config_isomip_vert_levels = 30
+ config_isomip_vertical_level_distribution = 'constant'
+ config_isomip_western_boundary = 0.0
+ config_isomip_y1 = 0.0
+ config_isomip_y2 = 400e3
+ config_isomip_y3 = 1000e3
+ config_isomip_z1 = -700.0
+ config_isomip_z2 = -200.0
+ config_isomip_z3 = -200.0
+/
+&isomip_plus
+ config_isomip_plus_coriolis_parameter = -1.409e-4
+ config_isomip_plus_effective_density = 1026.
+ config_isomip_plus_init_bot_sal = 34.5
+ config_isomip_plus_init_bot_temp = -1.9
+ config_isomip_plus_init_top_sal = 33.8
+ config_isomip_plus_init_top_temp = -1.9
+ config_isomip_plus_max_bottom_depth = -720.0
+ config_isomip_plus_min_column_thickness = 10.0
+ config_isomip_plus_min_ocean_fraction = 0.5
+ config_isomip_plus_minimum_levels = 3
+ config_isomip_plus_restore_bot_sal = 34.7
+ config_isomip_plus_restore_bot_temp = 1.0
+ config_isomip_plus_restore_evap_rate = 200
+ config_isomip_plus_restore_rate = 10.0
+ config_isomip_plus_restore_top_sal = 33.8
+ config_isomip_plus_restore_top_temp = -1.9
+ config_isomip_plus_restore_xmax = 800.0e3
+ config_isomip_plus_restore_xmin = 790.0e3
+ config_isomip_plus_topography_file = 'input_geometry_processed.nc'
+ config_isomip_plus_vert_levels = 36
+ config_isomip_plus_vertical_level_distribution = 'constant'
+/
+&tracer_forcing_activetracers
+ config_salinity_restoring_constant_piston_velocity = 0.0
+ config_salinity_restoring_max_difference = 0.5
+ config_use_activetracers = .true.
+ config_use_activetracers_exponential_decay = .false.
+ config_use_activetracers_idealage_forcing = .false.
+ config_use_activetracers_interior_restoring = .false.
+ config_use_activetracers_surface_bulk_forcing = .true.
+ config_use_activetracers_surface_restoring = .false.
+ config_use_activetracers_ttd_forcing = .false.
+ config_use_surface_salinity_monthly_restoring = .false.
+/
+&tracer_forcing_debugtracers
+ config_use_debugtracers = .false.
+ config_use_debugtracers_exponential_decay = .false.
+ config_use_debugtracers_idealage_forcing = .false.
+ config_use_debugtracers_interior_restoring = .false.
+ config_use_debugtracers_surface_bulk_forcing = .false.
+ config_use_debugtracers_surface_restoring = .false.
+ config_use_debugtracers_ttd_forcing = .false.
+/
+&tracer_forcing_ecosystracers
+ config_use_ecosystracers = .false.
+ config_use_ecosystracers_exponential_decay = .false.
+ config_use_ecosystracers_idealage_forcing = .false.
+ config_use_ecosystracers_interior_restoring = .false.
+ config_use_ecosystracers_sea_ice_coupling = .false.
+ config_use_ecosystracers_surface_bulk_forcing = .false.
+ config_use_ecosystracers_surface_restoring = .false.
+ config_use_ecosystracers_surface_value = .false.
+ config_use_ecosystracers_ttd_forcing = .false.
+/
+&tracer_forcing_dmstracers
+ config_use_dmstracers = .false.
+ config_use_dmstracers_exponential_decay = .false.
+ config_use_dmstracers_idealage_forcing = .false.
+ config_use_dmstracers_interior_restoring = .false.
+ config_use_dmstracers_sea_ice_coupling = .false.
+ config_use_dmstracers_surface_bulk_forcing = .false.
+ config_use_dmstracers_surface_restoring = .false.
+ config_use_dmstracers_surface_value = .false.
+ config_use_dmstracers_ttd_forcing = .false.
+/
+&tracer_forcing_macromoleculestracers
+ config_use_macromoleculestracers = .false.
+ config_use_macromoleculestracers_exponential_decay = .false.
+ config_use_macromoleculestracers_idealage_forcing = .false.
+ config_use_macromoleculestracers_interior_restoring = .false.
+ config_use_macromoleculestracers_sea_ice_coupling = .false.
+ config_use_macromoleculestracers_surface_bulk_forcing = .false.
+ config_use_macromoleculestracers_surface_restoring = .false.
+ config_use_macromoleculestracers_surface_value = .false.
+ config_use_macromoleculestracers_ttd_forcing = .false.
+/
+&am_globalstats
+ config_am_globalstats_compute_interval = 'output_interval'
+ config_am_globalstats_compute_on_startup = .true.
+ config_am_globalstats_directory = 'analysis_members'
+ config_am_globalstats_enable = .true.
+ config_am_globalstats_output_stream = 'globalStatsOutput'
+ config_am_globalstats_text_file = .false.
+ config_am_globalstats_write_on_startup = .true.
+/
+&am_surfaceareaweightedaverages
+ config_am_surfaceareaweightedaverages_compute_interval = '0000-00-00_01:00:00'
+ config_am_surfaceareaweightedaverages_compute_on_startup = .true.
+ config_am_surfaceareaweightedaverages_enable = .true.
+ config_am_surfaceareaweightedaverages_output_stream = 'surfaceAreaWeightedAveragesOutput'
+ config_am_surfaceareaweightedaverages_write_on_startup = .true.
+/
+&am_watermasscensus
+ config_am_watermasscensus_compute_interval = '0000-00-00_01:00:00'
+ config_am_watermasscensus_compute_on_startup = .true.
+ config_am_watermasscensus_enable = .false.
+ config_am_watermasscensus_maxsalinity = 37.0
+ config_am_watermasscensus_maxtemperature = 30.0
+ config_am_watermasscensus_minsalinity = 32.0
+ config_am_watermasscensus_mintemperature = -2.0
+ config_am_watermasscensus_output_stream = 'waterMassCensusOutput'
+ config_am_watermasscensus_write_on_startup = .true.
+/
+&am_layervolumeweightedaverage
+ config_am_layervolumeweightedaverage_compute_interval = '0000-00-00_01:00:00'
+ config_am_layervolumeweightedaverage_compute_on_startup = .true.
+ config_am_layervolumeweightedaverage_enable = .true.
+ config_am_layervolumeweightedaverage_output_stream = 'layerVolumeWeightedAverageOutput'
+ config_am_layervolumeweightedaverage_write_on_startup = .true.
+/
+&am_zonalmean
+ config_am_zonalmean_compute_interval = '0000-00-00_01:00:00'
+ config_am_zonalmean_compute_on_startup = .true.
+ config_am_zonalmean_enable = .false.
+ config_am_zonalmean_max_bin = -1.0e34
+ config_am_zonalmean_min_bin = -1.0e34
+ config_am_zonalmean_num_bins = 180
+ config_am_zonalmean_output_stream = 'zonalMeanOutput'
+ config_am_zonalmean_write_on_startup = .true.
+/
+&am_okuboweiss
+ config_am_okuboweiss_compute_eddy_census = .true.
+ config_am_okuboweiss_compute_interval = '0000-00-00_01:00:00'
+ config_am_okuboweiss_compute_on_startup = .true.
+ config_am_okuboweiss_directory = 'analysis_members'
+ config_am_okuboweiss_eddy_min_cells = 20
+ config_am_okuboweiss_enable = .false.
+ config_am_okuboweiss_lambda2_normalization = 1e-10
+ config_am_okuboweiss_normalization = 1e-10
+ config_am_okuboweiss_output_stream = 'okuboWeissOutput'
+ config_am_okuboweiss_threshold_value = -0.2
+ config_am_okuboweiss_use_lat_lon_coords = .true.
+ config_am_okuboweiss_write_on_startup = .true.
+/
+&am_meridionalheattransport
+ config_am_meridionalheattransport_compute_interval = '0000-00-00_01:00:00'
+ config_am_meridionalheattransport_compute_on_startup = .true.
+ config_am_meridionalheattransport_enable = .true.
+ config_am_meridionalheattransport_max_bin = -1.0e34
+ config_am_meridionalheattransport_min_bin = -1.0e34
+ config_am_meridionalheattransport_num_bins = 180
+ config_am_meridionalheattransport_output_stream = 'meridionalHeatTransportOutput'
+ config_am_meridionalheattransport_region_group = ''
+ config_am_meridionalheattransport_write_on_startup = .true.
+/
+&am_testcomputeinterval
+ config_am_testcomputeinterval_compute_interval = '00-00-01_00:00:00'
+ config_am_testcomputeinterval_compute_on_startup = .true.
+ config_am_testcomputeinterval_enable = .false.
+ config_am_testcomputeinterval_output_stream = 'testComputeIntervalOutput'
+ config_am_testcomputeinterval_write_on_startup = .true.
+/
+&am_highfrequencyoutput
+ config_am_highfrequencyoutput_compute_interval = 'output_interval'
+ config_am_highfrequencyoutput_compute_on_startup = .false.
+ config_am_highfrequencyoutput_enable = .true.
+ config_am_highfrequencyoutput_output_stream = 'highFrequencyOutput'
+ config_am_highfrequencyoutput_write_on_startup = .false.
+/
+&am_timefilters
+ config_am_timefilters_compute_cell_centered_values = .true.
+ config_am_timefilters_compute_interval = 'dt'
+ config_am_timefilters_compute_on_startup = .true.
+ config_am_timefilters_enable = .false.
+ config_am_timefilters_initialize_filters = .true.
+ config_am_timefilters_output_stream = 'timeFiltersOutput'
+ config_am_timefilters_restart_stream = 'timeFiltersRestart'
+ config_am_timefilters_tau = '90_00:00:00'
+ config_am_timefilters_write_on_startup = .true.
+/
+&am_lagrparttrack
+ config_am_lagrparttrack_compute_interval = 'dt'
+ config_am_lagrparttrack_compute_on_startup = .false.
+ config_am_lagrparttrack_enable = .false.
+ config_am_lagrparttrack_filter_number = 0
+ config_am_lagrparttrack_input_stream = 'lagrPartTrackInput'
+ config_am_lagrparttrack_output_stream = 'lagrPartTrackOutput'
+ config_am_lagrparttrack_region_stream = 'lagrPartTrackRegions'
+ config_am_lagrparttrack_reset_criteria = 'none'
+ config_am_lagrparttrack_reset_global_timestamp = '0000_00:00:00'
+ config_am_lagrparttrack_reset_if_inside_region = .false.
+ config_am_lagrparttrack_reset_if_outside_region = .false.
+ config_am_lagrparttrack_restart_stream = 'lagrPartTrackRestart'
+ config_am_lagrparttrack_write_on_startup = .true.
+/
+&am_eliassenpalm
+ config_am_eliassenpalm_compute_interval = 'output_interval'
+ config_am_eliassenpalm_compute_on_startup = .true.
+ config_am_eliassenpalm_debug = .false.
+ config_am_eliassenpalm_enable = .false.
+ config_am_eliassenpalm_nbuoyancylayers = 45
+ config_am_eliassenpalm_output_stream = 'eliassenPalmOutput'
+ config_am_eliassenpalm_restart_stream = 'eliassenPalmRestart'
+ config_am_eliassenpalm_rhomax_buoycoor = 1080
+ config_am_eliassenpalm_rhomin_buoycoor = 900
+ config_am_eliassenpalm_write_on_startup = .true.
+/
+&am_mixedlayerdepths
+ config_am_mixedlayerdepths_compute_interval = '0000-00-00_01:00:00'
+ config_am_mixedlayerdepths_compute_on_startup = .true.
+ config_am_mixedlayerdepths_crit_dens_threshold = 0.03
+ config_am_mixedlayerdepths_crit_temp_threshold = 0.2
+ config_am_mixedlayerdepths_den_gradient_threshold = 5E-8
+ config_am_mixedlayerdepths_dgradient = .true.
+ config_am_mixedlayerdepths_dthreshold = .true.
+ config_am_mixedlayerdepths_enable = .true.
+ config_am_mixedlayerdepths_interp_method = 1
+ config_am_mixedlayerdepths_output_stream = 'mixedLayerDepthsOutput'
+ config_am_mixedlayerdepths_reference_pressure = 1.0E5
+ config_am_mixedlayerdepths_temp_gradient_threshold = 5E-7
+ config_am_mixedlayerdepths_tgradient = .true.
+ config_am_mixedlayerdepths_tthreshold = .true.
+ config_am_mixedlayerdepths_write_on_startup = .true.
+/
+&am_regionalstatsdaily
+ config_am_regionalstatsdaily_1d_weighting_field = 'areaCell'
+ config_am_regionalstatsdaily_1d_weighting_function = 'mul'
+ config_am_regionalstatsdaily_2d_weighting_field = 'volumeCell'
+ config_am_regionalstatsdaily_2d_weighting_function = 'mul'
+ config_am_regionalstatsdaily_compute_interval = 'output_interval'
+ config_am_regionalstatsdaily_compute_on_startup = .false.
+ config_am_regionalstatsdaily_enable = .false.
+ config_am_regionalstatsdaily_input_stream = 'regionalMasksInput'
+ config_am_regionalstatsdaily_operation = 'avg'
+ config_am_regionalstatsdaily_output_stream = 'regionalStatsDailyOutput'
+ config_am_regionalstatsdaily_region_group = 'all'
+ config_am_regionalstatsdaily_region_type = 'cell'
+ config_am_regionalstatsdaily_restart_stream = 'regionalMasksInput'
+ config_am_regionalstatsdaily_vertical_dimension = 'nVertLevels'
+ config_am_regionalstatsdaily_vertical_mask = 'cellMask'
+ config_am_regionalstatsdaily_write_on_startup = .false.
+/
+&am_regionalstatsweekly
+ config_am_regionalstatsweekly_1d_weighting_field = 'areaCell'
+ config_am_regionalstatsweekly_1d_weighting_function = 'mul'
+ config_am_regionalstatsweekly_2d_weighting_field = 'volumeCell'
+ config_am_regionalstatsweekly_2d_weighting_function = 'mul'
+ config_am_regionalstatsweekly_compute_interval = 'output_interval'
+ config_am_regionalstatsweekly_compute_on_startup = .false.
+ config_am_regionalstatsweekly_enable = .false.
+ config_am_regionalstatsweekly_input_stream = 'regionalMasksInput'
+ config_am_regionalstatsweekly_operation = 'avg'
+ config_am_regionalstatsweekly_output_stream = 'regionalStatsWeeklyOutput'
+ config_am_regionalstatsweekly_region_group = 'all'
+ config_am_regionalstatsweekly_region_type = 'cell'
+ config_am_regionalstatsweekly_restart_stream = 'regionalMasksInput'
+ config_am_regionalstatsweekly_vertical_dimension = 'nVertLevels'
+ config_am_regionalstatsweekly_vertical_mask = 'cellMask'
+ config_am_regionalstatsweekly_write_on_startup = .false.
+/
+&am_regionalstatsmonthly
+ config_am_regionalstatsmonthly_1d_weighting_field = 'areaCell'
+ config_am_regionalstatsmonthly_1d_weighting_function = 'mul'
+ config_am_regionalstatsmonthly_2d_weighting_field = 'volumeCell'
+ config_am_regionalstatsmonthly_2d_weighting_function = 'mul'
+ config_am_regionalstatsmonthly_compute_interval = 'output_interval'
+ config_am_regionalstatsmonthly_compute_on_startup = .false.
+ config_am_regionalstatsmonthly_enable = .false.
+ config_am_regionalstatsmonthly_input_stream = 'regionalMasksInput'
+ config_am_regionalstatsmonthly_operation = 'avg'
+ config_am_regionalstatsmonthly_output_stream = 'regionalStatsMonthlyOutput'
+ config_am_regionalstatsmonthly_region_group = 'all'
+ config_am_regionalstatsmonthly_region_type = 'cell'
+ config_am_regionalstatsmonthly_restart_stream = 'regionalMasksInput'
+ config_am_regionalstatsmonthly_vertical_dimension = 'nVertLevels'
+ config_am_regionalstatsmonthly_vertical_mask = 'cellMask'
+ config_am_regionalstatsmonthly_write_on_startup = .false.
+/
+&am_regionalstatscustom
+ config_am_regionalstatscustom_1d_weighting_field = 'areaCell'
+ config_am_regionalstatscustom_1d_weighting_function = 'mul'
+ config_am_regionalstatscustom_2d_weighting_field = 'volumeCell'
+ config_am_regionalstatscustom_2d_weighting_function = 'mul'
+ config_am_regionalstatscustom_compute_interval = 'output_interval'
+ config_am_regionalstatscustom_compute_on_startup = .false.
+ config_am_regionalstatscustom_enable = .false.
+ config_am_regionalstatscustom_input_stream = 'regionalMasksInput'
+ config_am_regionalstatscustom_operation = 'avg'
+ config_am_regionalstatscustom_output_stream = 'regionalStatsCustomOutput'
+ config_am_regionalstatscustom_region_group = 'all'
+ config_am_regionalstatscustom_region_type = 'cell'
+ config_am_regionalstatscustom_restart_stream = 'regionalMasksInput'
+ config_am_regionalstatscustom_vertical_dimension = 'nVertLevels'
+ config_am_regionalstatscustom_vertical_mask = 'cellMask'
+ config_am_regionalstatscustom_write_on_startup = .false.
+/
+&am_timeseriesstatsdaily
+ config_am_timeseriesstatsdaily_backward_output_offset = '00-00-01_00:00:00'
+ config_am_timeseriesstatsdaily_compute_interval = '00-00-00_01:00:00'
+ config_am_timeseriesstatsdaily_compute_on_startup = .false.
+ config_am_timeseriesstatsdaily_duration_intervals = 'repeat_interval'
+ config_am_timeseriesstatsdaily_enable = .false.
+ config_am_timeseriesstatsdaily_operation = 'avg'
+ config_am_timeseriesstatsdaily_output_stream = 'timeSeriesStatsDailyOutput'
+ config_am_timeseriesstatsdaily_reference_times = 'initial_time'
+ config_am_timeseriesstatsdaily_repeat_intervals = 'reset_interval'
+ config_am_timeseriesstatsdaily_reset_intervals = '00-00-01_00:00:00'
+ config_am_timeseriesstatsdaily_restart_stream = 'timeSeriesStatsDailyRestart'
+ config_am_timeseriesstatsdaily_write_on_startup = .false.
+/
+&am_timeseriesstatsmonthly
+ config_am_timeseriesstatsmonthly_backward_output_offset = '00-01-00_00:00:00'
+ config_am_timeseriesstatsmonthly_compute_interval = '00-00-00_01:00:00'
+ config_am_timeseriesstatsmonthly_compute_on_startup = .false.
+ config_am_timeseriesstatsmonthly_duration_intervals = 'repeat_interval'
+ config_am_timeseriesstatsmonthly_enable = .true.
+ config_am_timeseriesstatsmonthly_operation = 'avg'
+ config_am_timeseriesstatsmonthly_output_stream = 'timeSeriesStatsMonthlyOutput'
+ config_am_timeseriesstatsmonthly_reference_times = 'initial_time'
+ config_am_timeseriesstatsmonthly_repeat_intervals = 'reset_interval'
+ config_am_timeseriesstatsmonthly_reset_intervals = '00-01-00_00:00:00'
+ config_am_timeseriesstatsmonthly_restart_stream = 'timeSeriesStatsMonthlyRestart'
+ config_am_timeseriesstatsmonthly_write_on_startup = .false.
+/
+&am_timeseriesstatsclimatology
+ config_am_timeseriesstatsclimatology_backward_output_offset = '00-03-00_00:00:00'
+ config_am_timeseriesstatsclimatology_compute_interval = '00-00-00_01:00:00'
+ config_am_timeseriesstatsclimatology_compute_on_startup = .false.
+ config_am_timeseriesstatsclimatology_duration_intervals = '00-03-00_00:00:00;00-03-00_00:00:00;00-03-00_00:00:00;00-03-00_00:00:00'
+ config_am_timeseriesstatsclimatology_enable = .false.
+ config_am_timeseriesstatsclimatology_operation = 'avg'
+ config_am_timeseriesstatsclimatology_output_stream = 'timeSeriesStatsClimatologyOutput'
+ config_am_timeseriesstatsclimatology_reference_times = '00-03-01_00:00:00;00-06-01_00:00:00;00-09-01_00:00:00;00-12-01_00:00:00'
+ config_am_timeseriesstatsclimatology_repeat_intervals = '01-00-00_00:00:00;01-00-00_00:00:00;01-00-00_00:00:00;01-00-00_00:00:00'
+ config_am_timeseriesstatsclimatology_reset_intervals = '1000-00-00_00:00:00;1000-00-00_00:00:00;1000-00-00_00:00:00;1000-00-00_00:00:00'
+ config_am_timeseriesstatsclimatology_restart_stream = 'timeSeriesStatsClimatologyRestart'
+ config_am_timeseriesstatsclimatology_write_on_startup = .false.
+/
+&am_timeseriesstatscustom
+ config_am_timeseriesstatscustom_backward_output_offset = '00-00-01_00:00:00'
+ config_am_timeseriesstatscustom_compute_interval = '00-00-00_01:00:00'
+ config_am_timeseriesstatscustom_compute_on_startup = .false.
+ config_am_timeseriesstatscustom_duration_intervals = 'repeat_interval'
+ config_am_timeseriesstatscustom_enable = .false.
+ config_am_timeseriesstatscustom_operation = 'avg'
+ config_am_timeseriesstatscustom_output_stream = 'timeSeriesStatsCustomOutput'
+ config_am_timeseriesstatscustom_reference_times = 'initial_time'
+ config_am_timeseriesstatscustom_repeat_intervals = 'reset_interval'
+ config_am_timeseriesstatscustom_reset_intervals = '00-00-07_00:00:00'
+ config_am_timeseriesstatscustom_restart_stream = 'timeSeriesStatsCustomRestart'
+ config_am_timeseriesstatscustom_write_on_startup = .false.
+/
+&am_pointwisestats
+ config_am_pointwisestats_compute_interval = 'output_interval'
+ config_am_pointwisestats_compute_on_startup = .true.
+ config_am_pointwisestats_enable = .false.
+ config_am_pointwisestats_output_stream = 'pointwiseStatsOutput'
+ config_am_pointwisestats_write_on_startup = .true.
+/
+&am_debugdiagnostics
+ config_am_debugdiagnostics_check_state = .true.
+ config_am_debugdiagnostics_compute_interval = 'dt'
+ config_am_debugdiagnostics_compute_on_startup = .true.
+ config_am_debugdiagnostics_enable = .false.
+ config_am_debugdiagnostics_output_stream = 'debugDiagnosticsOutput'
+ config_am_debugdiagnostics_write_on_startup = .false.
+/
+&am_rpncalculator
+ config_am_rpncalculator_compute_interval = '0010-00-00_00:00:00'
+ config_am_rpncalculator_compute_on_startup = .true.
+ config_am_rpncalculator_enable = .false.
+ config_am_rpncalculator_expression_1 = 'a b *'
+ config_am_rpncalculator_expression_2 = 'none'
+ config_am_rpncalculator_expression_3 = 'none'
+ config_am_rpncalculator_expression_4 = 'none'
+ config_am_rpncalculator_output_name_1 = 'volumeCell'
+ config_am_rpncalculator_output_name_2 = 'none'
+ config_am_rpncalculator_output_name_3 = 'none'
+ config_am_rpncalculator_output_name_4 = 'none'
+ config_am_rpncalculator_output_stream = 'none'
+ config_am_rpncalculator_variable_a = 'layerThickness'
+ config_am_rpncalculator_variable_b = 'areaCell'
+ config_am_rpncalculator_variable_c = 'none'
+ config_am_rpncalculator_variable_d = 'none'
+ config_am_rpncalculator_variable_e = 'none'
+ config_am_rpncalculator_variable_f = 'none'
+ config_am_rpncalculator_variable_g = 'none'
+ config_am_rpncalculator_variable_h = 'none'
+ config_am_rpncalculator_write_on_startup = .false.
+/
+&am_transecttransport
+ config_am_transecttransport_compute_interval = 'output_interval'
+ config_am_transecttransport_compute_on_startup = .true.
+ config_am_transecttransport_enable = .false.
+ config_am_transecttransport_output_stream = 'transectTransportOutput'
+ config_am_transecttransport_transect_group = 'all'
+ config_am_transecttransport_write_on_startup = .true.
+/
+&am_eddyproductvariables
+ config_am_eddyproductvariables_compute_interval = 'dt'
+ config_am_eddyproductvariables_compute_on_startup = .true.
+ config_am_eddyproductvariables_enable = .false.
+ config_am_eddyproductvariables_output_stream = 'eddyProductVariablesOutput'
+ config_am_eddyproductvariables_write_on_startup = .false.
+/
+&am_mocstreamfunction
+ config_am_mocstreamfunction_compute_interval = 'output_interval'
+ config_am_mocstreamfunction_compute_on_startup = .true.
+ config_am_mocstreamfunction_enable = .false.
+ config_am_mocstreamfunction_max_bin = -1.0e34
+ config_am_mocstreamfunction_min_bin = -1.0e34
+ config_am_mocstreamfunction_normal_velocity_value = 'normalVelocity'
+ config_am_mocstreamfunction_num_bins = 180
+ config_am_mocstreamfunction_output_stream = 'mocStreamfunctionOutput'
+ config_am_mocstreamfunction_region_group = 'all'
+ config_am_mocstreamfunction_transect_group = 'all'
+ config_am_mocstreamfunction_vertical_velocity_value = 'vertVelocityTop'
+ config_am_mocstreamfunction_write_on_startup = .true.
+/
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask/remap_mld_obs.py b/mpas_analysis/test/test_remap_obs_clim_subtask/remap_mld_obs.py
new file mode 100644
index 000000000..ff7ce2a20
--- /dev/null
+++ b/mpas_analysis/test/test_remap_obs_clim_subtask/remap_mld_obs.py
@@ -0,0 +1,31 @@
+import numpy
+
+from mpas_analysis.shared.interpolation import Remapper
+from mpas_analysis.shared.grid import LatLonGridDescriptor
+from mpas_analysis.shared.constants import constants
+
+inputFileName = '/media/xylar/extra_data/analysis/output/GMPAS-QU240/' \
+ 'remap_obs/clim/obs/mld_1.0x1.0degree.nc'
+
+obsDescriptor = LatLonGridDescriptor.read(fileName=inputFileName,
+ latVarName='lat',
+ lonVarName='lon')
+
+comparisonLatRes = 4.
+comparisonLonRes = 4.
+
+nLat = int((constants.latmax-constants.latmin)/comparisonLatRes)+1
+nLon = int((constants.lonmax-constants.lonmin)/comparisonLonRes)+1
+lat = numpy.linspace(constants.latmin, constants.latmax, nLat)
+lon = numpy.linspace(constants.lonmin, constants.lonmax, nLon)
+
+comparisonDescriptor = LatLonGridDescriptor.create(lat, lon, units='degrees')
+
+remapper = Remapper(obsDescriptor, comparisonDescriptor,
+ mappingFileName='map.nc')
+
+remapper.build_mapping_file()
+
+remapper.remap_file(inputFileName, 'mld_4.0x4.0degree.nc',
+ ['mld', 'month', 'year'],
+ renormalize=0.05)
diff --git a/mpas_analysis/test/test_remap_obs_clim_subtask/streams.ocean b/mpas_analysis/test/test_remap_obs_clim_subtask/streams.ocean
new file mode 100644
index 000000000..1a025681b
--- /dev/null
+++ b/mpas_analysis/test/test_remap_obs_clim_subtask/streams.ocean
@@ -0,0 +1,604 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/run_mpas_analysis b/run_mpas_analysis
index e69887bd7..f6b1ee5df 100755
--- a/run_mpas_analysis
+++ b/run_mpas_analysis
@@ -9,7 +9,8 @@ Authors
Xylar Asay-Davis, Phillip J. Wolfram
"""
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
import matplotlib as mpl
import argparse
@@ -191,7 +192,7 @@ def add_task_and_subtasks(analysisTask, analysesToGenerate,
print("ERROR: prerequisite task {} of analysis task {}"
" failed during check,\n"
" so this task will not be run".format(
- prereq.printTaskName, taskTitle))
+ prereq.printTaskName, taskTitle))
analysisTask._setupStatus = 'fail'
return
@@ -333,7 +334,8 @@ def run_analysis(config, analyses): # {{{
taskTitle = analysisTask.printTaskName
if analysisTask._runStatus.value == AnalysisTask.SUCCESS:
- print(" Task {} has finished successfully.".format(taskTitle))
+ print(" Task {} has finished successfully.".format(
+ taskTitle))
elif analysisTask._runStatus.value == AnalysisTask.FAIL:
print("ERROR in task {}. See log file {} for details".format(
taskTitle, analysisTask._logFileName))