Module gdess.data_source.models.e3sm.collection
Expand source code
from gdess import set_verbose, benchmark_recipe
from gdess.data_source.models.e3sm.calculation import getPMID
from gdess.data_source.multiset import Multiset
from gdess.operations.datasetdict import DatasetDict
from gdess.operations.time import to_datetimeindex
from gdess.operations.convert import co2_kgfrac_to_ppm
from gdess.graphics.utils import aesthetic_grid_no_spines, mysavefig
from gdess.formatters import append_before_extension
from gdess.recipe_parsers import add_shared_arguments_for_recipes, parse_recipe_options
import xarray as xr
import matplotlib.pyplot as plt
from typing import Union
import argparse, logging
_logger = logging.getLogger("{0}.{1}".format(__name__, "loader"))
class Collection(Multiset):
def __init__(self, verbose: Union[bool, str] = False):
"""Instantiate an E3SM Collection object.
Parameters
----------
verbose: Union[bool, str], default False
can be either True, False, or a string for level such as "INFO, DEBUG, etc."
"""
set_verbose(_logger, verbose)
super().__init__(verbose=verbose)
@classmethod
def _recipe_base(cls,
verbose: Union[bool, str] = False,
pickle_file: str = None,
nc_file: str = None
) -> ('Collection', bool):
"""Create an instance, and either preprocess or load already processed data.
Parameters
----------
verbose : Union[bool, str], default False
pickle_file : str, default None
nc_file : str, default None
Returns
-------
tuple
Collection
bool
Whether datasets were loaded from a pickle file or not. (If not, there is probably more processing needed.)
"""
# An empty instance is created.
new_self = cls(verbose=verbose)
# If a valid filename is provided, datasets are loaded into stepC attribute and this is True,
# otherwise, this is False.
loaded_from_pickle_bool = new_self.datasets_from_pickle(filename=pickle_file, replace=True)
if not loaded_from_pickle_bool:
# Data are formatted into the basic data structure common to various diagnostics.
new_self.preprocess(filepath=nc_file)
return new_self, loaded_from_pickle_bool
@classmethod
@benchmark_recipe
def run_recipe_for_timeseries(cls,
verbose: Union[bool, str] = False,
pickle_file=None,
options: dict = None
) -> 'Collection':
"""Execute a series of preprocessing steps and generate a diagnostic result.
Parameters
----------
verbose
can be either True, False, or a string for level such as "INFO, DEBUG, etc."
pickle_file : str, default None
path to pickled datastore
options : dict
Contains zero or more of these parameter keys:
test_data : str
path to NetCDF file of E3SM model output
start_yr : str, default '1960'
end_yr : str, default None
lev : int, default is the last value
Returns
-------
Collection object for E3SM that was used to generate the diagnostic
"""
set_verbose(_logger, verbose)
opts = parse_recipe_options(options, add_e3sm_collection_args_to_parser)
new_self, loaded_from_pickle = cls._recipe_base(verbose=verbose,
pickle_file=pickle_file,
nc_file=opts.ref_data)
n_lev = len(new_self.stepB_preprocessed_datasets['main'].lev) # get last level
if not opts.lev_index:
opts.lev_index = n_lev-1
# --- Apply diagnostic parameters and prep data for plotting ---
if not loaded_from_pickle:
_logger.info('Applying selected bounds..')
new_self.validate_time_options(opts.start_datetime, opts.end_datetime)
selection = {'time': slice(opts.start_datetime, opts.end_datetime)}
new_self.stepC_prepped_datasets = new_self.stepB_preprocessed_datasets.queue_selection(**selection,
inplace=False)
iselection = {'lev': opts.lev_index}
new_self.stepC_prepped_datasets.queue_selection(**iselection, isel=True, inplace=True)
# Spatial mean is calculated, leaving us with a time series.
new_self.stepC_prepped_datasets.queue_mean(dim=('ncol'), inplace=True)
# The lazily loaded selections and computations are here actually processed.
new_self.stepC_prepped_datasets.execute_all(inplace=True)
# --- Plotting ---
fig, ax, bbox_artists = new_self.plot_timeseries()
if opts.figure_savepath:
mysavefig(fig=fig, plot_save_name=append_before_extension(opts.figure_savepath, 'e3sm_timeseries'),
bbox_extra_artists=bbox_artists)
return new_self
@staticmethod
def _preprocess_functions(dataset: xr.Dataset):
"""Run a set of functions on a dataset
- Set coordinates
- Sort the time dimension
- Ensure time is a datetimeindex type
- Convert CO2 kg/kg to ppm
Parameters
----------
dataset : xr.Dataset
Returns
-------
An xr.Dataset
"""
dataset['PMID'] = getPMID(dataset['hyam'], dataset['hybm'], dataset['P0'], dataset['PS'])
dataset = (dataset
.set_coords(['time', 'lat', 'lon', 'PMID'])
.sortby(['time'])
.pipe(to_datetimeindex)
.pipe(co2_kgfrac_to_ppm, co2_var_name='CO2')
)
return dataset
def preprocess(self, filepath: str) -> None:
"""Set up the dataset that are common to every diagnostic
Parameters
----------
filepath : str
"""
_logger.info("Preprocessing...")
_logger.debug(' Opening the file..')
self.stepA_original_datasets = DatasetDict({'main': xr.open_dataset(filepath)})
self.stepB_preprocessed_datasets = self.stepA_original_datasets.copy()
_logger.debug(' Setting coords, formatting time, converting to ppm..')
self.stepB_preprocessed_datasets.apply_function_to_all(self._preprocess_functions, inplace=True)
_logger.info("Preprocessing is done.")
def plot_timeseries(self) -> (plt.Figure, plt.Axes, tuple):
"""Make timeseries plot of co2 concentrations from the E3SM output
Requires self.stepC_prepped_datasets attribute with a time dimension.
Returns
-------
matplotlib figure
matplotlib axis
tuple
Extra matplotlib artists used for the bounding box (bbox) when saving a figure
"""
my_cmap = self.categorical_cmap(nc=1, nsc=1, cmap="tab10")
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 4))
plt.plot(self.stepC_prepped_datasets['main']['time'],
self.stepC_prepped_datasets['main']['CO2'],
label=f"E3SM simulation", color=my_cmap.colors[0], alpha=0.6)
#
ax.set_ylabel('$CO_2$ [ppm]')
aesthetic_grid_no_spines(ax)
#
bbox_artists = ()
return fig, ax, bbox_artists
def __repr__(self) -> str:
""" String representation is built.
"""
strrep = f"-- E3SM Collection -- \n" \
f"Datasets:" \
f"\n\t" + \
self._original_datasets_list_str() + \
f"\n" \
f"All attributes:" \
f"\n\t" + \
'\n\t'.join(self._obj_attributes_list_str())
return strrep
def add_e3sm_collection_args_to_parser(parser: argparse.ArgumentParser) -> None:
"""Add recipe arguments to a parser object
Parameters
----------
parser : argparse.ArgumentParser
"""
add_shared_arguments_for_recipes(parser)
parser.add_argument('--lev_index', default=None, type=int)
Functions
def add_e3sm_collection_args_to_parser(parser: argparse.ArgumentParser) -> NoneType-
Add recipe arguments to a parser object
Parameters
parser:argparse.ArgumentParser
Expand source code
def add_e3sm_collection_args_to_parser(parser: argparse.ArgumentParser) -> None: """Add recipe arguments to a parser object Parameters ---------- parser : argparse.ArgumentParser """ add_shared_arguments_for_recipes(parser) parser.add_argument('--lev_index', default=None, type=int)
Classes
class Collection (verbose: Union[bool, str] = False)-
Work simultaneous with multiple, consistent xarray Datasets.
Instantiate an E3SM Collection object.
Parameters
verbose:Union[bool, str], defaultFalse- can be either True, False, or a string for level such as "INFO, DEBUG, etc."
Expand source code
class Collection(Multiset): def __init__(self, verbose: Union[bool, str] = False): """Instantiate an E3SM Collection object. Parameters ---------- verbose: Union[bool, str], default False can be either True, False, or a string for level such as "INFO, DEBUG, etc." """ set_verbose(_logger, verbose) super().__init__(verbose=verbose) @classmethod def _recipe_base(cls, verbose: Union[bool, str] = False, pickle_file: str = None, nc_file: str = None ) -> ('Collection', bool): """Create an instance, and either preprocess or load already processed data. Parameters ---------- verbose : Union[bool, str], default False pickle_file : str, default None nc_file : str, default None Returns ------- tuple Collection bool Whether datasets were loaded from a pickle file or not. (If not, there is probably more processing needed.) """ # An empty instance is created. new_self = cls(verbose=verbose) # If a valid filename is provided, datasets are loaded into stepC attribute and this is True, # otherwise, this is False. loaded_from_pickle_bool = new_self.datasets_from_pickle(filename=pickle_file, replace=True) if not loaded_from_pickle_bool: # Data are formatted into the basic data structure common to various diagnostics. new_self.preprocess(filepath=nc_file) return new_self, loaded_from_pickle_bool @classmethod @benchmark_recipe def run_recipe_for_timeseries(cls, verbose: Union[bool, str] = False, pickle_file=None, options: dict = None ) -> 'Collection': """Execute a series of preprocessing steps and generate a diagnostic result. Parameters ---------- verbose can be either True, False, or a string for level such as "INFO, DEBUG, etc." pickle_file : str, default None path to pickled datastore options : dict Contains zero or more of these parameter keys: test_data : str path to NetCDF file of E3SM model output start_yr : str, default '1960' end_yr : str, default None lev : int, default is the last value Returns ------- Collection object for E3SM that was used to generate the diagnostic """ set_verbose(_logger, verbose) opts = parse_recipe_options(options, add_e3sm_collection_args_to_parser) new_self, loaded_from_pickle = cls._recipe_base(verbose=verbose, pickle_file=pickle_file, nc_file=opts.ref_data) n_lev = len(new_self.stepB_preprocessed_datasets['main'].lev) # get last level if not opts.lev_index: opts.lev_index = n_lev-1 # --- Apply diagnostic parameters and prep data for plotting --- if not loaded_from_pickle: _logger.info('Applying selected bounds..') new_self.validate_time_options(opts.start_datetime, opts.end_datetime) selection = {'time': slice(opts.start_datetime, opts.end_datetime)} new_self.stepC_prepped_datasets = new_self.stepB_preprocessed_datasets.queue_selection(**selection, inplace=False) iselection = {'lev': opts.lev_index} new_self.stepC_prepped_datasets.queue_selection(**iselection, isel=True, inplace=True) # Spatial mean is calculated, leaving us with a time series. new_self.stepC_prepped_datasets.queue_mean(dim=('ncol'), inplace=True) # The lazily loaded selections and computations are here actually processed. new_self.stepC_prepped_datasets.execute_all(inplace=True) # --- Plotting --- fig, ax, bbox_artists = new_self.plot_timeseries() if opts.figure_savepath: mysavefig(fig=fig, plot_save_name=append_before_extension(opts.figure_savepath, 'e3sm_timeseries'), bbox_extra_artists=bbox_artists) return new_self @staticmethod def _preprocess_functions(dataset: xr.Dataset): """Run a set of functions on a dataset - Set coordinates - Sort the time dimension - Ensure time is a datetimeindex type - Convert CO2 kg/kg to ppm Parameters ---------- dataset : xr.Dataset Returns ------- An xr.Dataset """ dataset['PMID'] = getPMID(dataset['hyam'], dataset['hybm'], dataset['P0'], dataset['PS']) dataset = (dataset .set_coords(['time', 'lat', 'lon', 'PMID']) .sortby(['time']) .pipe(to_datetimeindex) .pipe(co2_kgfrac_to_ppm, co2_var_name='CO2') ) return dataset def preprocess(self, filepath: str) -> None: """Set up the dataset that are common to every diagnostic Parameters ---------- filepath : str """ _logger.info("Preprocessing...") _logger.debug(' Opening the file..') self.stepA_original_datasets = DatasetDict({'main': xr.open_dataset(filepath)}) self.stepB_preprocessed_datasets = self.stepA_original_datasets.copy() _logger.debug(' Setting coords, formatting time, converting to ppm..') self.stepB_preprocessed_datasets.apply_function_to_all(self._preprocess_functions, inplace=True) _logger.info("Preprocessing is done.") def plot_timeseries(self) -> (plt.Figure, plt.Axes, tuple): """Make timeseries plot of co2 concentrations from the E3SM output Requires self.stepC_prepped_datasets attribute with a time dimension. Returns ------- matplotlib figure matplotlib axis tuple Extra matplotlib artists used for the bounding box (bbox) when saving a figure """ my_cmap = self.categorical_cmap(nc=1, nsc=1, cmap="tab10") fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 4)) plt.plot(self.stepC_prepped_datasets['main']['time'], self.stepC_prepped_datasets['main']['CO2'], label=f"E3SM simulation", color=my_cmap.colors[0], alpha=0.6) # ax.set_ylabel('$CO_2$ [ppm]') aesthetic_grid_no_spines(ax) # bbox_artists = () return fig, ax, bbox_artists def __repr__(self) -> str: """ String representation is built. """ strrep = f"-- E3SM Collection -- \n" \ f"Datasets:" \ f"\n\t" + \ self._original_datasets_list_str() + \ f"\n" \ f"All attributes:" \ f"\n\t" + \ '\n\t'.join(self._obj_attributes_list_str()) return strrepAncestors
Static methods
def run_recipe_for_timeseries(*args, **kwargs)-
Expand source code
def display_time_and_call(*args, **kwargs): # Clock is started. start_time = time.time() # Recipe is run. returnval = func(*args, **kwargs) # Report the time this recipe took to execute. execution_time = (time.time() - start_time) _logger.info('recipe execution time (seconds): ' + str(execution_time)) return returnval
Methods
def plot_timeseries(self) -> (, , ) -
Make timeseries plot of co2 concentrations from the E3SM output
Requires self.stepC_prepped_datasets attribute with a time dimension.
Returns
matplotlib figurematplotlib axistuple- Extra matplotlib artists used for the bounding box (bbox) when saving a figure
Expand source code
def plot_timeseries(self) -> (plt.Figure, plt.Axes, tuple): """Make timeseries plot of co2 concentrations from the E3SM output Requires self.stepC_prepped_datasets attribute with a time dimension. Returns ------- matplotlib figure matplotlib axis tuple Extra matplotlib artists used for the bounding box (bbox) when saving a figure """ my_cmap = self.categorical_cmap(nc=1, nsc=1, cmap="tab10") fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 4)) plt.plot(self.stepC_prepped_datasets['main']['time'], self.stepC_prepped_datasets['main']['CO2'], label=f"E3SM simulation", color=my_cmap.colors[0], alpha=0.6) # ax.set_ylabel('$CO_2$ [ppm]') aesthetic_grid_no_spines(ax) # bbox_artists = () return fig, ax, bbox_artists def preprocess(self, filepath: str) -> NoneType-
Set up the dataset that are common to every diagnostic
Parameters
filepath:str
Expand source code
def preprocess(self, filepath: str) -> None: """Set up the dataset that are common to every diagnostic Parameters ---------- filepath : str """ _logger.info("Preprocessing...") _logger.debug(' Opening the file..') self.stepA_original_datasets = DatasetDict({'main': xr.open_dataset(filepath)}) self.stepB_preprocessed_datasets = self.stepA_original_datasets.copy() _logger.debug(' Setting coords, formatting time, converting to ppm..') self.stepB_preprocessed_datasets.apply_function_to_all(self._preprocess_functions, inplace=True) _logger.info("Preprocessing is done.")
Inherited members