def calc_r_r(p, ea, t_k): """ Calculates the resistance to radiative transfer Parameters ---------- p : float or array Surface atmospheric pressure (mb) ea : float or array Vapour pressure (mb). t_k : float or array surface temperature (K) Returns ------- r_r : float or array Resistance to radiative transfer (s m-1) References ---------- .. [Monteith2008] Monteith, JL, Unsworth MH, Principles of Environmental Physics, 2008. ISBN 978-0-12-505103-5 """ rho = met.calc_rho(p, ea, t_k) cp = met.calc_c_p(p, ea) r_r = rho * cp / (4. * rad.SB * t_k**3) # Eq. 4.7 of [Monteith2008]_ return r_r
def ESVEP(Tr_K, vza, T_A_K, u, ea, p, Sn_C, Sn_S, L_dn, LAI, emis_C, emis_S, z_0M, d_0, z_u, z_T, z0_soil=0.001, x_LAD=1, f_c=1.0, f_g=1.0, w_C=1.0, calcG_params=[[1], 0.35], UseL=False): '''ESVEP Calculates soil and vegetation energy fluxes using Soil and Vegetation Energy Patrtitioning `ESVEP` model and a single observation of composite radiometric temperature. Parameters ---------- Tr_K : float Radiometric composite temperature (Kelvin). vza : float View Zenith Angle (degrees). T_A_K : float Air temperature (Kelvin). u : float Wind speed above the canopy (m s-1). ea : float Water vapour pressure above the canopy (mb). p : float Atmospheric pressure (mb), use 1013 mb by default. Sn_C : float Canopy net shortwave radiation (W m-2). Sn_S : float Soil net shortwave radiation (W m-2). L_dn : float Downwelling longwave radiation (W m-2). LAI : float Effective Leaf Area Index (m2 m-2). emis_C : float Leaf emissivity. emis_S : flaot Soil emissivity. z_0M : float Aerodynamic surface roughness length for momentum transfer (m). d_0 : float Zero-plane displacement height (m). z_u : float Height of measurement of windspeed (m). z_T : float Height of measurement of air temperature (m). z0_soil : float, optional bare soil aerodynamic roughness length (m). x_LAD : float, optional Campbell 1990 leaf inclination distribution function chi parameter. f_c : float, optional Fractional cover. f_g : float, optional Fraction of vegetation that is green. w_C : float, optional Canopy width to height ratio. calcG_params : list[list,float or array], optional Method to calculate soil heat flux,parameters. * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35. * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G. * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with G_param list of parameters (see :func:`~TSEB.calc_G_time_diff`). UseL : float or None, optional If included, its value will be used to force the Moning-Obukhov stability length. Returns ------- flag : int Quality flag, see Appendix for description. T_S : float Soil temperature (Kelvin). T_C : float Canopy temperature (Kelvin). T_sd: float End-member temperature of dry soil (Kelvin) T_vd: float End-member temperature of dry vegetation (Kelvin) T_sw: float End-member temperature of saturated soil (Kelvin) T_vw: float End-member temperature of well-watered vegetation (Kelvin) T_star: float Critical surface temperature (Kelvin) L_nS : float Soil net longwave radiation (W m-2) L_nC : float Canopy net longwave radiation (W m-2) LE_C : float Canopy latent heat flux (W m-2). H_C : float Canopy sensible heat flux (W m-2). LE_S : float Soil latent heat flux (W m-2). H_S : float Soil sensible heat flux (W m-2). G : float Soil heat flux (W m-2). r_vw: float Canopy resistance to heat transport of well-watered vegetation (s m-1) r_vd: float Canopy resistance to heat transport of vegetation with zero soil water avaiability (s m-1) r_av: float Aerodynamic resistance to heat transport of the vegetation (s m-1) r_as: float Aerodynamic resistance to heat transport of the soil (s m-1) L : float Monin-Obuhkov length (m). n_iterations : int number of iterations until convergence of L. References ---------- .. [Tang2017] Tang, R., and Z. L. Li. An End-Member-Based Two-Source Approach for Estimating Land Surface Evapotranspiration From Remote Sensing Data. IEEE Transactions on Geoscience and Remote Sensing 55, no. 10 (October 2017): 5818–32. https://doi.org/10.1109/TGRS.2017.2715361. ''' # Convert input float scalars to arrays and check parameters size Tr_K = np.asarray(Tr_K) (vza, T_A_K, u, ea, p, Sn_C, Sn_S, L_dn, LAI, emis_C, emis_S, z_0M, d_0, z_u, z_T, z0_soil, x_LAD, f_c, f_g, w_C, calcG_array) = map(tseb._check_default_parameter_size, [ vza, T_A_K, u, ea, p, Sn_C, Sn_S, L_dn, LAI, emis_C, emis_S, z_0M, d_0, z_u, z_T, z0_soil, x_LAD, f_c, f_g, w_C, calcG_params[1] ], [Tr_K] * 21) # Create the output variables [ flag, T_S, T_C, T_sd, T_vd, T_sw, T_vw, T_star, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, r_vw, r_vd, r_av, r_as, iterations ] = [np.zeros(Tr_K.shape) + np.NaN for i in range(20)] # iteration of the Monin-Obukhov length if isinstance(UseL, bool): # Initially assume stable atmospheric conditions and set variables for L = np.asarray(np.zeros(T_S.shape) + np.inf) max_iterations = ITERATIONS else: # We force Monin-Obukhov lenght to the provided array/value L = np.asarray(np.ones(T_S.shape) * UseL) max_iterations = 1 # No iteration # Calculate the general parameters rho = met.calc_rho(p, ea, T_A_K) # Air density c_p = met.calc_c_p(p, ea) # Heat capacity of air z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport z_0H_soil = res.calc_z_0H(z0_soil, kB=kB) # Roughness length for heat transport s = met.calc_delta_vapor_pressure( T_A_K) * 10 # slope of the saturation pressure curve (mb C-1) lbd = met.calc_lambda(T_A_K) # latent heat of vaporisation (MJ./kg) gama = met.calc_psicr(p, lbd) # psychrometric constant (mb C-1) vpd = met.calc_vapor_pressure(T_A_K) - ea # vapor pressure deficit (mb) # Calculate LAI dependent parameters for dataset where LAI > 0 omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True) F = np.asarray(LAI / f_c) # Real LAI # Fraction of vegetation observed by the sensor f_theta = tseb.calc_F_theta_campbell(vza, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD) # Initially assume stable atmospheric conditions and set variables for # iteration of the Monin-Obukhov length u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M) u_friction = np.asarray(np.maximum(u_friction_min, u_friction)) u_friction_s = MO.calc_u_star(u, z_u, L, np.zeros(d_0.shape), z0_soil) u_friction_s = np.asarray(np.maximum(u_friction_min, u_friction_s)) L_old = np.ones(Tr_K.shape) L_diff = np.asarray(np.ones(Tr_K.shape) * float('inf')) # First assume that canopy temperature equals the minumum of Air or # radiometric T T_C = np.asarray(np.minimum(Tr_K, T_A_K)) flag, T_S = tseb.calc_T_S(Tr_K, T_C, f_theta) # Loop for estimating stability. # Stops when difference in consecutives L is below a given threshold for n_iterations in range(max_iterations): i = flag != 255 if np.all(L_diff[i] < L_thres): if L_diff[i].size == 0: print("Finished iterations with no valid solution") else: print("Finished interations with a max. L diff: " + str(np.max(L_diff[i]))) break i = np.logical_and(L_diff >= L_thres, flag != 255) print("Iteration " + str(n_iterations) + ", max. L diff: " + str(np.max(L_diff[i]))) iterations[i] = n_iterations # Calculate net longwave radiation with current values of T_C and T_S Ln_C[i], Ln_S[i] = rad.calc_L_n_Kustas(T_C[i], T_S[i], L_dn[i], LAI[i], emis_C[i], emis_S[i]) Rn_C = Sn_C + Ln_C Rn_S = Sn_S + Ln_S # Compute Soil Heat Flux G[i] = tseb.calc_G([calcG_params[0], calcG_array], Rn_S, i) # Calculate aerodynamic resistances r_vw[i] = 100.0 / LAI[i] r_vd[i] = np.zeros(LAI[i].shape) + 2000.0 r_av_params = { "z_T": z_T[i], "u_friction": u_friction[i], "L": L[i], "d_0": d_0[i], "z_0H": z_0H[i] } r_av[i] = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": r_av_params})[0] r_as_params = { "z_T": z_T[i], "u_friction": u_friction_s[i], "L": L[i], "d_0": np.zeros(d_0[i].shape), "z_0H": z_0H_soil[i] } r_as[i] = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": r_as_params})[0] # Estimate the surface temperatures of the end-members # Eq 8a T_sd[i] = r_as[i] * (Rn_S[i] - G[i]) / (rho[i] * c_p[i]) + T_A_K[i] # Eq 8b T_vd[i] = r_av[i] * Rn_C[i] / (rho[i] * c_p[i]) *\ gama[i] * (1 + r_vd[i] / r_av[i]) / (s[i] + gama[i] * (1 + r_vd[i] / r_av[i])) -\ vpd[i] / (s[i] + gama[i] * (1 + r_vd[i] / r_av[i])) + T_A_K[i] # Eq 8c T_sw[i] = r_as[i] * (Rn_S[i] - G[i]) / (rho[i] + c_p[i]) *\ gama[i] / (s[i] + gama[i]) - vpd[i] / (s[i] + gama[i]) + T_A_K[i] # Eq 8d T_vw[i] = r_av[i] * Rn_C[i] / (rho[i] * c_p[i]) *\ gama[i] * (1 + r_vw[i] / r_av[i]) / (s[i] + gama[i] * (1 + r_vw[i] / r_av[i])) -\ vpd[i] / (s[i] + gama[i] * (1 + r_vw[i] / r_av[i])) + T_A_K[i] # Estimate critical surface temperature - eq 10 T_star[i] = (T_sd[i]**4 * (1 - f_theta[i]) + T_vw[i]**4 * f_theta[i])**0.25 # Estimate latent heat fluxes when water in the top-soil is avaiable for evaporation j = np.logical_and(Tr_K <= T_star, i) # Eq 12a T_C[j] = T_vw[j] # Eq 12b T_S[j] = ((Tr_K[j]**4 - f_theta[j] * T_C[j]**4) / (1 - f_theta[j]))**0.25 # Eq 13a LE_C[j] = (s[j] * Rn_C[j] + rho[j] * c_p[j] * vpd[j] / r_av[j]) /\ (s[j] + gama[j] * (1 + r_vw[j] / r_av[j])) # Eq 13b LE_S[j] = (T_sd[j] - T_S[j]) / (T_sd[j] - T_sw[j]) *\ ((s[j] * (Rn_S[j] - G[j]) + rho[j] * c_p[j] * vpd[j] / r_as[j]) / (s[j] + gama[j])) # Estimate latent heat fluxes when no water in the top-soil is avaiable for evaporation j = np.logical_and(Tr_K > T_star, i) # Eq 14a T_S[j] = T_sd[j] # Eq 14b T_C[j] = ((Tr_K[j]**4 - (1 - f_theta[j]) * T_S[j]**4) / f_theta[j])**0.25 # Eq 15a LE_C[j] = (T_vd[j] - T_C[j]) / (T_vd[j] - T_vw[j]) *\ ((s[j] * Rn_C[j] + rho[j] * c_p[j] * vpd [j] / r_av[j]) / (s[j] + gama[j] * (1 + r_vw[j] / r_av[j]))) # Eq 15b LE_S[j] = 0 # Estimate sensible heat fluxes as residuals H_C[i] = Rn_C[i] - LE_C[i] H_S[i] = Rn_S[i] - G[i] - LE_S[i] # Calculate total fluxes H = np.asarray(H_C + H_S) LE = np.asarray(LE_C + LE_S) # Now L can be recalculated and the difference between iterations # derived if isinstance(UseL, bool): L[i] = MO.calc_L(u_friction[i], T_A_K[i], rho[i], c_p[i], H[i], LE[i]) L_diff = np.asarray(np.fabs(L - L_old) / np.fabs(L_old)) L_diff[np.isnan(L_diff)] = float('inf') L_old = np.array(L) L_old[L_old == 0] = 1e-36 # Calculate again the friction velocity with the new stability # correctios u_friction[i] = MO.calc_u_star(u[i], z_u[i], L[i], d_0[i], z_0M[i]) u_friction = np.asarray(np.maximum(u_friction_min, u_friction)) u_friction_s[i] = MO.calc_u_star(u[i], z_u[i], L[i], np.zeros(d_0[i].shape), np.zeros(z_0M[i].shape) + 0.005) u_friction_s = np.asarray(np.maximum(u_friction_min, u_friction_s)) return [ flag, T_S, T_C, T_sd, T_vd, T_sw, T_vw, T_star, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, r_vw, r_vd, r_av, r_as, L, n_iterations ]
def calc_stomatal_resistance_TSEB( LE_C, LE, R_A, R_x, e_a, T_A, T_C, F, p=1013.0, leaf_type=1, f_g=1, f_dry=1): ''' TSEB Stomatal conductace Estimates the effective Stomatal conductace by inverting the resistance-based canopy latent heat flux from a Two source perspective Parameters ---------- LE_C : float Canopy latent heat flux (W m-2). LE : float Surface (bulk) latent heat flux (W m-2). R_A : float Aerodynamic resistance to heat transport (s m-1). R_x : float Bulk aerodynamic resistance to heat transport at the canopy boundary layer (s m-1). e_a : float Water vapour pressure at the reference height (mb). T_A : float Air temperature at the reference height (K). T_C : float Canopy (leaf) temperature (K). F : float local Leaf Area Index. p : float, optional Atmospheric pressure (mb) use 1013.0 as default. leaf_type : int, optional type of leaf regarding stomata distribution. 1=HYPOSTOMATOUS stomata in the lower surface of the leaf (default). 2=AMPHISTOMATOUS, stomata in both surfaces of the leaf. f_g : float, optional Fraction of green leaves. f_dry : float, optional Fraction of dry (non-wet) leaves. Returns ------- G_s : float effective leaf stomata conductance (m s-1). References ---------- .. [Anderson2000] M.C. Anderson, J.M. Norman, T.P. Meyers, G.R. Diak, An analytical model for estimating canopy transpiration and carbon assimilation fluxes based on canopy light-use efficiency, Agricultural and Forest Meteorology, Volume 101, Issue 4, 12 April 2000, Pages 265-289, ISSN 0168-1923, http://dx.doi.org/10.1016/S0168-1923(99)00170-7.''' # Convert input scalars to numpy arrays LE_C, LE, R_A, R_x, e_a, T_A, T_C, F, p, leaf_type, f_g, f_dry = map( np.asarray, (LE_C, LE, R_A, R_x, e_a, T_A, T_C, F, p, leaf_type, f_g, f_dry)) # Invert the bulk SW to obtain eb (vapor pressure at the canopy interface) rho = met.calc_rho(p, e_a, T_A) Cp = met.calc_c_p(p, e_a) Lambda = met.calc_lambda(T_A) psicr = met.calc_psicr(Cp, p, Lambda) e_ac = e_a + LE * R_A * psicr / (rho * Cp) # Calculate the saturation vapour pressure in the leaf in mb e_star = met.calc_vapor_pressure(T_C) # Calculate the boundary layer canopy resisitance to water vapour (Anderson et al. 2000) # Invert the SW LE_S equation to calculate the bulk stomatal resistance R_c = np.asarray((rho * Cp * (e_star - e_ac) / (LE_C * psicr)) - R_x) K_c = np.asarray(f_dry * f_g * leaf_type) # Get the mean stomatal resistance (here LAI comes in as stomatal resistances # are in parallel: 1/Rc=sum(1/R_st)=LAI/Rst r_st = R_c * K_c * F return np.asarray(r_st)
def pet_fao56(T_A_K, u, ea, p, Sdn, z_u, z_T, f_cd=1, reference=SHORT_REFERENCE): '''Calcultaes the latent heat flux for well irrigated and cold pixel using FAO56 potential ET from a short (grass) crop Parameters ---------- T_A_K : float or array Air temperature (Kelvin). u : float or array Wind speed above the canopy (m s-1). ea : float or array Water vapour pressure above the canopy (mb). p : float or array Atmospheric pressure (mb), use 1013 mb by default. Sdn : float or array Solar irradiance (W m-2). z_u : float or array Height of measurement of windspeed (m). z_T : float or array Height of measurement of air temperature (m). f_cd : float or array cloudiness factor, default = 1 reference : bool If true, reference ET is for a tall canopy (i.e. alfalfa) ''' # Atmospheric constants delta = 10. * met.calc_delta_vapor_pressure( T_A_K) # slope of saturation water vapour pressure in mb K-1 lambda_ = met.calc_lambda(T_A_K) # latent heat of vaporization MJ kg-1 c_p = met.calc_c_p(p, ea) # Heat capacity of air psicr = met.calc_psicr(c_p, p, lambda_) # Psicrometric constant (mb K-1) es = met.calc_vapor_pressure( T_A_K) # saturation water vapour pressure in mb rho = met.calc_rho(p, ea, T_A_K) # Net shortwave radiation # Sdn = Sdn * 3600 / 1e6 # W m-2 to MJ m-2 h-1 albedo = 0.23 Sn = Sdn * (1.0 - albedo) # Net longwave radiation Ln = calc_Ln(T_A_K, ea, f_cd=f_cd) # Net radiation Rn = Sn + Ln if reference == TALL_REFERENCE: G_ratio = 0.04 h_c = 0.5 else: G_ratio = 0.1 h_c = 0.12 R_c = 70.0 # Soil heat flux G = G_ratio * Rn # Windspeed at 2m height z_0M = h_c * 0.123 d = h_c * 2. / 3. u_2 = wind_profile(u, z_u, z_0M, d, 2.0) R_a = 208. / u_2 LE = (delta * (Rn - G) + rho * c_p * (es - ea) / R_a) / (delta + psicr * (1.0 + R_c / R_a)) return LE
def METRIC(Tr_K, T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, cold_pixel, hot_pixel, LE_cold, LE_hot=0, use_METRIC_resistance=True, calcG_params=[[1], 0.35], UseL=False, UseDEM=False): '''Calulates bulk fluxes using METRIC model Parameters ---------- Tr_K : float Radiometric composite temperature (Kelvin). T_A_K : float Air temperature (Kelvin). u : float Wind speed above the canopy (m s-1). ea : float Water vapour pressure above the canopy (mb). p : float Atmospheric pressure (mb), use 1013 mb by default. S_n : float Solar irradiance (W m-2). L_dn : float Downwelling longwave radiation (W m-2) emis : float Surface emissivity. z_0M : float Aerodynamic surface roughness length for momentum transfer (m). d_0 : float Zero-plane displacement height (m). z_u : float Height of measurement of windspeed (m). z_T : float Height of measurement of air temperature (m). cold_pixel : tuple pixel coordinates (row, col) for the cold endmember hot_pixel : tuple pixel coordinates (row, col) for the hot endmember calcG_params : list[list,float or array], optional Method to calculate soil heat flux,parameters. * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35. * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G. * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with G_param list of parameters (see :func:`~TSEB.calc_G_time_diff`). UseL : Optional[float] If included, its value will be used to force the Moning-Obukhov stability length. Returns ------- flag : int Quality flag, see Appendix for description. Ln : float Net longwave radiation (W m-2) LE : float Latent heat flux (W m-2). H : float Sensible heat flux (W m-2). G : float Soil heat flux (W m-2). R_A : float Aerodynamic resistance to heat transport (s m-1). u_friction : float Friction velocity (m s-1). L : float Monin-Obuhkov length (m). n_iterations : int number of iterations until convergence of L. References ---------- ''' # Convert input scalars to numpy arrays and check parameters size Tr_K = np.asarray(Tr_K) (T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, LE_cold, LE_hot, calcG_array) = map(tseb._check_default_parameter_size, [ T_A_K, u, ea, p, Sn, L_dn, emis, z_0M, d_0, z_u, z_T, LE_cold, LE_hot, calcG_params[1] ], [Tr_K] * 14) # Create the output variables [Ln, LE, H, G, R_A, iterations] = [np.zeros(Tr_K.shape) + np.NaN for i in range(6)] flag = np.zeros(Tr_K.shape, dtype=np.byte) # iteration of the Monin-Obukhov length if isinstance(UseL, bool): # Initially assume stable atmospheric conditions and set variables for L = np.zeros(Tr_K.shape) + np.inf max_iterations = ITERATIONS else: # We force Monin-Obukhov lenght to the provided array/value L = np.ones(Tr_K.shape) * UseL max_iterations = 1 # No iteration if isinstance(UseDEM, bool): Tr_datum = np.asarray(Tr_K) Ta_datum = np.asarray(T_A_K) else: gamma_w = met.calc_lapse_rate_moist(T_A_K, ea, p) Tr_datum = Tr_K + gamma_w * UseDEM Ta_datum = T_A_K + gamma_w * UseDEM # Calculate the general parameters rho = met.calc_rho(p, ea, T_A_K) # Air density c_p = met.calc_c_p(p, ea) # Heat capacity of air rho_datum = met.calc_rho(p, ea, Ta_datum) # Air density # Calc initial Monin Obukhov variables u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M) u_friction = np.maximum(u_friction_min, u_friction) z_0H = res.calc_z_0H(z_0M, kB=kB) # Calculate Net radiation Ln = emis * L_dn - emis * met.calc_stephan_boltzmann(Tr_K) Rn = np.asarray(Sn + Ln) # Compute Soil Heat Flux i = np.ones(Rn.shape, dtype=bool) G[i] = tseb.calc_G([calcG_params[0], calcG_array], Rn, i) # Get cold and hot variables Rn_endmembers = np.array([Rn[cold_pixel], Rn[hot_pixel]]) G_endmembers = np.array([G[cold_pixel], G[hot_pixel]]) LE_endmembers = np.array([LE_cold[cold_pixel], LE_hot[hot_pixel]]) u_friction_endmembers = np.array( [u_friction[cold_pixel], u_friction[hot_pixel]]) u_endmembers = np.array([u[cold_pixel], u[hot_pixel]]) z_u_endmembers = np.array([z_u[cold_pixel], z_u[hot_pixel]]) Ta_datum_endmembers = np.array([Ta_datum[cold_pixel], Ta_datum[hot_pixel]]) z_T_endmembers = np.array([z_T[cold_pixel], z_T[hot_pixel]]) rho_datum_endmembers = np.array( [rho_datum[cold_pixel], rho_datum[hot_pixel]]) c_p_endmembers = np.array([c_p[cold_pixel], c_p[hot_pixel]]) d_0_endmembers = np.array([d_0[cold_pixel], d_0[hot_pixel]]) z_0M_endmembers = np.array([z_0M[cold_pixel], z_0M[hot_pixel]]) z_0H_endmembers = np.array([z_0H[cold_pixel], z_0H[hot_pixel]]) H_endmembers = calc_H_residual(Rn_endmembers, G_endmembers, LE=LE_endmembers) # ============================================================================== # HOT and COLD PIXEL ITERATIONS FOR MONIN-OBUKHOV LENGTH TO CONVERGE # ============================================================================== # Initially assume stable atmospheric conditions and set variables for L_old = np.ones(2) L_diff = np.ones(2) * float('inf') for iteration in range(max_iterations): if np.all(L_diff < L_thres): break if isinstance(UseL, bool): # Recaulculate L and the difference between iterations L_endmembers = MO.calc_L(u_friction_endmembers, Ta_datum_endmembers, rho_datum_endmembers, c_p_endmembers, H_endmembers, LE_endmembers) L_diff = np.fabs(L_endmembers - L_old) / np.fabs(L_old) L_old = np.array(L_endmembers) L_old[np.fabs(L_old) == 0] = 1e-36 u_friction_endmembers = MO.calc_u_star(u_endmembers, z_u_endmembers, L_endmembers, d_0_endmembers, z_0M_endmembers) u_friction_endmembers = np.maximum(u_friction_min, u_friction_endmembers) # Hot and Cold aerodynamic resistances if use_METRIC_resistance is True: R_A_params = { "z_T": np.array([2.0, 2.0]), "u_friction": u_friction_endmembers, "L": L_endmembers, "d_0": np.array([0.0, 0.0]), "z_0H": np.array([0.1, 0.1]) } else: R_A_params = { "z_T": z_T_endmembers, "u_friction": u_friction_endmembers, "L": L_endmembers, "d_0": d_0_endmembers, "z_0H": z_0H_endmembers } R_A_endmembers, _, _ = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": R_A_params}) # Calculate the temperature gradients dT_endmembers = calc_dT(H_endmembers, R_A_endmembers, rho_datum_endmembers, c_p_endmembers) # dT constants # Note: the equations for a and b in the Allen 2007 paper (eq 50 and 51) appear to be wrong. dT_b = (dT_endmembers[1] - dT_endmembers[0]) / (Tr_datum[hot_pixel] - Tr_datum[cold_pixel]) dT_a = dT_endmembers[1] - dT_b * Tr_datum[hot_pixel] # Apply the constant to the whole image dT = dT_a + dT_b * Tr_datum # Allen 2007 eq. 29 # ============================================================================== # ITERATIONS FOR MONIN-OBUKHOV LENGTH AND H TO CONVERGE # ============================================================================== # Initially assume stable atmospheric conditions and set variables for L_queue = deque([np.ones(dT.shape)], 6) L_converged = np.asarray(np.zeros(Tr_K.shape)).astype(bool) L_diff_max = np.inf i = np.ones(dT.shape, dtype=bool) start_time = time.time() loop_time = time.time() for n_iterations in range(max_iterations): iterations[i] = n_iterations if np.all(L_converged): break current_time = time.time() loop_duration = current_time - loop_time loop_time = current_time total_duration = loop_time - start_time print( "Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" % (n_iterations, np.sum(~L_converged[i]), L_diff_max, total_duration, loop_duration)) i = ~L_converged if use_METRIC_resistance is True: R_A_params = { "z_T": np.array([2.0, 2.0]), "u_friction": u_friction[i], "L": L[i], "d_0": np.array([0.0, 0.0]), "z_0H": np.array([0.1, 0.1]) } else: R_A_params = { "z_T": z_T[i], "u_friction": u_friction[i], "L": L[i], "d_0": d_0[i], "z_0H": z_0H[i] } R_A[i], _, _ = tseb.calc_resistances(tseb.KUSTAS_NORMAN_1999, {"R_A": R_A_params}) H[i] = calc_H(dT[i], rho[i], c_p[i], R_A[i]) LE[i] = Rn[i] - G[i] - H[i] if isinstance(UseL, bool): # Now L can be recalculated and the difference between iterations # derived L[i] = MO.calc_L(u_friction[i], T_A_K[i], rho[i], c_p[i], H[i], LE[i]) u_friction[i] = MO.calc_u_star(u[i], z_u[i], L[i], d_0[i], z_0M[i]) u_friction[i] = np.asarray( np.maximum(u_friction_min, u_friction[i])) # We check convergence against the value of L from previous iteration but as well # against values from 2 or 3 iterations back. This is to catch situations (not # infrequent) where L oscillates between 2 or 3 steady state values. L_new = np.array(L) L_new[L_new == 0] = 1e-36 L_queue.appendleft(L_new) i = ~L_converged L_converged[i] = _L_diff(L_queue[0][i], L_queue[1][i]) < L_thres L_diff_max = np.max(_L_diff(L_queue[0][i], L_queue[1][i])) if len(L_queue) >= 4: i = ~L_converged L_converged[i] = np.logical_and( _L_diff(L_queue[0][i], L_queue[2][i]) < L_thres, _L_diff(L_queue[1][i], L_queue[3][i]) < L_thres) if len(L_queue) == 6: i = ~L_converged L_converged[i] = np.logical_and.reduce( (_L_diff(L_queue[0][i], L_queue[3][i]) < L_thres, _L_diff(L_queue[1][i], L_queue[4][i]) < L_thres, _L_diff(L_queue[2][i], L_queue[5][i]) < L_thres)) flag, Ln, LE, H, G, R_A, u_friction, L, iterations = map( np.asarray, (flag, Ln, LE, H, G, R_A, u_friction, L, iterations)) return flag, Ln, LE, H, G, R_A, u_friction, L, iterations