Source code for climlab.domain.domain

from __future__ import division
from builtins import str
from builtins import object
from climlab.domain.axis import Axis
from climlab.utils import heat_capacity


[docs] class _Domain(object): """Private parent class for `Domains`. A `Domain` defines an area or spatial base for a climlab :class:`~climlab.process.process.Process` object. It consists of axes which are :class:`~climlab.domain.axis.Axis` objects that define the dimensions of the `Domain`. In a `Domain` the heat capacity of grid points, bounds or cells/boxes is specified. There are daughter classes :class:`~climlab.domain.domain.Atmosphere` and :class:`~climlab.domain.domain.Ocean` of the private :class:`~climlab.domain.domain._Domain` class implemented which themselves have daughter classes :class:`~climlab.domain.domain.SlabAtmosphere` and :class:`~climlab.domain.domain.SlabOcean`. Several methods are implemented that create `Domains` with special specifications. These are - :func:`~climlab.domain.domain.single_column` - :func:`~climlab.domain.domain.zonal_mean_column` - :func:`~climlab.domain.domain.box_model_domain` **Initialization parameters** \n An instance of ``_Domain`` is initialized with the following arguments: :param axes: Axis object or dictionary of Axis object where domain will be defined on. :type axes: dict or :class:`~climlab.domain.axis.Axis` **Object attributes** \n Following object attributes are generated during initialization: :ivar str domain_type: Set to ``'undefined'``. :ivar dict axes: A dictionary of the domains axes. Created by :func:`_make_axes_dict` called with input argument ``axes`` :ivar int numdims: Number of :class:`~climlab.domain.axis.Axis` objects in ``self.axes`` dictionary. :ivar dict ax_index: A dictionary of domain axes and their corresponding index in an ordered list of the axes with: \n - ``'lev'`` or ``'depth'`` is last - ``'lat'`` is second last :ivar tuple shape: Number of points of all domain axes. Order in tuple given by ``self.ax_index``. :ivar array heat_capacity: the domain's heat capacity over axis specified in function call of :func:`set_heat_capacity` """ def __str__(self): return ("climlab Domain object with domain_type=" + self.domain_type + " and shape=" + str(self.shape)) def __init__(self, axes=None, **kwargs): self.domain_type = 'undefined' # self.axes should be a dictionary of axes # make it possible to give just a single axis: self.axes = self._make_axes_dict(axes) self.numdims = len(list(self.axes.keys())) shape = [] axcount = 0 axindex = {} # ordered list of axes # lev OR depth is last # lat is second-last add_lev = False add_depth = False add_lon = False add_lat = False axlist = list(self.axes.keys()) if 'lev' in axlist: axlist.remove('lev') add_lev = True elif 'depth' in axlist: axlist.remove('depth') add_depth = True if 'lon' in axlist: axlist.remove('lon') add_lon = True if 'lat' in axlist: axlist.remove('lat') add_lat = True axlist2 = axlist[:] if add_lat: axlist2.append('lat') if add_lon: axlist2.append('lon') if add_depth: axlist2.append('depth') if add_lev: axlist2.append('lev') #for axType, ax in self.axes.iteritems(): for axType in axlist2: ax = self.axes[axType] shape.append(ax.num_points) # can access axes as object attributes setattr(self, axType, ax) # axindex[axType] = axcount axcount += 1 self.axis_index = axindex self.shape = tuple(shape) self.set_heat_capacity()
[docs] def set_heat_capacity(self): """A dummy function to set the heat capacity of a domain. *Should be overridden by daugter classes.* """ self.heat_capacity = None
# implemented by daughter classes
[docs] def _make_axes_dict(self, axes): """Makes an axes dictionary. .. note:: In case the input is ``None``, the dictionary :code:`{'empty': None}` is returned. **Function-call argument** \n :param axes: axes input :type axes: dict or single instance of :class:`~climlab.domain.axis.Axis` object or ``None`` :raises: :exc:`ValueError` if input is not an instance of Axis class or a dictionary of Axis objetcs :returns: dictionary of input axes :rtype: dict """ if type(axes) is dict: axdict = axes elif type(axes) is Axis: ax = axes axdict = {ax.axis_type: ax} elif axes is None: axdict = {'empty': None} else: raise ValueError('axes needs to be Axis object or dictionary of Axis object') return axdict
def __getitem__(self, indx): # Make domains sliceable # First create a bare domain object (without calling the __init__ method) dout = type(self).__new__(type(self)) # inherit *most* of the attributes of self # For now we are just slicing the heat capacity # But would be great to have some logic for slicing axes # I am not 100% percent clear on how all this works # But for now we're just going to "try" to slice to avoid # some failures for key, value in self.__dict__.items(): if key == 'heat_capacity': try: dout.heat_capacity = self.heat_capacity[indx] except: dout.heat_capacity = self.heat_capacity elif key == 'shape': try: dout.shape = self.heat_capacity[indx].shape except: dout.shape = self.shape else: setattr(dout, key, value) return dout
[docs] class Atmosphere(_Domain): """Class for the implementation of an Atmosphere Domain. **Object attributes** \n Additional to the parent class :class:`~climlab.domain.domain._Domain` the following object attribute is modified during initialization: :ivar str domain_type: is set to ``'atm'`` :Example: Setting up an Atmosphere Domain:: >>> import climlab >>> atm_ax = climlab.domain.Axis(axis_type='pressure', num_points=10) >>> atm_domain = climlab.domain.Atmosphere(axes=atm_ax) >>> print atm_domain climlab Domain object with domain_type=atm and shape=(10,) >>> atm_domain.axes {'lev': <climlab.domain.axis.Axis object at 0x7fe5b8ef8e10>} >>> atm_domain.heat_capacity array([ 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837, 1024489.79591837]) """ def __init__(self, **kwargs): super(Atmosphere, self).__init__(**kwargs) self.domain_type = 'atm'
[docs] def set_heat_capacity(self): """Sets the heat capacity of the Atmosphere Domain. Calls the utils heat capacity function :func:`~climlab.utils.heat_capacity.atmosphere` and gives the delta array of grid points of it's level axis ``self.axes['lev'].delta`` as input. **Object attributes** \n During method execution following object attribute is modified: :ivar array heat_capacity: the ocean domain's heat capacity over the ``'lev'`` Axis. """ self.heat_capacity = heat_capacity.atmosphere(self.axes['lev'].delta)
[docs] class Ocean(_Domain): """Class for the implementation of an Ocean Domain. **Object attributes** \n Additional to the parent class :class:`~climlab.domain.domain._Domain` the following object attribute is modified during initialization: :ivar str domain_type: is set to ``'ocean'`` :Example: Setting up an Ocean Domain:: >>> import climlab >>> ocean_ax = climlab.domain.Axis(axis_type='depth', num_points=5) >>> ocean_domain = climlab.domain.Ocean(axes=ocean_ax) >>> print ocean_domain climlab Domain object with domain_type=ocean and shape=(5,) >>> ocean_domain.axes {'depth': <climlab.domain.axis.Axis object at 0x7fe5b8f102d0>} >>> ocean_domain.heat_capacity array([ 8362600., 8362600., 8362600., 8362600., 8362600.]) """ def __init__(self, **kwargs): super(Ocean, self).__init__(**kwargs) self.domain_type = 'ocean'
[docs] def set_heat_capacity(self): """Sets the heat capacity of the Ocean Domain. Calls the utils heat capacity function :func:`~climlab.utils.heat_capacity.ocean` and gives the delta array of grid points of it's depth axis ``self.axes['depth'].delta`` as input. **Object attributes** \n During method execution following object attribute is modified: :ivar array heat_capacity: the ocean domain's heat capacity over the ``'depth'`` Axis. """ self.heat_capacity = heat_capacity.ocean(self.axes['depth'].delta)
[docs] def make_slabocean_axis(num_points=1): """Convenience method to create a simple axis for a slab ocean. **Function-call argument** \n :param int num_points: number of points for the slabocean Axis [default: 1] :returns: an Axis with ``axis_type='depth'`` and ``num_points=num_points`` :rtype: :class:`~climlab.domain.axis.Axis` :Example: :: >>> import climlab >>> slab_ocean_axis = climlab.domain.make_slabocean_axis() >>> print slab_ocean_axis Axis of type depth with 1 points. >>> slab_ocean_axis.axis_type 'depth' >>> slab_ocean_axis.bounds array([ 0., 10.]) >>> slab_ocean_axis.units 'meters' """ depthax = Axis(axis_type='depth', num_points=num_points) return depthax
[docs] def make_slabatm_axis(num_points=1): """Convenience method to create a simple axis for a slab atmosphere. **Function-call argument** \n :param int num_points: number of points for the slabatmosphere Axis [default: 1] :returns: an Axis with ``axis_type='lev'`` and ``num_points=num_points`` :rtype: :class:`~climlab.domain.axis.Axis` :Example: :: >>> import climlab >>> slab_atm_axis = climlab.domain.make_slabatm_axis() >>> print slab_atm_axis Axis of type lev with 1 points. >>> slab_atm_axis.axis_type 'lev' >>> slab_atm_axis.bounds array([ 0., 1000.]) >>> slab_atm_axis.units 'mb' """ depthax = Axis(axis_type='lev', num_points=num_points) return depthax
[docs] class SlabOcean(Ocean): """A class to create a SlabOcean Domain by default. Initializes the parent :class:`Ocean` class with a simple axis for a Slab Ocean created by :func:`make_slabocean_axis` which has just 1 cell in depth by default. :Example: Creating a SlabOcean Domain:: >>> import climlab >>> slab_ocean_domain = climlab.domain.SlabOcean() >>> print slab_ocean_domain climlab Domain object with domain_type=ocean and shape=(1,) >>> slab_ocean_domain.axes {'depth': <climlab.domain.axis.Axis object at 0x7fe5c42814d0>} >>> slab_ocean_domain.heat_capacity array([ 41813000.]) """ def __init__(self, axes=make_slabocean_axis(), **kwargs): super(SlabOcean, self).__init__(axes=axes, **kwargs)
[docs] class SlabAtmosphere(Atmosphere): """A class to create a SlabAtmosphere Domain by default. Initializes the parent :class:`Atmosphere` class with a simple axis for a Slab Atmopshere created by :func:`make_slabatm_axis` which has just 1 cell in height by default. :Example: Creating a SlabAtmosphere Domain:: >>> import climlab >>> slab_atm_domain = climlab.domain.SlabAtmosphere() >>> print slab_atm_domain climlab Domain object with domain_type=atm and shape=(1,) >>> slab_atm_domain.axes {'lev': <climlab.domain.axis.Axis object at 0x7fe5c4281610>} >>> slab_atm_domain.heat_capacity array([ 10244897.95918367]) """ def __init__(self, axes=make_slabatm_axis(), **kwargs): super(SlabAtmosphere, self).__init__(axes=axes, **kwargs)
[docs] def single_column(num_lev=30, water_depth=1., lev=None, **kwargs): """Creates domains for a single column of atmosphere overlying a slab of water. Can also pass a pressure array or pressure level axis object specified in ``lev``. If argument ``lev`` is not ``None`` then function tries to build a level axis and ``num_lev`` is ignored. **Function-call argument** \n :param int num_lev: number of pressure levels (evenly spaced from surface to TOA) [default: 30] :param float water_depth: depth of the ocean slab [default: 1.] :param lev: specification for height axis (optional) :type lev: :class:`~climlab.domain.axis.Axis` or pressure array :raises: :exc:`ValueError` if `lev` is given but neither Axis nor pressure array. :returns: a list of 2 Domain objects (slab ocean, atmosphere) :rtype: :py:class:`list` of :class:`SlabOcean`, :class:`SlabAtmosphere` :Example: :: >>> from climlab import domain >>> sfc, atm = domain.single_column(num_lev=2, water_depth=10.) >>> print sfc climlab Domain object with domain_type=ocean and shape=(1,) >>> print atm climlab Domain object with domain_type=atm and shape=(2,) """ if lev is None: levax = Axis(axis_type='lev', num_points=num_lev) elif isinstance(lev, Axis): levax = lev else: try: levax = Axis(axis_type='lev', points=lev) except: raise ValueError('lev must be Axis object or pressure array') depthax = Axis(axis_type='depth', bounds=[water_depth, 0.]) slab = SlabOcean(axes=depthax, **kwargs) atm = Atmosphere(axes=levax, **kwargs) return slab, atm
[docs] def zonal_mean_surface(num_lat=90, water_depth=10., lat=None, **kwargs): """Creates a 1D slab ocean Domain in latitude with uniform water depth. Domain has a single heat capacity according to the specified water depth. **Function-call argument** \n :param int num_lat: number of latitude points [default: 90] :param float water_depth: depth of the slab ocean in meters [default: 10.] :param lat: specification for latitude axis (optional) :type lat: :class:`~climlab.domain.axis.Axis` or latitude array :raises: :exc:`ValueError` if `lat` is given but neither Axis nor latitude array. :returns: surface domain :rtype: :class:`SlabOcean` :Example: :: >>> from climlab import domain >>> sfc = domain.zonal_mean_surface(num_lat=36) >>> print sfc climlab Domain object with domain_type=ocean and shape=(36, 1) """ if lat is None: latax = Axis(axis_type='lat', num_points=num_lat) elif isinstance(lat, Axis): latax = lat else: try: latax = Axis(axis_type='lat', points=lat) except: raise ValueError('lat must be Axis object or latitude array') depthax = Axis(axis_type='depth', bounds=[water_depth, 0.]) axes = {'depth': depthax, 'lat': latax} slab = SlabOcean(axes=axes, **kwargs) return slab
[docs] def surface_2D(num_lat=90, num_lon=180, water_depth=10., lon=None, lat=None, **kwargs): """Creates a 2D slab ocean Domain in latitude and longitude with uniform water depth. Domain has a single heat capacity according to the specified water depth. **Function-call argument** \n :param int num_lat: number of latitude points [default: 90] :param int num_lon: number of longitude points [default: 180] :param float water_depth: depth of the slab ocean in meters [default: 10.] :param lat: specification for latitude axis (optional) :type lat: :class:`~climlab.domain.axis.Axis` or latitude array :param lon: specification for longitude axis (optional) :type lon: :class:`~climlab.domain.axis.Axis` or longitude array :raises: :exc:`ValueError` if `lat` is given but neither Axis nor latitude array. :raises: :exc:`ValueError` if `lon` is given but neither Axis nor longitude array. :returns: surface domain :rtype: :class:`SlabOcean` :Example: :: >>> from climlab import domain >>> sfc = domain.surface_2D(num_lat=36, num_lat=72) >>> print sfc climlab Domain object with domain_type=ocean and shape=(36, 72, 1) """ if lat is None: latax = Axis(axis_type='lat', num_points=num_lat) elif isinstance(lat, Axis): latax = lat else: try: latax = Axis(axis_type='lat', points=lat) except: raise ValueError('lat must be Axis object or latitude array') if lon is None: lonax = Axis(axis_type='lon', num_points=num_lon) elif isinstance(lon, Axis): lonax = lon else: try: lonax = Axis(axis_type='lon', points=lon) except: raise ValueError('lon must be Axis object or longitude array') depthax = Axis(axis_type='depth', bounds=[water_depth, 0.]) axes = {'lat': latax, 'lon': lonax, 'depth': depthax} slab = SlabOcean(axes=axes, **kwargs) return slab
[docs] def zonal_mean_column(num_lat=90, num_lev=30, water_depth=10., lat=None, lev=None, **kwargs): """Creates two Domains with one water cell, a latitude axis and a level/height axis. * SlabOcean: one water cell and a latitude axis above (similar to :func:`zonal_mean_surface`) * Atmosphere: a latitude axis and a level/height axis (two dimensional) **Function-call argument** \n :param int num_lat: number of latitude points on the axis [default: 90] :param int num_lev: number of pressure levels (evenly spaced from surface to TOA) [default: 30] :param float water_depth: depth of the water cell (slab ocean) [default: 10.] :param lat: specification for latitude axis (optional) :type lat: :class:`~climlab.domain.axis.Axis` or latitude array :param lev: specification for height axis (optional) :type lev: :class:`~climlab.domain.axis.Axis` or pressure array :raises: :exc:`ValueError` if `lat` is given but neither Axis nor latitude array. :raises: :exc:`ValueError` if `lev` is given but neither Axis nor pressure array. :returns: a list of 2 Domain objects (slab ocean, atmosphere) :rtype: :py:class:`list` of :class:`SlabOcean`, :class:`Atmosphere` :Example: :: >>> from climlab import domain >>> sfc, atm = domain.zonal_mean_column(num_lat=36,num_lev=10) >>> print sfc climlab Domain object with domain_type=ocean and shape=(36, 1) >>> print atm climlab Domain object with domain_type=atm and shape=(36, 10) """ if lat is None: latax = Axis(axis_type='lat', num_points=num_lat) elif isinstance(lat, Axis): latax = lat else: try: latax = Axis(axis_type='lat', points=lat) except: raise ValueError('lat must be Axis object or latitude array') if lev is None: levax = Axis(axis_type='lev', num_points=num_lev) elif isinstance(lev, Axis): levax = lev else: try: levax = Axis(axis_type='lev', points=lev) except: raise ValueError('lev must be Axis object or pressure array') depthax = Axis(axis_type='depth', bounds=[water_depth, 0.]) #axes = {'depth': depthax, 'lat': latax, 'lev': levax} slab = SlabOcean(axes={'lat':latax, 'depth':depthax}, **kwargs) atm = Atmosphere(axes={'lat':latax, 'lev':levax}, **kwargs) return slab, atm
[docs] def box_model_domain(num_points=2, **kwargs): """Creates a box model domain (a single abstract axis). :param int num_points: number of boxes [default: 2] :returns: Domain with single axis of type ``'abstract'`` and ``self.domain_type = 'box'`` :rtype: :class:`_Domain` :Example: :: >>> from climlab import domain >>> box = domain.box_model_domain(num_points=2) >>> print box climlab Domain object with domain_type=box and shape=(2,) """ ax = Axis(axis_type='abstract', num_points=num_points) boxes = _Domain(axes=ax, **kwargs) boxes.domain_type = 'box' return boxes