Source code for pywatershed.base.adapter
"""The adapter module.
This module contains the Adapter base class, its several concrete subclasses
and an adapter factory to dispatch you the right subclass when you ask for it.
"""
import pathlib as pl
from typing import Union
import numpy as np
from ..base.control import Control
from ..base.timeseries import TimeseriesArray
from ..constants import fileish
from ..utils.netcdf_utils import NetCdfRead
[docs]
class Adapter:
"""Adapter base class for getting data from a variety of sources.
Args:
variable: string name of variable
"""
[docs]
def __init__(
self,
variable: str,
) -> None:
self.name = "Adapter"
self._variable = variable
return None
[docs]
def advance(self) -> None:
"""Advance the adapter in time"""
raise NotImplementedError("Must be overridden")
@property
def current(self):
"""Current time of the Adapter instance."""
return self._current_value
[docs]
class AdapterNetcdf(Adapter):
"""Adapter subclass for a NetCDF file
This requires that the NetCDF file have a time dimension named "time" or
"doy" (day of year) to be properly handled as a timeseries for input, etc.
Args:
fname: filename of netcdf as string or Path
variable: variable name string
dim_sizes: a tuple of dimension sizes
type: a variable dtype
control: a Control object
load_n_time_batches: number of times to read from file.
"""
[docs]
def __init__(
self,
fname: fileish,
variable: str,
control: Control,
load_n_time_batches: int = 1,
) -> None:
super().__init__(variable)
self.name = "AdapterNetcdf"
self._fname = fname
self.control = control
self._start_time = self.control.start_time
self._end_time = self.control.end_time
self._nc_read = NetCdfRead(
fname,
start_time=self._start_time,
end_time=self._end_time,
load_n_time_batches=load_n_time_batches,
)
# would like to make this a check if dim_sizes and type are available
nc_type = self._nc_read.dataset[variable].dtype
nc_shape = list(self._nc_read.dataset[variable].shape)
nc_dims = list(self._nc_read.dataset[variable].dimensions)
for time_dim in ["time", "doy"]:
if time_dim in nc_dims:
_ = nc_shape.pop(nc_dims.index(time_dim))
self.time = self._nc_read.times
if "int" in str(nc_type):
fill_value = -9999
else:
fill_value = np.nan
self._current_value = np.full(nc_shape, fill_value, nc_type)
return
[docs]
def advance(self):
if self._nc_read._itime_step[self._variable] > self.control.itime_step:
return
self._current_value[:] = self._nc_read.advance(
self._variable, self.control.current_time
)
return None
@property
def data(self) -> np.array:
"""Return the data for the current time."""
# TODO JLM: seems like we'd want to cache this data if we invoke once
return self._nc_read.all_time(self._variable).data
class AdapterOnedarray(Adapter):
"""Adapter subclass for an invariant 1-D numpy.array
The data are constant and do not advance in time.
Args:
data: the data to be adapted
variable: variable name string
"""
def __init__(
self,
data: np.ndarray,
variable: str,
control: Control = None,
) -> None:
super().__init__(variable)
self.name = "AdapterOnedarray"
self.control = control
self._current_value = data
return
def advance(self, *args) -> None:
"""A dummy method for compliance."""
return None
adaptable = Union[str, pl.Path, np.ndarray, Adapter]
[docs]
def adapter_factory(
var: adaptable,
variable_name: str = None,
control: Control = None,
load_n_time_batches: int = 1,
) -> "Adapter":
"""A function to return the appropriate subclass of Adapter
Args:
var: the quantity to be adapted
variable_name: what you call the above var
control: a Control object
variable_dim_sizes: for an AdapterNetcdf
variable_type: for an AdapterNetcdf
load_n_time_batches: for an AdapterNetcdf
"""
if isinstance(var, Adapter):
# Adapt an adapter.
return var
elif isinstance(var, (str, pl.Path)):
# Paths and strings are considered paths to netcdf files
if pl.Path(var).suffix == ".nc":
return AdapterNetcdf(
var,
variable=variable_name,
control=control,
load_n_time_batches=load_n_time_batches,
)
elif isinstance(var, np.ndarray) and len(var.shape) == 1:
# Adapt 1-D np.ndarrays
return AdapterOnedarray(var, variable=variable_name, control=control)
elif isinstance(var, TimeseriesArray):
# Adapt TimeseriesArrays as is.
return var
elif var is None:
# var is specified as None so return None
return None
else:
raise TypeError("oops you screwed up")