def column_state(num_lev=30, num_lat=1, lev=None, lat=None, water_depth=1.0): '''Set up a state variable dictionary consisting of temperatures for atmospheric column (`Tatm`) and surface mixed layer (`Ts`). ''' if lat is not None: num_lat = np.array(lat).size if lev is not None: num_lev = np.array(lev).size if num_lat is 1: sfc, atm = domain.single_column(water_depth=water_depth, num_lev=num_lev, lev=lev) else: sfc, atm = domain.zonal_mean_column(water_depth=water_depth, num_lev=num_lev, lev=lev, num_lat=num_lat, lat=lat) num_lev = atm.lev.num_points Ts = Field(288. * np.ones(sfc.shape), domain=sfc) Tinitial = np.tile(np.linspace(200., 288. - 10., num_lev), sfc.shape) Tatm = Field(Tinitial, domain=atm) state = AttrDict() state['Ts'] = Ts state['Tatm'] = Tatm return state
def _compute(self): #lapse_rate = self.param['adj_lapse_rate'] if self.adj_lapse_rate is None: #self.adjustment = self.state * 0. self.adjustment['Ts'] = self.Ts * 0. self.adjustment['Tatm'] = self.Tatm * 0. else: # For now, let's assume that the vertical axis is the last axis unstable_Tatm = self.Tatm if 'Ts' in self.state: unstable_Ts = np.atleast_1d(self.Ts) #Tcol = np.concatenate((unstable_Ts, unstable_Tatm),axis=-1) Tcol = np.concatenate((unstable_Tatm, unstable_Ts),axis=-1) else: Tcol = unstable_Tatm # convective adjustment routine expect reversered vertical axis pflip = self.pnew[..., ::-1] Tflip = Tcol[..., ::-1] cflip = self.cnew[..., ::-1] Tadj_flip = convective_adjustment_direct(pflip, Tflip, cflip, lapserate=self.adj_lapse_rate) Tadj = Tadj_flip[..., ::-1] if 'Ts' in self.state: Ts = Field(Tadj[...,-1], domain=self.Ts.domain) Tatm = Field(Tadj[...,:-1], domain=self.Tatm.domain) self.adjustment['Ts'] = Ts - self.Ts else: Tatm = Field(Tadj, domain=self.Tatm.domain) self.adjustment['Tatm'] = Tatm - self.Tatm # return the adjustment, independent of timestep # because the parent process might have set a different timestep! return self.adjustment
def _compute(self): if self.adj_lapse_rate is None: self.adjustment['Ts'] = self.Ts * 0. self.adjustment['Tatm'] = self.Tatm * 0. else: # convective adjustment routine expect reversered vertical axis pflip = self.pcol[..., ::-1] Tflip = self.Tcol[..., ::-1] cflip = self.ccol[..., ::-1] lapseflip = np.atleast_1d(self.adj_lapse_rate)[..., ::-1] Tadj_flip = convective_adjustment_direct(pflip, Tflip, cflip, lapserate=lapseflip) Tadj = Tadj_flip[..., ::-1] if 'Ts' in self.state: Ts = Field(Tadj[..., -1], domain=self.Ts.domain) Tatm = Field(Tadj[..., :-1], domain=self.Tatm.domain) self.adjustment['Ts'] = Ts - self.Ts else: Tatm = Field(Tadj, domain=self.Tatm.domain) self.adjustment['Tatm'] = Tatm - self.Tatm # return the adjustment, independent of timestep # because the parent process might have set a different timestep! return self.adjustment
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = Field(self.q[..., -1, np.newaxis], domain=self.Ts.domain) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) qs = qsat(self.Ts, self.ps) Deltaq = Field(qs - q, domain=self.Ts.domain) rho = self._air_density(Ta) # flux from bulk formula self._flux = const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF = self._flux
def test_float(): '''Check that we can step forward the model after setting the state variable with an ndarray of integers through 2 different methods''' from climlab.domain import initial from climlab.domain.field import Field state = initial.surface_state() sfc = climlab.domain.zonal_mean_surface(num_lat=4) state.Ts = Field([10, 15, 15, 10], domain=sfc) m = climlab.EBM(state=state) m.step_forward() k = climlab.EBM(num_lat=4) k.set_state('Ts', Field([10, 15, 15, 10], domain=k.domains['Ts'])) k.step_forward()
def _compute_flux(self): # specific humidity at lowest model level # assumes pressure is the last axis q = Field(self.q[..., -1, np.newaxis], domain=self.Ts.domain) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) qs = qsat(self.Ts, self.ps) Deltaq = Field(qs - q, domain=self.Ts.domain) rho = self._air_density(Ta) # flux from bulk formula self._flux = self.resistance * const.Lhvap * rho * self.Cd * self.U * Deltaq self.LHF[:] = self._flux # evporation rate, convert from W/m2 to kg/m2/s (or mm/s) self.evaporation[:] = self.LHF / const.Lhvap
def compute(self): #lapse_rate = self.param['adj_lapse_rate'] if self.adj_lapse_rate is None: self.adjusted_state = self.state else: # For now, let's assume that the vertical axis is the last axis unstable_Ts = np.atleast_1d(self.Ts) unstable_Tatm = self.Tatm Tcol = np.concatenate((unstable_Ts, unstable_Tatm),axis=-1) Tadj = convective_adjustment_direct(self.pnew, Tcol, self.cnew, lapserate=self.adj_lapse_rate) Ts = Field(Tadj[...,0], domain=self.Ts.domain) Tatm = Field(Tadj[...,1:], domain=self.Tatm.domain) self.adjustment['Ts'] = Ts - self.Ts self.adjustment['Tatm'] = Tatm - self.Tatm
def __init__(self, S0=const.S0, **kwargs): super(_Insolation, self).__init__(**kwargs) # initialize diagnostics with correct shape self.add_diagnostic('insolation') self.add_diagnostic('coszen') try: domain = self.domains['sfc'] except: domain = self.domains['default'] self.insolation = Field(np.zeros(domain.shape), domain=domain) self.coszen = Field(np.zeros(domain.shape), domain=domain) self.declare_diagnostics(['insolation', 'coszen']) self.S0 = S0 # Now that we have a value for self.S0 we can compute the correct coszen self.coszen = self._coszen_from_insolation() self.declare_input(['S0'])
def __init__(self, num_lev=30, num_lat=1, lev=None, lat=None, water_depth=1.0, albedo_sfc=0.299, timestep=1. * const.seconds_per_day, Q=341.3, # absorption coefficient in m**2 / kg abs_coeff=1.229E-4, **kwargs): # Check to see if an initial state is already provided # If not, make one if 'state' not in kwargs: state = column_state(num_lev, num_lat, lev, lat, water_depth) kwargs.update({'state': state}) super(GreyRadiationModel, self).__init__(timestep=timestep, **kwargs) self.param['water_depth'] = water_depth self.param['albedo_sfc'] = albedo_sfc self.param['Q'] = Q self.param['abs_coeff'] = abs_coeff sfc = self.Ts.domain atm = self.Tatm.domain # create sub-models for longwave and shortwave radiation dp = self.Tatm.domain.lev.delta absorbLW = compute_layer_absorptivity(self.param['abs_coeff'], dp) absorbLW = Field(np.tile(absorbLW, sfc.shape), domain=atm) absorbSW = np.zeros_like(absorbLW) longwave = GreyGas(state=self.state, absorptivity=absorbLW, albedo_sfc=0) shortwave = GreyGasSW(state=self.state, absorptivity=absorbSW, albedo_sfc=self.param['albedo_sfc']) # sub-model for insolation ... here we just set constant Q thisQ = self.param['Q']*np.ones_like(self.Ts) Q = FixedInsolation(S0=thisQ, domains=sfc, **self.param) self.add_subprocess('LW', longwave) self.add_subprocess('SW', shortwave) self.add_subprocess('insolation', Q) newdiags = ['OLR', 'LW_down_sfc', 'LW_up_sfc', 'LW_absorbed_sfc', 'LW_absorbed_atm', 'LW_emission', 'ASR', 'SW_absorbed_sfc', 'SW_absorbed_atm', 'SW_up_sfc', 'SW_up_TOA', 'SW_down_TOA', 'planetary_albedo'] for name in newdiags: self.init_diagnostic(name) # This process has to handle the coupling between # insolation and column radiation self.subprocess['SW'].flux_from_space = \ self.subprocess['insolation'].diagnostics['insolation']
def initial_state(num_lev, num_lat, lev, lat, water_depth): if num_lat is 1: sfc, atm = domain.single_column(water_depth=water_depth, num_lev=num_lev, lev=lev) else: sfc, atm = domain.zonal_mean_column(water_depth=water_depth, num_lev=num_lev, lev=lev, num_lat=num_lat, lat=lat) num_lev = atm.lev.num_points Ts = Field(288. * np.ones(sfc.shape), domain=sfc) Tinitial = np.tile(np.linspace(288. - 10., 200., num_lev), sfc.shape) Tatm = Field(Tinitial, domain=atm) state = {'Ts': Ts, 'Tatm': Tatm} return state
def _get_current_insolation(self): # this probably only works for 1D (latitude) domains insolation_array = self.insolation_array # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] time_index = self.time['day_of_year_index'] # THIS ONLY WORKS IF self IS THE MASTER PROCESS insolation = insolation_array[..., time_index] self.diagnostics['insolation'] = Field(insolation, domain=dom)
def set_state(self, name, value): """Sets the variable ``name`` to a new state ``value``. :param string name: name of the state :param value: state variable :type value: :class:`~climlab.domain.field.Field` or *array* :raises: :exc:`ValueError` if state variable ``value`` is not having a domain. :raises: :exc:`ValueError` if shape mismatch between existing domain and new state variable. :Example: Resetting the surface temperature of an EBM to :math:`-5 ^{\circ} \\textrm{C}` on all latitues:: >>> import climlab >>> from climlab import Field >>> import numpy as np >>> # setup model >>> model = climlab.EBM(num_lat=36) >>> # create new temperature distribution >>> initial = -5 * ones(size(model.lat)) >>> model.set_state('Ts', Field(initial, domain=model.domains['Ts'])) >>> np.squeeze(model.Ts) Field([-5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5., -5.]) """ if isinstance(value, Field): # populate domains dictionary with domains from state variables self.domains.update({name: value.domain}) else: try: thisdom = self.state[name].domain domshape = thisdom.shape except: raise ValueError('State variable needs a domain.') value = np.atleast_1d(value) if value.shape == domshape: value = Field(value, domain=thisdom) else: raise ValueError( 'Shape mismatch between existing domain and new state variable.' ) # set the state dictionary self.state[name] = value for name, value in self.state.items(): #convert int dtype to float if np.issubdtype(self.state[name].dtype, np.dtype('int').type): value = self.state[name].astype(float) self.state[name] = value setattr(self, name, value)
def _compute_fixed(self): try: temp_array = self._daily_insolation_array() insolation = np.mean(temp_array, axis=1) # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] self.insolation[:] = Field(insolation, domain=dom) except: pass
def compute(self): #lapse_rate = self.param['adj_lapse_rate'] Tadj = convective_adjustment_direct(self.pnew, Tcol, self.cnew, lapserate=self.adj_lapse_rate) Tatm = Field(Tadj[..., 1:], domain=self.Tatm.domain) self.adjustment['Ts'] = Ts - self.Ts self.adjustment['Tatm'] = Tatm - self.Tatm
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_fixed(self): '''Recompute any fixed quantities after a change in parameters''' phi = np.deg2rad(self.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'] self.albedo = Field(albedo, domain=dom)
def make_column(lev=None, ps=1013, tmp=None, ts=None): state = climlab.column_state(lev=lev) num_lev = np.array(lev).size lev = state.Tatm.domain.lev lev_values = lev.points lev_dfs = np.zeros(num_lev) lev_dfs[1:] = np.diff(lev_values) lev_dfs[0] = lev_values[0] lev_dfs = lev_dfs / 2. lev.bounds = np.full(num_lev + 1, ps) lev.bounds[:-1] = lev_values - lev_dfs lev.delta = np.abs(np.diff(lev.bounds)) sfc, atm = domain.single_column(lev=lev) state['Ts'] = Field(ts, domain=sfc) state['Tatm'] = Field(tmp, domain=atm) return state
def _compute_fixed(self): phi = np.deg2rad(self.lat) # Why is there a silent fail here? Should get rid of this. try: insolation = self.S0 / 4 * (1. + self.s2 * P2(np.sin(phi))) dom = self.domains['default'] try: insolation = to_latlon(insolation, domain=dom) self.insolation[:] = insolation except: self.insolation[:] = Field(insolation, domain=dom) # make sure that the diagnostic has the correct field dimensions. #self.insolation = Field(insolation, domain=dom) self.insolation[:] = Field(insolation, domain=dom) self.coszen[:] = self._coszen_from_insolation() # Silent fail only for attribute error: _s2 is not an attribute of self # but s2 parameter is being stored in self._s2 except AttributeError: pass
def init_interface(field): '''Return a Field object defined at the vertical interfaces of the input Field object.''' interface_shape = np.array(field.shape) interface_shape[-1] += 1 interfaces = np.tile(False, len(interface_shape)) interfaces[-1] = True interface_zero = Field(np.zeros(interface_shape), domain=field.domain, interfaces=interfaces) return interface_zero
def _compute_flux(self): # this ensure same dimensions as Ts # (and use only the lowest model level) Ta = Field(self.Tatm[..., -1, np.newaxis], domain=self.Ts.domain) Ts = self.Ts DeltaT = Ts - Ta rho = self._air_density(Ta) # flux from bulk formula self._flux = self.resistance * const.cp * rho * self.Cd * self.U * DeltaT self.SHF = self._flux
def _compute_fixed(self): #lat = self.domains['default'].axes['lat'].points lat = self.lat phi = np.deg2rad(lat) try: insolation = self.S0 / 4 * (1. + self.s2 * P2(np.sin(phi))) # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] self.diagnostics['insolation'] = Field(insolation, domain=dom) except: pass
def _compute_fixed(self): phi = np.deg2rad(self.lat) # Why is there a silent fail here? Should get rid of this. try: insolation = self.S0 / 4 * (1. + self.s2 * P2(np.sin(phi))) # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] #self.insolation = Field(insolation, domain=dom) self.insolation[:] = Field(insolation, domain=dom) except: pass
def surface_state(num_lat=90, water_depth=10., T0=12., T2=-40.): '''Set up a state variable dictionary for a zonal-mean surface model (e.g. basic EBM). There is a single state variable `Ts`, the temperature of the surface mixed layer. ''' sfc = domain.zonal_mean_surface(num_lat=num_lat, water_depth=water_depth) sinphi = np.sin(np.deg2rad(sfc.axes['lat'].points)) initial = T0 + T2 * legendre.P2(sinphi) Ts = Field(initial, domain=sfc) state = AttrDict() state['Ts'] = Ts return state
def __init__( self, num_lev=30, num_lat=1, lev=None, lat=None, water_depth=1.0, albedo_sfc=0.299, timestep=1. * const.seconds_per_day, Q=341.3, # absorption coefficient in m**2 / kg abs_coeff=1.229E-4, **kwargs): if lat is not None: num_lat = np.array(lat).size if lev is not None: num_lev = np.array(lev).size # Check to see if an initial state is already provided # If not, make one if 'state' not in kwargs: state = self.initial_state(num_lev, num_lat, lev, lat, water_depth) kwargs.update({'state': state}) super(GreyRadiationModel, self).__init__(timestep=timestep, **kwargs) self.param['water_depth'] = water_depth self.param['albedo_sfc'] = albedo_sfc self.param['Q'] = Q self.param['abs_coeff'] = abs_coeff sfc = self.Ts.domain atm = self.Tatm.domain # create sub-models for longwave and shortwave radiation dp = self.Tatm.domain.lev.delta absorbLW = compute_layer_absorptivity(self.param['abs_coeff'], dp) absorbLW = Field(np.tile(absorbLW, sfc.shape), domain=atm) absorbSW = np.zeros_like(absorbLW) longwave = Radiation(state=self.state, absorptivity=absorbLW, albedo_sfc=0) shortwave = RadiationSW(state=self.state, absorptivity=absorbSW, albedo_sfc=self.param['albedo_sfc']) # sub-model for insolation ... here we just set constant Q thisQ = self.param['Q'] * np.ones_like(self.Ts) Q = FixedInsolation(S0=thisQ, domain=sfc, **self.param) # surface sub-model surface = SurfaceRadiation(state=self.state, **self.param) self.add_subprocess('LW', longwave) self.add_subprocess('SW', shortwave) self.add_subprocess('insolation', Q) self.add_subprocess('surface', surface)
def _compute(self): #lapse_rate = self.param['adj_lapse_rate'] if self.adj_lapse_rate is None: self.adjustment = self.state * 0. else: # For now, let's assume that the vertical axis is the last axis unstable_Tatm = self.Tatm if 'Ts' in self.state: unstable_Ts = np.atleast_1d(self.Ts) #Tcol = np.concatenate((unstable_Ts, unstable_Tatm),axis=-1) Tcol = np.concatenate((unstable_Tatm, unstable_Ts), axis=-1) else: Tcol = unstable_Tatm # convective adjustment routine expect reversered vertical axis pflip = self.pnew[..., ::-1] Tflip = Tcol[..., ::-1] cflip = self.cnew[..., ::-1] Tadj_flip = convective_adjustment_direct( pflip, Tflip, cflip, lapserate=self.adj_lapse_rate) Tadj = Tadj_flip[..., ::-1] if 'Ts' in self.state: Ts = Field(Tadj[..., -1], domain=self.Ts.domain) Tatm = Field(Tadj[..., :-1], domain=self.Tatm.domain) self.adjustment['Ts'] = Ts - self.Ts else: Tatm = Field(Tadj, domain=self.Tatm.domain) self.adjustment['Tatm'] = Tatm - self.Tatm # # express the adjustment (already accounting for the finite time step) # # as a tendency per unit time, so that it can be applied along with explicit # tendencies = {} # for name, adj in self.adjustment.iteritems(): # tendencies[name] = adj / self.param['timestep'] # return tendencies # go back to just returning the adjustment, independent of timestep # because the parent process might have set a different timestep! return self.adjustment
def _compute_fixed(self): try: temp_array = self._daily_insolation_array() insolation = np.mean(temp_array, axis=1) # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] try: insolation = to_latlon(insolation, domain=dom) self.insolation[:] = insolation except: self.insolation[:] = Field(insolation, domain=dom) self.coszen[:] = self._coszen_from_insolation() # Silent fail only for attribute error: _orb is not an attribute of self # but orb parameter is being stored in self._orb except AttributeError: pass
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)
def _get_current_insolation(self): insolation_array = self.insolation_array # make sure that the diagnostic has the correct field dimensions. dom = self.domains['default'] time_index = self.time[ 'day_of_year_index'] # THIS ONLY WORKS IF self IS THE MASTER PROCESS insolation = insolation_array[..., time_index] if 'lon' in dom.axes: # insolation is latitude-only, need to broadcast across longitude # assumption is axes are ordered (lat, lon, depth) # NOTE this is a clunky hack and all this will go away # when we use xarray structures for these internals insolation = np.tile(insolation[..., np.newaxis], dom.axes['lon'].num_points) self.insolation[:] = Field(insolation, domain=dom) self.coszen[:] = self._coszen_from_insolation()
def __init__( self, num_lat=90, S0=const.S0, A=210., B=2., D=0.555, # in W / m^2 / degC, same as B water_depth=10.0, Tf=-10., a0=0.3, a2=0.078, ai=0.62, timestep=const.seconds_per_year / 90., T_init_0=12., T_init_P2=-40., **kwargs): super(EBM, self).__init__(timestep=timestep, **kwargs) if not self.domains and not self.state: # no state vars or domains yet sfc = domain.zonal_mean_surface(num_lat=num_lat, water_depth=water_depth) lat = sfc.axes['lat'].points initial = T_init_0 + T_init_P2 * legendre.P2( np.sin(np.deg2rad(lat))) self.set_state('Ts', Field(initial, domain=sfc)) self.param['S0'] = S0 self.param['A'] = A self.param['B'] = B self.param['D'] = D self.param['Tf'] = Tf self.param['water_depth'] = water_depth self.param['a0'] = a0 self.param['a2'] = a2 self.param['ai'] = ai # create sub-models self.add_subprocess('LW', AplusBT(state=self.state, **self.param)) self.add_subprocess('insolation', P2Insolation(domains=sfc, **self.param)) self.add_subprocess( 'albedo', albedo.StepFunctionAlbedo(state=self.state, **self.param)) # diffusivity in units of 1/s K = self.param['D'] / self.domains['Ts'].heat_capacity self.add_subprocess( 'diffusion', MeridionalDiffusion(state=self.state, K=K, **self.param)) self.topdown = False # call subprocess compute methods first
def set_state(self, name, value): if isinstance(value, Field): # populate domains dictionary with domains from state variables self.domains.update({name: value.domain}) else: try: thisdom = self.state[name].domain domshape = thisdom.shape except: raise ValueError('State variable needs a domain.') value = np.atleast_1d(value) if value.shape == domshape: value = Field(value, domain=thisdom) else: raise ValueError( 'Shape mismatch between existing domain and new state variable.' ) # set the state dictionary self.state[name] = value setattr(self, name, value)