Source code for climlab.radiation.greygas

from __future__ import division
from builtins import range
import numpy as np
from climlab.utils.thermo import blackbody_emission
from climlab.radiation.transmissivity import Transmissivity
from climlab.process import EnergyBudget


[docs] class GreyGas(EnergyBudget): '''Base class for all band radiation models, including grey and semi-grey model. Input argument absorptivity is band absorptivity (should be same size as the grid). By default emissivity = absorptivity. Subclasses can override this is necessary (e.g. for shortwave model). The following boundary and input values need to be specified by user or parent process: - albedo_sfc (default is zero) - flux_from_space - absorptivity - reflectivity (default is zero) These are accessible (and settable) as process attributes Also stored in process.input dictionary The following values are computed are stored in the .diagnostics dictionary: - flux_from_sfc - flux_to_sfc - flux_to_space - absorbed - absorbed_total - emission - emission_sfc - flux_reflected_up (all in W/m2) ''' def __init__(self, absorptivity=None, reflectivity=None, emissivity_sfc=1., albedo_sfc=0., **kwargs): super(GreyGas, self).__init__(**kwargs) self.add_input('flux_from_space', 0. * self.Ts) # initialize all diagnostics to zero self.add_diagnostic('flux_from_sfc', 0. * self.Ts) self.add_diagnostic('flux_to_sfc', 0. * self.Ts) self.add_diagnostic('flux_to_space', 0. * self.Ts) self.add_diagnostic('absorbed', 0. * self.Tatm) self.add_diagnostic('absorbed_total', 0. * self.Ts) newinput = ['reflectivity', 'absorptivity', 'emissivity_sfc', 'albedo_sfc',] self.declare_input(newinput) if reflectivity is None: reflectivity = np.zeros_like(self.Tatm) if absorptivity is None: absorptivity = np.zeros_like(self.Tatm) self.absorptivity = absorptivity self.reflectivity = reflectivity self.emissivity_sfc = emissivity_sfc * np.ones_like(self.Ts) self.albedo_sfc = albedo_sfc * np.ones_like(self.Ts) # Initialize diagnostics self.add_diagnostic('emission', 0. * self.Tatm) self.add_diagnostic('emission_sfc', 0. * self.Ts) self.add_diagnostic('flux_reflected_up') @property def absorptivity(self): return self.trans.absorptivity @absorptivity.setter def absorptivity(self, value): # value should be a Field, # or numpy array of same size as self.Tatm try: axis = value.domain.axis_index['lev'] except: axis = self.Tatm.domain.axis_index['lev'] # if a single scalar is given, broadcast that to all levels if len(np.shape(np.array(value))) == 0: value = np.ones_like(self.Tatm) * value elif value.shape != self.Tatm.shape: raise ValueError('absorptivity must be a Field, a scalar, or match atm grid dimensions') try: self.trans = Transmissivity(absorptivity=value, reflectivity=self.reflectivity, axis=axis) except: self.trans = Transmissivity(absorptivity=value, axis=axis) self.input['absorptivity'] = value @property def emissivity(self): # This ensures that emissivity = absorptivity at all times # needs to be overridden for shortwave classes return self.absorptivity @property def transmissivity(self): return self.trans.transmissivity @transmissivity.setter def transmissivity(self, value): self.absorptivity = 1 - value @property def reflectivity(self): return self.trans.reflectivity @reflectivity.setter def reflectivity(self, value): # value should be a Field, # or numpy array of same size as self.Tatm try: axis = value.domain.axis_index['lev'] except: axis = self.Tatm.domain.axis_index['lev'] # if a single scalar is given, broadcast that to all levels if len(np.shape(np.array(value))) == 0: value = np.ones_like(self.Tatm) * value elif value.shape != self.Tatm.shape: raise ValueError('reflectivity must be a Field, a scalar, or match atm grid dimensions') self.trans = Transmissivity(absorptivity=self.absorptivity, reflectivity=value, axis=axis) #self.input['reflectivity'] = value def _compute_emission_sfc(self): return self.emissivity_sfc * blackbody_emission(self.Ts) def _compute_emission(self): return self.emissivity * blackbody_emission(self.Tatm) def _compute_fluxes(self): ''' All fluxes are band by band''' self.emission = self._compute_emission() self.emission_sfc = self._compute_emission_sfc() fromspace = self._from_space() self.flux_down = self.trans.flux_down(fromspace, self.emission) self.flux_reflected_up = self.trans.flux_reflected_up(self.flux_down, self.albedo_sfc) # this ensure same dimensions as other fields self.flux_to_sfc = self.flux_down[..., -1, np.newaxis] self.flux_from_sfc = (self.emission_sfc + self.flux_reflected_up[..., -1, np.newaxis]) self.flux_up = self.trans.flux_up(self.flux_from_sfc, self.emission + self.flux_reflected_up[...,0:-1]) self.flux_net = self.flux_up - self.flux_down # absorbed radiation (flux convergence) in W / m**2 (per band) self.absorbed = np.diff(self.flux_net, axis=-1) self.absorbed_total = np.sum(self.absorbed, axis=-1) self.flux_to_space = self._compute_flux_top() def _compute_flux_top(self): bandflux = self.flux_up[..., 0, np.newaxis] return self._join_channels(bandflux) def _flux_convergence_atm(self): return self.absorbed def _flux_convergence_sfc(self): return ( self.flux_to_sfc - self.flux_from_sfc ) def _compute_heating_rates(self): '''Compute energy flux convergences to get heating rates in :math:`W/m^2`.''' self._compute_radiative_heating() def _compute_radiative_heating(self): self._compute_fluxes() self.heating_rate['Tatm'] = self._join_channels(self.absorbed) self.heating_rate['Ts'] = self._join_channels( self.flux_to_sfc - self.flux_from_sfc ) def _from_space(self): try: fromspace = self.flux_from_space except: fromspace = np.zeros_like(self.Ts) return self._split_channels(fromspace) def _split_channels(self, flux): '''Single channel for Grey Gas model.''' return flux def _join_channels(self, flux): '''Single channel for Grey Gas model.''' return flux ##### These routines may need fixing with flipped vertical grid...
[docs] def flux_components_top(self): '''Compute the contributions to the outgoing flux to space due to emissions from each level and the surface.''' N = self.lev.size flux_up_bottom = self.flux_from_sfc emission = np.zeros_like(self.emission) this_flux_up = (np.ones_like(self.Ts) * self.trans.flux_up(flux_up_bottom, emission)) sfcComponent = this_flux_up[..., -1] atmComponents = np.zeros_like(self.Tatm) flux_up_bottom = np.zeros_like(self.Ts) # I'm sure there's a way to write this as a vectorized operation # but the speed doesn't really matter if it's just for diagnostic # and we are not calling it every timestep for n in range(N): emission = np.zeros_like(self.emission) emission[..., n] = self.emission[..., n] this_flux_up = self.trans.flux_up(flux_up_bottom, emission) atmComponents[..., n] = this_flux_up[..., -1] return sfcComponent, atmComponents
[docs] def flux_components_bottom(self): '''Compute the contributions to the downwelling flux to surface due to emissions from each level.''' N = self.lev.size atmComponents = np.zeros_like(self.Tatm) flux_down_top = np.zeros_like(self.Ts) # same comment as above... would be nice to vectorize for n in range(N): emission = np.zeros_like(self.emission) emission[..., n] = self.emission[..., n] this_flux_down = self.trans.flux_down(flux_down_top, emission) atmComponents[..., n] = this_flux_down[..., 0] return atmComponents
[docs] class GreyGasSW(GreyGas): '''Emissivity is always set to zero for shortwave classes.''' def __init__(self, albedo_sfc=0.33, emissivity_sfc=0., **kwargs): super(GreyGasSW, self).__init__(albedo_sfc=albedo_sfc, emissivity_sfc=emissivity_sfc, **kwargs) @property def emissivity(self): # This ensures that emissivity is always zero for shortwave classes return np.zeros_like(self.absorptivity)