Source code for climlab.surface.albedo

from __future__ import division
from builtins import next
import numpy as np
from climlab.process.diagnostic import DiagnosticProcess
from climlab.utils.legendre import P2
from climlab.domain.field import Field, global_mean


[docs] class ConstantAlbedo(DiagnosticProcess): """A class for constant albedo values at all spatial points of the domain. **Initialization parameters** \n :param float albedo: albedo values [default: 0.33] **Object attributes** \n Additional to the parent class :class:`~climlab.process.diagnostic.DiagnosticProcess` following object attributes are generated and updated during initialization: :ivar Field albedo: attribute to store the albedo value. During initialization the :func:`albedo` setter is called. :Example: Creation of a constant albedo subprocess on basis of an EBM domain:: >>> import climlab >>> from climlab.surface.albedo import ConstantAlbedo >>> # model creation >>> model = climlab.EBM() >>> sfc = model.domains['Ts'] >>> # subprocess creation >>> const_alb = ConstantAlbedo(albedo=0.3, domains=sfc, **model.param) """ def __init__(self, albedo=0.33, **kwargs): '''Uniform prescribed albedo.''' super(ConstantAlbedo, self).__init__(**kwargs) dom = next(iter(self.domains.values())) self.add_diagnostic('albedo', Field(albedo, domain=dom))
#self.albedo = albedo # @property # def albedo(self): # """Property of albedo value. # # :getter: Returns the albedo value which is stored in diagnostic dict # ``self.diagnostic['albedo']`` # :setter: * sets albedo which is addressed as ``diagnostics['albedo']`` # to the new value through creating a Field on the basis # of domain ``self.domain['default']`` # * updates the parameter dictionary ``self.param['albedo']`` # :type: Field # # """ # return self.diagnostics['albedo'] # @albedo.setter # def albedo(self, value): # #dom = self.domains['default'] # # this is a more robust way to get the single value from dictionary: # dom = self.domains.itervalues().next() # self.diagnostics['albedo'] = Field(value, domain=dom) # self.param['albedo'] = value
[docs] class P2Albedo(DiagnosticProcess): """A class for parabolic distributed albedo values across the domain on basis of the second order Legendre Polynomial. Calculates the latitude dependent albedo values as .. math:: \\alpha(\\varphi) = a_0 + a_2 P_2(x) where :math:`P_2(x) = \\frac{1}{2} (3x^2 - 1)` is the second order Legendre Polynomial and :math:`x=sin(\\varphi)`. **Initialization parameters** \n :param float a0: basic parameter for albedo function [default: 0.33] :param float a2: factor for second legendre polynominal term in albedo function [default: 0.25] **Object attributes** \n Additional to the parent class :class:`~climlab.process.diagnostic.DiagnosticProcess` following object attributes are generated and updated during initialization: :ivar float a0: attribute to store the albedo parameter a0. During initialization the :func:`a0` setter is called. :ivar float a2: attribute to store the albedo parameter a2. During initialization the :func:`a2` setter is called. :ivar dict diagnostics: key ``'albedo'`` initialized :ivar Field albedo: the subprocess attribute ``self.albedo`` is created with correct dimensions (according to ``self.lat``) :Example: Creation of a parabolic albedo subprocess on basis of an EBM domain:: >>> import climlab >>> from climlab.surface.albedo import P2Albedo >>> # model creation >>> model = climlab.EBM() >>> # modify a0 and a2 values in model parameter dictionary >>> model.param['a0']=0.35 >>> model.param['a2']= 0.10 >>> # subprocess creation >>> p2_alb = P2Albedo(domains=model.domains['Ts'], **model.param) >>> p2_alb.a0 0.33 >>> p2_alb.a2 0.1 """ def __init__(self, a0=0.33, a2=0.25, **kwargs): super(P2Albedo, self).__init__(**kwargs) self.a0 = a0 self.a2 = a2 self.add_diagnostic('albedo') self._compute_fixed() @property def a0(self): """Property of albedo parameter a0. :getter: Returns the albedo parameter value which is stored in attribute ``self._a0`` :setter: * sets albedo parameter which is addressed as ``self._a0`` to the new value * updates the parameter dictionary ``self.param['a0']`` * calls method :func:`_compute_fixed` :type: float """ return self._a0 @a0.setter def a0(self, value): self._a0 = value self.param['a0'] = value self._compute_fixed() @property def a2(self): """Property of albedo parameter a2. :getter: Returns the albedo parameter value which is stored in attribute ``self._a2`` :setter: * sets albedo parameter which is addressed as ``self._a2`` to the new value * updates the parameter dictionary ``self.param['a2']`` * calls method :func:`_compute_fixed` :type: float """ return self._a2 @a2.setter def a2(self, value): self._a2 = value self.param['a2'] = value self._compute_fixed() def _compute_fixed(self): '''Recompute any fixed quantities after a change in parameters''' try: lon, lat = np.meshgrid(self.lon, self.lat) except: lat = self.lat phi = np.deg2rad(lat) try: albedo = self.a0 + self.a2 * P2(np.sin(phi)) except: albedo = np.zeros_like(phi) # make sure that the diagnostic has the correct field dimensions. #dom = self.domains['default'] # this is a more robust way to get the single value from dictionary: dom = next(iter(self.domains.values())) self.albedo = Field(albedo, domain=dom)
[docs] class Iceline(DiagnosticProcess): """A class for an Iceline subprocess. Depending on a freezing temperature it calculates where on the domain the surface is covered with ice, where there is no ice and on which latitude the ice-edge is placed. **Initialization parameters** \n :param float Tf: freezing temperature where sea water freezes and surface is covered with ice \n - unit: :math:`^{\circ} \\textrm{C}` \n - default value: ``-10`` **Object attributes** \n Additional to the parent class :class:`~climlab.process.diagnostic.DiagnosticProcess` following object attributes are generated and updated during initialization: :ivar dict param: The parameter dictionary is updated with the input argument ``'Tf'``. :ivar dict diagnostics: keys ``'icelat'`` and ``'ice_area'`` initialized :ivar array icelat: the subprocess attribute ``self.icelat`` is created :ivar float ice_area: the subprocess attribute ``self.ice_area`` is created """ def __init__(self, Tf=-10., **kwargs): super(Iceline, self).__init__(**kwargs) self.param['Tf'] = Tf self.add_diagnostic('icelat') self.add_diagnostic('ice_area') # Set diagnostics based on initial conditions self.find_icelines()
[docs] def find_icelines(self): """Finds iceline according to the surface temperature. This method is called by the private function :func:`~climlab.surface.albedo.Iceline._compute` and updates following attributes according to the freezing temperature ``self.param['Tf']`` and the surface temperature ``self.param['Ts']``: **Object attributes** \n :ivar Field noice: a Field of booleans which are ``True`` where :math:`T_s \\ge T_f` :ivar Field ice: a Field of booleans which are ``True`` where :math:`T_s < T_f` :ivar array icelat: an array with two elements indicating the ice-edge latitudes :ivar float ice_area: fractional area covered by ice (0 - 1) :ivar dict diagnostics: keys ``'icelat'`` and ``'ice_area'`` are updated """ Tf = self.param['Tf'] Ts = self.state['Ts'] lat_bounds = self.domains['Ts'].axes['lat'].bounds self.noice = np.where(Ts >= Tf, True, False) self.ice = np.where(Ts < Tf, True, False) # Ice cover in fractional area self.ice_area = global_mean(self.ice * np.ones_like(self.Ts)) # Express ice cover in terms of ice edge latitudes if self.ice.all(): # 100% ice cover self.icelat = np.array([-0., 0.]) elif self.noice.all(): # zero ice cover self.icelat = np.array([-90., 90.]) else: # there is some ice edge # Taking np.diff of a boolean array gives True at the boundaries between True and False boundary_indices = np.where(np.diff(self.ice.squeeze()))[0]+1 # check for asymmetry case: [-90,x] or [x,90] # -> boundary_indices hold only one value for icelat if boundary_indices.size == 1: if self.ice[0] == True: # case: [x,90] # extend indice array by missing value for northpole boundary_indices = np.append(boundary_indices, self.ice.size) elif self.ice[-1] == True: # case: [-90,x] # extend indice array by missing value for northpole boundary_indices = np.insert(boundary_indices,0 ,0) # check for asymmetry case: [-90,x] or [x,90] # -> boundary_indices hold only one value for icelat if boundary_indices.size == 1: if self.ice[0] == True: # case: [x,90] # extend indice array by missing value for northpole boundary_indices = np.append(boundary_indices, self.ice.size) elif self.ice[-1] == True: # case: [-90,x] # extend indice array by missing value for northpole boundary_indices = np.insert(boundary_indices,0 ,0) self.icelat = lat_bounds[boundary_indices] # an array of boundary latitudes
def _compute(self): self.find_icelines() return {}
[docs] class StepFunctionAlbedo(DiagnosticProcess): """A step function albedo suprocess. This class itself defines three subprocesses that are created during initialization: * ``'iceline'`` - :class:`Iceline` * ``'warm_albedo'`` - :class:`P2Albedo` * ``'cold_albedo'`` - :class:`ConstantAlbedo` **Initialization parameters** \n :param float Tf: freezing temperature for Iceline subprocess \n - unit: :math:`^{\circ} \\textrm{C}` \n - default value: ``-10`` :param float a0: basic parameter for P2Albedo subprocess [default: 0.3] :param float a2: factor for second legendre polynominal term in P2Albedo subprocess [default: 0.078] :param float ai: ice albedo value for ConstantAlbedo subprocess [default: 0.62] Additional to the parent class :class:`~climlab.process.diagnostic.DiagnosticProcess` following object attributes are generated/updated during initialization: :ivar dict param: The parameter dictionary is updated with a couple of the initatilzation input arguments, namely ``'Tf'``, ``'a0'``, ``'a2'`` and ``'ai'``. :ivar bool topdown: is set to ``False`` to call subprocess compute method first :ivar dict diagnostics: key ``'albedo'`` initialized :ivar Field albedo: the subprocess attribute ``self.albedo`` is created :Example: Creation of a step albedo subprocess on basis of an EBM domain:: >>> import climlab >>> from climlab.surface.albedo import StepFunctionAlbedo >>> >>> model = climlab.EBM(a0=0.29, a2=0.1, ai=0.65, Tf=-2) >>> >>> step_alb = StepFunctionAlbedo(state= model.state, **model.param) >>> >>> print step_alb climlab Process of type <class 'climlab.surface.albedo.StepFunctionAlbedo'>. State variables and domain shapes: Ts: (90, 1) The subprocess tree: top: <class 'climlab.surface.albedo.StepFunctionAlbedo'> iceline: <class 'climlab.surface.albedo.Iceline'> cold_albedo: <class 'climlab.surface.albedo.ConstantAlbedo'> warm_albedo: <class 'climlab.surface.albedo.P2Albedo'> """ def __init__(self, Tf=-10., a0=0.3, a2=0.078, ai=0.62, **kwargs): super(StepFunctionAlbedo, self).__init__(**kwargs) self.param['Tf'] = Tf self.param['a0'] = a0 self.param['a2'] = a2 self.param['ai'] = ai sfc = self.domains['Ts'] self.add_subprocess('iceline', Iceline(Tf=Tf, state=self.state, timestep=self.timestep)) warm = P2Albedo(a0=a0, a2=a2, domains=sfc, timestep=self.timestep) cold = ConstantAlbedo(albedo=ai, domains=sfc, timestep=self.timestep) # remove `albedo` from the diagnostics list for the two subprocesses # because they cause conflicts when passed up the subprocess tree for proc in [warm, cold]: proc._diag_vars.remove('albedo') self.add_subprocess('warm_albedo', warm) self.add_subprocess('cold_albedo', cold) self.topdown = False # call subprocess compute methods first self.add_diagnostic('albedo', self._get_current_albedo()) def _get_current_albedo(self): '''Simple step-function albedo based on ice line at temperature Tf.''' ice = self.subprocess['iceline'].ice # noice = self.subprocess['iceline'].diagnostics['noice'] cold_albedo = self.subprocess['cold_albedo'].albedo warm_albedo = self.subprocess['warm_albedo'].albedo albedo = Field(np.where(ice, cold_albedo, warm_albedo), domain=self.domains['Ts']) return albedo def _compute(self): self.albedo[:] = self._get_current_albedo() return {}