def soil_temperature(g, meteo, temp_sky_eff, soil_label_prefix='other'): """Computes soil temperature Args: g: a multiscale tree graph object meteo (DataFrame): forcing meteorological variables t_sky_eff (float): [°C] effective sky temperature soil_label_prefix(str): prefix of soil nodes Returns: (double): [°C] soil temperature Notes: Heat loss into deeper soil layers is not considered. """ relative_humidity, atm_pressure, temp_air = [ float(meteo[x]) for x in ('hs', 'Pa', 'Tac') ] temp_sky_eff = utils.celsius_to_kelvin(temp_sky_eff) soil_nodes = [ g.node(vid) for vid in g.property('geometry') if g.node(vid).label.startswith(soil_label_prefix) ][0] temp_leaves = utils.celsius_to_kelvin( mean(list(g.property('Tlc').values()))) shortwave_inc = old_div(soil_nodes.Ei, (0.48 * 4.6)) # Ei not Eabs temp_soil = soil_nodes.Tsoil if 'Tsoil' in soil_nodes.properties( ) else temp_air def _SoilEnergyX(temp_soil): shortwave_abs = ( 1 - 0.25 ) * shortwave_inc # 0.25 is rough estimation of albedo of a bare soil longwave_net = e_soil * sigma * (1. * e_sky * temp_sky_eff**4 + 1. * e_leaf * temp_leaves**4 - temp_soil**4) latent_heat = old_div( -lambda_ * 0.06 * utils.vapor_pressure_deficit( temp_air, temp_soil, relative_humidity), atm_pressure) # 0.06 is gM from Bailey 2016 AFM 218-219:146-160 sensible_heat = -0.5 * Cp * utils.celsius_to_kelvin( temp_soil - temp_air) # 0.5 is gH from Bailey 2016 AFM 218-219:146-160 energy_balance = shortwave_abs + longwave_net + latent_heat + sensible_heat return energy_balance t_soil0 = utils.kelvin_to_celsius( optimize.newton_krylov(_SoilEnergyX, utils.celsius_to_kelvin(temp_soil))) soil_nodes.Tsoil = t_soil0 return t_soil0
def arrhenius_1(param_name, leaf_temperature, photo_params): """Computes the effect of temperature on the photosynthetic parameters `Tx`, `Kc`, and `Ko`. Args: param_name (str): name of the parameter to be considered, one of 'Tx', 'Kc', and 'Ko' leaf_temperature (float): [°C] leaf temperature photo_params (dict): default values at 25 °C of Farquhar's model (cf. :func:`par_photo_default`) Returns: (float): the value of the given :arg:`param_name` at the given :arg:`leaf_temperature` References: Bernacchi et al. (2003) In vivo temperature response functions of parameters required to model RuBP-limited photosynthesis. Plant, Cell and Environment 26, 1419 – 1430 """ param_list = { 'Tx': ('Tx25', 'RespT_Tx'), 'Kc': ('Kc25', 'RespT_Kc'), 'Ko': ('Ko25', 'RespT_Ko') } temp_k = utils.celsius_to_kelvin(leaf_temperature) param_key = param_list[param_name][1] shape_param, activation_energy = [ photo_params[param_key][x] for x in ('c', 'deltaHa') ] param_value = exp(shape_param - (activation_energy / (R * temp_k))) return param_value
def arrhenius_2(param_name, leaf_temperature, photo_params): """Computes the effect of temperature on the photosynthetic parameters `Vcmax`, `Jmax`, `TPUmax`, and `Rdmax`. Args: param_name (str): name of the parameter to be considered, one of `Vcmax`, `Jmax`, `TPUmax`, and `Rdmax` leaf_temperature (float): [°C] leaf temperature photo_params (dict): default values at 25 °C of Farquhar's model (cf. :func:`par_photo_default`) Returns: (float): the value of the given :arg:`param_name` at the given :arg:`leaf_temperature` References: Bernacchi et al. (2003) In vivo temperature response functions of parameters required to model RuBP-limited photosynthesis. Plant, Cell and Environment 26, 1419 – 1430 """ enthalpy_activation = photo_params['ds'] enthalpy_deactivation = photo_params['dHd'] param_list = { 'Vcmax': ('Vcm25', 'RespT_Vcm'), 'Jmax': ('Jm25', 'RespT_Jm'), 'TPUmax': ('TPU25', 'RespT_TPU'), 'Rdmax': ('Rd', 'RespT_Rd')} temp_k = utils.celsius_to_kelvin(leaf_temperature) param_key = param_list[param_name][1] param_value_at_25 = photo_params[param_list[param_name][0]] shape_param, activation_energy = [photo_params[param_key][x] for x in ('c', 'deltaHa')] param_value = param_value_at_25 * (exp(shape_param - (activation_energy / (R * temp_k)))) / ( 1. + exp((enthalpy_activation * temp_k - enthalpy_deactivation) / (R * temp_k))) return param_value
def _SoilEnergyX(temp_soil): shortwave_abs = ( 1 - 0.25 ) * shortwave_inc # 0.25 is rough estimation of albedo of a bare soil longwave_net = e_soil * sigma * (1. * e_sky * temp_sky_eff**4 + 1. * e_leaf * temp_leaves**4 - temp_soil**4) latent_heat = -lambda_ * 0.06 * utils.vapor_pressure_deficit( temp_air, temp_soil, relative_humidity ) / atm_pressure # 0.06 is gM from Bailey 2016 AFM 218-219:146-160 sensible_heat = -0.5 * Cp * utils.celsius_to_kelvin( temp_soil - temp_air) # 0.5 is gH from Bailey 2016 AFM 218-219:146-160 energy_balance = shortwave_abs + longwave_net + latent_heat + sensible_heat return energy_balance
def leaf_temperature(g, meteo, t_soil, t_sky_eff, t_init=None, form_factors=None, gbh=None, ev=None, ei=None, solo=True, ff_type=True, leaf_lbl_prefix='L', max_iter=100, t_error_crit=0.01, t_step=0.5): """Computes the temperature of each individual leaf and soil elements. Args: g: a multiscale tree graph object meteo (DataFrame): forcing meteorological variables t_soil (float): [°C] soil surface temperature t_sky_eff (float): [°C] effective sky temperature t_init(float or dict): [°C] temperature used for initialisation form_factors(3-tuple of float or dict): form factors for soil, sky and leaves. if None (default) (0.5, 0.5, 0.5) is used for all leaves gbh (float or dict): [W m-2 K-1] boundary layer conductance for heat if None (default) a default model is called with length=10cm and wind_speed as found in meteo ev (float or dict): [mol m-2 s-1] evaporation flux if None (default) evaporation is set to zero for all leaves ei (float or dict): [umol m-2 s-1] photosynthetically active radition (PAR) incident on leaves if None (default) PAR is set to zero for all leaves solo (bool): if True (default), calculates energy budget for each element assuming the temperatures of surrounding leaves as constant (from previous calculation step) if False, computes simultaneously all temperatures using `sympy.solvers.nsolve` (**very costly!!!**) ff_type (bool): form factor type flag. If true fform factor for a given leaf is expected to be a single value, or a dict of ff otherwxie leaf_lbl_prefix (str): the prefix of the leaf label max_iter (int): maximum allowed iteration (used only when :arg:`solo` is True) t_error_crit (float): [°C] maximum allowed error in leaf temperature (used only when :arg:`solo` is True) t_step (float): [°C] maximum temperature step between two consecutive iterations Returns: (dict): [°C] the tempearture of individual leaves given as the dictionary keys (int): [-] the number of iterations (not None only when :arg:`solo` is True) """ leaves = get_leaves(g, leaf_lbl_prefix) it = 0 if t_init is None: t_init = meteo.Tac[0] if form_factors is None: form_factors = 0.5, 0.5, 0.5 if gbh is None: gbh = _gbH(0.1, meteo.u[0]) if ev is None: ev = 0 if ei is None: ei = 0 k_soil, k_sky, k_leaves = form_factors properties = {} for what in ('t_init', 'gbh', 'ev', 'ei', 'k_soil', 'k_sky', 'k_leaves'): val = eval(what) if isinstance(val, dict): properties[what] = val else: properties[what] = {vid: val for vid in leaves} # macro-scale climatic data temp_sky = utils.celsius_to_kelvin(t_sky_eff) temp_air = utils.celsius_to_kelvin(meteo.Tac[0]) temp_soil = utils.celsius_to_kelvin(t_soil) # initialisation t_prev = properties['t_init'] # iterative calculation of leaves temperature if solo: t_error_trace = [] it_step = t_step for it in range(max_iter): t_dict = {} for vid in leaves: shortwave_inc = properties['ei'][vid] / (0.48 * 4.6 ) # Ei not Eabs ff_sky = properties['k_sky'][vid] ff_leaves = properties['k_leaves'][vid] ff_soil = properties['k_soil'][vid] gb_h = properties['gbh'][vid] evap = properties['ev'][vid] t_leaf = t_prev[vid] if not ff_type: longwave_grain_from_leaves = -sigma * sum([ ff_leaves[ivid] * (utils.celsius_to_kelvin(t_prev[ivid]))**4 for ivid in ff_leaves ]) else: longwave_grain_from_leaves = ff_leaves * sigma * ( utils.celsius_to_kelvin(t_leaf))**4 def _VineEnergyX(t_leaf): shortwave_abs = a_glob * shortwave_inc longwave_net = e_leaf * (ff_sky * e_sky * sigma * temp_sky ** 4 + e_leaf * longwave_grain_from_leaves + ff_soil * e_soil * sigma * temp_soil ** 4) \ - 2 * e_leaf * sigma * t_leaf ** 4 latent_heat_loss = -lambda_ * evap sensible_heat_net = -gb_h * (t_leaf - temp_air) energy_balance = shortwave_abs + longwave_net + latent_heat_loss + sensible_heat_net return energy_balance t_leaf0 = utils.kelvin_to_celsius( optimize.newton_krylov(_VineEnergyX, utils.celsius_to_kelvin(t_leaf))) t_dict[vid] = t_leaf0 t_new = t_dict # evaluation of leaf temperature conversion criterion error_dict = {vtx: abs(t_prev[vtx] - t_new[vtx]) for vtx in leaves} t_error = max(error_dict.values()) t_error_trace.append(t_error) if t_error < t_error_crit: break else: try: if abs(t_error_trace[-1] - t_error_trace[-2]) < t_error_crit: it_step = max(0.01, it_step / 2.) except IndexError: pass t_next = {} for vtx_id in t_new.keys(): tx = t_prev[vtx_id] + it_step * (t_new[vtx_id] - t_prev[vtx_id]) t_next[vtx_id] = tx t_prev = t_next # matrix iterative calculation of leaves temperature ('not solo' case) else: it = 1 t_lst = [] t_dict = {vid: Symbol('t%d' % vid) for vid in leaves} eq_lst = [] t_leaf_lst = [] for vid in leaves: shortwave_inc = properties['ei'][vid] / (0.48 * 4.6) # Ei not Eabs ff_sky = properties['k_sky'][vid] ff_leaves = properties['k_leaves'][vid] ff_soil = properties['k_soil'][vid] gb_h = properties['gbh'][vid] evap = properties['ev'][vid] t_leaf = t_prev[vid] t_leaf_lst.append(t_leaf) t_lst.append(t_dict[vid]) eq_aux = 0. for ivid in ff_leaves: if not g.node(ivid).label.startswith('soil'): eq_aux += -ff_leaves[ivid] * ((t_dict[ivid])**4) eq = (a_glob * shortwave_inc + e_leaf * sigma * (ff_sky * e_sky * (temp_sky**4) + e_leaf * eq_aux + ff_soil * e_soil * (temp_sky**4) - 2 * (t_dict[vid])**4) - lambda_ * evap - gb_h * Cp * (t_dict[vid] - temp_air)) eq_lst.append(eq) tt = time.time() t_leaf0_lst = nsolve(eq_lst, t_lst, t_leaf_lst, verify=False) - 273.15 print("---%s seconds ---" % (time.time() - tt)) t_new = {} for ivid, vid in enumerate(leaves): t_new[vid] = float(t_leaf0_lst[ivid]) ivid += 1 return t_new, it