pyTSEB 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyTSEB/TSEB.py ADDED
@@ -0,0 +1,3806 @@
1
+ # This file is part of pyTSEB for running different TSEB models
2
+ # Copyright 2016 Hector Nieto and contributors listed in the README.md file.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Lesser General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ '''
18
+ Created on Apr 6 2015
19
+ @author: Hector Nieto (hector.nieto@ica.csic.es)
20
+
21
+ DESCRIPTION
22
+ ===========
23
+ This package contains the main routines inherent of Two Source Energy Balance `TSEB` models.
24
+ Additional functions needed in TSEB, such as computing of net radiation or estimating the
25
+ resistances to heat and momentum transport are imported.
26
+
27
+ * :doc:`net_radiation` for the estimation of net radiation and radiation partitioning.
28
+ * :doc:`clumping_index` for the estimatio of canopy clumping index.
29
+ * :doc:`meteo_utils` for the estimation of meteorological variables.
30
+ * :doc:`resistances` for the estimation of the resistances to heat and momentum transport.
31
+ * :doc:`MO_similarity` for the estimation of the Monin-Obukhov length and MOST-related variables.
32
+ * :doc:`wind_profile` for the estimation of wind attenuation profile
33
+
34
+ PACKAGE CONTENTS
35
+ ================
36
+
37
+ TSEB models
38
+ -----------
39
+ * :func:`TSEB_2T` TSEB using derived/measured canopy and soil component temperatures.
40
+ * :func:`TSEB_PT` Priestley-Taylor TSEB using a
41
+ single observation of composite radiometric temperature.
42
+ * :func:`DTD` Dual-Time Differenced TSEB using composite radiometric temperatures at two times:
43
+ early morning and near afternoon.
44
+
45
+ OSEB models
46
+ -----------
47
+ * :func:`OSEB`. One Source Energy Balance Model.
48
+
49
+ Ancillary functions
50
+ -------------------
51
+ * :func:`calc_F_theta_campbell`. Gap fraction estimation.
52
+ * :func:`calc_G_time_diff`. Santanello & Friedl (2003) [Santanello2003]_ soil heat flux model.
53
+ * :func:`calc_G_ratio`. Soil heat flux as a fixed fraction of net radiation [Choudhury1987]_.
54
+ * :func:`calc_H_C`. canopy sensible heat flux in a parallel resistance network.
55
+ * :func:`calc_H_C_PT`. Priestley- Taylor Canopy sensible heat flux.
56
+ * :func:`calc_H_DTD_parallel`. Priestley- Taylor Canopy sensible
57
+ heat flux for DTD and resistances in parallel.
58
+ * :func:`calc_H_DTD_series`. Priestley- Taylor Canopy sensible heat flux
59
+ for DTD and resistances in series.
60
+ * :func:`calc_H_S`. Soil heat flux with resistances in parallel.
61
+ * :func:`calc_T_C`. Canopy temperature form composite radiometric temperature.
62
+ * :func:`calc_T_C_series.` Canopy temperature from canopy sensible
63
+ heat flux and resistance in series.
64
+ * :func:`calc_T_CS_Norman`. Component temperatures from dual angle
65
+ composite radiometric temperatures.
66
+ * :func:`calc_T_CS_4SAIL`. Component temperatures from dual angle composite radiometric tempertures.
67
+ Using 4SAIl for the inversion.
68
+ * :func:`calc_4SAIL_emission_param`. Effective surface reflectance, and emissivities for soil and
69
+ canopy using 4SAIL.
70
+ * :func:`calc_T_S`. Soil temperature from form composite radiometric temperature.
71
+ * :func:`calc_T_S_series`. Soil temperature from soil sensible heat flux and resistance in series.
72
+ '''
73
+
74
+ from collections import deque
75
+ import time
76
+
77
+ import numpy as np
78
+ from pypro4sail.four_sail import foursail
79
+
80
+ from . import meteo_utils as met
81
+ from . import resistances as res
82
+ from . import MO_similarity as MO
83
+ from . import net_radiation as rad
84
+ from . import clumping_index as CI
85
+ from . import wind_profile as wnd
86
+ from . import energy_combination_ET as pet
87
+
88
+ # ==============================================================================
89
+ # List of constants used in TSEB model and sub-routines
90
+ # ==============================================================================
91
+ # Threshold for relative change in Monin-Obukhov lengh to stop the iterations
92
+ L_thres = 0.001
93
+ # mimimun allowed friction velocity
94
+ U_FRICTION_MIN = 0.01
95
+ U_S_MIN = 0.01
96
+ U_C_MIN = 0.01
97
+ R_A_MIN = 1e-1
98
+ R_A_MAX = None
99
+ RES_MIN = 1e-1
100
+ RES_MAX = None
101
+
102
+ # Maximum number of interations
103
+ ITERATIONS = 15
104
+ # kB coefficient
105
+ KB_1_DEFAULT = 0.0
106
+ # Stephan Boltzmann constant (W m-2 K-4)
107
+ SB = 5.670373e-8
108
+
109
+ # Resistance formulation constants
110
+ KUSTAS_NORMAN_1999 = 0
111
+ CHOUDHURY_MONTEITH_1988 = 1
112
+ MCNAUGHTON_VANDERHURK = 2
113
+ CHOUDHURY_MONTEITH_ALPHA_1988 = 3
114
+ HADHIGHI_AND_OR_2015 = 4
115
+
116
+ # Soil heat flux formulation constants
117
+ G_CONSTANT = 0
118
+ G_RATIO = 1
119
+ G_TIME_DIFF = 2
120
+ G_TIME_DIFF_SIGMOID = 3
121
+
122
+ # Flag constants
123
+ F_ALL_FLUXES = 0 # All fluxes produced with no reduction of PT parameter (i.e. positive soil evaporation)
124
+ F_ZERO_LE_C = 1 # Negative canopy latent heat flux, forced to zero
125
+ F_ZERO_H_C = 2 # Negative canopy sensible heat flux, forced to zero
126
+ F_ZERO_LE_S = 3 # Negative soil evaporation, forced to zero (the PT parameter is reduced in TSEB-PT and DTD)
127
+ F_ZERO_H_S = 4 # Negative soil sensible heat flux, forced to zero
128
+ F_ZERO_LE = 5 # No positive latent fluxes found, G recomputed to close the energy balance (G=Rn-H)
129
+ F_ALL_FLUXES_OS = 10 # All positive fluxes for soil only, produced using one-source energy balance (OSEB) model.
130
+ F_ZERO_LE_OS = 15 # No positive latent fluxes found using OSEB, G recomputed to close the energy balance (G=Rn-H)
131
+ F_INVALID = 255 # Arithmetic error. BAD data, it should be discarded
132
+
133
+ # Steps for decreasing transpiration efficiency in TSEB-SW
134
+ STEP_BETA = 0.05
135
+ # Steps for increasing soil surface resistance to water transport in TSEB-SW
136
+ STEP_RSS = 500.
137
+ MAX_RST = 5000.
138
+ RELATIVE_INCREASE = 0.10
139
+ STEP_RST = 10.
140
+ # Steps for increasing surface resistance to water transport in TSEB-PM
141
+ MAX_RC = 5000.
142
+ STEP_RC = 5.
143
+
144
+ def TSEB_2T(T_C,
145
+ T_S,
146
+ T_A_K,
147
+ u,
148
+ ea,
149
+ p,
150
+ Sn_C,
151
+ Sn_S,
152
+ L_dn,
153
+ LAI,
154
+ h_C,
155
+ emis_C,
156
+ emis_S,
157
+ z_0M,
158
+ d_0,
159
+ z_u,
160
+ z_T,
161
+ leaf_width=0.1,
162
+ z0_soil=0.01,
163
+ alpha_PT=1.26,
164
+ x_LAD=1.0,
165
+ f_c=1.0,
166
+ f_g=1.0,
167
+ w_C=1.0,
168
+ resistance_form=None,
169
+ calcG_params=None,
170
+ const_L=None,
171
+ kB=KB_1_DEFAULT,
172
+ massman_profile=None,
173
+ verbose=True):
174
+ """ TSEB using component canopy and soil temperatures.
175
+
176
+ Calculates the turbulent fluxes by the Two Source Energy Balance model
177
+ using canopy and soil component temperatures that were derived or measured
178
+ previously.
179
+
180
+ Parameters
181
+ ----------
182
+ T_S : float
183
+ Soil Temperature (Kelvin).
184
+ T_C : float
185
+ Canopy Temperature (Kelvin).
186
+ T_A_K : float
187
+ Air temperature (Kelvin).
188
+ u : float
189
+ Wind speed above the canopy (m s-1).
190
+ ea : float
191
+ Water vapour pressure above the canopy (mb).
192
+ p : float
193
+ Atmospheric pressure (mb), use 1013 mb by default.
194
+ Sn_C : float
195
+ Canopy net shortwave radiation (W m-2).
196
+ Sn_S : float
197
+ Soil net shortwave radiation (W m-2).
198
+ L_dn : float
199
+ Downwelling longwave radiation (W m-2)
200
+ LAI : float
201
+ Effective Leaf Area Index (m2 m-2).
202
+ h_C : float
203
+ Canopy height (m).
204
+ z_0M : float
205
+ Aerodynamic surface roughness length for momentum transfer (m).
206
+ d_0 : float
207
+ Zero-plane displacement height (m).
208
+ z_u : float
209
+ Height of measurement of windspeed (m).
210
+ z_T : float
211
+ Height of measurement of air temperature (m).
212
+ leaf_width : float, optional
213
+ average/effective leaf width (m).
214
+ z0_soil : float, optional
215
+ bare soil aerodynamic roughness length (m).
216
+ alpha_PT : float, optional
217
+ Priestley Taylor coeffient for canopy potential transpiration,
218
+ use 1.26 by default.
219
+ x_LAD : float, optional
220
+ Campbell 1990 leaf inclination distribution function chi parameter.
221
+ f_c : float, optional
222
+ Fractional cover.
223
+ f_g : float, optional
224
+ Fraction of vegetation that is green.
225
+ w_C : float, optional
226
+ Canopy width to height ratio.
227
+
228
+ resistance_form : int, optional
229
+ Flag to determine which Resistances R_x, R_S model to use.
230
+
231
+ * 0 [Default] Norman et al 1995 and Kustas et al 1999.
232
+ * 1 : Choudhury and Monteith 1988.
233
+ * 2 : McNaughton and Van der Hurk 1995.
234
+
235
+ calcG_params : list[list,float or array], optional
236
+ Method to calculate soil heat flux,parameters.
237
+
238
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
239
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
240
+ * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with
241
+ G_param list of parameters
242
+ (see :func:`~TSEB.calc_G_time_diff`).
243
+ const_L : float or None, optional
244
+ If included, its value will be used to force the Moning-Obukhov stability length.
245
+
246
+ Returns
247
+ -------
248
+ flag : int
249
+ Quality flag, see Appendix for description.
250
+ T_AC : float
251
+ Air temperature at the canopy interface (Kelvin).
252
+ LE_C : float
253
+ Canopy latent heat flux (W m-2).
254
+ H_C : float
255
+ Canopy sensible heat flux (W m-2).
256
+ LE_S : float
257
+ Soil latent heat flux (W m-2).
258
+ H_S : float
259
+ Soil sensible heat flux (W m-2).
260
+ G : float
261
+ Soil heat flux (W m-2).
262
+ R_S : float
263
+ Soil aerodynamic resistance to heat transport (s m-1).
264
+ R_x : float
265
+ Bulk canopy aerodynamic resistance to heat transport (s m-1).
266
+ R_A : float
267
+ Aerodynamic resistance to heat transport (s m-1).
268
+ u_friction : float
269
+ Friction velocity (m s-1).
270
+ L : float
271
+ Monin-Obuhkov length (m).
272
+ n_iterations : int
273
+ number of iterations until convergence of L.
274
+
275
+ References
276
+ ----------
277
+ .. [Kustas1997] Kustas, W. P., and J. M. Norman (1997), A two-source approach for estimating
278
+ turbulent fluxes using multiple angle thermal infrared observations,
279
+ Water Resour. Res., 33(6), 1495-1508,
280
+ http://dx.doi.org/10.1029/97WR00704.
281
+ """
282
+
283
+ if resistance_form is None:
284
+ resistance_form = [0, {}]
285
+ if calcG_params is None:
286
+ calcG_params = [[1], 0.35]
287
+ if massman_profile is None:
288
+ massman_profile = [0, []]
289
+ # Convert float scalars into numpy arrays and check parameters size
290
+ T_C = np.asarray(T_C)
291
+ [T_S,
292
+ T_A_K,
293
+ u,
294
+ ea,
295
+ p,
296
+ Sn_C,
297
+ Sn_S,
298
+ L_dn,
299
+ LAI,
300
+ h_C,
301
+ emis_C,
302
+ emis_S,
303
+ z_0M,
304
+ d_0,
305
+ z_u,
306
+ z_T,
307
+ leaf_width,
308
+ z0_soil,
309
+ alpha_PT,
310
+ x_LAD,
311
+ f_c,
312
+ f_g,
313
+ w_C,
314
+ calcG_array] = map(_check_default_parameter_size,
315
+ [T_S,
316
+ T_A_K,
317
+ u,
318
+ ea,
319
+ p,
320
+ Sn_C,
321
+ Sn_S,
322
+ L_dn,
323
+ LAI,
324
+ h_C,
325
+ emis_C,
326
+ emis_S,
327
+ z_0M,
328
+ d_0,
329
+ z_u,
330
+ z_T,
331
+ leaf_width,
332
+ z0_soil,
333
+ alpha_PT,
334
+ x_LAD,
335
+ f_c,
336
+ f_g,
337
+ w_C,
338
+ calcG_params[1]],
339
+ [T_C] * 24)
340
+
341
+ res_params = resistance_form[1]
342
+ resistance_form = resistance_form[0]
343
+ # calcG_params[1] = None
344
+ # Create the output variables
345
+ [flag, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, R_S, R_x,
346
+ R_A, iterations] = [np.zeros(T_S.shape, np.float32)+np.nan for i in range(12)]
347
+ T_AC = T_A_K.copy()
348
+
349
+ # iteration of the Monin-Obukhov length
350
+ if const_L is None:
351
+ # Initially assume stable atmospheric conditions and set variables for
352
+ L = np.asarray(np.zeros(T_S.shape) + np.inf)
353
+ max_iterations = ITERATIONS
354
+ else: # We force Monin-Obukhov lenght to the provided array/value
355
+ L = np.asarray(np.ones(T_S.shape) * const_L)
356
+ max_iterations = 1 # No iteration
357
+
358
+ # Calculate the general parameters
359
+ rho = met.calc_rho(p, ea, T_A_K) # Air density
360
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
361
+ z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport
362
+
363
+ # Calculate LAI dependent parameters for dataset where LAI > 0
364
+ F = np.asarray(LAI / f_c) # Real LAI
365
+ # Calculate LAI dependent parameters for dataset where LAI > 0
366
+ omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True)
367
+
368
+ # And the net longwave radiation
369
+ Ln_C, Ln_S = rad.calc_L_n_Campbell(T_C, T_S, L_dn, LAI, emis_C, emis_S, x_LAD=x_LAD)
370
+
371
+ # Compute Net Radiation
372
+ Rn_S = Sn_S + Ln_S
373
+ Rn_C = Sn_C + Ln_C
374
+ Rn = Rn_S + Rn_C
375
+
376
+ # Compute Soil Heat Flux
377
+ i = np.ones(Rn_S.shape, dtype=bool)
378
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn_S, i)
379
+ # iteration of the Monin-Obukhov length
380
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
381
+ u_friction = np.asarray(np.maximum(U_FRICTION_MIN, u_friction))
382
+ l_queue = deque([np.array(L)], 6)
383
+ l_converged = np.asarray(np.zeros(T_S.shape)).astype(bool)
384
+ l_diff_max = np.inf
385
+
386
+ # Outer loop for estimating stability.
387
+ # Stops when difference in consecutives L is below a given threshold
388
+ start_time = time.time()
389
+ loop_time = time.time()
390
+ for n_iterations in range(max_iterations):
391
+ if np.all(l_converged[i]):
392
+ if verbose:
393
+ if l_converged[i].size == 0:
394
+ print("Finished iterations with no valid solution")
395
+ else:
396
+ print(f"Finished interations with a max. L diff: {l_diff_max}")
397
+ break
398
+ current_time = time.time()
399
+ loop_duration = current_time - loop_time
400
+ loop_time = current_time
401
+ total_duration = loop_time - start_time
402
+ if verbose:
403
+ print("Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" %
404
+ (n_iterations, np.sum(~l_converged[i]), l_diff_max, total_duration, loop_duration))
405
+ iterations[np.logical_and(~l_converged, flag != F_INVALID)] = n_iterations
406
+
407
+ i = np.logical_and(~l_converged, flag != F_INVALID)
408
+ iterations[i] = n_iterations
409
+ flag[i] = F_ALL_FLUXES
410
+
411
+ # Calculate aerodynamic resistances
412
+ R_A[i], R_x[i], R_S[i] = calc_resistances(
413
+ resistance_form, {"R_A": {"z_T": z_T[i], "u_friction": u_friction[i], "L": L[i],
414
+ "d_0": d_0[i], "z_0H": z_0H[i]},
415
+ "R_x": {"u_friction": u_friction[i], "h_C": h_C[i],
416
+ "d_0": d_0[i],
417
+ "z_0M": z_0M[i], "L": L[i], "F": F[i], "LAI": LAI[i],
418
+ "leaf_width": leaf_width[i],
419
+ "z0_soil": z0_soil[i],
420
+ "massman_profile": massman_profile,
421
+ "res_params": {k: res_params[k][i] for k in res_params.keys()}},
422
+ "R_S": {"u_friction": u_friction[i], "h_C": h_C[i],
423
+ "d_0": d_0[i],
424
+ "z_0M": z_0M[i], "L": L[i], "F": F[i], "omega0": omega0[i],
425
+ "LAI": LAI[i], "leaf_width": leaf_width[i],
426
+ "z0_soil": z0_soil[i], "z_u": z_u[i],
427
+ "deltaT": T_S[i] - T_AC[i], 'u': u[i], 'rho': rho[i],
428
+ "c_p": c_p[i], "f_cover": f_c[i], "w_C": w_C[i],
429
+ "massman_profile": massman_profile,
430
+ "res_params": {k: res_params[k][i] for k in res_params.keys()}}
431
+ })
432
+
433
+ # Compute air temperature at the canopy interface
434
+ T_AC[i] = ((T_A_K[i] / R_A[i] + T_S[i] / R_S[i] + T_C[i] / R_x[i])
435
+ / (1 / R_A[i] + 1 / R_S[i] + 1 / R_x[i]))
436
+ T_AC = np.asarray(np.maximum(1e-3, T_AC))
437
+
438
+ # Calculate canopy sensible heat flux (Norman et al 1995)
439
+ H_C[i] = rho[i] * c_p[i] * (T_C[i] - T_AC[i]) / R_x[i]
440
+ # Assume no condensation in the canopy (LE_C<0)
441
+ noC = np.logical_and(i, H_C > Rn_C)
442
+ H_C[noC] = Rn_C[noC]
443
+ flag[noC] = F_ZERO_LE_C
444
+ # Assume no thermal inversion in the canopy
445
+ noI = np.logical_and.reduce(
446
+ (i,
447
+ H_C < calc_H_C_PT(
448
+ Rn_C,
449
+ f_g,
450
+ T_A_K,
451
+ p,
452
+ c_p,
453
+ alpha_PT),
454
+ Rn_C > 0))
455
+ H_C[noI] = 0
456
+ flag[noI] = F_ZERO_H_C
457
+
458
+ # Calculate soil sensible heat flux (Norman et al 1995)
459
+ H_S[i] = rho[i] * c_p[i] * (T_S[i] - T_AC[i]) / R_S[i]
460
+ # Assume that there is no condensation in the soil (LE_S<0)
461
+ noC = np.logical_and.reduce((i, H_S > Rn_S - G, (Rn_S - G) > 0))
462
+ H_S[noC] = Rn_S[noC] - G[noC]
463
+ flag[noC] = F_ZERO_LE_S
464
+ # Assume no thermal inversion in the soil
465
+ noI = np.logical_and.reduce((i, H_S < 0, Rn_S - G > 0))
466
+ H_S[noI] = 0
467
+ flag[noI] = F_ZERO_H_S
468
+
469
+ # Evaporation Rate (Kustas and Norman 1999)
470
+ H = np.asarray(H_S + H_C)
471
+ LE = np.asarray(Rn - G - H)
472
+ # Now L can be recalculated and the difference between iterations
473
+ # derived
474
+ if const_L is None:
475
+ L[i] = MO.calc_L(u_friction[i],
476
+ T_A_K[i],
477
+ rho[i],
478
+ c_p[i],
479
+ H[i],
480
+ LE[i])
481
+
482
+ i, l_queue, l_converged, l_diff_max = monin_obukhov_convergence(L,
483
+ l_queue,
484
+ l_converged,
485
+ flag)
486
+
487
+ # Compute soil and canopy latent heat fluxes
488
+ LE_S = Rn_S - G - H_S
489
+ LE_C = Rn_C - H_C
490
+
491
+ (flag, T_AC, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A, u_friction, L,
492
+ n_iterations) = map(np.asarray, (flag, T_AC, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S,
493
+ G, R_S, R_x, R_A, u_friction, L, iterations))
494
+
495
+ return (flag, T_AC, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A, u_friction, L,
496
+ n_iterations)
497
+
498
+
499
+ def TSEB_PT(Tr_K,
500
+ vza,
501
+ T_A_K,
502
+ u,
503
+ ea,
504
+ p,
505
+ Sn_C,
506
+ Sn_S,
507
+ L_dn,
508
+ LAI,
509
+ h_C,
510
+ emis_C,
511
+ emis_S,
512
+ z_0M,
513
+ d_0,
514
+ z_u,
515
+ z_T,
516
+ leaf_width=0.1,
517
+ z0_soil=0.01,
518
+ alpha_PT=1.26,
519
+ x_LAD=1,
520
+ f_c=1.0,
521
+ f_g=1.0,
522
+ w_C=1.0,
523
+ resistance_form=None,
524
+ calcG_params=None,
525
+ const_L=None,
526
+ kB=KB_1_DEFAULT,
527
+ massman_profile=None,
528
+ verbose=True):
529
+ '''Priestley-Taylor TSEB
530
+
531
+ Calculates the Priestley Taylor TSEB fluxes using a single observation of
532
+ composite radiometric temperature and using resistances in series.
533
+
534
+ Parameters
535
+ ----------
536
+ Tr_K : float
537
+ Radiometric composite temperature (Kelvin).
538
+ vza : float
539
+ View Zenith Angle (degrees).
540
+ T_A_K : float
541
+ Air temperature (Kelvin).
542
+ u : float
543
+ Wind speed above the canopy (m s-1).
544
+ ea : float
545
+ Water vapour pressure above the canopy (mb).
546
+ p : float
547
+ Atmospheric pressure (mb), use 1013 mb by default.
548
+ Sn_C : float
549
+ Canopy net shortwave radiation (W m-2).
550
+ Sn_S : float
551
+ Soil net shortwave radiation (W m-2).
552
+ L_dn : float
553
+ Downwelling longwave radiation (W m-2).
554
+ LAI : float
555
+ Effective Leaf Area Index (m2 m-2).
556
+ h_C : float
557
+ Canopy height (m).
558
+ emis_C : float
559
+ Leaf emissivity.
560
+ emis_S : flaot
561
+ Soil emissivity.
562
+ z_0M : float
563
+ Aerodynamic surface roughness length for momentum transfer (m).
564
+ d_0 : float
565
+ Zero-plane displacement height (m).
566
+ z_u : float
567
+ Height of measurement of windspeed (m).
568
+ z_T : float
569
+ Height of measurement of air temperature (m).
570
+ leaf_width : float, optional
571
+ average/effective leaf width (m).
572
+ z0_soil : float, optional
573
+ bare soil aerodynamic roughness length (m).
574
+ alpha_PT : float, optional
575
+ Priestley Taylor coeffient for canopy potential transpiration,
576
+ use 1.26 by default.
577
+ x_LAD : float, optional
578
+ Campbell 1990 leaf inclination distribution function chi parameter.
579
+ f_c : float, optional
580
+ Fractional cover.
581
+ f_g : float, optional
582
+ Fraction of vegetation that is green.
583
+ w_C : float, optional
584
+ Canopy width to height ratio.
585
+ resistance_form : int, optional
586
+ Flag to determine which Resistances R_x, R_S model to use.
587
+
588
+ * 0 [Default] Norman et al 1995 and Kustas et al 1999.
589
+ * 1 : Choudhury and Monteith 1988.
590
+ * 2 : McNaughton and Van der Hurk 1995.
591
+
592
+ calcG_params : list[list,float or array], optional
593
+ Method to calculate soil heat flux,parameters.
594
+
595
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
596
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
597
+ * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with
598
+ G_param list of parameters
599
+ (see :func:`~TSEB.calc_G_time_diff`).
600
+ const_L : float or None, optional
601
+ If included, its value will be used to force the Moning-Obukhov stability length.
602
+
603
+ Returns
604
+ -------
605
+ flag : int
606
+ Quality flag, see Appendix for description.
607
+ T_S : float
608
+ Soil temperature (Kelvin).
609
+ T_C : float
610
+ Canopy temperature (Kelvin).
611
+ T_AC : float
612
+ Air temperature at the canopy interface (Kelvin).
613
+ L_nS : float
614
+ Soil net longwave radiation (W m-2)
615
+ L_nC : float
616
+ Canopy net longwave radiation (W m-2)
617
+ LE_C : float
618
+ Canopy latent heat flux (W m-2).
619
+ H_C : float
620
+ Canopy sensible heat flux (W m-2).
621
+ LE_S : float
622
+ Soil latent heat flux (W m-2).
623
+ H_S : float
624
+ Soil sensible heat flux (W m-2).
625
+ G : float
626
+ Soil heat flux (W m-2).
627
+ R_S : float
628
+ Soil aerodynamic resistance to heat transport (s m-1).
629
+ R_x : float
630
+ Bulk canopy aerodynamic resistance to heat transport (s m-1).
631
+ R_A : float
632
+ Aerodynamic resistance to heat transport (s m-1).
633
+ u_friction : float
634
+ Friction velocity (m s-1).
635
+ L : float
636
+ Monin-Obuhkov length (m).
637
+ n_iterations : int
638
+ number of iterations until convergence of L.
639
+
640
+ References
641
+ ----------
642
+ .. [Norman1995] J.M. Norman, W.P. Kustas, K.S. Humes, Source approach for estimating
643
+ soil and vegetation energy fluxes in observations of directional radiometric
644
+ surface temperature, Agricultural and Forest Meteorology, Volume 77, Issues 3-4,
645
+ Pages 263-293,
646
+ http://dx.doi.org/10.1016/0168-1923(95)02265-Y.
647
+ .. [Kustas1999] William P Kustas, John M Norman, Evaluation of soil and vegetation heat
648
+ flux predictions using a simple two-source model with radiometric temperatures for
649
+ partial canopy cover, Agricultural and Forest Meteorology, Volume 94, Issue 1,
650
+ Pages 13-29,
651
+ http://dx.doi.org/10.1016/S0168-1923(99)00005-2.
652
+ '''
653
+
654
+ # Convert input float scalars to arrays and parameters size
655
+ if calcG_params is None:
656
+ calcG_params = [[1], 0.35]
657
+ if resistance_form is None:
658
+ resistance_form = [0, {}]
659
+ if massman_profile is None:
660
+ massman_profile = [0, []]
661
+
662
+ Tr_K = np.asarray(Tr_K, dtype=np.float32)
663
+ (vza,
664
+ T_A_K,
665
+ u,
666
+ ea,
667
+ p,
668
+ Sn_C,
669
+ Sn_S,
670
+ L_dn,
671
+ LAI,
672
+ h_C,
673
+ emis_C,
674
+ emis_S,
675
+ z_0M,
676
+ d_0,
677
+ z_u,
678
+ z_T,
679
+ leaf_width,
680
+ z0_soil,
681
+ alpha_PT,
682
+ x_LAD,
683
+ f_c,
684
+ f_g,
685
+ w_C,
686
+ calcG_array) = map(_check_default_parameter_size,
687
+ [vza,
688
+ T_A_K,
689
+ u,
690
+ ea,
691
+ p,
692
+ Sn_C,
693
+ Sn_S,
694
+ L_dn,
695
+ LAI,
696
+ h_C,
697
+ emis_C,
698
+ emis_S,
699
+ z_0M,
700
+ d_0,
701
+ z_u,
702
+ z_T,
703
+ leaf_width,
704
+ z0_soil,
705
+ alpha_PT,
706
+ x_LAD,
707
+ f_c,
708
+ f_g,
709
+ w_C,
710
+ calcG_params[1]],
711
+ [Tr_K] * 24)
712
+ res_params = resistance_form[1]
713
+ resistance_form = resistance_form[0]
714
+ # calcG_params[1] = None
715
+ # Create the output variables
716
+ [Ln_S, Ln_C, H, LE, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A, delta_Rn,
717
+ Rn_S, iterations] = [np.zeros(Tr_K.shape, np.float32)+np.nan for i in range(15)]
718
+
719
+ # iteration of the Monin-Obukhov length
720
+ if const_L is None:
721
+ # Initially assume stable atmospheric conditions and set variables for
722
+ L = np.zeros(Tr_K.shape) + np.inf
723
+ max_iterations = ITERATIONS
724
+ else: # We force Monin-Obukhov lenght to the provided array/value
725
+ L = np.ones(Tr_K.shape) * const_L
726
+ max_iterations = 1 # No iteration
727
+ # Calculate the general parameters
728
+ rho = met.calc_rho(p, ea, T_A_K) # Air density
729
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
730
+ z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport
731
+
732
+ # Calculate LAI dependent parameters for dataset where LAI > 0
733
+ omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True)
734
+ F = np.asarray(LAI / f_c, dtype=np.float32) # Real LAI
735
+ # Fraction of vegetation observed by the sensor
736
+ f_theta = calc_F_theta_campbell(vza, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
737
+ del vza, ea
738
+ # Initially assume stable atmospheric conditions and set variables for
739
+ # iteration of the Monin-Obukhov length
740
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
741
+ u_friction = np.asarray(np.maximum(U_FRICTION_MIN, u_friction), dtype=np.float32)
742
+ L_queue = deque([np.array(L, np.float32)], 6)
743
+ L_converged = np.zeros(Tr_K.shape, bool)
744
+ L_diff_max = np.inf
745
+
746
+ # First assume that canopy temperature equals the minumum of Air or
747
+ # radiometric T
748
+ T_C = np.asarray(np.minimum(Tr_K, T_A_K), dtype=np.float32)
749
+ flag, T_S = calc_T_S(Tr_K, T_C, f_theta)
750
+ T_AC = T_A_K.copy()
751
+
752
+ # Outer loop for estimating stability.
753
+ # Stops when difference in consecutives L is below a given threshold
754
+ start_time = time.time()
755
+ loop_time = time.time()
756
+ for n_iterations in range(max_iterations):
757
+ i = flag != F_INVALID
758
+ if np.all(L_converged[i]):
759
+ if verbose:
760
+ if L_converged[i].size == 0:
761
+ print("Finished iterations with no valid solution")
762
+ else:
763
+ print(f"Finished interations with a max. L diff: {L_diff_max}")
764
+ break
765
+ current_time = time.time()
766
+ loop_duration = current_time - loop_time
767
+ loop_time = current_time
768
+ total_duration = loop_time - start_time
769
+ if verbose:
770
+ print("Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" %
771
+ (n_iterations, np.sum(~L_converged[i]), L_diff_max, total_duration, loop_duration))
772
+ iterations[np.logical_and(~L_converged, flag != F_INVALID)] = n_iterations
773
+
774
+ # Inner loop to iterativelly reduce alpha_PT in case latent heat flux
775
+ # from the soil is negative. The initial assumption is of potential
776
+ # canopy transpiration.
777
+ flag[np.logical_and(~L_converged, flag != F_INVALID)] = F_ALL_FLUXES
778
+ LE_S[np.logical_and(~L_converged, flag != F_INVALID)] = -1
779
+ alpha_PT_rec = np.asarray(alpha_PT + 0.1, dtype=np.float32)
780
+ while np.any(LE_S[i] < 0):
781
+ i = np.logical_and.reduce((LE_S < 0, ~L_converged, flag != F_INVALID))
782
+
783
+ alpha_PT_rec[i] -= 0.1
784
+
785
+ # There cannot be negative transpiration from the vegetation
786
+ alpha_PT_rec[alpha_PT_rec <= 0.0] = 0.0
787
+ flag[np.logical_and(i, alpha_PT_rec == 0.0)] = F_ZERO_LE
788
+
789
+ flag[np.logical_and.reduce((i, alpha_PT_rec < alpha_PT, alpha_PT_rec > 0.0))] =\
790
+ F_ZERO_LE_S
791
+
792
+ # Calculate aerodynamic resistances
793
+ R_A[i], R_x[i], R_S[i] = calc_resistances(
794
+ resistance_form,
795
+ {"R_A": {"z_T": z_T[i], "u_friction": u_friction[i], "L": L[i],
796
+ "d_0": d_0[i], "z_0H": z_0H[i]},
797
+ "R_x": {"u_friction": u_friction[i], "h_C": h_C[i],
798
+ "d_0": d_0[i],
799
+ "z_0M": z_0M[i], "L": L[i], "F": F[i], "LAI": LAI[i],
800
+ "leaf_width": leaf_width[i],
801
+ "z0_soil": z0_soil[i],
802
+ "massman_profile": massman_profile,
803
+ "res_params": {k: res_params[k][i] for k in res_params.keys()}},
804
+ "R_S": {"u_friction": u_friction[i], "h_C": h_C[i],
805
+ "d_0": d_0[i],
806
+ "z_0M": z_0M[i], "L": L[i], "F": F[i], "omega0": omega0[i],
807
+ "LAI": LAI[i], "leaf_width": leaf_width[i],
808
+ "z0_soil": z0_soil[i], "z_u": z_u[i],
809
+ "deltaT": T_S[i] - T_AC[i], 'u': u[i], 'rho': rho[i],
810
+ "c_p": c_p[i], "f_cover": f_c[i], "w_C": w_C[i],
811
+ "massman_profile": massman_profile,
812
+ "res_params": {k: res_params[k][i] for k in res_params.keys()}}
813
+ }
814
+ )
815
+
816
+ # Calculate net longwave radiation with current values of T_C and T_S
817
+ Ln_C[i], Ln_S[i] = rad.calc_L_n_Campbell(
818
+ T_C[i], T_S[i], L_dn[i], LAI[i], emis_C[i], emis_S[i], x_LAD=x_LAD[i])
819
+ delta_Rn[i] = Sn_C[i] + Ln_C[i]
820
+ Rn_S[i] = Sn_S[i] + Ln_S[i]
821
+
822
+ # Calculate the canopy and soil temperatures using the Priestley
823
+ # Taylor appoach
824
+ H_C[i] = calc_H_C_PT(
825
+ delta_Rn[i],
826
+ f_g[i],
827
+ T_A_K[i],
828
+ p[i],
829
+ c_p[i],
830
+ alpha_PT_rec[i])
831
+ T_C[i] = calc_T_C_series(Tr_K[i], T_A_K[i], R_A[i], R_x[i], R_S[i],
832
+ f_theta[i], H_C[i], rho[i], c_p[i])
833
+
834
+ # Calculate soil temperature
835
+ flag_t = np.zeros(flag.shape) + F_ALL_FLUXES
836
+ flag_t[i], T_S[i] = calc_T_S(Tr_K[i], T_C[i], f_theta[i])
837
+ flag[flag_t == F_INVALID] = F_INVALID
838
+ LE_S[flag_t == F_INVALID] = 0
839
+
840
+ # Recalculate soil resistance using new soil temperature
841
+ _, _, R_S[i] = calc_resistances(
842
+ resistance_form,
843
+ {"R_S": {"u_friction": u_friction[i], "h_C": h_C[i], "d_0": d_0[i],
844
+ "z_0M": z_0M[i], "L": L[i], "F": F[i], "omega0": omega0[i],
845
+ "LAI": LAI[i], "leaf_width": leaf_width[i],
846
+ "z0_soil": z0_soil[i], "z_u": z_u[i],
847
+ "deltaT": T_S[i] - T_AC[i], "u": u[i], "rho": rho[i],
848
+ "c_p": c_p[i], "f_cover": f_c[i], "w_C": w_C[i],
849
+ "massman_profile": massman_profile,
850
+ "res_params": {k: res_params[k][i] for k in res_params.keys()}}
851
+ }
852
+ )
853
+
854
+ i = np.logical_and.reduce((LE_S < 0, ~L_converged, flag != F_INVALID))
855
+
856
+ # Get air temperature at canopy interface
857
+ T_AC[i] = ((T_A_K[i] / R_A[i] + T_S[i] / R_S[i] + T_C[i] / R_x[i])
858
+ / (1.0 / R_A[i] + 1.0 / R_S[i] + 1.0 / R_x[i]))
859
+
860
+ # Calculate soil fluxes
861
+ H_S[i] = rho[i] * c_p[i] * (T_S[i] - T_AC[i]) / R_S[i]
862
+
863
+ # Compute Soil Heat Flux Ratio
864
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn_S, i)
865
+
866
+ # Estimate latent heat fluxes as residual of energy balance at the
867
+ # soil and the canopy
868
+ LE_S[i] = Rn_S[i] - G[i] - H_S[i]
869
+ LE_C[i] = delta_Rn[i] - H_C[i]
870
+
871
+ # Special case if there is no transpiration from vegetation.
872
+ # In that case, there should also be no evaporation from the soil
873
+ # and the energy at the soil should be conserved.
874
+ # See end of appendix A1 in Guzinski et al. (2015).
875
+ noT = np.logical_and(i, LE_C == 0)
876
+ H_S[noT] = np.minimum(H_S[noT], Rn_S[noT] - G[noT])
877
+ G[noT] = np.maximum(G[noT], Rn_S[noT] - H_S[noT])
878
+ LE_S[noT] = 0
879
+
880
+ # Calculate total fluxes
881
+ H[i] = np.asarray(H_C[i] + H_S[i], dtype=np.float32)
882
+ LE[i] = np.asarray(LE_C[i] + LE_S[i], dtype=np.float32)
883
+ # Now L can be recalculated and the difference between iterations
884
+ # derived
885
+ if const_L is None:
886
+ L[i] = MO.calc_L(
887
+ u_friction[i],
888
+ T_A_K[i],
889
+ rho[i],
890
+ c_p[i],
891
+ H[i],
892
+ LE[i])
893
+ # Calculate again the friction velocity with the new stability
894
+ # correctios
895
+ u_friction[i] = MO.calc_u_star(
896
+ u[i], z_u[i], L[i], d_0[i], z_0M[i])
897
+ u_friction[i] = np.asarray(np.maximum(U_FRICTION_MIN, u_friction[i]), dtype=np.float32)
898
+
899
+ if const_L is None:
900
+ # We check convergence against the value of L from previous iteration but as well
901
+ # against values from 2 or 3 iterations back. This is to catch situations (not
902
+ # infrequent) where L oscillates between 2 or 3 steady state values.
903
+ i, L_queue, L_converged, L_diff_max = monin_obukhov_convergence(L,
904
+ L_queue,
905
+ L_converged,
906
+ flag)
907
+
908
+ (flag,
909
+ T_S,
910
+ T_C,
911
+ T_AC,
912
+ L_nS,
913
+ L_nC,
914
+ LE_C,
915
+ H_C,
916
+ LE_S,
917
+ H_S,
918
+ G,
919
+ R_S,
920
+ R_x,
921
+ R_A,
922
+ u_friction,
923
+ L,
924
+ n_iterations) = map(np.asarray,
925
+ (flag,
926
+ T_S,
927
+ T_C,
928
+ T_AC,
929
+ Ln_S,
930
+ Ln_C,
931
+ LE_C,
932
+ H_C,
933
+ LE_S,
934
+ H_S,
935
+ G,
936
+ R_S,
937
+ R_x,
938
+ R_A,
939
+ u_friction,
940
+ L,
941
+ iterations))
942
+
943
+ return (flag, T_S, T_C, T_AC, L_nS, L_nC, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A, u_friction,
944
+ L, n_iterations)
945
+
946
+ def TSEB_SW(Tr_K,
947
+ vza,
948
+ T_A_K,
949
+ u,
950
+ ea,
951
+ p,
952
+ Sn_C,
953
+ Sn_S,
954
+ L_dn,
955
+ LAI,
956
+ h_C,
957
+ emis_C,
958
+ emis_S,
959
+ z_0M,
960
+ d_0,
961
+ z_u,
962
+ z_T,
963
+ leaf_width=0.1,
964
+ z0_soil=0.01,
965
+ Rst_min=100,
966
+ Rss_min=500,
967
+ x_LAD=1,
968
+ f_c=1.0,
969
+ f_g=1.0,
970
+ w_C=1.0,
971
+ resistance_form=None,
972
+ calcG_params=None,
973
+ const_L=None,
974
+ massman_profile=None,
975
+ leaf_type=2,
976
+ kB=KB_1_DEFAULT):
977
+ '''Shuttleworth & Wallace TSEB
978
+
979
+ Calculates the Shuttleworth & Wallace TSEB fluxes using a single observation of
980
+ composite radiometric temperature and using resistances in series.
981
+
982
+ Parameters
983
+ ----------
984
+ Tr_K : float
985
+ Radiometric composite temperature (Kelvin).
986
+ vza : float
987
+ View Zenith Angle (degrees).
988
+ T_A_K : float
989
+ Air temperature (Kelvin).
990
+ u : float
991
+ Wind speed above the canopy (m s-1).
992
+ ea : float
993
+ Water vapour pressure above the canopy (mb).
994
+ p : float
995
+ Atmospheric pressure (mb), use 1013 mb by default.
996
+ Sn_C : float
997
+ Canopy net shortwave radiation (W m-2).
998
+ Sn_S : float
999
+ Soil net shortwave radiation (W m-2).
1000
+ L_dn : float
1001
+ Downwelling longwave radiation (W m-2).
1002
+ LAI : float
1003
+ Effective Leaf Area Index (m2 m-2).
1004
+ h_C : float
1005
+ Canopy height (m).
1006
+ emis_C : float
1007
+ Leaf emissivity.
1008
+ emis_S : flaot
1009
+ Soil emissivity.
1010
+ z_0M : float
1011
+ Aerodynamic surface roughness length for momentum transfer (m).
1012
+ d_0 : float
1013
+ Zero-plane displacement height (m).
1014
+ z_u : float
1015
+ Height of measurement of windspeed (m).
1016
+ z_T : float
1017
+ Height of measurement of air temperature (m).
1018
+ leaf_width : float, optional
1019
+ average/effective leaf width (m).
1020
+ z0_soil : float, optional
1021
+ bare soil aerodynamic roughness length (m).
1022
+ alpha_PT : float, optional
1023
+ Priestley Taylor coeffient for canopy potential transpiration,
1024
+ use 1.26 by default.
1025
+ x_LAD : float, optional
1026
+ Campbell 1990 leaf inclination distribution function chi parameter.
1027
+ f_c : float, optional
1028
+ Fractional cover.
1029
+ f_g : float, optional
1030
+ Fraction of vegetation that is green.
1031
+ w_C : float, optional
1032
+ Canopy width to height ratio.
1033
+ resistance_form : int, optional
1034
+ Flag to determine which Resistances R_x, R_S model to use.
1035
+
1036
+ * 0 [Default] Norman et al 1995 and Kustas et al 1999.
1037
+ * 1 : Choudhury and Monteith 1988.
1038
+ * 2 : McNaughton and Van der Hurk 1995.
1039
+
1040
+ calcG_params : list[list,float or array], optional
1041
+ Method to calculate soil heat flux,parameters.
1042
+
1043
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
1044
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
1045
+ * [[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`).
1046
+ const_L : float or None, optional
1047
+ If included, its value will be used to force the Moning-Obukhov stability length.
1048
+
1049
+ Returns
1050
+ -------
1051
+ flag : int
1052
+ Quality flag, see Appendix for description.
1053
+ T_S : float
1054
+ Soil temperature (Kelvin).
1055
+ T_C : float
1056
+ Canopy temperature (Kelvin).
1057
+ T_AC : float
1058
+ Air temperature at the canopy interface (Kelvin).
1059
+ L_nS : float
1060
+ Soil net longwave radiation (W m-2)
1061
+ L_nC : float
1062
+ Canopy net longwave radiation (W m-2)
1063
+ LE_C : float
1064
+ Canopy latent heat flux (W m-2).
1065
+ H_C : float
1066
+ Canopy sensible heat flux (W m-2).
1067
+ LE_S : float
1068
+ Soil latent heat flux (W m-2).
1069
+ H_S : float
1070
+ Soil sensible heat flux (W m-2).
1071
+ G : float
1072
+ Soil heat flux (W m-2).
1073
+ R_S : float
1074
+ Soil aerodynamic resistance to heat transport (s m-1).
1075
+ R_x : float
1076
+ Bulk canopy aerodynamic resistance to heat transport (s m-1).
1077
+ R_A : float
1078
+ Aerodynamic resistance to heat transport (s m-1).
1079
+ u_friction : float
1080
+ Friction velocity (m s-1).
1081
+ L : float
1082
+ Monin-Obuhkov length (m).
1083
+ n_iterations : int
1084
+ number of iterations until convergence of L.
1085
+
1086
+ References
1087
+ ----------
1088
+ .. [Norman1995] J.M. Norman, W.P. Kustas, K.S. Humes, Source approach for estimating
1089
+ soil and vegetation energy fluxes in observations of directional radiometric
1090
+ surface temperature, Agricultural and Forest Meteorology, Volume 77, Issues 3-4,
1091
+ Pages 263-293,
1092
+ http://dx.doi.org/10.1016/0168-1923(95)02265-Y.
1093
+ .. [Kustas1999] William P Kustas, John M Norman, Evaluation of soil and vegetation heat
1094
+ flux predictions using a simple two-source model with radiometric temperatures for
1095
+ partial canopy cover, Agricultural and Forest Meteorology, Volume 94, Issue 1,
1096
+ Pages 13-29,
1097
+ http://dx.doi.org/10.1016/S0168-1923(99)00005-2.
1098
+ '''
1099
+ if massman_profile is None:
1100
+ massman_profile = [0, []]
1101
+ if calcG_params is None:
1102
+ calcG_params = [[1], 0.35]
1103
+ if resistance_form is None:
1104
+ resistance_form = [0, {}]
1105
+
1106
+ # Convert input float scalars to arrays and parameters size
1107
+ Tr_K = np.asarray(Tr_K)
1108
+ (vza,
1109
+ T_A_K,
1110
+ u,
1111
+ ea,
1112
+ p,
1113
+ Sn_C,
1114
+ Sn_S,
1115
+ L_dn,
1116
+ LAI,
1117
+ h_C,
1118
+ emis_C,
1119
+ emis_S,
1120
+ z_0M,
1121
+ d_0,
1122
+ z_u,
1123
+ z_T,
1124
+ leaf_width,
1125
+ z0_soil,
1126
+ Rst_min,
1127
+ Rss_min,
1128
+ x_LAD,
1129
+ f_c,
1130
+ f_g,
1131
+ w_C,
1132
+ leaf_type,
1133
+ calcG_array) = map(_check_default_parameter_size,
1134
+ [vza,
1135
+ T_A_K,
1136
+ u,
1137
+ ea,
1138
+ p,
1139
+ Sn_C,
1140
+ Sn_S,
1141
+ L_dn,
1142
+ LAI,
1143
+ h_C,
1144
+ emis_C,
1145
+ emis_S,
1146
+ z_0M,
1147
+ d_0,
1148
+ z_u,
1149
+ z_T,
1150
+ leaf_width,
1151
+ z0_soil,
1152
+ Rst_min,
1153
+ Rss_min,
1154
+ x_LAD,
1155
+ f_c,
1156
+ f_g,
1157
+ w_C,
1158
+ leaf_type,
1159
+ calcG_params[1]],
1160
+ [Tr_K] * 26)
1161
+ res_params = resistance_form[1]
1162
+ resistance_form = resistance_form[0]
1163
+ # calcG_params[1] = None
1164
+ # Create the output variables
1165
+ [ H, LE, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A,
1166
+ Rss_out, Rst_out, iterations, R_c] = [np.zeros(Tr_K.shape)+np.nan for i in range(14)]
1167
+ # iteration of the Monin-Obukhov length
1168
+ if const_L is None:
1169
+ # Initially assume stable atmospheric conditions and set variables for
1170
+ L = np.asarray(np.zeros(Tr_K.shape) + np.inf)
1171
+ max_iterations = ITERATIONS
1172
+ else: # We force Monin-Obukhov lenght to the provided array/value
1173
+ L = np.asarray(np.ones(Tr_K.shape) * const_L)
1174
+ max_iterations = 1 # No iteration
1175
+ # Calculate the general parameters
1176
+ rho = met.calc_rho(p, ea, T_A_K) # Air density
1177
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
1178
+ z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport
1179
+ delta = 10. * met.calc_delta_vapor_pressure(T_A_K) # slope of saturation water vapour pressure in mb K-1
1180
+ lambda_= met.calc_lambda(T_A_K) # latent heat of vaporization MJ kg-1
1181
+ psicr = met.calc_psicr(c_p, p, lambda_) # Psicrometric constant (mb K-1)
1182
+ es = met.calc_vapor_pressure(T_A_K) # saturation water vapour pressure in mb
1183
+
1184
+ rho_cp = rho * c_p
1185
+ vpd = es - ea
1186
+
1187
+ # Calculate LAI dependent parameters for dataset where LAI > 0
1188
+ omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True)
1189
+ F = np.asarray(LAI / f_c) # Real LAI
1190
+ # Fraction of vegetation observed by the sensor
1191
+ f_theta = calc_F_theta_campbell(vza, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
1192
+ del vza
1193
+ # Initially assume stable atmospheric conditions and set variables for
1194
+ # iteration of the Monin-Obukhov length
1195
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
1196
+ u_friction = np.asarray(np.maximum(U_FRICTION_MIN, u_friction))
1197
+ L_queue = deque([np.array(L)], 6)
1198
+ L_converged = np.asarray(np.zeros(Tr_K.shape)).astype(bool)
1199
+ L_diff_max = np.inf
1200
+
1201
+ # First assume that canopy temperature equals the minumum of Air or
1202
+ # radiometric T
1203
+ T_C = np.asarray(np.minimum(Tr_K, T_A_K))
1204
+ flag, T_S = calc_T_S(Tr_K, T_C, f_theta)
1205
+ T_AC = T_A_K.copy()
1206
+
1207
+ _, _, _, taudl = rad.calc_spectra_Cambpell(LAI,
1208
+ np.zeros(emis_C.shape),
1209
+ 1.0 - emis_C,
1210
+ np.zeros(emis_S.shape),
1211
+ 1.0 - emis_S,
1212
+ x_lad=x_LAD,
1213
+ lai_eff=None)
1214
+
1215
+ emiss = taudl * emis_S + (1 - taudl) * emis_C
1216
+
1217
+ Ln = emiss * (L_dn - met.calc_stephan_boltzmann(T_AC))
1218
+ Ln_C = (1. - taudl) * Ln
1219
+ Ln_S = taudl * Ln
1220
+ delta_Rn = Sn_C + Ln_C
1221
+ Rn_S = Sn_S + Ln_S
1222
+ Rn = delta_Rn + Rn_S
1223
+
1224
+ # Outer loop for estimating stability.
1225
+ # Stops when difference in consecutives L is below a given threshold
1226
+ start_time = time.time()
1227
+ loop_time = time.time()
1228
+ for n_iterations in range(max_iterations):
1229
+ i = flag != F_INVALID
1230
+ if np.all(L_converged[i]):
1231
+ if L_converged[i].size == 0:
1232
+ print("Finished iterations with no valid solution")
1233
+ else:
1234
+ print("Finished interations with a max. L diff: " + str(L_diff_max))
1235
+ break
1236
+ current_time = time.time()
1237
+ loop_duration = current_time - loop_time
1238
+ loop_time = current_time
1239
+ total_duration = loop_time - start_time
1240
+ print("Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" %
1241
+ (n_iterations, np.sum(~L_converged[i]), L_diff_max, total_duration, loop_duration))
1242
+ iterations[np.logical_and(~L_converged, flag != F_INVALID)] = n_iterations
1243
+
1244
+ # Inner loop to iterativelly reduce alpha_PT in case latent heat flux
1245
+ # from the soil is negative. The initial assumption is of potential
1246
+ # canopy transpiration.
1247
+ flag[np.logical_and(~L_converged, flag != F_INVALID)] = F_ALL_FLUXES
1248
+ LE_S[np.logical_and(~L_converged, flag != F_INVALID)] = -1
1249
+
1250
+ rst_step = STEP_RST
1251
+ Rst = Rst_min[:] - STEP_RST
1252
+ Rss = Rss_min[:] - STEP_RSS
1253
+ while np.any(LE_S[i] < 0):
1254
+ i = np.logical_and.reduce((LE_S < 0,
1255
+ ~L_converged,
1256
+ flag != F_INVALID,
1257
+ Rst <= MAX_RST))
1258
+ Rst[i] += rst_step
1259
+ Rss[i] += STEP_RSS # Soil is drier and hence we increase soil surface resistance
1260
+ rst_step += RELATIVE_INCREASE * rst_step
1261
+ # Ensure that for almost wet soil surface T is also maximum
1262
+ # Rst[Rss <= 500] = Rst_min[Rss <= 500]
1263
+
1264
+ # There cannot be negative transpiration from the vegetation
1265
+ flag[np.logical_and(i, Rst > MAX_RST)] = F_ZERO_LE
1266
+
1267
+ flag[np.logical_and.reduce((i, Rss > 500, Rst < MAX_RST))] =\
1268
+ F_ZERO_LE_S
1269
+
1270
+ # Calculate aerodynamic resistances
1271
+ R_A[i], R_x[i], R_S[i] = calc_resistances(resistance_form,
1272
+ {"R_A": {"z_T": z_T[i],
1273
+ "u_friction": u_friction[i],
1274
+ "L": L[i],
1275
+ "d_0": d_0[i],
1276
+ "z_0H": z_0H[i]},
1277
+ "R_x": {"u_friction": u_friction[i],
1278
+ "h_C": h_C[i],
1279
+ "d_0": d_0[i],
1280
+ "z_0M": z_0M[i],
1281
+ "L": L[i],
1282
+ "F": F[i],
1283
+ "LAI": LAI[i],
1284
+ "leaf_width": leaf_width[i],
1285
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1286
+ "massman_profile": massman_profile},
1287
+ "R_S": {"u_friction": u_friction[i],
1288
+ "h_C": h_C[i],
1289
+ "d_0": d_0[i],
1290
+ "z_0M": z_0M[i],
1291
+ "L": L[i],
1292
+ "F": F[i],
1293
+ "omega0": omega0[i],
1294
+ "LAI": LAI[i],
1295
+ "leaf_width": leaf_width[i],
1296
+ "z0_soil": z0_soil[i],
1297
+ "z_u": z_u[i],
1298
+ "deltaT": T_S[i] - T_AC[i],
1299
+ 'u': u[i],
1300
+ 'rho': rho[i],
1301
+ "c_p": c_p[i],
1302
+ "f_cover": f_c[i],
1303
+ "w_C": w_C[i],
1304
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1305
+ "massman_profile": massman_profile}
1306
+ }
1307
+ )
1308
+
1309
+ R_c[i] = pet.bulk_stomatal_resistance(LAI[i] * f_g[i], Rst[i], leaf_type=leaf_type[i])
1310
+ # Calculate the canopy and soil temperatures using the Priestley Taylor approach
1311
+ _, _, _, C_s, C_c = pet.calc_effective_resistances_SW(R_A[i],
1312
+ R_x[i],
1313
+ R_S[i],
1314
+ R_c[i],
1315
+ Rss[i],
1316
+ delta[i],
1317
+ psicr[i])
1318
+
1319
+
1320
+ # Compute Soil Heat Flux Ratio
1321
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn_S, i)
1322
+
1323
+ # Eq. 12 in [Shuttleworth1988]_
1324
+ PM_C = (delta[i] * (Rn[i] - G[i]) + (rho_cp[i] * vpd[i] - delta[i] * R_x[i] * (Rn_S[i] - G[i])) / (
1325
+ R_A[i] + R_x[i])) / \
1326
+ (delta[i] + psicr[i] * (1. + R_c[i] / (R_A[i] + R_x[i])))
1327
+ PM_C[np.isnan(PM_C)] = 0
1328
+ # Eq. 13 in [Shuttleworth1988]_
1329
+ PM_S = (delta[i] * (Rn[i] - G[i]) + (rho_cp[i] * vpd[i] - delta[i] * R_S[i] * delta_Rn[i]) / (
1330
+ R_A[i] + R_S[i])) / \
1331
+ (delta[i] + psicr[i] * (1. + Rss[i] / (R_A[i] + R_S[i])))
1332
+ PM_S[np.isnan(PM_S)] = 0
1333
+ # Eq. 11 in [Shuttleworth1988]_
1334
+ LE[i] = C_c * PM_C + C_s * PM_S
1335
+ H[i] = Rn[i] - G[i] - LE[i]
1336
+
1337
+ # Compute canopy and soil fluxes
1338
+ # Vapor pressure deficit at canopy source height (mb) # Eq. 8 in [Shuttleworth1988]_
1339
+ vpd_0 = vpd[i] + (delta[i] * (Rn[i] - G[i]) - (delta[i] + psicr[i]) * LE[i]) * R_A[i] / (rho_cp[i])
1340
+ # Eq. 10 in Shuttleworth & Wallace 1985
1341
+ LE_C[i] = (delta[i] * delta_Rn[i] + rho_cp[i] * vpd_0 / R_x[i]) / \
1342
+ (delta[i] + psicr[i] * (1. + R_c[i] / R_x[i]))
1343
+
1344
+ H_C[i] = delta_Rn[i] - LE_C[i]
1345
+
1346
+ T_C[i] = calc_T_C_series(Tr_K[i], T_A_K[i], R_A[i], R_x[i],
1347
+ R_S[i], f_theta[i], H_C[i], rho[i], c_p[i])
1348
+
1349
+ # Calculate soil temperature
1350
+ flag_t = np.zeros(flag.shape) + F_ALL_FLUXES
1351
+ flag_t[i], T_S[i] = calc_T_S(Tr_K[i], T_C[i], f_theta[i])
1352
+ flag[flag_t == F_INVALID] = F_INVALID
1353
+ LE_S[flag_t == F_INVALID] = 0
1354
+
1355
+ # Calculate net longwave radiation with current values of T_C and T_S
1356
+ Ln_C[i], Ln_S[i] = rad.calc_L_n_Campbell(
1357
+ T_C[i], T_S[i], L_dn[i], LAI[i], emis_C[i], emis_S[i], x_LAD=x_LAD[i])
1358
+
1359
+ delta_Rn[i] = Sn_C[i] + Ln_C[i]
1360
+ Rn_S[i] = Sn_S[i] + Ln_S[i]
1361
+ Rn[i] = delta_Rn[i] + Rn_S[i]
1362
+
1363
+ # Recalculate soil resistance using new soil temperature
1364
+ _, _, R_S[i] = calc_resistances(resistance_form, {"R_S": {"u_friction": u_friction[i],
1365
+ "h_C": h_C[i],
1366
+ "d_0": d_0[i],
1367
+ "z_0M": z_0M[i],
1368
+ "L": L[i],
1369
+ "F": F[i],
1370
+ "omega0": omega0[i],
1371
+ "LAI": LAI[i],
1372
+ "leaf_width": leaf_width[i],
1373
+ "z0_soil": z0_soil[i],
1374
+ "z_u": z_u[i],
1375
+ "deltaT": T_S[i] - T_AC[i],
1376
+ "u": u[i],
1377
+ "rho": rho[i],
1378
+ "c_p": c_p[i],
1379
+ "f_cover": f_c[i],
1380
+ "w_C": w_C[i],
1381
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1382
+ "massman_profile": massman_profile}
1383
+ }
1384
+ )
1385
+
1386
+ i = np.logical_and.reduce((LE_S < 0, ~L_converged, flag != F_INVALID))
1387
+ # # Get air temperature at canopy interface
1388
+ T_AC[i] = ((T_A_K[i] / R_A[i] + T_S[i] / R_S[i] + T_C[i] / R_x[i])
1389
+ / (1.0 / R_A[i] + 1.0 / R_S[i] + 1.0 / R_x[i]))
1390
+
1391
+ # Calculate heat fluxes
1392
+ H_S[i] = rho[i] * c_p[i] * (T_S[i] - T_AC[i]) / R_S[i]
1393
+ H_C[i] = rho[i] * c_p[i] * (T_C[i] - T_AC[i]) / R_x[i]
1394
+
1395
+ # Estimate latent heat fluxes as residual of energy balance at the
1396
+ # soil and the canopy
1397
+ LE_S[i] = Rn_S[i] - G[i] - H_S[i]
1398
+ LE_C[i] = delta_Rn[i] - H_C[i]
1399
+
1400
+ # Special case if there is no transpiration from vegetation.
1401
+ # In that case, there should also be no evaporation from the soil
1402
+ # and the energy at the soil should be conserved.
1403
+ # See end of appendix A1 in Guzinski et al. (2015).
1404
+ noT = np.logical_and(i, Rst > MAX_RST)
1405
+ H_S[noT] = np.minimum(H_S[noT], Rn_S[noT] - G[noT])
1406
+ G[noT] = np.maximum(G[noT], Rn_S[noT] - H_S[noT])
1407
+ LE_S[noT] = 0
1408
+
1409
+ # Calculate total fluxes
1410
+ H[i] = np.asarray(H_C[i] + H_S[i])
1411
+ LE[i] = np.asarray(LE_C[i] + LE_S[i])
1412
+
1413
+ # Transfer the resistances
1414
+ Rst_out[i] = Rst[i]
1415
+ Rss_out[i] = Rss[i]
1416
+ # Now L can be recalculated and the difference between iterations
1417
+ # derived
1418
+ if const_L is None:
1419
+ L[i] = MO.calc_L(
1420
+ u_friction[i],
1421
+ T_A_K[i],
1422
+ rho[i],
1423
+ c_p[i],
1424
+ H[i],
1425
+ LE[i])
1426
+ # Calculate again the friction velocity with the new stability
1427
+ # correctios
1428
+ u_friction[i] = MO.calc_u_star(
1429
+ u[i], z_u[i], L[i], d_0[i], z_0M[i])
1430
+ u_friction[i] = np.asarray(np.maximum(U_FRICTION_MIN, u_friction[i]))
1431
+
1432
+ if const_L is None:
1433
+ # We check convergence against the value of L from previous iteration but as well
1434
+ # against values from 2 or 3 iterations back. This is to catch situations (not
1435
+ # infrequent) where L oscillates between 2 or 3 steady state values.
1436
+ i, L_queue, L_converged, L_diff_max = monin_obukhov_convergence(L,
1437
+ L_queue,
1438
+ L_converged,
1439
+ flag)
1440
+
1441
+ (flag,
1442
+ T_S,
1443
+ T_C,
1444
+ T_AC,
1445
+ L_nS,
1446
+ L_nC,
1447
+ LE_C,
1448
+ H_C,
1449
+ LE_S,
1450
+ H_S,
1451
+ G,
1452
+ R_S,
1453
+ R_x,
1454
+ R_A,
1455
+ Rss_out,
1456
+ Rst_out,
1457
+ u_friction,
1458
+ L,
1459
+ n_iterations) = map(np.asarray,
1460
+ (flag,
1461
+ T_S,
1462
+ T_C,
1463
+ T_AC,
1464
+ Ln_S,
1465
+ Ln_C,
1466
+ LE_C,
1467
+ H_C,
1468
+ LE_S,
1469
+ H_S,
1470
+ G,
1471
+ R_S,
1472
+ R_x,
1473
+ R_A,
1474
+ Rss_out,
1475
+ Rst_out,
1476
+ u_friction,
1477
+ L,
1478
+ iterations))
1479
+
1480
+ return (flag, T_S, T_C, T_AC, L_nS, L_nC, LE_C, H_C, LE_S, H_S, G, R_S, R_x,
1481
+ R_A, Rss_out, Rst_out, u_friction, L, n_iterations)
1482
+
1483
+
1484
+ def TSEB_PM(Tr_K,
1485
+ vza,
1486
+ T_A_K,
1487
+ u,
1488
+ ea,
1489
+ p,
1490
+ Sn_C,
1491
+ Sn_S,
1492
+ L_dn,
1493
+ LAI,
1494
+ h_C,
1495
+ emis_C,
1496
+ emis_S,
1497
+ z_0M,
1498
+ d_0,
1499
+ z_u,
1500
+ z_T,
1501
+ leaf_width=0.1,
1502
+ z0_soil=0.01,
1503
+ r_c_min=50.,
1504
+ x_LAD=1,
1505
+ f_c=1.0,
1506
+ f_g=1.0,
1507
+ w_C=1.0,
1508
+ resistance_form=None,
1509
+ calcG_params=None,
1510
+ const_L=None,
1511
+ massman_profile=None,
1512
+ kB=KB_1_DEFAULT):
1513
+ '''Shuttleworth & Wallace TSEB
1514
+
1515
+ Calculates the Shuttleworth & Wallace TSEB fluxes using a single observation of
1516
+ composite radiometric temperature and using resistances in series.
1517
+
1518
+ Parameters
1519
+ ----------
1520
+ Tr_K : float
1521
+ Radiometric composite temperature (Kelvin).
1522
+ vza : float
1523
+ View Zenith Angle (degrees).
1524
+ T_A_K : float
1525
+ Air temperature (Kelvin).
1526
+ u : float
1527
+ Wind speed above the canopy (m s-1).
1528
+ ea : float
1529
+ Water vapour pressure above the canopy (mb).
1530
+ p : float
1531
+ Atmospheric pressure (mb), use 1013 mb by default.
1532
+ Sn_C : float
1533
+ Canopy net shortwave radiation (W m-2).
1534
+ Sn_S : float
1535
+ Soil net shortwave radiation (W m-2).
1536
+ L_dn : float
1537
+ Downwelling longwave radiation (W m-2).
1538
+ LAI : float
1539
+ Effective Leaf Area Index (m2 m-2).
1540
+ h_C : float
1541
+ Canopy height (m).
1542
+ emis_C : float
1543
+ Leaf emissivity.
1544
+ emis_S : flaot
1545
+ Soil emissivity.
1546
+ z_0M : float
1547
+ Aerodynamic surface roughness length for momentum transfer (m).
1548
+ d_0 : float
1549
+ Zero-plane displacement height (m).
1550
+ z_u : float
1551
+ Height of measurement of windspeed (m).
1552
+ z_T : float
1553
+ Height of measurement of air temperature (m).
1554
+ leaf_width : float, optional
1555
+ average/effective leaf width (m).
1556
+ z0_soil : float, optional
1557
+ bare soil aerodynamic roughness length (m).
1558
+ alpha_PT : float, optional
1559
+ Priestley Taylor coeffient for canopy potential transpiration,
1560
+ use 1.26 by default.
1561
+ x_LAD : float, optional
1562
+ Campbell 1990 leaf inclination distribution function chi parameter.
1563
+ f_c : float, optional
1564
+ Fractional cover.
1565
+ f_g : float, optional
1566
+ Fraction of vegetation that is green.
1567
+ w_C : float, optional
1568
+ Canopy width to height ratio.
1569
+ resistance_form : int, optional
1570
+ Flag to determine which Resistances R_x, R_S model to use.
1571
+
1572
+ * 0 [Default] Norman et al 1995 and Kustas et al 1999.
1573
+ * 1 : Choudhury and Monteith 1988.
1574
+ * 2 : McNaughton and Van der Hurk 1995.
1575
+
1576
+ calcG_params : list[list,float or array], optional
1577
+ Method to calculate soil heat flux,parameters.
1578
+
1579
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
1580
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
1581
+ * [[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`).
1582
+ const_L : float or None, optional
1583
+ If included, its value will be used to force the Moning-Obukhov stability length.
1584
+
1585
+ Returns
1586
+ -------
1587
+ flag : int
1588
+ Quality flag, see Appendix for description.
1589
+ T_S : float
1590
+ Soil temperature (Kelvin).
1591
+ T_C : float
1592
+ Canopy temperature (Kelvin).
1593
+ T_AC : float
1594
+ Air temperature at the canopy interface (Kelvin).
1595
+ L_nS : float
1596
+ Soil net longwave radiation (W m-2)
1597
+ L_nC : float
1598
+ Canopy net longwave radiation (W m-2)
1599
+ LE_C : float
1600
+ Canopy latent heat flux (W m-2).
1601
+ H_C : float
1602
+ Canopy sensible heat flux (W m-2).
1603
+ LE_S : float
1604
+ Soil latent heat flux (W m-2).
1605
+ H_S : float
1606
+ Soil sensible heat flux (W m-2).
1607
+ G : float
1608
+ Soil heat flux (W m-2).
1609
+ R_S : float
1610
+ Soil aerodynamic resistance to heat transport (s m-1).
1611
+ R_x : float
1612
+ Bulk canopy aerodynamic resistance to heat transport (s m-1).
1613
+ R_A : float
1614
+ Aerodynamic resistance to heat transport (s m-1).
1615
+ u_friction : float
1616
+ Friction velocity (m s-1).
1617
+ L : float
1618
+ Monin-Obuhkov length (m).
1619
+ n_iterations : int
1620
+ number of iterations until convergence of L.
1621
+
1622
+ References
1623
+ ----------
1624
+ .. [Norman1995] J.M. Norman, W.P. Kustas, K.S. Humes, Source approach for estimating
1625
+ soil and vegetation energy fluxes in observations of directional radiometric
1626
+ surface temperature, Agricultural and Forest Meteorology, Volume 77, Issues 3-4,
1627
+ Pages 263-293,
1628
+ http://dx.doi.org/10.1016/0168-1923(95)02265-Y.
1629
+ .. [Kustas1999] William P Kustas, John M Norman, Evaluation of soil and vegetation heat
1630
+ flux predictions using a simple two-source model with radiometric temperatures for
1631
+ partial canopy cover, Agricultural and Forest Meteorology, Volume 94, Issue 1,
1632
+ Pages 13-29,
1633
+ http://dx.doi.org/10.1016/S0168-1923(99)00005-2.
1634
+ '''
1635
+
1636
+
1637
+ if massman_profile is None:
1638
+ massman_profile = [0, []]
1639
+ if calcG_params is None:
1640
+ calcG_params = [[1], 0.35]
1641
+ if resistance_form is None:
1642
+ resistance_form = [0, {}]
1643
+
1644
+ # Convert input float scalars to arrays and parameters size
1645
+ Tr_K = np.asarray(Tr_K)
1646
+ (vza,
1647
+ T_A_K,
1648
+ u,
1649
+ ea,
1650
+ p,
1651
+ Sn_C,
1652
+ Sn_S,
1653
+ L_dn,
1654
+ LAI,
1655
+ h_C,
1656
+ emis_C,
1657
+ emis_S,
1658
+ z_0M,
1659
+ d_0,
1660
+ z_u,
1661
+ z_T,
1662
+ leaf_width,
1663
+ z0_soil,
1664
+ r_c_min,
1665
+ x_LAD,
1666
+ f_c,
1667
+ f_g,
1668
+ w_C,
1669
+ calcG_array) = map(_check_default_parameter_size,
1670
+ [vza,
1671
+ T_A_K,
1672
+ u,
1673
+ ea,
1674
+ p,
1675
+ Sn_C,
1676
+ Sn_S,
1677
+ L_dn,
1678
+ LAI,
1679
+ h_C,
1680
+ emis_C,
1681
+ emis_S,
1682
+ z_0M,
1683
+ d_0,
1684
+ z_u,
1685
+ z_T,
1686
+ leaf_width,
1687
+ z0_soil,
1688
+ r_c_min,
1689
+ x_LAD,
1690
+ f_c,
1691
+ f_g,
1692
+ w_C,
1693
+ calcG_params[1]],
1694
+ [Tr_K] * 24)
1695
+ res_params = resistance_form[1]
1696
+ resistance_form = resistance_form[0]
1697
+ # calcG_params[1] = None
1698
+ # Create the output variables
1699
+ [flag, H, LE, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A,
1700
+ iterations, R_c] = [np.zeros(Tr_K.shape)+np.nan for i in range(13)]
1701
+
1702
+ # iteration of the Monin-Obukhov length
1703
+ if const_L is None:
1704
+ # Initially assume stable atmospheric conditions and set variables for
1705
+ L = np.asarray(np.zeros(Tr_K.shape) + np.inf)
1706
+ max_iterations = ITERATIONS
1707
+ else: # We force Monin-Obukhov lenght to the provided array/value
1708
+ L = np.asarray(np.ones(Tr_K.shape) * const_L)
1709
+ max_iterations = 1 # No iteration
1710
+ # Calculate the general parameters
1711
+ rho = met.calc_rho(p, ea, T_A_K) # Air density
1712
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
1713
+ z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport
1714
+ delta = 10. * met.calc_delta_vapor_pressure(T_A_K) # slope of saturation water vapour pressure in mb K-1
1715
+ lambda_= met.calc_lambda(T_A_K) # latent heat of vaporization MJ kg-1
1716
+ psicr = met.calc_psicr(c_p, p, lambda_) # Psicrometric constant (mb K-1)
1717
+ es = met.calc_vapor_pressure(T_A_K) # saturation water vapour pressure in mb
1718
+
1719
+ rho_cp = rho * c_p
1720
+ vpd = es - ea
1721
+
1722
+ # Calculate LAI dependent parameters for dataset where LAI > 0
1723
+ omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True)
1724
+ F = np.asarray(LAI / f_c) # Real LAI
1725
+ # Fraction of vegetation observed by the sensor
1726
+ f_theta = calc_F_theta_campbell(vza, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
1727
+ del vza
1728
+ # Initially assume stable atmospheric conditions and set variables for
1729
+ # iteration of the Monin-Obukhov length
1730
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
1731
+ u_friction = np.asarray(np.maximum(U_FRICTION_MIN, u_friction))
1732
+ L_queue = deque([np.array(L)], 6)
1733
+ L_converged = np.asarray(np.zeros(Tr_K.shape)).astype(bool)
1734
+ L_diff_max = np.inf
1735
+
1736
+ # First assume that canopy temperature equals the minumum of Air or
1737
+ # radiometric T
1738
+ T_C = np.asarray(np.minimum(Tr_K, T_A_K))
1739
+ flag, T_S = calc_T_S(Tr_K, T_C, f_theta)
1740
+ T_AC = T_A_K.copy()
1741
+
1742
+ # Calculate net longwave radiation with current values of T_C and T_S
1743
+ _, _, _, taudl = rad.calc_spectra_Cambpell(LAI,
1744
+ np.zeros(emis_C.shape),
1745
+ 1.0 - emis_C,
1746
+ np.zeros(emis_S.shape),
1747
+ 1.0 - emis_S,
1748
+ x_lad=x_LAD,
1749
+ lai_eff=None)
1750
+ emiss = taudl * emis_S + (1 - taudl) * emis_C
1751
+
1752
+ Ln = emiss * (L_dn - met.calc_stephan_boltzmann(T_AC))
1753
+ Ln_C = (1. - taudl) * Ln
1754
+ Ln_S = taudl * Ln
1755
+ delta_Rn = Sn_C + Ln_C
1756
+ Rn_S = Sn_S + Ln_S
1757
+ Rn = delta_Rn + Rn_S
1758
+
1759
+ # Outer loop for estimating stability.
1760
+ # Stops when difference in consecutives L is below a given threshold
1761
+ start_time = time.time()
1762
+ loop_time = time.time()
1763
+ for n_iterations in range(max_iterations):
1764
+ i = flag != F_INVALID
1765
+ if np.all(L_converged[i]):
1766
+ if L_converged[i].size == 0:
1767
+ print("Finished iterations with no valid solution")
1768
+ else:
1769
+ print("Finished interations with a max. L diff: " + str(L_diff_max))
1770
+ break
1771
+ current_time = time.time()
1772
+ loop_duration = current_time - loop_time
1773
+ loop_time = current_time
1774
+ total_duration = loop_time - start_time
1775
+ print("Iteration: %d, non-converged pixels: %d, max L diff: %f, total time: %f, loop time: %f" %
1776
+ (n_iterations, np.sum(~L_converged[i]), L_diff_max, total_duration, loop_duration))
1777
+ iterations[np.logical_and(~L_converged, flag != F_INVALID)] = n_iterations
1778
+
1779
+ # Inner loop to iterativelly reduce alpha_PT in case latent heat flux
1780
+ # from the soil is negative. The initial assumption is of potential
1781
+ # canopy transpiration.
1782
+ flag[np.logical_and(~L_converged, flag != F_INVALID)] = F_ALL_FLUXES
1783
+ LE_S[np.logical_and(~L_converged, flag != F_INVALID)] = -1
1784
+ step_rc = STEP_RC
1785
+ r_c = np.full(Tr_K.shape, r_c_min - step_rc)
1786
+
1787
+ while np.any(LE_S[i] < 0):
1788
+ i = np.logical_and.reduce((LE_S < 0,
1789
+ ~L_converged,
1790
+ flag != F_INVALID,
1791
+ r_c <= MAX_RC))
1792
+ r_c[i] += step_rc
1793
+ step_rc += RELATIVE_INCREASE * step_rc
1794
+ # There cannot be negative transpiration from the vegetation
1795
+ flag[np.logical_and(i, r_c > MAX_RC)] = F_ZERO_LE
1796
+
1797
+ # Calculate aerodynamic resistances
1798
+ R_A[i], R_x[i], R_S[i] = calc_resistances(resistance_form,
1799
+ {"R_A": {"z_T": z_T[i],
1800
+ "u_friction": u_friction[i],
1801
+ "L": L[i],
1802
+ "d_0": d_0[i],
1803
+ "z_0H": z_0H[i]},
1804
+ "R_x": {"u_friction": u_friction[i],
1805
+ "h_C": h_C[i],
1806
+ "d_0": d_0[i],
1807
+ "z_0M": z_0M[i],
1808
+ "L": L[i],
1809
+ "F": F[i],
1810
+ "LAI": LAI[i],
1811
+ "leaf_width": leaf_width[i],
1812
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1813
+ "massman_profile": massman_profile},
1814
+ "R_S": {"u_friction": u_friction[i],
1815
+ "h_C": h_C[i],
1816
+ "d_0": d_0[i],
1817
+ "z_0M": z_0M[i],
1818
+ "L": L[i],
1819
+ "F": F[i],
1820
+ "omega0": omega0[i],
1821
+ "LAI": LAI[i],
1822
+ "leaf_width": leaf_width[i],
1823
+ "z0_soil": z0_soil[i],
1824
+ "z_u": z_u[i],
1825
+ "deltaT": T_S[i] - T_AC[i],
1826
+ 'u': u[i],
1827
+ 'rho': rho[i],
1828
+ "c_p": c_p[i],
1829
+ "f_cover": f_c[i],
1830
+ "w_C": w_C[i],
1831
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1832
+ "massman_profile": massman_profile}
1833
+ }
1834
+ )
1835
+
1836
+
1837
+
1838
+
1839
+ # Compute Soil Heat Flux Ratio
1840
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn_S, i)
1841
+
1842
+ # Eq. B1 in [Colaizzi2012]_
1843
+ gamma_star = psicr[i] * (1. + r_c[i] / R_A[i])
1844
+ LE_C[i] = f_g[i] * (delta[i] * delta_Rn[i] / (delta[i] + gamma_star) +
1845
+ (rho[i] * c_p[i] * vpd[i])
1846
+ / (R_A[i] * (delta[i] + gamma_star)))
1847
+
1848
+ H_C[i] = delta_Rn[i] - LE_C[i]
1849
+
1850
+ T_C[i] = calc_T_C_series(Tr_K[i], T_A_K[i], R_A[i], R_x[i], R_S[i],
1851
+ f_theta[i], H_C[i], rho[i], c_p[i])
1852
+
1853
+
1854
+ # Calculate soil temperature
1855
+ flag_t = np.zeros(flag.shape) + F_ALL_FLUXES
1856
+ flag_t[i], T_S[i] = calc_T_S(Tr_K[i], T_C[i], f_theta[i])
1857
+ flag[flag_t == F_INVALID] = F_INVALID
1858
+ LE_S[flag_t == F_INVALID] = 0
1859
+
1860
+ # Recalculate soil resistance using new soil temperature
1861
+ _, _, R_S[i] = calc_resistances(resistance_form, {"R_S": {"u_friction": u_friction[i],
1862
+ "h_C": h_C[i],
1863
+ "d_0": d_0[i],
1864
+ "z_0M": z_0M[i],
1865
+ "L": L[i],
1866
+ "F": F[i],
1867
+ "omega0": omega0[i],
1868
+ "LAI": LAI[i],
1869
+ "leaf_width": leaf_width[i],
1870
+ "z0_soil": z0_soil[i],
1871
+ "z_u": z_u[i],
1872
+ "deltaT": T_S[i] - T_AC[i],
1873
+ "u": u[i],
1874
+ "rho": rho[i],
1875
+ "c_p": c_p[i],
1876
+ "f_cover": f_c[i],
1877
+ "w_C": w_C[i],
1878
+ "res_params": {k: res_params[k][i] for k in res_params.keys()},
1879
+ "massman_profile": massman_profile}
1880
+ }
1881
+ )
1882
+
1883
+ i = np.logical_and.reduce((LE_S < 0, ~L_converged, flag != F_INVALID))
1884
+ # Get air temperature at canopy interface
1885
+ T_AC[i] = ((T_A_K[i] / R_A[i] + T_S[i] / R_S[i] + T_C[i] / R_x[i])
1886
+ / (1.0 / R_A[i] + 1.0 / R_S[i] + 1.0 / R_x[i]))
1887
+
1888
+ # Calculate heat fluxes
1889
+ H_S[i] = rho[i] * c_p[i] * (T_S[i] - T_AC[i]) / R_S[i]
1890
+ H_C[i] = rho[i] * c_p[i] * (T_C[i] - T_AC[i]) / R_x[i]
1891
+
1892
+ # Calculate net longwave radiation with current values of T_C and T_S
1893
+ Ln_C[i], Ln_S[i] = rad.calc_L_n_Campbell(
1894
+ T_C[i], T_S[i], L_dn[i], LAI[i], emis_C[i], emis_S[i], x_LAD=x_LAD[i])
1895
+
1896
+ delta_Rn[i] = Sn_C[i] + Ln_C[i]
1897
+ Rn_S[i] = Sn_S[i] + Ln_S[i]
1898
+ Rn[i] = delta_Rn[i] + Rn_S[i]
1899
+ # Estimate latent heat fluxes as residual of energy balance at the
1900
+ # soil and the canopy
1901
+ LE_S[i] = Rn_S[i] - G[i] - H_S[i]
1902
+ LE_C[i] = delta_Rn[i] - H_C[i]
1903
+
1904
+ # Special case if there is no transpiration from vegetation.
1905
+ # In that case, there should also be no evaporation from the soil
1906
+ # and the energy at the soil should be conserved.
1907
+ # See end of appendix A1 in Guzinski et al. (2015).
1908
+ noT = np.logical_and(i, r_c > MAX_RC)
1909
+ H_S[noT] = np.minimum(H_S[noT], Rn_S[noT] - G[noT])
1910
+ G[noT] = np.maximum(G[noT], Rn_S[noT] - H_S[noT])
1911
+ LE_S[noT] = 0
1912
+
1913
+ # Calculate total fluxes
1914
+ H[i] = np.asarray(H_C[i] + H_S[i])
1915
+ LE[i] = np.asarray(LE_C[i] + LE_S[i])
1916
+ # Now L can be recalculated and the difference between iterations
1917
+ # derived
1918
+ if const_L is None:
1919
+ L[i] = MO.calc_L(u_friction[i], T_A_K[i], rho[i], c_p[i], H[i], LE[i])
1920
+ # Calculate again the friction velocity with the new stability
1921
+ # corrections
1922
+ u_friction[i] = MO.calc_u_star(
1923
+ u[i], z_u[i], L[i], d_0[i], z_0M[i])
1924
+ u_friction[i] = np.asarray(np.maximum(U_FRICTION_MIN, u_friction[i]))
1925
+
1926
+ if const_L is None:
1927
+ # We check convergence against the value of L from previous iteration but as well
1928
+ # against values from 2 or 3 iterations back. This is to catch situations (not
1929
+ # infrequent) where L oscillates between 2 or 3 steady state values.
1930
+ i, L_queue, L_converged, L_diff_max = monin_obukhov_convergence(L,
1931
+ L_queue,
1932
+ L_converged,
1933
+ flag)
1934
+
1935
+ (flag,
1936
+ T_S,
1937
+ T_C,
1938
+ T_AC,
1939
+ L_nS,
1940
+ L_nC,
1941
+ LE_C,
1942
+ H_C,
1943
+ LE_S,
1944
+ H_S,
1945
+ G,
1946
+ R_S,
1947
+ R_x,
1948
+ R_A,
1949
+ u_friction,
1950
+ L,
1951
+ n_iterations) = map(np.asarray,
1952
+ (flag,
1953
+ T_S,
1954
+ T_C,
1955
+ T_AC,
1956
+ Ln_S,
1957
+ Ln_C,
1958
+ LE_C,
1959
+ H_C,
1960
+ LE_S,
1961
+ H_S,
1962
+ G,
1963
+ R_S,
1964
+ R_x,
1965
+ R_A,
1966
+ u_friction,
1967
+ L,
1968
+ iterations))
1969
+
1970
+ return (flag, T_S, T_C, T_AC, L_nS, L_nC, LE_C, H_C, LE_S, H_S, G, R_S, R_x, R_A, u_friction,
1971
+ L, n_iterations)
1972
+
1973
+
1974
+ def _L_diff(L, L_old):
1975
+ L_diff = np.asarray(np.fabs(L - L_old) / np.fabs(L_old), dtype=np.float32)
1976
+ L_diff[np.isnan(L_diff)] = float('inf')
1977
+ return L_diff
1978
+
1979
+
1980
+ def DTD(Tr_K_0,
1981
+ Tr_K_1,
1982
+ vza,
1983
+ T_A_K_0,
1984
+ T_A_K_1,
1985
+ u,
1986
+ ea,
1987
+ p,
1988
+ Sn_C,
1989
+ Sn_S,
1990
+ L_dn,
1991
+ LAI,
1992
+ h_C,
1993
+ emis_C,
1994
+ emis_S,
1995
+ z_0M,
1996
+ d_0,
1997
+ z_u,
1998
+ z_T,
1999
+ leaf_width=0.1,
2000
+ z0_soil=0.01,
2001
+ alpha_PT=1.26,
2002
+ x_LAD=1,
2003
+ f_c=1.0,
2004
+ f_g=1.0,
2005
+ w_C=1.0,
2006
+ resistance_form=None,
2007
+ calcG_params=None,
2008
+ calc_Ri=True,
2009
+ kB=KB_1_DEFAULT,
2010
+ massman_profile=None,
2011
+ verbose=True):
2012
+ ''' Calculate daytime Dual Time Difference TSEB fluxes
2013
+
2014
+ Parameters
2015
+ ----------
2016
+ Tr_K_0 : float
2017
+ Radiometric composite temperature around sunrise(Kelvin).
2018
+ Tr_K_1 : float
2019
+ Radiometric composite temperature near noon (Kelvin).
2020
+ vza : float
2021
+ View Zenith Angle near noon (degrees).
2022
+ T_A_K_0 : float
2023
+ Air temperature around sunrise (Kelvin).
2024
+ T_A_K_1 : float
2025
+ Air temperature near noon (Kelvin).
2026
+ u : float
2027
+ Wind speed above the canopy (m s-1).
2028
+ ea : float
2029
+ Water vapour pressure above the canopy (mb).
2030
+ p : float
2031
+ Atmospheric pressure (mb), use 1013 mb by default.
2032
+ Sn_C : float
2033
+ Canopy net shortwave radiation (W m-2).
2034
+ Sn_S : float
2035
+ Soil net shortwave radiation (W m-2).
2036
+ L_dn : float
2037
+ Downwelling longwave radiation (W m-2).
2038
+ LAI : float
2039
+ Effective Leaf Area Index (m2 m-2).
2040
+ h_C : float
2041
+ Canopy height (m).
2042
+ emis_C : float
2043
+ Leaf emissivity.
2044
+ emis_S : flaot
2045
+ Soil emissivity.
2046
+ z_0M : float
2047
+ Aerodynamic surface roughness length for momentum transfer (m).
2048
+ d_0 : float
2049
+ Zero-plane displacement height (m).
2050
+ z_u : float
2051
+ Height of measurement of windspeed (m).
2052
+ z_T : float
2053
+ Height of measurement of air temperature (m).
2054
+ leaf_width : Optional[float]
2055
+ average/effective leaf width (m).
2056
+ z0_soil : Optional[float]
2057
+ bare soil aerodynamic roughness length (m).
2058
+ alpha_PT : Optional[float]
2059
+ Priestley Taylor coeffient for canopy potential transpiration,
2060
+ use 1.26 by default.
2061
+ x_LAD : Optional[float]
2062
+ Campbell 1990 leaf inclination distribution function chi parameter.
2063
+ f_c : Optiona;[float]
2064
+ Fractional cover.
2065
+ f_g : Optional[float]
2066
+ Fraction of vegetation that is green.
2067
+ w_C : Optional[float]
2068
+ Canopy width to height ratio.
2069
+ resistance_form : int, optional
2070
+ Flag to determine which Resistances R_x, R_S model to use.
2071
+
2072
+ * 0 [Default] Norman et al 1995 and Kustas et al 1999.
2073
+ * 1 : Choudhury and Monteith 1988.
2074
+ * 2 : McNaughton and Van der Hurk 1995.
2075
+
2076
+ calcG_params : list[list,float or array], optional
2077
+ Method to calculate soil heat flux,parameters.
2078
+
2079
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
2080
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
2081
+ * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with
2082
+ G_param list of parameters
2083
+ (see :func:`~TSEB.calc_G_time_diff`).
2084
+ calc_Ri : float or None, optional
2085
+ If included, its value will be used to force the Richardson Number.
2086
+
2087
+ Returns
2088
+ -------
2089
+ flag : int
2090
+ Quality flag, see Appendix for description.
2091
+ T_S : float
2092
+ Soil temperature (Kelvin).
2093
+ T_C : float
2094
+ Canopy temperature (Kelvin).
2095
+ T_AC : float
2096
+ Air temperature at the canopy interface (Kelvin).
2097
+ L_nS : float
2098
+ Soil net longwave radiation (W m-2).
2099
+ L_nC : float
2100
+ Canopy net longwave radiation (W m-2).
2101
+ LE_C : float
2102
+ Canopy latent heat flux (W m-2).
2103
+ H_C : float
2104
+ Canopy sensible heat flux (W m-2).
2105
+ LE_S : float
2106
+ Soil latent heat flux (W m-2).
2107
+ H_S : float
2108
+ Soil sensible heat flux (W m-2).
2109
+ G : float
2110
+ Soil heat flux (W m-2).
2111
+ R_S : float
2112
+ Soil aerodynamic resistance to heat transport (s m-1).
2113
+ R_x : float
2114
+ Bulk canopy aerodynamic resistance to heat transport (s m-1).
2115
+ R_A : float
2116
+ Aerodynamic resistance to heat transport (s m-1).
2117
+ u_friction : float
2118
+ Friction velocity (m s-1).
2119
+ L : float
2120
+ Monin-Obuhkov length (m).
2121
+ Ri : float
2122
+ Richardson number.
2123
+ n_iterations : int
2124
+ number of iterations until convergence of L.
2125
+
2126
+ References
2127
+ ----------
2128
+ .. [Norman2000] Norman, J. M., W. P. Kustas, J. H. Prueger, and G. R. Diak (2000),
2129
+ Surface flux estimation using radiometric temperature: A dual-temperature-difference
2130
+ method to minimize measurement errors, Water Resour. Res., 36(8), 2263-2274,
2131
+ http://dx.doi.org/10.1029/2000WR900033.
2132
+ .. [Guzinski2015] Guzinski, R., Nieto, H., Stisen, S., and Fensholt, R. (2015) Inter-comparison
2133
+ of energy balance and hydrological models for land surface energy flux estimation over
2134
+ a whole river catchment, Hydrol. Earth Syst. Sci., 19, 2017-2036,
2135
+ http://dx.doi.org/10.5194/hess-19-2017-2015.
2136
+ '''
2137
+
2138
+ # Convert input scalars to numpy arrays and parameters size
2139
+ if calcG_params is None:
2140
+ calcG_params = [[1], 0.35]
2141
+ if resistance_form is None:
2142
+ resistance_form = [0, {}]
2143
+ if massman_profile is None:
2144
+ massman_profile = [0, []]
2145
+
2146
+ Tr_K_0 = np.asarray(Tr_K_0)
2147
+ (Tr_K_1,
2148
+ vza,
2149
+ T_A_K_0,
2150
+ T_A_K_1,
2151
+ u,
2152
+ ea,
2153
+ p,
2154
+ Sn_C,
2155
+ Sn_S,
2156
+ L_dn,
2157
+ LAI,
2158
+ h_C,
2159
+ emis_C,
2160
+ emis_S,
2161
+ z_0M,
2162
+ d_0,
2163
+ z_u,
2164
+ z_T,
2165
+ leaf_width,
2166
+ z0_soil,
2167
+ alpha_PT,
2168
+ x_LAD,
2169
+ f_c,
2170
+ f_g,
2171
+ w_C,
2172
+ calcG_array) = map(_check_default_parameter_size,
2173
+ [Tr_K_1,
2174
+ vza,
2175
+ T_A_K_0,
2176
+ T_A_K_1,
2177
+ u,
2178
+ ea,
2179
+ p,
2180
+ Sn_C,
2181
+ Sn_S,
2182
+ L_dn,
2183
+ LAI,
2184
+ h_C,
2185
+ emis_C,
2186
+ emis_S,
2187
+ z_0M,
2188
+ d_0,
2189
+ z_u,
2190
+ z_T,
2191
+ leaf_width,
2192
+ z0_soil,
2193
+ alpha_PT,
2194
+ x_LAD,
2195
+ f_c,
2196
+ f_g,
2197
+ w_C,
2198
+ calcG_params[1]],
2199
+ [Tr_K_0] * 26)
2200
+ res_params = resistance_form[1]
2201
+ resistance_form = resistance_form[0]
2202
+ # Create the output variables
2203
+ [flag, T_S, T_C, T_AC, Ln_S, Ln_C, LE_C, H_C, LE_S, H_S, G, R_S, R_x,
2204
+ R_A, H, iterations] = [np.zeros(Tr_K_1.shape, np.float32) + np.nan for i in range(16)]
2205
+
2206
+ # Calculate the general parameters
2207
+ rho = met.calc_rho(p, ea, T_A_K_1) # Air density
2208
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
2209
+ z_0H = res.calc_z_0H(z_0M, kB=kB) # Roughness length for heat transport
2210
+
2211
+ # Calculate LAI dependent parameters for dataset where LAI > 0
2212
+ # Clumping factor at nadir
2213
+ omega0 = CI.calc_omega0_Kustas(LAI, f_c, x_LAD=x_LAD, isLAIeff=True)
2214
+ F = np.asarray(LAI / f_c) # Real LAI
2215
+ # Fraction of vegetation observed by the sensor
2216
+ f_theta = calc_F_theta_campbell(vza, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
2217
+
2218
+ # L is not used in the DTD, since Richardson number is used instead to
2219
+ # avoid dependance on non-differential temperatures. But it is still saved
2220
+ # in the output for testing purposes.
2221
+ if isinstance(calc_Ri, bool):
2222
+ # Calculate the Richardson number
2223
+ Ri = MO.calc_richardson(u, z_u, d_0, Tr_K_0, Tr_K_1, T_A_K_0, T_A_K_1)
2224
+ else: # We force Monin-Obukhov lenght to the provided array/value
2225
+ Ri = np.asarray(np.ones(Tr_K_1.shape) * calc_Ri)
2226
+ # Use the approximation Ri ~ (z-d_0)./L from end of section 2.2 from
2227
+ # Norman et. al., 2000 (DTD paper)
2228
+ L_from_Ri = (z_u - d_0) / Ri
2229
+
2230
+ # calculate the resistances
2231
+ # First calcualte u_S, wind speed at the soil surface
2232
+ u_friction = MO.calc_u_star(u, z_u, L_from_Ri, d_0, z_0M)
2233
+ u_friction = np.asarray(np.maximum(U_FRICTION_MIN, u_friction))
2234
+
2235
+ # First assume that canopy temperature equals the minumum of Air or
2236
+ # radiometric T
2237
+ T_C = np.asarray(np.minimum(Tr_K_1, T_A_K_1))
2238
+ flag, T_S = calc_T_S(Tr_K_1, T_C, f_theta)
2239
+
2240
+ # Calculate aerodynamic resistances
2241
+ R_A_params = {"z_T": z_T, "u_friction": u_friction,
2242
+ "L": L_from_Ri, "d_0": d_0, "z_0H": z_0H}
2243
+ params = {k: res_params[k] for k in res_params.keys()}
2244
+ R_x_params = {"u_friction": u_friction, "h_C": h_C, "d_0": d_0,
2245
+ "z_0M": z_0M, "L": L_from_Ri, "F": F, "LAI": LAI,
2246
+ "leaf_width": leaf_width,
2247
+ "z0_soil": z0_soil,
2248
+ "massman_profile": massman_profile,
2249
+ "res_params": params}
2250
+ # based on equation from Guzinski et. al., 2015
2251
+ deltaT = (Tr_K_1 - Tr_K_0) - (T_A_K_1 - T_A_K_0)
2252
+ R_S_params = {"u_friction": u_friction, "h_C": h_C, "d_0": d_0,
2253
+ "z_0M": z_0M, "L": L_from_Ri, "F": F,
2254
+ "omega0": omega0, "LAI": LAI,
2255
+ "leaf_width": leaf_width, "z0_soil": z0_soil, "z_u": z_u,
2256
+ "deltaT": deltaT,
2257
+ "massman_profile": massman_profile,
2258
+ "res_params": params}
2259
+ res_types = {"R_A": R_A_params, "R_x": R_x_params, "R_S": R_S_params}
2260
+ del R_A_params, R_x_params, R_S_params
2261
+ R_A, R_x, R_S = calc_resistances(resistance_form, res_types)
2262
+ del res_types
2263
+
2264
+ # Outer loop until canopy and soil temperatures have stabilised
2265
+ T_C_prev = np.zeros(Tr_K_1.shape)
2266
+ T_C_thres = 0.1
2267
+ T_C_diff = np.fabs(T_C - T_C_prev)
2268
+ for n_iterations in range(ITERATIONS):
2269
+ i = flag != F_INVALID
2270
+ if np.all(T_C_diff[i] < T_C_thres):
2271
+ if verbose:
2272
+ if T_C_diff[i].size == 0:
2273
+ print("Finished iterations with no valid solution")
2274
+ else:
2275
+ print(f"Finished iteration with a max. T_C diff: {np.max(T_C_diff[i])}")
2276
+ break
2277
+ if verbose:
2278
+ print(f"Iteration {n_iterations},"
2279
+ f"maximum T_C difference between iterations: {np.max(T_C_diff[i])}")
2280
+ iterations[np.logical_and(T_C_diff >= T_C_thres, flag != F_INVALID)] = n_iterations
2281
+
2282
+ # Inner loop to iterativelly reduce alpha_PT in case latent heat flux
2283
+ # from the soil is negative. The initial assumption is of potential
2284
+ # canopy transpiration.
2285
+ flag[np.logical_and(T_C_diff >= T_C_thres, flag != F_INVALID)] = F_ALL_FLUXES
2286
+ LE_S[np.logical_and(T_C_diff >= T_C_thres, flag != F_INVALID)] = -1
2287
+ alpha_PT_rec = np.asarray(alpha_PT + 0.1)
2288
+
2289
+ while np.any(LE_S[i] < 0):
2290
+ i = np.logical_and.reduce(
2291
+ (LE_S < 0, T_C_diff >= T_C_thres, flag != F_INVALID))
2292
+
2293
+ alpha_PT_rec[i] -= 0.1
2294
+
2295
+ # There cannot be negative transpiration from the vegetation
2296
+ alpha_PT_rec[alpha_PT_rec <= 0.0] = 0.0
2297
+ flag[np.logical_and(i, alpha_PT_rec == 0.0)] = F_ZERO_LE
2298
+
2299
+ flag[np.logical_and.reduce((i, alpha_PT_rec < alpha_PT, alpha_PT_rec > 0.0))] =\
2300
+ F_ZERO_LE_S
2301
+
2302
+ # Calculate net longwave radiation with current values of T_C and T_S
2303
+ Ln_C[i], Ln_S[i] = rad.calc_L_n_Campbell(
2304
+ T_C[i], T_S[i], L_dn[i], LAI[i], emis_C[i], emis_S[i], x_LAD=x_LAD[i])
2305
+
2306
+ # Calculate total net radiation of soil and canopy
2307
+ delta_Rn = Sn_C + Ln_C
2308
+ Rn_S = Sn_S + Ln_S
2309
+
2310
+ # Calculate sensible heat fluxes at time t1
2311
+ H_C[i] = calc_H_C_PT(
2312
+ delta_Rn[i],
2313
+ f_g[i],
2314
+ T_A_K_1[i],
2315
+ p[i],
2316
+ c_p[i],
2317
+ alpha_PT_rec[i])
2318
+ H[i] = calc_H_DTD_series(
2319
+ Tr_K_1[i],
2320
+ Tr_K_0[i],
2321
+ T_A_K_1[i],
2322
+ T_A_K_0[i],
2323
+ rho[i],
2324
+ c_p[i],
2325
+ f_theta[i],
2326
+ R_S[i],
2327
+ R_A[i],
2328
+ R_x[i],
2329
+ H_C[i])
2330
+ H_S[i] = H[i] - H_C[i]
2331
+
2332
+ # Calculate ground heat flux
2333
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn_S, i)
2334
+
2335
+ # Calculate latent heat fluxes as residuals
2336
+ LE_S[i] = Rn_S[i] - H_S[i] - G[i]
2337
+ LE_C[i] = delta_Rn[i] - H_C[i]
2338
+
2339
+ # Special case if there is no transpiration from vegetation.
2340
+ # In that case, there should also be no evaporation from the soil
2341
+ # and the energy at the soil should be conserved.
2342
+ # See end of appendix A1 in Guzinski et al. (2015).
2343
+ noT = np.logical_and(i, LE_C == 0)
2344
+ H_S[noT] = np.minimum(H_S[noT], Rn_S[noT] - G[noT])
2345
+ G[noT] = np.maximum(G[noT], Rn_S[noT] - H_S[noT])
2346
+ LE_S[noT] = 0
2347
+
2348
+ # Recalculate soil and canopy temperatures. They are used only for
2349
+ # estimation of longwave radiation, so the use of non-differential Tr
2350
+ # and T_A shouldn't affect the turbulent fluxes much
2351
+ T_C[i] = calc_T_C_series(
2352
+ Tr_K_1[i],
2353
+ T_A_K_1[i],
2354
+ R_A[i],
2355
+ R_x[i],
2356
+ R_S[i],
2357
+ f_theta[i],
2358
+ H_C[i],
2359
+ rho[i],
2360
+ c_p[i])
2361
+ flag_t = np.zeros(flag.shape) + F_ALL_FLUXES
2362
+ flag_t[i], T_S[i] = calc_T_S(Tr_K_1[i], T_C[i], f_theta[i])
2363
+ flag[flag_t == F_INVALID] = F_INVALID
2364
+ LE_S[flag_t == F_INVALID] = 0
2365
+
2366
+ # Recalculate soil resistance using new difference between soil
2367
+ # and canopy temperatures. deltaT is equivalent to T_S - T_C while
2368
+ # not being dependent on non-differential T_A.
2369
+ params = {k: res_params[k][i] for k in res_params.keys()}
2370
+ deltaT = (H_S[i] * R_S[i] - H_C[i] * R_x[i]) / (rho[i] * c_p[i])
2371
+ R_S_params = {"u_friction": u_friction[i], "h_C": h_C[i], "d_0": d_0[i],
2372
+ "z_0M": z_0M[i], "L": L_from_Ri[i], "F": F[i], "omega0": omega0[i],
2373
+ "LAI": LAI[i], "leaf_width": leaf_width[i],
2374
+ "z0_soil": z0_soil[i], "z_u": z_u[i],
2375
+ "deltaT": deltaT,
2376
+ "massman_profile": massman_profile,
2377
+ "res_params": params}
2378
+ _, _, R_S[i] = calc_resistances(resistance_form, {"R_S": R_S_params})
2379
+
2380
+ T_C_diff = np.asarray(np.fabs(T_C - T_C_prev))
2381
+ T_C_prev = np.array(T_C)
2382
+
2383
+ # L and T_AC are only calculated for testing purposes
2384
+ L = MO.calc_L(u_friction, T_A_K_1, rho, c_p, H, LE_C + LE_S)
2385
+ T_AC = ((T_A_K_1 / R_A + T_S / R_S + T_C / R_x)
2386
+ / (1.0 / R_A + 1.0 / R_S + 1.0 / R_x))
2387
+
2388
+ (flag,
2389
+ T_S,
2390
+ T_C,
2391
+ T_AC,
2392
+ L_nS,
2393
+ L_nC,
2394
+ LE_C,
2395
+ H_C,
2396
+ LE_S,
2397
+ H_S,
2398
+ G,
2399
+ R_S,
2400
+ R_x,
2401
+ R_A,
2402
+ u_friction,
2403
+ L,
2404
+ Ri,
2405
+ n_iterations) = map(np.asarray,
2406
+ (flag,
2407
+ T_S,
2408
+ T_C,
2409
+ T_AC,
2410
+ Ln_S,
2411
+ Ln_C,
2412
+ LE_C,
2413
+ H_C,
2414
+ LE_S,
2415
+ H_S,
2416
+ G,
2417
+ R_S,
2418
+ R_x,
2419
+ R_A,
2420
+ u_friction,
2421
+ L,
2422
+ Ri,
2423
+ iterations))
2424
+ return [
2425
+ flag,
2426
+ T_S,
2427
+ T_C,
2428
+ T_AC,
2429
+ L_nS,
2430
+ L_nC,
2431
+ LE_C,
2432
+ H_C,
2433
+ LE_S,
2434
+ H_S,
2435
+ G,
2436
+ R_S,
2437
+ R_x,
2438
+ R_A,
2439
+ u_friction,
2440
+ L,
2441
+ Ri,
2442
+ n_iterations]
2443
+
2444
+
2445
+ def OSEB(Tr_K,
2446
+ T_A_K,
2447
+ u,
2448
+ ea,
2449
+ p,
2450
+ Sn,
2451
+ L_dn,
2452
+ emis,
2453
+ z_0M,
2454
+ d_0,
2455
+ z_u,
2456
+ z_T,
2457
+ calcG_params=[
2458
+ [1],
2459
+ 0.35],
2460
+ const_L=None,
2461
+ T0_K=[],
2462
+ kB=KB_1_DEFAULT):
2463
+ '''Calulates bulk fluxes from a One Source Energy Balance model
2464
+
2465
+ Parameters
2466
+ ----------
2467
+ Tr_K : float or array
2468
+ Radiometric composite temperature (Kelvin).
2469
+ T_A_K : float or array
2470
+ Air temperature (Kelvin).
2471
+ u : float or array
2472
+ Wind speed above the canopy (m s-1).
2473
+ ea : float or array
2474
+ Water vapour pressure above the canopy (mb).
2475
+ p : float or array
2476
+ Atmospheric pressure (mb), use 1013 mb by default.
2477
+ Sn : float or array
2478
+ Net shortwave radiation (W m-2).
2479
+ L_dn : float or array
2480
+ Downwelling longwave radiation (W m-2)
2481
+ emis : float or array
2482
+ Surface emissivity.
2483
+ z_0M : float or array
2484
+ Aerodynamic surface roughness length for momentum transfer (m).
2485
+ d_0 : float or array
2486
+ Zero-plane displacement height (m).
2487
+ z_u : float or array
2488
+ Height of measurement of windspeed (m).
2489
+ z_T : float or array
2490
+ Height of measurement of air temperature (m).
2491
+ calcG_params : list[list,float or array], optional
2492
+ Method to calculate soil heat flux,parameters.
2493
+
2494
+ * [[1],G_ratio]: default, estimate G as a ratio of Rn_S, default Gratio=0.35.
2495
+ * [[0],G_constant] : Use a constant G, usually use 0 to ignore the computation of G.
2496
+ * [[2,Amplitude,phase_shift,shape],time] : estimate G from Santanello and Friedl with
2497
+ G_param list of parameters
2498
+ (see :func:`~TSEB.calc_G_time_diff`).
2499
+ const_L : Optional[float]
2500
+ If included, its value will be used to force the Moning-Obukhov stability length.
2501
+ T0_K: Optional[tuple(float or array,float or array)]
2502
+ If given it contains radiometric composite temperature (K) at time 0 as
2503
+ the first element and air temperature (K) at time 0 as the second element,
2504
+ in order to derive differential temperatures like is done in DTD
2505
+
2506
+
2507
+ Returns
2508
+ -------
2509
+ flag : int or array
2510
+ Quality flag, see Appendix for description.
2511
+ Ln : float or array
2512
+ Net longwave radiation (W m-2)
2513
+ LE : float or array
2514
+ Latent heat flux (W m-2).
2515
+ H : float or array
2516
+ Sensible heat flux (W m-2).
2517
+ G : float or array
2518
+ Soil heat flux (W m-2).
2519
+ R_A : float or array
2520
+ Aerodynamic resistance to heat transport (s m-1).
2521
+ u_friction : float or array
2522
+ Friction velocity (m s-1).
2523
+ L : float or array
2524
+ Monin-Obuhkov length (m).
2525
+ n_iterations : int or array
2526
+ number of iterations until convergence of L.
2527
+ '''
2528
+
2529
+ # Convert input scalars to numpy arrays and check parameters size
2530
+ Tr_K = np.asarray(Tr_K)
2531
+ (T_A_K,
2532
+ u,
2533
+ ea,
2534
+ p,
2535
+ Sn,
2536
+ L_dn,
2537
+ emis,
2538
+ z_0M,
2539
+ d_0,
2540
+ z_u,
2541
+ z_T,
2542
+ calcG_array) = map(_check_default_parameter_size,
2543
+ [T_A_K,
2544
+ u,
2545
+ ea,
2546
+ p,
2547
+ Sn,
2548
+ L_dn,
2549
+ emis,
2550
+ z_0M,
2551
+ d_0,
2552
+ z_u,
2553
+ z_T,
2554
+ calcG_params[1]],
2555
+ [Tr_K] * 12)
2556
+ # Create the output variables
2557
+ [flag, Ln, LE, H, G, R_A] = [np.zeros(Tr_K.shape, np.float32) + np.nan for i in range(6)]
2558
+
2559
+ # iteration of the Monin-Obukhov length
2560
+ if const_L is None:
2561
+ # Initially assume stable atmospheric conditions and set variables for
2562
+ L = np.zeros(Tr_K.shape) + np.inf
2563
+ max_iterations = ITERATIONS
2564
+ else: # We force Monin-Obukhov lenght to the provided array/value
2565
+ L = np.ones(Tr_K.shape) * const_L
2566
+ max_iterations = 1 # No iteration
2567
+
2568
+ # Check if differential temperatures are to be used
2569
+ if len(T0_K) == 2:
2570
+ differentialT = True
2571
+ Tr_K_0 = np.asarray(T0_K[0])
2572
+ T_A_K_0 = np.asarray(T0_K[1])
2573
+ else:
2574
+ differentialT = False
2575
+
2576
+ # Initially assume stable atmospheric conditions and set variables for
2577
+ L_old = np.ones(Tr_K.shape)
2578
+ # Calculate the general parameters
2579
+ rho = met.calc_rho(p, ea, T_A_K) # Air density
2580
+ c_p = met.calc_c_p(p, ea) # Heat capacity of air
2581
+
2582
+ # With differential temperatures use Richardson number to approximate L,
2583
+ # same as is done in DTD
2584
+ if differentialT:
2585
+ if const_L is None:
2586
+ Ri = MO.calc_richardson(u, z_u, d_0, Tr_K_0, Tr_K, T_A_K_0, T_A_K)
2587
+ else:
2588
+ Ri = np.array(L)
2589
+ # Use the approximation Ri ~ (z-d_0)./L from end of section 2.2 from
2590
+ # Norman et. al., 2000 (DTD paper)
2591
+ L_from_Ri = (z_u - d_0) / Ri
2592
+ u_friction = MO.calc_u_star(u, z_u, L_from_Ri, d_0, z_0M)
2593
+ else:
2594
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
2595
+ u_friction = np.maximum(U_FRICTION_MIN, u_friction)
2596
+ L_old = np.ones(Tr_K.shape)
2597
+ L_diff = np.ones(Tr_K.shape) * float('inf')
2598
+
2599
+ z_0H = res.calc_z_0H(z_0M, kB=kB)
2600
+
2601
+ # Calculate Net radiation
2602
+ Ln = emis * L_dn - emis * met.calc_stephan_boltzmann(Tr_K)
2603
+ Rn = np.asarray(Sn + Ln)
2604
+
2605
+ # Compute Soil Heat Flux
2606
+ i = np.ones(Rn.shape, dtype=bool)
2607
+ G[i] = calc_G([calcG_params[0], calcG_array], Rn, i)
2608
+
2609
+ # Loop for estimating atmospheric stability.
2610
+ # Stops when difference in consecutive L and u_friction is below a
2611
+ # given threshold
2612
+ for n_iterations in range(max_iterations):
2613
+ flag = np.zeros(Tr_K.shape) + F_ALL_FLUXES_OS
2614
+ # Stop the iteration if differences are below the threshold
2615
+ if np.all(L_diff < L_thres):
2616
+ break
2617
+
2618
+ # Calculate aerodynamic resistances
2619
+ if differentialT:
2620
+ R_A_params = {"z_T": z_T, "u_friction": u_friction,
2621
+ "L": L_from_Ri, "d_0": d_0, "z_0H": z_0H}
2622
+ else:
2623
+ R_A_params = {"z_T": z_T, "u_friction": u_friction,
2624
+ "L": L, "d_0": d_0, "z_0H": z_0H}
2625
+ R_A, _, _ = calc_resistances(KUSTAS_NORMAN_1999, {"R_A": R_A_params})
2626
+
2627
+ # Calculate bulk fluxes assuming that since there is no vegetation,
2628
+ # Tr is the heat source
2629
+ if differentialT:
2630
+ H = rho * c_p * ((Tr_K - Tr_K_0) - (T_A_K - T_A_K_0)) / R_A
2631
+ else:
2632
+ H = rho * c_p * (Tr_K - T_A_K) / R_A
2633
+ H = np.asarray(H)
2634
+ LE = np.asarray(Rn - G - H)
2635
+
2636
+ # Avoid negative ET during daytime and make sure that energy is
2637
+ # conserved
2638
+ flag[LE < 0] = F_ZERO_LE_OS
2639
+ H[LE < 0] = np.minimum(H[LE < 0], Rn[LE < 0] - G[LE < 0])
2640
+ G[LE < 0] = np.maximum(G[LE < 0], Rn[LE < 0] - H[LE < 0])
2641
+ LE[LE < 0] = 0
2642
+
2643
+ if const_L is None:
2644
+ # Now L can be recalculated and the difference between iterations
2645
+ # derived
2646
+ L = MO.calc_L(u_friction, T_A_K, rho, c_p, H, LE)
2647
+ L_diff = np.fabs(L - L_old) / np.fabs(L_old)
2648
+ L_old = np.array(L)
2649
+ L_old[np.fabs(L_old) == 0] = 1e-36
2650
+
2651
+ # Calculate again the friction velocity with the new stability correction
2652
+ # and derive the change between iterations
2653
+ if not differentialT:
2654
+ u_friction = MO.calc_u_star(u, z_u, L, d_0, z_0M)
2655
+ u_friction = np.maximum(U_FRICTION_MIN, u_friction)
2656
+
2657
+ flag, Ln, LE, H, G, R_A, u_friction, L, n_iterations = map(
2658
+ np.asarray, (flag, Ln, LE, H, G, R_A, u_friction, L, n_iterations))
2659
+
2660
+ return flag, Ln, LE, H, G, R_A, u_friction, L, n_iterations
2661
+
2662
+
2663
+ def calc_F_theta_campbell(theta, F, w_C=1, Omega0=1, x_LAD=1):
2664
+ '''Calculates the fraction of vegetatinon observed at an angle.
2665
+
2666
+ Parameters
2667
+ ----------
2668
+ theta : float
2669
+ Angle of incidence (degrees).
2670
+ F : float
2671
+ Real Leaf (Plant) Area Index.
2672
+ w_C : float
2673
+ Canopy width to height ratio, optional (default = 1).
2674
+ Omega0 : float
2675
+ Clumping index at nadir, optional (default =1).
2676
+ x_LAD : float
2677
+ Chi parameter for the ellipsoidal Leaf Angle Distribution function,
2678
+ use x_LAD=1 for a spherical LAD.
2679
+
2680
+ Returns
2681
+ -------
2682
+ f_theta : float
2683
+ fraction of vegetation obsserved at an angle.
2684
+
2685
+ References
2686
+ ----------
2687
+ .. [Campbell1998] Campbell, G. S. & Norman, J. M. (1998), An introduction to environmental
2688
+ biophysics. Springer, New York
2689
+ https://archive.org/details/AnIntroductionToEnvironmentalBiophysics.
2690
+ .. [Norman1995] J.M. Norman, W.P. Kustas, K.S. Humes, Source approach for estimating
2691
+ soil and vegetation energy fluxes in observations of directional radiometric
2692
+ surface temperature, Agricultural and Forest Meteorology, Volume 77, Issues 3-4,
2693
+ Pages 263-293, http://dx.doi.org/10.1016/0168-1923(95)02265-Y.
2694
+ '''
2695
+ # Convert from canopy width/height to height/width as required by Kustas' Omega function
2696
+ w_C = 1. / w_C
2697
+ # First calcualte the angular clumping factor Omega based on eq (3) from
2698
+ # W.P. Kustas, J.M. Norman, Agricultural and Forest Meteorology 94 (1999)
2699
+ # CHECK: should theta here be in degrees or radians
2700
+ OmegaTheta = (Omega0 / (Omega0 + (1.0 - Omega0)
2701
+ * np.exp(-2.2 * np.radians(theta)**(3.8 - 0.46 * w_C))))
2702
+ # Estimate the beam extinction coefficient based on a elipsoidal LAD function
2703
+ # Eq. 15.4 of Campbell and Norman (1998)
2704
+ K_be = rad.calc_K_be_Campbell(theta, x_LAD)
2705
+ ftheta = 1.0 - np.exp(-K_be * OmegaTheta * F)
2706
+ return np.asarray(ftheta, dtype=np.float32)
2707
+
2708
+
2709
+ def calc_G(calcG_params, Rn_S, i=None):
2710
+
2711
+ if i is None:
2712
+ i = np.ones(Rn_S.shape, dtype=bool)
2713
+ if calcG_params[0][0] == G_CONSTANT:
2714
+ G = calcG_params[1][i]
2715
+ elif calcG_params[0][0] == G_RATIO:
2716
+ G = calc_G_ratio(Rn_S[i], calcG_params[1][i])
2717
+ elif calcG_params[0][0] == G_TIME_DIFF:
2718
+ G = calc_G_time_diff(Rn_S[i],
2719
+ [calcG_params[1][i], calcG_params[0][1],
2720
+ calcG_params[0][2], calcG_params[0][3]])
2721
+ elif calcG_params[0][0] == G_TIME_DIFF_SIGMOID:
2722
+ G = calc_G_time_diff_sigmoid(Rn_S[i], [calcG_params[1][i], calcG_params[0][1],
2723
+ calcG_params[0][2], calcG_params[0][3], calcG_params[0][4],
2724
+ calcG_params[0][5], calcG_params[0][6]])
2725
+
2726
+ return np.asarray(G)
2727
+
2728
+
2729
+ def calc_G_time_diff(R_n, G_param=[12.0, 0.35, 3.0, 24.0]):
2730
+ ''' Estimates Soil Heat Flux as function of time and net radiation.
2731
+
2732
+ Parameters
2733
+ ----------
2734
+ R_n : float
2735
+ Net radiation (W m-2).
2736
+ G_param : tuple(float,float,float,float)
2737
+ tuple with parameters required (time, Amplitude,phase_shift,shape).
2738
+
2739
+ time: float
2740
+ time of interest (decimal hours).
2741
+ Amplitude : float
2742
+ maximum value of G/Rn, amplitude, default=0.35.
2743
+ phase_shift : float
2744
+ shift of peak G relative to solar noon (default 3hrs before noon).
2745
+ shape : float
2746
+ shape of G/Rn, default 24 hrs.
2747
+
2748
+ Returns
2749
+ -------
2750
+ G : float
2751
+ Soil heat flux (W m-2).
2752
+
2753
+ References
2754
+ ----------
2755
+ .. [Santanello2003] Joseph A. Santanello Jr. and Mark A. Friedl, 2003: Diurnal Covariation in
2756
+ Soil Heat Flux and Net Radiation. J. Appl. Meteor., 42, 851-862,
2757
+ http://dx.doi.org/10.1175/1520-0450(2003)042<0851:DCISHF>2.0.CO;2.'''
2758
+
2759
+ # Get parameters
2760
+ time = G_param[0] - 12.0
2761
+ A = G_param[1]
2762
+ phase_shift = G_param[2]
2763
+ B = G_param[3]
2764
+ G_ratio = A * np.cos(2.0 * np.pi * (time + phase_shift) / B)
2765
+ G = R_n * G_ratio
2766
+ return np.asarray(G, dtype=np.float32)
2767
+
2768
+
2769
+ def calc_G_time_diff_sigmoid(R_n, G_param=[12, 0, 0.35, 10.0, 14.0, 1.0, 1.0]):
2770
+ ''' Estimates Soil Heat Flux as function of time and net radiation using an asymmetric sigmoid
2771
+ function
2772
+
2773
+ Parameters
2774
+ ----------
2775
+ R_n : float
2776
+ Net radiation (W m-2).
2777
+ G_param : tuple(float,float,float,float)
2778
+ tuple with parameters required (time, Amplitude,phase_shift,shape).
2779
+
2780
+ time: float
2781
+ time of interest (decimal hours).
2782
+ Amplitude : float
2783
+ maximum value of G/Rn, amplitude, default=0.35.
2784
+ phase_shift : float
2785
+ shift of peak G relative to solar noon (default 3hrs after noon).
2786
+ shape : float
2787
+ shape of G/Rn, default 24 hrs.
2788
+
2789
+ Returns
2790
+ -------
2791
+ G : float
2792
+ Soil heat flux (W m-2).
2793
+
2794
+ References
2795
+ ----------
2796
+ .. [Santanello2003] Joseph A. Santanello Jr. and Mark A. Friedl, 2003: Diurnal Covariation in
2797
+ Soil Heat Flux and Net Radiation. J. Appl. Meteor., 42, 851-862,
2798
+ http://dx.doi.org/10.1175/1520-0450(2003)042<0851:DCISHF>2.0.CO;2.'''
2799
+
2800
+ # Get parameters
2801
+ time, G_ratio_min, G_ratio_max, phase_shift_0, phase_shift_1, shape_0, shape_1 = G_param
2802
+ G_ratio = (G_ratio_min + (G_ratio_max - G_ratio_min)
2803
+ * 0.5 * (np.tanh((time - phase_shift_0) / shape_0)
2804
+ - np.tanh((time - phase_shift_1) / shape_1)))
2805
+ G = R_n * G_ratio
2806
+ return np.asarray(G, dtype=np.float32)
2807
+
2808
+
2809
+ def calc_G_ratio(Rn_S, G_ratio=0.35):
2810
+ '''Estimates Soil Heat Flux as ratio of net soil radiation.
2811
+
2812
+ Parameters
2813
+ ----------
2814
+ Rn_S : float
2815
+ Net soil radiation (W m-2).
2816
+ G_ratio : float, optional
2817
+ G/Rn_S ratio, default=0.35.
2818
+
2819
+ Returns
2820
+ -------
2821
+ G : float
2822
+ Soil heat flux (W m-2).
2823
+
2824
+ References
2825
+ ----------
2826
+ .. [Choudhury1987] B.J. Choudhury, S.B. Idso, R.J. Reginato, Analysis of an empirical model
2827
+ for soil heat flux under a growing wheat crop for estimating evaporation by an
2828
+ infrared-temperature based energy balance equation, Agricultural and Forest Meteorology,
2829
+ Volume 39, Issue 4, 1987, Pages 283-297,
2830
+ http://dx.doi.org/10.1016/0168-1923(87)90021-9.
2831
+ '''
2832
+
2833
+ G = G_ratio * Rn_S
2834
+ return np.asarray(G, dtype=np.float32)
2835
+
2836
+
2837
+ def calc_H_C(T_C, T_A, R_A, rho, c_p):
2838
+ '''Calculates canopy sensible heat flux in a parallel resistance network.
2839
+
2840
+ Parameters
2841
+ ----------
2842
+ T_C : float
2843
+ Canopy temperature (K).
2844
+ T_A : float
2845
+ Air temperature (K).
2846
+ R_A : float
2847
+ Aerodynamic resistance to heat transport (s m-1).
2848
+ rho : float
2849
+ air density (kg m-3).
2850
+ c_p : float
2851
+ Heat capacity of air at constant pressure (J kg-1 K-1).
2852
+
2853
+ Returns
2854
+ -------
2855
+ H_C : float
2856
+ Canopy sensible heat flux (W m-2).'''
2857
+
2858
+ H_C = rho * c_p * (T_C - T_A) / R_A
2859
+ return np.asarray(H_C, dtype=np.float32)
2860
+
2861
+
2862
+ def calc_H_C_PT(delta_R_ni, f_g, T_A_K, P, c_p, alpha):
2863
+ '''Calculates canopy sensible heat flux based on the Priestley and Taylor formula.
2864
+
2865
+ Parameters
2866
+ ----------
2867
+ delta_R_ni : float
2868
+ net radiation divergence of the vegetative canopy (W m-2).
2869
+ f_g : float
2870
+ fraction of vegetative canopy that is green.
2871
+ T_A_K : float
2872
+ air temperature (Kelvin).
2873
+ P : float
2874
+ air pressure (mb).
2875
+ c_p : float
2876
+ heat capacity of moist air (J kg-1 K-1).
2877
+ alpha : float
2878
+ the Priestley Taylor parameter.
2879
+
2880
+ Returns
2881
+ -------
2882
+ H_C : float
2883
+ Canopy sensible heat flux (W m-2).
2884
+
2885
+ References
2886
+ ----------
2887
+ Equation 14 in [Norman1995]_
2888
+ '''
2889
+
2890
+ # slope of the saturation pressure curve (kPa./deg C)
2891
+ s = met.calc_delta_vapor_pressure(T_A_K)
2892
+ s = s * 10 # to mb
2893
+ # latent heat of vaporisation (J./kg)
2894
+ Lambda = met.calc_lambda(T_A_K)
2895
+ # psychrometric constant (mb C-1)
2896
+ gama = met.calc_psicr(c_p, P, Lambda)
2897
+ s_gama = s / (s + gama)
2898
+ H_C = delta_R_ni * (1.0 - alpha * f_g * s_gama)
2899
+ return np.asarray(H_C, dtype=np.float32)
2900
+
2901
+
2902
+ def calc_H_DTD_parallel(
2903
+ T_R1,
2904
+ T_R0,
2905
+ T_A1,
2906
+ T_A0,
2907
+ rho,
2908
+ c_p,
2909
+ f_theta1,
2910
+ R_S1,
2911
+ R_A1,
2912
+ R_AC1,
2913
+ H_C1):
2914
+ '''Calculates the DTD total sensible heat flux at time 1 with resistances in parallel.
2915
+
2916
+ Parameters
2917
+ ----------
2918
+ T_R1 : float
2919
+ radiometric surface temperature at time t1 (K).
2920
+ T_R0 : float
2921
+ radiometric surface temperature at time t0 (K).
2922
+ T_A1 : float
2923
+ air temperature at time t1 (K).
2924
+ T_A0 : float
2925
+ air temperature at time t0 (K).
2926
+ rho : float
2927
+ air density at time t1 (kg m-3).
2928
+ cp : float
2929
+ heat capacity of moist air (J kg-1 K-1).
2930
+ f_theta_1 : float
2931
+ fraction of radiometer field of view that is occupied by vegetative cover at time t1.
2932
+ R_S1 : float
2933
+ resistance to heat transport from the soil surface at time t1 (s m-1).
2934
+ R_A1 : float
2935
+ resistance to heat transport in the surface layer at time t1 (s m-1).
2936
+ R_A1 : float
2937
+ resistance to heat transport at the canopy interface at time t1 (s m-1).
2938
+ H_C1 : float
2939
+ canopy sensible heat flux at time t1 (W m-2).
2940
+
2941
+ Returns
2942
+ -------
2943
+ H : float
2944
+ Total sensible heat flux at time t1 (W m-2).
2945
+
2946
+ References
2947
+ ----------
2948
+ .. [Guzinski2013] Guzinski, R., Anderson, M. C., Kustas, W. P., Nieto, H., and Sandholt, I.
2949
+ (2013) Using a thermal-based two source energy balance model with time-differencing to
2950
+ estimate surface energy fluxes with day-night MODIS observations,
2951
+ Hydrol. Earth Syst. Sci., 17, 2809-2825,
2952
+ http://dx.doi.org/10.5194/hess-17-2809-2013.
2953
+ '''
2954
+
2955
+ # Ignore night fluxes
2956
+ H = (rho * c_p * (((T_R1 - T_R0) - (T_A1 - T_A0)) / ((1.0 - f_theta1) * (R_A1 + R_S1)))
2957
+ + H_C1 * (1.0 - ((f_theta1 * R_AC1) / ((1.0 - f_theta1) * (R_A1 + R_S1)))))
2958
+ return np.asarray(H, dtype=np.float32)
2959
+
2960
+
2961
+ def calc_H_DTD_series(
2962
+ T_R1,
2963
+ T_R0,
2964
+ T_A1,
2965
+ T_A0,
2966
+ rho,
2967
+ c_p,
2968
+ f_theta,
2969
+ R_S,
2970
+ R_A,
2971
+ R_x,
2972
+ H_C):
2973
+ '''Calculates the DTD total sensible heat flux at time 1 with resistances in series
2974
+
2975
+ Parameters
2976
+ ----------
2977
+ T_R1 : float
2978
+ radiometric surface temperature at time t1 (K).
2979
+ T_R0 : float
2980
+ radiometric surface temperature at time t0 (K).
2981
+ T_A1 : float
2982
+ air temperature at time t1 (K).
2983
+ T_A0 : float
2984
+ air temperature at time t0 (K).
2985
+ rho : float
2986
+ air density at time t1 (kg m-3).
2987
+ cp : float
2988
+ heat capacity of moist air (J kg-1 K-1).
2989
+ f_theta : float
2990
+ fraction of radiometer field of view that is occupied by vegetative cover at time t1.
2991
+ R_S : float
2992
+ resistance to heat transport from the soil surface at time t1 (s m-1).
2993
+ R_A : float
2994
+ resistance to heat transport in the surface layer at time t1 (s m-1).
2995
+ R_x : float
2996
+ Canopy boundary resistance to heat transport at time t1 (s m-1).
2997
+ H_C : float
2998
+ canopy sensible heat flux at time t1 (W m-2).
2999
+
3000
+ Returns
3001
+ -------
3002
+ H : float
3003
+ Total sensible heat flux at time t1 (W m-2).
3004
+
3005
+ References
3006
+ ----------
3007
+ .. [Guzinski2014] Guzinski, R., Nieto, H., Jensen, R., and Mendiguren, G. (2014)
3008
+ Remotely sensed land-surface energy fluxes at sub-field scale in heterogeneous
3009
+ agricultural landscape and coniferous plantation, Biogeosciences, 11, 5021-5046,
3010
+ http://dx.doi.org/10.5194/bg-11-5021-2014.
3011
+ '''
3012
+
3013
+ H = (rho * c_p * ((T_R1 - T_R0) - (T_A1 - T_A0)) / ((1.0 - f_theta) * R_S + R_A)
3014
+ + H_C * ((1.0 - f_theta) * R_S - f_theta * R_x) / ((1.0 - f_theta) * R_S + R_A))
3015
+ return np.asarray(H, dtype=np.float32)
3016
+
3017
+
3018
+ def calc_H_S(T_S, T_A, R_A, R_S, rho, c_p):
3019
+ '''Calculates soil sensible heat flux in a parallel resistance network.
3020
+
3021
+ Parameters
3022
+ ----------
3023
+ T_S : float
3024
+ Soil temperature (K).
3025
+ T_A : float
3026
+ Air temperature (K).
3027
+ R_A : float
3028
+ Aerodynamic resistance to heat transport (s m-1).
3029
+ R_A : float
3030
+ Aerodynamic resistance at the soil boundary layer (s m-1).
3031
+ rho : float
3032
+ air density (kg m-3).
3033
+ c_p : float
3034
+ Heat capacity of air at constant pressure (J kg-1 K-1).
3035
+
3036
+ Returns
3037
+ -------
3038
+ H_C : float
3039
+ Canopy sensible heat flux (W m-2).
3040
+
3041
+ References
3042
+ ----------
3043
+ Equation 7 in [Norman1995]_
3044
+ '''
3045
+
3046
+ H_S = rho * c_p * ((T_S - T_A) / (R_S + R_A))
3047
+ return np.asarray(H_S, dtype=np.float32)
3048
+
3049
+
3050
+ def calc_T_C(T_R, T_S, f_theta):
3051
+ '''Estimates canopy temperature from the directional composite radiometric temperature.
3052
+
3053
+ Parameters
3054
+ ----------
3055
+ T_R : float
3056
+ Directional Radiometric Temperature (K).
3057
+ T_S : float
3058
+ Soil Temperature (K).
3059
+ f_theta : float
3060
+ Fraction of vegetation observed.
3061
+
3062
+ Returns
3063
+ -------
3064
+ flag : int
3065
+ Error flag if inversion not possible (255).
3066
+ T_C : float
3067
+ Canopy temperature (K).
3068
+
3069
+ References
3070
+ ----------
3071
+ Eq. 1 in [Norman1995]_
3072
+ '''
3073
+
3074
+ # Convert input scalars to numpy array
3075
+ (T_R, T_S, f_theta) = map(np.asarray, (T_R, T_S, f_theta))
3076
+ T_temp = np.asarray(T_R ** 4 - (1.0 - f_theta) * T_S**4)
3077
+ T_C = np.zeros(T_R.shape)
3078
+ flag = np.zeros(T_R.shape) + F_ALL_FLUXES
3079
+
3080
+ # Succesfull inversion
3081
+ T_C[T_temp >= 0] = (T_temp[T_temp >= 0] / f_theta[T_temp >= 0])**0.25
3082
+
3083
+ # Unsuccesfull inversion
3084
+ T_C[T_temp < 0] = 1e-6
3085
+ flag[T_temp < 0] = F_INVALID
3086
+
3087
+ return np.asarray(flag), np.asarray(T_C, dtype=np.float32)
3088
+
3089
+
3090
+ def calc_T_C_series(Tr_K, T_A_K, R_A, R_x, R_S, f_theta, H_C, rho, c_p):
3091
+ '''Estimates canopy temperature from canopy sensible heat flux and
3092
+ resistance network in series.
3093
+
3094
+ Parameters
3095
+ ----------
3096
+ Tr_K : float
3097
+ Directional Radiometric Temperature (K).
3098
+ T_A_K : float
3099
+ Air Temperature (K).
3100
+ R_A : float
3101
+ Aerodynamic resistance to heat transport (s m-1).
3102
+ R_x : float
3103
+ Bulk aerodynamic resistance to heat transport at the canopy boundary layer (s m-1).
3104
+ R_S : float
3105
+ Aerodynamic resistance to heat transport at the soil boundary layer (s m-1).
3106
+ f_theta : float
3107
+ Fraction of vegetation observed.
3108
+ H_C : float
3109
+ Sensible heat flux of the canopy (W m-2).
3110
+ rho : float
3111
+ Density of air (km m-3).
3112
+ c_p : float
3113
+ Heat capacity of air at constant pressure (J kg-1 K-1).
3114
+
3115
+ Returns
3116
+ -------
3117
+ T_C : float
3118
+ Canopy temperature (K).
3119
+
3120
+ References
3121
+ ----------
3122
+ Eqs. A5-A13 in [Norman1995]_'''
3123
+
3124
+ T_R_K_4 = Tr_K**4
3125
+ # equation A7 from Norman 1995, linear approximation of temperature of the
3126
+ # canopy
3127
+ T_C_lin = ((T_A_K / R_A + Tr_K / (R_S * (1.0 - f_theta))
3128
+ + H_C * R_x / (rho * c_p) * (1.0 / R_A + 1.0 / R_S + 1.0 / R_x))
3129
+ / (1.0 / R_A + 1.0 / R_S + f_theta / (R_S * (1.0 - f_theta))))
3130
+ # equation A12 from Norman 1995
3131
+ T_D = (T_C_lin * (1 + R_S / R_A) - H_C * R_x / (rho * c_p)
3132
+ * (1.0 + R_S / R_x + R_S / R_A) - T_A_K * R_S / R_A)
3133
+ # equation A11 from Norman 1995
3134
+ delta_T_C = (((T_R_K_4 - f_theta * T_C_lin**4 - (1.0 - f_theta) * T_D**4)
3135
+ / (4.0 * (1.0 - f_theta) * T_D**3 * (1.0 + R_S / R_A)
3136
+ + 4.0 * f_theta * T_C_lin**3)))
3137
+ # get canopy temperature in Kelvin
3138
+ T_C = T_C_lin + delta_T_C
3139
+ return np.asarray(T_C, dtype=np.float32)
3140
+
3141
+
3142
+ def calc_T_CS_Norman(F, vza_n, vza_f, T_n, T_f, w_C=1, x_LAD=1, omega0=1):
3143
+ '''Estimates canopy and soil temperature by analytical inversion of Eq 1 in [Norman1995]
3144
+ of two directional radiometric observations. Ignoring shawows.
3145
+
3146
+ Parameters
3147
+ ----------
3148
+ F : float
3149
+ Real Leaf (Plant) Area Index.
3150
+ vza_n : float
3151
+ View Zenith Angle during the nadir observation (degrees).
3152
+ vza_f : float
3153
+ View Zenith Angle during the oblique observation (degrees).
3154
+ T_n : float
3155
+ Radiometric temperature in the nadir obsevation (K).
3156
+ T_f : float
3157
+ Radiometric temperature in the oblique observation (K).
3158
+ w_C : float,optional
3159
+ Canopy height to width ratio, use w_C=1 by default.
3160
+ x_LAD : float,optional
3161
+ Chi parameter for the ellipsoildal Leaf Angle Distribution function of
3162
+ Campbell 1988 [default=1, spherical LIDF].
3163
+ omega0 : float,optional
3164
+ Clumping index at nadir, use omega0=1 by default.
3165
+
3166
+ Returns
3167
+ -------
3168
+ T_C : float
3169
+ Canopy temperature (K).
3170
+ T_S : float
3171
+ Soil temperature (K).
3172
+
3173
+ References
3174
+ ----------
3175
+ inversion of Eq. 1 in [Norman1995]_
3176
+ '''
3177
+
3178
+ # Convert the input scalars to numpy arrays
3179
+ F, vza_n, vza_f, T_n, T_f, w_C, x_LAD, omega0 = map(
3180
+ np.asarray, (F, vza_n, vza_f, T_n, T_f, w_C, x_LAD, omega0))
3181
+ # Calculate the fraction of vegetation observed by each angle
3182
+ f_theta_n = calc_F_theta_campbell(vza_n, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
3183
+ f_theta_f = calc_F_theta_campbell(vza_f, F, w_C=w_C, Omega0=omega0, x_LAD=x_LAD)
3184
+ # Solve the sytem of two unknowns and two equations
3185
+ T_S_4 = np.asarray((f_theta_f * T_n**4 - f_theta_n * T_f**4)
3186
+ / (f_theta_f - f_theta_n))
3187
+ T_C_4 = np.asarray((T_n ** 4 - (1.0 - f_theta_n) * T_S_4) / f_theta_n)
3188
+
3189
+ T_C_K = np.zeros(T_n.shape)
3190
+ T_S_K = np.zeros(T_n.shape)
3191
+
3192
+ # Successful inversion
3193
+ i = np.logical_and(T_C_4 > 0, T_S_4 > 0)
3194
+ T_C_K[i] = T_C_4[i]**0.25
3195
+ T_S_K[i] = T_S_4[i]**0.25
3196
+
3197
+ # Unsuccessful inversion
3198
+ T_C_K[~i] = float('nan')
3199
+ T_S_K[~i] = float('nan')
3200
+
3201
+ return np.asarray(T_C_K, dtype=np.float32), np.asarray(T_S_K, dtype=np.float32)
3202
+
3203
+
3204
+ def calc_T_CS_4SAIL(
3205
+ LAI,
3206
+ lidf,
3207
+ hotspot,
3208
+ Eo_n,
3209
+ Eo_f,
3210
+ L_sky,
3211
+ sza_n,
3212
+ sza_f,
3213
+ vza_n,
3214
+ vza_f,
3215
+ psi_n,
3216
+ psi_f,
3217
+ e_v,
3218
+ e_s):
3219
+ '''Estimates canopy and soil temperature by analytical inversion of 4SAIL
3220
+ (Eq. 12 in [Verhoef2007]_) of two directional radiometric observations. Ignoring shadows.
3221
+
3222
+ Parameters
3223
+ ----------
3224
+ LAI : float
3225
+ Leaf (Plant) Area Index.
3226
+ lidf : list
3227
+ Campbell 1988 Leaf Inclination Distribution Function, default 5 degrees angle step.
3228
+ hotspot : float
3229
+ hotspot parameters, use 0 to ignore the hotspot effect (turbid medium).
3230
+ Eo_n : float
3231
+ Surface land Leaving thermal radiance (emitted thermal radiation).
3232
+ at the nadir observation (W m-2).
3233
+ Eo_f : float
3234
+ Surface land Leaving thermal radiance (emitted thermal radiation)
3235
+ at the oblique observation (W m-2).
3236
+ L_dn : float
3237
+ Broadband incoming longwave radiation (W m-2).
3238
+ sza_n : float
3239
+ Sun Zenith Angle during the nadir observation (degrees).
3240
+ sza_f : float
3241
+ Sun Zenith Angle during the oblique observation (degrees).
3242
+ vza_n : float
3243
+ View Zenith Angle during the nadir observation (degrees).
3244
+ vza_f : float
3245
+ View Zenith Angle during the oblique observation (degrees).
3246
+ psi_n : float
3247
+ Relative (sensor-sun) Azimuth Angle during the nadir observation (degrees).
3248
+ psi_f : float
3249
+ Relative (sensor-sun) Azimuth Angle during the oblique observation (degrees).
3250
+ e_v : float
3251
+ broadband leaf emissivity.
3252
+ e_s : float
3253
+ broadband soil emissivity.
3254
+
3255
+ Returns
3256
+ -------
3257
+ T_C_K : float
3258
+ Canopy temperature (K).
3259
+ T_S_K : float
3260
+ Soil temperature (K).
3261
+
3262
+ References
3263
+ ----------
3264
+ .. [Verhoef2007] Verhoef, W.; Jia, Li; Qing Xiao; Su, Z., (2007) Unified Optical-Thermal
3265
+ Four-Stream Radiative Transfer Theory for Homogeneous Vegetation Canopies,
3266
+ IEEE Transactions on Geoscience and Remote Sensing, vol.45, no.6, pp.1808-1822,
3267
+ http://dx.doi.org/10.1109/TGRS.2007.895844 based on in Verhoef et al. (2007)
3268
+ '''
3269
+
3270
+ # Apply Kirkchoff to work with reflectances instead of emissivities
3271
+ r_s = 1. - e_s
3272
+ r_v = 1. - e_v
3273
+ # Get nadir parameters for the inversion
3274
+ [rdot_star_n,
3275
+ emiss_v_eff_n,
3276
+ emiss_s_eff_n,
3277
+ gamma_sot,
3278
+ emiss_sot] = calc_4SAIL_emission_param(LAI,
3279
+ hotspot,
3280
+ lidf,
3281
+ sza_n,
3282
+ vza_n,
3283
+ psi_n,
3284
+ r_v,
3285
+ r_s)
3286
+ # Calculate the total emission of the surface at nadir observation
3287
+ L_emiss_n = Eo_n - rdot_star_n * L_sky
3288
+ # Get forward parameters for the inversion
3289
+ [rdot_star_f,
3290
+ emiss_v_eff_f,
3291
+ emiss_s_eff_f,
3292
+ gamma_sot,
3293
+ emiss_sot] = calc_4SAIL_emission_param(LAI,
3294
+ hotspot,
3295
+ lidf,
3296
+ sza_f,
3297
+ vza_f,
3298
+ psi_f,
3299
+ r_v,
3300
+ r_s)
3301
+ # Calculate the total emission of the surface at oblique observation
3302
+ L_emiss_f = Eo_f - rdot_star_f * L_sky
3303
+ # Invert 4SAIL to get the BB emission of vegetation and soil
3304
+ H_v = ((emiss_s_eff_n * L_emiss_f - emiss_s_eff_f * L_emiss_n)
3305
+ / (emiss_s_eff_n * emiss_v_eff_f - emiss_s_eff_f * emiss_v_eff_n))
3306
+ H_S = (L_emiss_n - emiss_v_eff_n * H_v) / emiss_s_eff_n
3307
+ # Invert Stephan Boltzmann to obtain vegetation and soil temperatures
3308
+ T_C_K = (H_v / SB)**0.25
3309
+ T_S_K = (H_S / SB)**0.25
3310
+ return np.asarray(T_C_K, dtype=np.float32), np.asarray(T_S_K, dtype=np.float32)
3311
+
3312
+
3313
+ def calc_4SAIL_emission_param(
3314
+ LAI,
3315
+ hotspot,
3316
+ lidf,
3317
+ sza,
3318
+ vza,
3319
+ psi,
3320
+ rho_v,
3321
+ rho_s,
3322
+ tau_v=0.0):
3323
+ '''Calculates the effective surface reflectance, and emissivities for
3324
+ soil and canopy using 4SAIL.
3325
+
3326
+ Parameters
3327
+ ----------
3328
+ LAI : float
3329
+ Leaf (Plant) Area Index.
3330
+ hotspot : float
3331
+ hotspot parameters, use 0 to ignore the hotspot effect (turbid medium).
3332
+ lidf : list
3333
+ Campbell 1988 Leaf Inclination Distribution Function, 5 angle step.
3334
+ sza : float
3335
+ Sun Zenith Angle during the nadir observation (degrees).
3336
+ vza : float
3337
+ View Zenith Angle during the nadir observation (degrees).
3338
+ psi : float
3339
+ Relative (sensor-sun) Azimuth Angle during the nadir observation (degrees).
3340
+ psi_f : float
3341
+ Relative (sensor-sun) Azimuth Angle during the oblique observation (degrees).
3342
+ rho_v : float
3343
+ leaf reflectance (1-leaf emissivity).
3344
+ rho_s : float
3345
+ soil emissivity (1-soil emissivity).
3346
+ tau_v : float
3347
+ leaf transmittance (default zero transmittance in the TIR).
3348
+
3349
+ Returns
3350
+ -------
3351
+ rdot_star : float
3352
+ surface effective reflectance.
3353
+ emiss_v_eff : float
3354
+ canopy effective emissivity.
3355
+ emiss_s_eff : float
3356
+ soil effective emissivity.
3357
+ gamma_sot : float
3358
+ directional canopy absortivity.
3359
+ emiss_sot : float
3360
+ directional canopy emissivity.
3361
+
3362
+ References
3363
+ ----------
3364
+ Equations 5, 11, and 13 in [Verhoef2007]_
3365
+ '''
3366
+
3367
+ # Run 4 SAIL
3368
+ [tss,
3369
+ too,
3370
+ tsstoo,
3371
+ rdd,
3372
+ tdd,
3373
+ rsd,
3374
+ tsd,
3375
+ rdo,
3376
+ tdo,
3377
+ rso,
3378
+ rsos,
3379
+ rsod,
3380
+ rddt,
3381
+ rsdt,
3382
+ rdot,
3383
+ rsodt,
3384
+ rsost,
3385
+ rsot,
3386
+ gamma_sdf,
3387
+ gammas_db,
3388
+ gamma_so] = foursail(LAI,
3389
+ hotspot,
3390
+ lidf,
3391
+ sza,
3392
+ vza,
3393
+ psi,
3394
+ rho_v,
3395
+ tau_v,
3396
+ rho_s)
3397
+ # Eq. 5 in [Verhoef2007]_
3398
+ gamma_d = 1. - rdd - tdd
3399
+ gamma_o = 1. - rdo - tdo - too
3400
+ # Eq. 13 in [Verhoef2007]_
3401
+ dn = 1. - rddt - rdd
3402
+ emiss_o = 1. - rdot
3403
+ emiss_d = 1. - rddt
3404
+ rdot_star = rdo + (tdd * (rddt * tdo + rdot * too) / dn)
3405
+ # Get the coefficients from Eq 11 in [Verhoef2007]_
3406
+ # 2nd element in Eq. 11 [Verhoef2007]_
3407
+ emiss_v_eff = gamma_o + (gamma_d * (rddt * tdo + rdot * too) / dn)
3408
+ # 3rd element in Eq. 11 [Verhoef2007]_
3409
+ emiss_s_eff = emiss_o * too + (emiss_d * (tdo + rdd * rdot * too) / dn)
3410
+ # 4th element in Eq. 11 [Verhoef2007]_
3411
+ gamma_sot = gamma_so + (gamma_sdf * (rddt * tdo + rdot * too) / dn)
3412
+ # 5th element in Eq. 11 [Verhoef2007]_
3413
+ emiss_sot = emiss_o * tsstoo + tss * \
3414
+ (emiss_d * (tdo + rdd * rdot * too) / dn)
3415
+
3416
+ rdot_star, emiss_v_eff, emiss_s_eff, gamma_sot, emiss_sot = map(
3417
+ np.asarray, (rdot_star, emiss_v_eff, emiss_s_eff, gamma_sot, emiss_sot))
3418
+ return rdot_star, emiss_v_eff, emiss_s_eff, gamma_sot, emiss_sot
3419
+
3420
+
3421
+ def calc_T_S(T_R, T_C, f_theta):
3422
+ '''Estimates soil temperature from the directional LST.
3423
+
3424
+ Parameters
3425
+ ----------
3426
+ T_R : float
3427
+ Directional Radiometric Temperature (K).
3428
+ T_C : float
3429
+ Canopy Temperature (K).
3430
+ f_theta : float
3431
+ Fraction of vegetation observed.
3432
+
3433
+ Returns
3434
+ -------
3435
+ flag : float
3436
+ Error flag if inversion not possible (255).
3437
+ T_S: float
3438
+ Soil temperature (K).
3439
+
3440
+ References
3441
+ ----------
3442
+ Eq. 1 in [Norman1995]_'''
3443
+
3444
+ # Convert the input scalars to numpy arrays
3445
+ T_R, T_C, f_theta = map(np.asarray, (T_R, T_C, f_theta))
3446
+ T_temp = T_R**4 - f_theta * T_C**4
3447
+ T_S = np.zeros(T_R.shape, np.float32)
3448
+ flag = np.zeros(T_R.shape, np.int32) + F_ALL_FLUXES
3449
+
3450
+ # Succesfull inversion
3451
+ T_S[T_temp >= 0] = (T_temp[T_temp >= 0]
3452
+ / (1.0 - f_theta[T_temp >= 0]))**0.25
3453
+
3454
+ # Unsuccesfull inversion
3455
+ T_S[np.logical_or(T_temp < 0, np.isnan(T_temp))] = 1e-6
3456
+ flag[np.logical_or(T_temp < 0, np.isnan(T_temp))] = F_INVALID
3457
+
3458
+ return np.asarray(flag, dtype=np.int32), np.asarray(T_S, dtype=np.float32)
3459
+
3460
+
3461
+ def calc_T_S_4SAIL(T_R, T_C, rdot_star, emiss_v_eff, emiss_s_eff, L_dn=0):
3462
+ '''Estimates canopy temperature from the directional LST using 4SAIL parameters.
3463
+
3464
+ Parameters
3465
+ ----------
3466
+ T_R : float
3467
+ Directional Radiometric Temperature (K)
3468
+ T_S : float
3469
+ Soil Temperature (K)
3470
+ rdot_star : float
3471
+ surface effective reflectance
3472
+ emiss_v_eff : float
3473
+ canopy effective emissivity
3474
+ emiss_s_eff : float
3475
+ soil effective emissivity
3476
+ L_dn : float
3477
+ downwelling atmospheric longwave radiance (W m-2)
3478
+
3479
+ Returns
3480
+ -------
3481
+ flag : int
3482
+ Error flag if inversion not possible (255).
3483
+ T_S : float
3484
+ Soil temperature (K).'''
3485
+
3486
+ Hv = met.calc_stephan_boltzmann(T_C)
3487
+ Eo = met.calc_stephan_boltzmann(T_R)
3488
+ Hs = np.asarray((Eo - rdot_star * L_dn - emiss_v_eff * Hv) / emiss_s_eff)
3489
+
3490
+ flag = np.zeros(T_R.shape) + F_ALL_FLUXES
3491
+ T_S = np.zeros(T_R.shape)
3492
+
3493
+ # Succesfull inversion
3494
+ T_S[Hs >= 0] = (Hs / SB)**0.25
3495
+
3496
+ # Unsuccesfull inversion
3497
+ T_S[Hs < 0] = 1e-6
3498
+ flag[Hs < 0] = F_INVALID
3499
+
3500
+ return np.asarray(flag), np.asarray(T_S, dtype=np.float32)
3501
+
3502
+
3503
+ def calc_T_S_series(Tr_K, T_A_K, R_A, R_x, R_S, f_theta, H_S, rho, c_p):
3504
+ '''Estimates soil temperature from soil sensible heat flux and
3505
+ resistance network in series.
3506
+
3507
+ Parameters
3508
+ ----------
3509
+ Tr_K : float
3510
+ Directional Radiometric Temperature (K).
3511
+ T_A_K : float
3512
+ Air Temperature (K).
3513
+ R_A : float
3514
+ Aerodynamic resistance to heat transport (s m-1).
3515
+ R_x : float
3516
+ Bulk aerodynamic resistance to heat transport at the canopy boundary layer (s m-1).
3517
+ R_S : float
3518
+ Aerodynamic resistance to heat transport at the soil boundary layer (s m-1).
3519
+ f_theta : float
3520
+ Fraction of vegetation observed.
3521
+ H_S : float
3522
+ Sensible heat flux of the soil (W m-2).
3523
+ rho : float
3524
+ Density of air (km m-3).
3525
+ c_p : float
3526
+ Heat capacity of air at constant pressure (J kg-1 K-1).
3527
+
3528
+ Returns
3529
+ -------
3530
+ T_S: float
3531
+ Soil temperature (K).
3532
+ T_C : float
3533
+ Air temperature at the canopy interface (K).
3534
+
3535
+ References
3536
+ ----------
3537
+ Eqs. A15-A19 from [Norman1995]_'''
3538
+
3539
+ # Eq. A.15 Norman 1995
3540
+ T_AC_lin = (((T_A_K / R_A) + (Tr_K / (f_theta * R_x))
3541
+ - (((1.0 - f_theta) / (f_theta * R_x)) * H_S * R_S / (rho * c_p))
3542
+ + H_S / (rho * c_p))
3543
+ / ((1.0 / R_A) + (1.0 / R_x) + (1.0 - f_theta) / (f_theta * R_x)))
3544
+ # Eq. A.17 Norman 1995
3545
+ T_e = T_AC_lin * (1.0 + (R_x / R_A)) - H_S * R_x / \
3546
+ (rho * c_p) - T_A_K * R_x / R_A
3547
+ # Eq. A.16 Norman 1995
3548
+ Delta_T_AC = ((Tr_K**4 - (1.0 - f_theta) * (H_S * R_S / (rho * c_p) + T_AC_lin)**4
3549
+ - f_theta * T_e**4) / (4 * f_theta * T_e**3.0 * (1.0 + (R_x / R_A))
3550
+ + 4.0 * (1.0 - f_theta) * (H_S * R_S / (rho * c_p) + T_AC_lin)**3))
3551
+ # Eq. A.18 Norman 1995
3552
+ T_AC = T_AC_lin + Delta_T_AC
3553
+ T_S = T_AC + H_S * R_S / (rho * c_p)
3554
+ return np.asarray(T_S, dtype=np.float32), np.asarray(T_AC, dtype=np.float32)
3555
+
3556
+
3557
+ def _check_default_parameter_size(parameter, input_array):
3558
+
3559
+ parameter = np.asarray(parameter, dtype=np.float32)
3560
+ if parameter.size == 1:
3561
+ parameter = np.ones(input_array.shape) * parameter
3562
+ return np.asarray(parameter, dtype=np.float32)
3563
+ elif parameter.shape != input_array.shape:
3564
+ raise ValueError(
3565
+ 'dimension mismatch between parameter array and input array with shapes %s and %s' %
3566
+ (parameter.shape, input_array.shape))
3567
+ else:
3568
+ return np.asarray(parameter, dtype=np.float32)
3569
+
3570
+
3571
+ def calc_resistances(res_form, res_types):
3572
+ '''Calculate the aerodynamic resistances: R_A, R_x and R_S.
3573
+
3574
+ Parameters
3575
+ ----------
3576
+ res_form : int
3577
+ Constant specifying which resistance formulation to use:
3578
+ KUSTAS_NORMAN_1999 (0), CHOUDHURY_MONTEITH_1988 (1),
3579
+ MCNAUGHTON_VANDERHURK (2), CHOUDHURY_MONTEITH_ALPHA_1988(3)
3580
+ If the constant is not any of the above then KUSTAS_NORMAN_1999 is
3581
+ used.
3582
+ res_types : Dictionary of dictionaries
3583
+ Dictionary specifying which of the three resistances to calculate. For
3584
+ each resistance to calculate the dictionary must contain a key-value
3585
+ pair with the key being the name of the resistance and value being
3586
+ another dictionary with all the parameters required to calculate the
3587
+ given resistance.
3588
+ Key: R_A
3589
+ R_A Parameters: 'z_T', 'u_friction', 'L', 'd_0', 'z_0H'
3590
+ Key: R_x
3591
+ R_x Parameters: 'u_friction', 'h_C', 'd_0', 'z_0M', 'L', 'F', 'LAI',
3592
+ 'leaf_width', 'res_params'
3593
+ Key: R_S
3594
+ R_S Parameters: 'u_friction', 'h_C', 'd_0', 'z_0M', 'L', 'omega0', 'F',
3595
+ 'leaf_width', 'z0_soil', 'z_u', 'deltaT', 'res_params'
3596
+
3597
+ Returns
3598
+ -------
3599
+ R_A: float array or None
3600
+ Aerodyamic resistance to heat transport in the surface layer (s m-1)
3601
+ R_x : float array or None
3602
+ Aerodynamic resistance at the canopy boundary layer (s m-1)
3603
+ R_S: float array or None
3604
+ Aerodynamic resistance at the soil boundary layer (s m-1)
3605
+
3606
+ '''
3607
+
3608
+ R_A = 0
3609
+ R_x = 0
3610
+ R_S = 0
3611
+ u_C = None
3612
+
3613
+ if res_form not in [KUSTAS_NORMAN_1999, CHOUDHURY_MONTEITH_1988,
3614
+ MCNAUGHTON_VANDERHURK, CHOUDHURY_MONTEITH_ALPHA_1988,
3615
+ HADHIGHI_AND_OR_2015]:
3616
+ res_form = KUSTAS_NORMAN_1999
3617
+
3618
+ # Determine which resistances to calculate and get the required parameters
3619
+ if 'R_A' in res_types.keys():
3620
+ z_T, u_friction, L, d_0, z_0H = [res_types['R_A'].get(k)
3621
+ for k in ['z_T',
3622
+ 'u_friction',
3623
+ 'L',
3624
+ 'd_0',
3625
+ 'z_0H']]
3626
+ del res_types['R_A']
3627
+ calc_R_A = True
3628
+ else:
3629
+ calc_R_A = False
3630
+ if 'R_x' in res_types.keys():
3631
+ u_friction, h_C, d_0, z_0M, L, F, LAI, leaf_width, z0_soil, massman_profile, res_params = \
3632
+ [res_types['R_x'].get(k) for k in ['u_friction', 'h_C', 'd_0', 'z_0M',
3633
+ 'L', 'F', 'LAI', 'leaf_width',
3634
+ 'z0_soil', 'massman_profile',
3635
+ 'res_params']]
3636
+
3637
+ del res_types['R_x']
3638
+ calc_R_x = True
3639
+ else:
3640
+ calc_R_x = False
3641
+ if 'R_S' in res_types.keys():
3642
+ u_friction, h_C, d_0, z_0M, L, omega0, F, leaf_width, z0_soil, z_u, deltaT, u, rho,\
3643
+ c_p, f_cover, w_C, res_params, LAI, massman_profile = \
3644
+ [res_types['R_S'].get(k) for k in ['u_friction', 'h_C', 'd_0', 'z_0M',
3645
+ 'L', 'omega0', 'F', 'leaf_width',
3646
+ 'z0_soil', 'z_u', 'deltaT',
3647
+ 'u', 'rho', 'c_p', 'f_cover',
3648
+ 'w_C', 'res_params', "LAI",
3649
+ "massman_profile"]]
3650
+
3651
+ del res_types['R_S']
3652
+ calc_R_S = True
3653
+ else:
3654
+ calc_R_S = False
3655
+
3656
+ # Calculate the aerodynamic resistance
3657
+ if calc_R_A:
3658
+ R_A = res.calc_R_A(z_T, u_friction, L, d_0, z_0H)
3659
+ del z_T, z_0H
3660
+
3661
+ # Calculate soil and canopy resistances
3662
+ if res_form == KUSTAS_NORMAN_1999:
3663
+ if calc_R_x:
3664
+ u_C = wnd.calc_u_C_star(u_friction, h_C, d_0, z_0M, L)
3665
+ u_C = np.maximum(u_C, U_C_MIN)
3666
+ # Wind speed is highly attenuated within the canopy volume
3667
+ if massman_profile[0] <= 0:
3668
+ u_d_zm = wnd.calc_u_Goudriaan(u_C, h_C, F, leaf_width, d_0+z_0M)
3669
+ else:
3670
+ u_d_zm = wnd.calc_u_Massman(u_C, h_C, F, d_0+z_0M,
3671
+ massman_profile[1],
3672
+ xi_soil=z0_soil/h_C,
3673
+ c_d=massman_profile[0])
3674
+
3675
+ u_d_zm = np.maximum(u_d_zm, U_C_MIN)
3676
+ # Vegetation in series with soil, i.e. well mixed, so we use
3677
+ # the landscape LAI
3678
+ R_x = res.calc_R_x_Norman(LAI, leaf_width, u_d_zm, res_params)
3679
+ del u_d_zm
3680
+ if calc_R_S:
3681
+ if u_C is None:
3682
+ u_C = wnd.calc_u_C_star(u_friction, h_C, d_0, z_0M, L)
3683
+ u_C = np.maximum(u_C, U_C_MIN)
3684
+ # Clumped vegetation enhanced wind speed for the soil surface
3685
+ if massman_profile[0] <= 0:
3686
+ u_S = wnd.calc_u_Goudriaan(u_C, h_C, LAI, leaf_width, z0_soil)
3687
+ else:
3688
+ u_S = wnd.calc_u_Massman(u_C, h_C, LAI, z0_soil,
3689
+ massman_profile[1],
3690
+ xi_soil=z0_soil/h_C,
3691
+ c_d=massman_profile[0])
3692
+ u_S = np.maximum(u_S, U_S_MIN)
3693
+ R_S = res.calc_R_S_Kustas(u_S, deltaT, params=res_params)
3694
+
3695
+ elif res_form == CHOUDHURY_MONTEITH_1988:
3696
+ if calc_R_x:
3697
+ u_C = wnd.calc_u_C_star(u_friction, h_C, d_0, z_0M, L)
3698
+ u_C = np.maximum(u_C, U_C_MIN)
3699
+ # Vegetation in series with soil, i.e. well mixed, so we use
3700
+ # the landscape LAI
3701
+ R_x = res.calc_R_x_Choudhury(u_C, LAI, leaf_width)
3702
+ del LAI, leaf_width
3703
+ if calc_R_S:
3704
+ R_S = res.calc_R_S_Choudhury(u_friction, h_C, z_0M, d_0, z_u, z0_soil)
3705
+
3706
+ elif res_form == MCNAUGHTON_VANDERHURK:
3707
+ if calc_R_x:
3708
+ # Vegetation in series with soil, i.e. well mixed, so we use
3709
+ # the landscape LAI
3710
+ R_x = res.calc_R_x_McNaughton(LAI, leaf_width, u_friction)
3711
+ del LAI, leaf_width
3712
+ if calc_R_S:
3713
+ R_S = res.calc_R_S_McNaughton(u_friction)
3714
+
3715
+ elif res_form == CHOUDHURY_MONTEITH_ALPHA_1988:
3716
+ if calc_R_x:
3717
+ u_C = wnd.calc_u_C_star(u_friction, h_C, d_0, z_0M, L)
3718
+ u_C = np.maximum(u_C, U_C_MIN)
3719
+ # Wind speed is highly attenuated within the canopy volume
3720
+ alpha_prime = wnd.calc_A_Goudriaan(h_C, LAI, leaf_width)
3721
+ # Vegetation in series with soil, i.e. well mixed, so we use
3722
+ # the landscape LAI
3723
+ R_x = res.calc_R_x_Choudhury(u_C, LAI, leaf_width, alpha_prime=alpha_prime)
3724
+ del LAI, alpha_prime
3725
+
3726
+ if calc_R_S:
3727
+ # Clumped vegetation enhanced wind speed for the soil surface
3728
+ alpha_k = wnd.calc_A_Goudriaan(h_C, LAI, leaf_width)
3729
+ R_S = res.calc_R_S_Choudhury(u_friction, h_C, z_0M, d_0, z_u, z0_soil, alpha_k=alpha_k)
3730
+
3731
+ elif res_form == HADHIGHI_AND_OR_2015:
3732
+ if calc_R_x:
3733
+ u_C = wnd.calc_u_C_star(u_friction, h_C, d_0, z_0M, L)
3734
+ u_C = np.maximum(u_C, U_C_MIN)
3735
+ # Wind speed is highly attenuated within the canopy volume
3736
+ if massman_profile[0] <= 0:
3737
+ u_d_zm = wnd.calc_u_Goudriaan(u_C, h_C, F, leaf_width, d_0+z_0M)
3738
+ else:
3739
+ u_d_zm = wnd.calc_u_Massman(u_C, h_C, F, d_0+z_0M,
3740
+ massman_profile[1],
3741
+ xi_soil=z0_soil/h_C,
3742
+ c_d=massman_profile[0])
3743
+
3744
+ u_d_zm = np.maximum(u_d_zm, U_C_MIN)
3745
+ # Vegetation in series with soil, i.e. well mixed, so we use
3746
+ # the landscape LAI
3747
+ R_x = res.calc_R_x_Norman(LAI, leaf_width, u_d_zm, res_params)
3748
+ del LAI, leaf_width, u_d_zm
3749
+ if calc_R_S:
3750
+ # Wind speed is highly attenuated within the canopy volume
3751
+ if massman_profile[0] <= 0:
3752
+ u_star_soil = None
3753
+ else:
3754
+ u_star_ratio_2 = wnd.calc_ustar_massman(h_C, F, z0_soil,
3755
+ massman_profile[1],
3756
+ Xi_soil=z0_soil/h_C,
3757
+ C_d=massman_profile[0])
3758
+
3759
+ u_star_soil = u_friction * np.sqrt(u_star_ratio_2)
3760
+
3761
+
3762
+ R_S = res.calc_R_S_Haghighi(u, h_C, z_u, rho, c_p,
3763
+ z0_soil=z0_soil,
3764
+ f_cover=f_cover,
3765
+ w_C=w_C)
3766
+
3767
+ R_A = np.asarray(np.clip(R_A, R_A_MIN, R_A_MAX))
3768
+ R_x = np.asarray(np.clip(R_x, RES_MIN, RES_MAX))
3769
+ R_S = np.asarray(np.clip(R_S, RES_MIN, RES_MAX))
3770
+
3771
+ return R_A, R_x, R_S
3772
+
3773
+
3774
+
3775
+ def monin_obukhov_convergence(l_mo, l_queue, l_converged, flag):
3776
+ l_new = np.array(l_mo)
3777
+ l_new[l_new == 0] = 1e-36
3778
+ l_queue.appendleft(l_new)
3779
+ i = np.logical_and(~l_converged, flag != F_INVALID)
3780
+ if np.sum(i) <= 1 or np.size(i) == 0:
3781
+ return i, l_queue, l_converged, np.inf
3782
+
3783
+
3784
+ if len(l_queue) >= 4:
3785
+ i = np.logical_and(~l_converged, flag != F_INVALID)
3786
+ if np.any(i):
3787
+ l_converged[i] = np.logical_and(
3788
+ _L_diff(l_queue[0][i], l_queue[2][i]) < L_thres,
3789
+ _L_diff(l_queue[1][i], l_queue[3][i]) < L_thres)
3790
+
3791
+ if len(l_queue) == 6:
3792
+ i = np.logical_and(~l_converged, flag != F_INVALID)
3793
+ if np.any(i):
3794
+ l_converged[i] = np.logical_and.reduce(
3795
+ (_L_diff(l_queue[0][i], l_queue[3][i]) < L_thres,
3796
+ _L_diff(l_queue[1][i], l_queue[4][i]) < L_thres,
3797
+ _L_diff(l_queue[2][i], l_queue[5][i]) < L_thres))
3798
+
3799
+ if np.sum(i) == 0 or np.size(i) == 0:
3800
+ return i, l_queue, l_converged, np.inf
3801
+
3802
+ l_diff_max = np.nanmax(_L_diff(l_queue[0][i], l_queue[1][i]))
3803
+
3804
+ return i, l_queue, l_converged, l_diff_max
3805
+
3806
+