BESS-JPL 1.6.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.

Potentially problematic release.


This version of BESS-JPL might be problematic. Click here for more details.

Files changed (56) hide show
  1. BESS_JPL/BESS.py +468 -0
  2. BESS_JPL/BESS_JPL.py +23 -0
  3. BESS_JPL/C3_photosynthesis.py +72 -0
  4. BESS_JPL/C4_fraction.jpeg +0 -0
  5. BESS_JPL/C4_fraction.tif +0 -0
  6. BESS_JPL/C4_photosynthesis.py +56 -0
  7. BESS_JPL/FVC_from_NDVI.py +22 -0
  8. BESS_JPL/LAI_from_NDVI.py +29 -0
  9. BESS_JPL/NDVI_maximum.jpeg +0 -0
  10. BESS_JPL/NDVI_maximum.tif +0 -0
  11. BESS_JPL/NDVI_minimum.jpeg +0 -0
  12. BESS_JPL/NDVI_minimum.tif +0 -0
  13. BESS_JPL/SZA/__init__.py +1 -0
  14. BESS_JPL/SZA/daylight_hours.py +74 -0
  15. BESS_JPL/__init__.py +10 -0
  16. BESS_JPL/ball_berry_intercept_C3.jpeg +0 -0
  17. BESS_JPL/ball_berry_intercept_C3.tif +0 -0
  18. BESS_JPL/ball_berry_slope_C3.jpeg +0 -0
  19. BESS_JPL/ball_berry_slope_C3.tif +0 -0
  20. BESS_JPL/ball_berry_slope_C4.jpeg +0 -0
  21. BESS_JPL/ball_berry_slope_C4.tif +0 -0
  22. BESS_JPL/calculate_VCmax.py +54 -0
  23. BESS_JPL/canopy_energy_balance.py +146 -0
  24. BESS_JPL/canopy_longwave_radiation.py +92 -0
  25. BESS_JPL/canopy_shortwave_radiation.py +234 -0
  26. BESS_JPL/carbon_uptake_efficiency.jpeg +0 -0
  27. BESS_JPL/carbon_uptake_efficiency.tif +0 -0
  28. BESS_JPL/carbon_water_fluxes.py +277 -0
  29. BESS_JPL/constants.py +8 -0
  30. BESS_JPL/interpolate_C3_C4.py +12 -0
  31. BESS_JPL/kn.jpeg +0 -0
  32. BESS_JPL/kn.tif +0 -0
  33. BESS_JPL/load_C4_fraction.py +10 -0
  34. BESS_JPL/load_NDVI_maximum.py +10 -0
  35. BESS_JPL/load_NDVI_minimum.py +10 -0
  36. BESS_JPL/load_ball_berry_intercept_C3.py +10 -0
  37. BESS_JPL/load_ball_berry_slope_C3.py +10 -0
  38. BESS_JPL/load_ball_berry_slope_C4.py +10 -0
  39. BESS_JPL/load_carbon_uptake_efficiency.py +10 -0
  40. BESS_JPL/load_kn.py +10 -0
  41. BESS_JPL/load_peakVCmax_C3.py +10 -0
  42. BESS_JPL/load_peakVCmax_C4.py +10 -0
  43. BESS_JPL/meteorology.py +204 -0
  44. BESS_JPL/peakVCmax_C3.jpeg +0 -0
  45. BESS_JPL/peakVCmax_C3.tif +0 -0
  46. BESS_JPL/peakVCmax_C4.jpeg +0 -0
  47. BESS_JPL/peakVCmax_C4.tif +0 -0
  48. BESS_JPL/soil_energy_balance.py +35 -0
  49. BESS_JPL/vegetation_conversion/__init__.py +1 -0
  50. BESS_JPL/vegetation_conversion/vegetation_conversion.py +71 -0
  51. BESS_JPL/version.txt +1 -0
  52. bess_jpl-1.6.0.dist-info/METADATA +95 -0
  53. bess_jpl-1.6.0.dist-info/RECORD +56 -0
  54. bess_jpl-1.6.0.dist-info/WHEEL +5 -0
  55. bess_jpl-1.6.0.dist-info/licenses/LICENSE +201 -0
  56. bess_jpl-1.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ from .SZA import *
@@ -0,0 +1,74 @@
1
+ """
2
+ This module calculates sunrise hour and daylight hours.
3
+
4
+ Developed by Gregory Halverson in the Jet Propulsion Laboratory Year-Round Internship Program (Columbus Technologies and Services, ANRE Tech.), in coordination with the ECOSTRESS mission and master's thesis studies at California State University, Northridge.
5
+ """
6
+ import warnings
7
+ from numpy import tan, cos, sin, pi, arccos, where, radians, degrees
8
+
9
+ __author__ = 'Gregory Halverson'
10
+
11
+
12
+ def day_angle_rad_from_doy(doy):
13
+ """
14
+ This function calculates day angle in radians from day of year between 1 and 365.
15
+ """
16
+ return (2 * pi * (doy - 1)) / 365
17
+
18
+
19
+ def solar_dec_deg_from_day_angle_rad(day_angle_rad):
20
+ """
21
+ This function calculates solar declination in degrees from day angle in radians.
22
+ """
23
+ return (0.006918 - 0.399912 * cos(day_angle_rad) + 0.070257 * sin(day_angle_rad) - 0.006758 * cos(
24
+ 2 * day_angle_rad) + 0.000907 * sin(2 * day_angle_rad) - 0.002697 * cos(3 * day_angle_rad) + 0.00148 * sin(
25
+ 3 * day_angle_rad)) * (180 / pi)
26
+
27
+
28
+ def sha_deg_from_doy_lat(doy, latitude):
29
+ """
30
+ This function calculates sunrise hour angle in degrees from latitude in degrees and day of year between 1 and 365.
31
+ """
32
+ # calculate day angle in radians
33
+ day_angle_rad = day_angle_rad_from_doy(doy)
34
+
35
+ # calculate solar declination in degrees
36
+ solar_dec_deg = solar_dec_deg_from_day_angle_rad(day_angle_rad)
37
+
38
+ # convert latitude to radians
39
+ latitude_rad = radians(latitude)
40
+
41
+ # convert solar declination to radians
42
+ solar_dec_rad = radians(solar_dec_deg)
43
+
44
+ # calculate cosine of sunrise angle at latitude and solar declination
45
+ # need to keep the cosine for polar correction
46
+ sunrise_cos = -tan(latitude_rad) * tan(solar_dec_rad)
47
+
48
+ # calculate sunrise angle in radians from cosine
49
+ warnings.filterwarnings('ignore')
50
+ sunrise_rad = arccos(sunrise_cos)
51
+ warnings.resetwarnings()
52
+
53
+ # convert to degrees
54
+ sunrise_deg = degrees(sunrise_rad)
55
+
56
+ # apply polar correction
57
+ sunrise_deg = where(sunrise_cos >= 1, 0, sunrise_deg)
58
+ sunrise_deg = where(sunrise_cos <= -1, 180, sunrise_deg)
59
+
60
+ return sunrise_deg
61
+
62
+
63
+ def sunrise_from_sha(sha_deg):
64
+ """
65
+ This function calculates sunrise hour from sunrise hour angle in degrees.
66
+ """
67
+ return 12.0 - (sha_deg / 15.0)
68
+
69
+
70
+ def daylight_from_sha(sha_deg):
71
+ """
72
+ This function calculates daylight hours from sunrise hour angle in degrees.
73
+ """
74
+ return (2.0 / 15.0) * sha_deg
BESS_JPL/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from .BESS_JPL import *
2
+
3
+ from os.path import join, dirname, abspath
4
+
5
+ with open(join(abspath(dirname(__file__)), "version.txt")) as f:
6
+ version = f.read()
7
+
8
+ __version__ = version
9
+ __author__ = "Gregory H. Halverson"
10
+
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,54 @@
1
+ from os.path import join, abspath, dirname
2
+
3
+ import rasters as rt
4
+ import numpy as np
5
+
6
+ from .constants import *
7
+
8
+ def calculate_VCmax(
9
+ LAI: np.ndarray,
10
+ LAI_minimum: np.ndarray,
11
+ LAI_maximum: np.ndarray,
12
+ peakVCmax_C3: np.ndarray,
13
+ peakVCmax_C4: np.ndarray,
14
+ SZA: np.ndarray,
15
+ kn: np.ndarray,
16
+ A: np.ndarray = A):
17
+ sf = np.clip(np.clip(LAI - LAI_minimum, 0, None) / np.clip(LAI_maximum - LAI_minimum, 1, None), 0, 1)
18
+ sf = np.where(np.isreal(sf), sf, 0)
19
+ sf = np.where(np.isnan(sf), 0, sf)
20
+
21
+ # calculate maximum carboxylation rate at 25C for C3 plants
22
+ VCmax_C3 = A * peakVCmax_C3 + (1 - A) * peakVCmax_C3 * sf
23
+
24
+ # calculate maximum carboxylation rate at 25C for C4 plants
25
+ VCmax_C4 = A * peakVCmax_C4 + (1 - A) * peakVCmax_C4 * sf
26
+
27
+ # kb = 0.5 / np.cos(np.radians(SZA))
28
+ kb = np.where(SZA > 89, 50.0, 0.5 / np.cos(np.radians(SZA)))
29
+ kn_kb_Lc = kn + kb * LAI
30
+ exp_neg_kn_kb_Lc = np.exp(-kn_kb_Lc)
31
+ LAI_VCmax_C3 = LAI * VCmax_C3
32
+ exp_neg_kn = np.exp(-kn)
33
+
34
+ # calculate total maximum carboxylation rate at 25C for C3 plants
35
+ VCmax_C3_total = LAI_VCmax_C3 * (1 - exp_neg_kn) / kn
36
+
37
+ # calculate sunlit maximum carboxylation rate at 25C for C3 plants
38
+ VCmax_C3_sunlit = LAI_VCmax_C3 * (1 - exp_neg_kn_kb_Lc) / kn_kb_Lc
39
+
40
+ # calculate shaded maximum carboxylation rate at 25C for C3 plants
41
+ VCmax_C3_shaded = VCmax_C3_total - VCmax_C3_sunlit
42
+
43
+ LAI_VCmax_C4 = LAI * VCmax_C4
44
+
45
+ # calculate total maximum carboxylation rate at 25C for C4 plants
46
+ VCmax_C4_total = LAI_VCmax_C4 * (1 - exp_neg_kn) / kn
47
+
48
+ # calculate sunlit maximum carboxylation rate at 25C for C4 plants
49
+ VCmax_C4_sunlit = LAI_VCmax_C4 * (1 - exp_neg_kn_kb_Lc) / kn_kb_Lc
50
+
51
+ # calculate shaded maximum carboxylation rate at 25C for C4 plants
52
+ VCmax_C4_shaded = VCmax_C4_total - VCmax_C4_sunlit
53
+
54
+ return VCmax_C3_sunlit, VCmax_C4_sunlit, VCmax_C3_shaded, VCmax_C4_shaded
@@ -0,0 +1,146 @@
1
+ import numpy as np
2
+
3
+ def process_paw_and_gao_LE(
4
+ Rn: np.ndarray, # net radiation (W m-2)
5
+ Ta_K: np.ndarray, # air temperature (K)
6
+ VPD_Pa: np.ndarray, # vapor pressure (Pa)
7
+ Cp: np.ndarray, # specific heat of air (J kg-1 K-1)
8
+ rhoa: np.ndarray, # air density (kg m-3)
9
+ gamma: np.ndarray, # psychrometric constant (Pa K-1)
10
+ Rc: np.ndarray,
11
+ rs: np.ndarray,
12
+ desTa: np.ndarray,
13
+ ddesTa: np.ndarray) -> np.ndarray:
14
+ """
15
+ :param Rn: net radiation (W m-2)
16
+ :param Ta_K: air temperature (K)
17
+ :param VPD_Pa: vapor pressure (Pa)
18
+ :param Cp: specific heat of air (J kg-1 K-1)
19
+ :param rhoa: air density (kg m-3)
20
+ :param gamma: psychrometric constant (Pa K-1)
21
+ :param Rc:
22
+ :param rs:
23
+ :param desTa:
24
+ :param ddesTa:
25
+ :return: latent heat flux (W m-2)
26
+ """
27
+ # To reduce redundant computation
28
+ rc = rs
29
+ ddesTa_Rc2 = ddesTa * Rc * Rc
30
+ gamma_Rc_rc = gamma * (Rc + rc)
31
+ rhoa_Cp_gamma_Rc_rc = rhoa * Cp * gamma_Rc_rc
32
+
33
+ # Solution (Paw and Gao 1988)
34
+ a = 1.0 / 2.0 * ddesTa_Rc2 / rhoa_Cp_gamma_Rc_rc # Eq. (10b)
35
+ b = -1.0 - Rc * desTa / gamma_Rc_rc - ddesTa_Rc2 * Rn / rhoa_Cp_gamma_Rc_rc # Eq. (10c)
36
+ c = rhoa * Cp / gamma_Rc_rc * VPD_Pa + desTa * Rc / gamma_Rc_rc * Rn + 1.0 / 2.0 * ddesTa_Rc2 / rhoa_Cp_gamma_Rc_rc * Rn * Rn # Eq. (10d) in Paw and Gao (1988)
37
+
38
+ # calculate latent heat flux
39
+ LE = (-b + np.sign(b) * np.sqrt(b * b - 4.0 * a * c)) / (2.0 * a) # Eq. (10a)
40
+ LE = np.real(LE)
41
+
42
+ # Constraints
43
+ # LE[LE > Rn] = Rn[LE > Rn]
44
+ LE = np.clip(LE, 0, Rn)
45
+ # LE[Rn < 0.0] = 0.0
46
+ # LE[LE < 0.0] = 0.0
47
+ # LE[Ta < 273.15] = 0.0
48
+ LE = np.where(Ta_K < 273.15, 0, LE)
49
+
50
+ return LE
51
+
52
+ def canopy_energy_balance(
53
+ An: np.ndarray, # net assimulation (An) [umol m-2 s-1],
54
+ ASW: np.ndarray, # total absorbed shortwave radiation by sunlit/shade canopy (ASW) [umol m-2 s-1],
55
+ ALW: np.ndarray, # total absorbed longwave radiation by sunlit/shade canopy (ALW) [umol m-2 s-1],
56
+ Tf_K: np.ndarray, # leaf temperature in Kelvin
57
+ Ps_Pa: np.ndarray, # surface pressure in Pascal
58
+ Ca: np.ndarray, # ambient CO2 concentration [umol mol-1],
59
+ Ta_K: np.ndarray, # air temperature in Kelvin
60
+ RH: np.ndarray, # relative humidity as a fraction
61
+ VPD_Pa: np.ndarray, # vapor pressure deficit in Pascal
62
+ desTa: np.ndarray, # 1st derivative of saturated vapour pressure
63
+ ddesTa: np.ndarray, # 2nd derivative of saturated vapour pressure
64
+ gamma: np.ndarray, # psychrometric constant [pa K-1],
65
+ Cp: np.ndarray, # specific heat of air [J kg-1 K-1],
66
+ rhoa: np.ndarray, # air density [kg m-3],
67
+ Rc: np.ndarray, # aerodynamic resistance [s m-1],
68
+ ball_berry_slope: np.ndarray, # Ball-Berry slope [-],
69
+ ball_berry_intercept: np.ndarray, # Ball-Berry intercept [-].
70
+ C4_photosynthesis: bool): # flag for C4 photosynthesis
71
+ """
72
+ =============================================================================
73
+
74
+ Module : Canopy energy balance
75
+ Input : net assimulation (An) [umol m-2 s-1],
76
+ : total absorbed shortwave radiation by sunlit/shade canopy (ASW) [umol m-2 s-1],
77
+ : total absorbed longwave radiation by sunlit/shade canopy (ALW) [umol m-2 s-1],
78
+ : sunlit/shade leaf temperature (Tf) [K],
79
+ : surface pressure (Ps) [Pa],
80
+ : ambient CO2 concentration (Ca) [umol mol-1],
81
+ : air temperature (Ta) [K],
82
+ : relative humidity (RH) [-],
83
+ : water vapour deficit (VPD) [Pa],
84
+ : 1st derivative of saturated vapour pressure (desTa),
85
+ : 2nd derivative of saturated vapour pressure (ddesTa),
86
+ : psychrometric constant (gamma) [pa K-1],
87
+ : air density (rhoa) [kg m-3],
88
+ : aerodynamic resistance (ra) [s m-1],
89
+ : Ball-Berry slope (m) [-],
90
+ : Ball-Berry intercept (b0) [-].
91
+ Output : sunlit/shade canopy net radiation (Rn) [W m-2],
92
+ : sunlit/shade canopy latent heat (LE) [W m-2],
93
+ : sunlit/shade canopy sensible heat (H) [W m-2],
94
+ : sunlit/shade leaf temperature (Tl) [K],
95
+ : stomatal resistance to vapour transfer from cell to leaf surface (rs) [s m-1],
96
+ : inter-cellular CO2 concentration (Ci) [umol mol-1].
97
+ References : Paw U, K. T., & Gao, W. (1988). Applications of solutions to non-linear energy budget equations.
98
+ Agricultural and Forest Meteorology, 43(2), 121�145. doi:10.1016/0168-1923(88)90087-1
99
+
100
+
101
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
102
+ March 2020
103
+
104
+ =============================================================================
105
+ """
106
+ # Convert factor
107
+ cf = 0.446 * (273.15 / Tf_K) * (Ps_Pa / 101325.0)
108
+
109
+ # Stefan_Boltzmann_constant
110
+ sigma = 5.670373e-8 # [W m-2 K-4] (Wiki)
111
+
112
+ # Ball-Berry stomatal conductance
113
+ # https://onlinelibrary.wiley.com/doi/full/10.1111/pce.12990
114
+ gs1 = ball_berry_slope * RH * An / Ca + ball_berry_intercept # [mol m-2 s-1]
115
+
116
+ # intercellular CO2 concentration
117
+ Ci = Ca - 1.6 * An / gs1 # [umol./mol]
118
+
119
+ # constrain intercellular CO2 concentration based on ambient CO2 concentration differently depending on C3 or C4 photosynthesis
120
+ Ci = np.clip(Ci, 0.2 * Ca, 0.6 * Ca) if C4_photosynthesis else np.clip(Ci, 0.5 * Ca, 0.9 * Ca)
121
+
122
+ # Stomatal resistance to vapour transfer from cell to leaf surface
123
+ rs = 1.0 / (gs1 / cf * 1e-2) # [s m-1]
124
+
125
+ # Stomatal H2O conductance
126
+ gs2 = 1.0 / rs # [m s-1]
127
+
128
+ # Canopy net radiation
129
+ Rn = np.clip(ASW + ALW - 4.0 * 0.98 * sigma * (Ta_K ** 3) * (Tf_K - Ta_K), 0, None)
130
+
131
+ # TODO explore options for alternate latent heat flux models in the BESS canopy energy balance calculation
132
+
133
+ # caluclate latent heat flux using Paw and Gao (1988)
134
+ # https://www.sciencedirect.com/science/article/abs/pii/0168192388900871
135
+ LE = process_paw_and_gao_LE(Rn, Ta_K, VPD_Pa, Cp, rhoa, gamma, Rc, rs, desTa, ddesTa)
136
+
137
+ # update sensible heat flux
138
+ H = np.clip(Rn - LE, 0, Rn)
139
+
140
+ # update difference between air and canopy temperature
141
+ dT = np.clip(Rc / (rhoa * Cp) * H, -20, 20) # Eq. (6)
142
+
143
+ # update canopy temperature in Kelvin
144
+ Tf_K = Ta_K + dT
145
+
146
+ return Rn, LE, H, Tf_K, gs2, Ci
@@ -0,0 +1,92 @@
1
+ import numpy as np
2
+
3
+
4
+ def canopy_longwave_radiation(
5
+ LAI: np.ndarray,
6
+ SZA: np.ndarray,
7
+ Ts_K: np.ndarray,
8
+ Tf_K: np.ndarray,
9
+ Ta_K: np.ndarray,
10
+ epsa: np.ndarray,
11
+ epsf: float,
12
+ epss: float,
13
+ ALW_min: float = None,
14
+ intermediate_min: float = None,
15
+ intermediate_max: float = None):
16
+ """
17
+ =============================================================================
18
+
19
+ Module : Canopy longwave radiation transfer
20
+ Input : leaf area index (LAI) [-],
21
+ : extinction coefficient for longwave radiation (kd) [m-1],
22
+ : extinction coefficient for beam radiation (kb) [m-1],
23
+ : air temperature (Ta) [K],
24
+ : soil temperature (Ts) [K],
25
+ : foliage temperature (Tf) [K],
26
+ : clear-sky emissivity (epsa) [-],
27
+ : soil emissivity (epss) [-],
28
+ : foliage emissivity (epsf) [-].
29
+ Output : total absorbed LW by sunlit leaves (Q_LSun),
30
+ : total absorbed LW by shade leaves (Q_LSh).
31
+ References : Wang, Y., Law, R. M., Davies, H. L., McGregor, J. L., & Abramowitz, G. (2006).
32
+ The CSIRO Atmosphere Biosphere Land Exchange (CABLE) model for use in climate models and as an offline model.
33
+
34
+
35
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
36
+ March 2020
37
+
38
+ =============================================================================
39
+ """
40
+ # SZA[SZA > 89.0] = 89.0
41
+ SZA = np.clip(SZA, None, 89)
42
+ kb = 0.5 / np.cos(SZA * np.pi / 180.0) # Table A1 in Ryu et al 2011
43
+ kd = 0.78 # Table A1 in Ryu et al 2011
44
+
45
+ # Stefan_Boltzmann_constant
46
+ sigma = 5.670373e-8 # [W m-2 K-4] (Wiki)
47
+
48
+ # Long wave radiation flux densities from air, soil and leaf
49
+ La = np.clip(epsa * sigma * Ta_K ** 4, 0, None)
50
+ Ls = np.clip(epss * sigma * Ts_K ** 4, 0, None)
51
+ Lf = np.clip(epsf * sigma * Tf_K ** 4, 0, None)
52
+
53
+ # For simplicity
54
+ kd_LAI = kd * LAI
55
+
56
+ soil_leaf_difference = Ls - Lf
57
+
58
+ if intermediate_min is not None:
59
+ soil_leaf_difference = np.clip(soil_leaf_difference, intermediate_min, None)
60
+
61
+ air_leaf_difference = La - Lf
62
+
63
+ if intermediate_min is not None:
64
+ air_leaf_difference = np.clip(air_leaf_difference, intermediate_min, intermediate_max)
65
+
66
+ # Absorbed longwave radiation by sunlit leaves
67
+ numerator = soil_leaf_difference * kd * (np.exp(-kd_LAI) - np.exp(-kb * LAI)) / (kd - kb) + kd * air_leaf_difference * (1.0 - np.exp(-(kb + kd) * LAI))
68
+ denominator = kd + kb
69
+
70
+ if ALW_min is not None:
71
+ numerator = np.clip(numerator, ALW_min, None)
72
+
73
+ ALW_sunlit = numerator / denominator # Eq. (44)
74
+
75
+ soil_air_leaf = Ls + La - 2 * Lf
76
+
77
+ if intermediate_min is not None or intermediate_max is not None:
78
+ soil_air_leaf = np.clip(soil_air_leaf, intermediate_min, intermediate_max)
79
+
80
+ # Absorbed longwave radiation by shaded leaves
81
+ ALW_shaded = (1.0 - np.exp(-kd_LAI)) * soil_air_leaf - ALW_sunlit # Eq. (45)
82
+
83
+ if ALW_min is not None:
84
+ ALW_shaded = np.clip(ALW_shaded, ALW_min, None)
85
+
86
+ # Absorbed longwave radiation by soil
87
+ ALW_soil = (1.0 - np.exp(-kd_LAI)) * Lf + np.exp(-kd_LAI) * La # Eq. (41)
88
+
89
+ if ALW_min is not None:
90
+ ALW_soil = np.clip(ALW_soil, ALW_min, None)
91
+
92
+ return ALW_sunlit, ALW_shaded, ALW_soil, Ls, La, Lf
@@ -0,0 +1,234 @@
1
+ import numpy as np
2
+
3
+ from check_distribution import check_distribution
4
+
5
+
6
+ def canopy_shortwave_radiation(
7
+ PARDiff: np.ndarray,
8
+ PARDir: np.ndarray,
9
+ NIRDiff: np.ndarray,
10
+ NIRDir: np.ndarray,
11
+ UV: np.ndarray,
12
+ SZA: np.ndarray,
13
+ LAI: np.ndarray,
14
+ CI: np.ndarray,
15
+ albedo_visible: np.ndarray,
16
+ albedo_NIR: np.ndarray):
17
+ """
18
+ =============================================================================
19
+
20
+ Module : Canopy radiative transfer
21
+ Input : diffuse PAR radiation (PARDiff) [W m-2],
22
+ : direct PAR radiation (PARDir) [W m-2],
23
+ : diffuse NIR radiation (NIRDiff) [W m-2],
24
+ : direct NIR radiation (NIRDir) [W m-2],
25
+ : ultroviolet radiation (UV) [W m-2],
26
+ : solar zenith angle (SZA) [degree],
27
+ : leaf area index (LAI) [-],
28
+ : clumping index (CI) [-],
29
+ : VIS albedo (ALB_VIS) [-],
30
+ : NIR albedo (ALB_NIR) [-],
31
+ : leaf maximum carboxylation rate at 25C for C3 plant (Vcmax25_C3Leaf) [umol m-2 s-1],
32
+ : leaf maximum carboxylation rate at 25C for C4 plant (Vcmax25_C4Leaf) [umol m-2 s-1].
33
+ Output : total absorbed PAR by sunlit leaves (APAR_Sun) [umol m-2 s-1],
34
+ : total absorbed PAR by shade leaves (APAR_Sh) [umol m-2 s-1],
35
+ : total absorbed SW by sunlit leaves (ASW_Sun) [W m-2],
36
+ : total absorbed SW by shade leaves (ASW_Sh) [W m-2],
37
+ : sunlit canopy maximum carboxylation rate at 25C for C3 plant (Vcmax25_C3Sun) [umol m-2 s-1],
38
+ : shade canopy maximum carboxylation rate at 25C for C3 plant (Vcmax25_C3Sh) [umol m-2 s-1],
39
+ : sunlit canopy maximum carboxylation rate at 25C for C4 plant (Vcmax25_C4Sun) [umol m-2 s-1],
40
+ : shade canopy maximum carboxylation rate at 25C for C4 plant (Vcmax25_C4Sh) [umol m-2 s-1],
41
+ : fraction of sunlit canopy (fSun) [-],
42
+ : ground heat storage (G) [W m-2],
43
+ : total absorbed SW by soil (ASW_Soil) [W m-2].
44
+ References : Ryu, Y., Baldocchi, D. D., Kobayashi, H., Van Ingen, C., Li, J., Black, T. A., Beringer, J.,
45
+ Van Gorsel, E., Knohl, A., Law, B. E., & Roupsard, O. (2011).
46
+
47
+ Integration of MODIS land and atmosphere products with a coupled-process model i
48
+ to estimate gross primary productivity and evapotranspiration from 1 km to global scales.
49
+ Global Biogeochemical Cycles, 25(GB4017), 1-24. doi:10.1029/2011GB004053.1.
50
+
51
+
52
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
53
+ March 2020
54
+
55
+ =============================================================================
56
+ """
57
+ # Leaf scattering coefficients and soil reflectance (Sellers 1985)
58
+ SIGMA_P = 0.175
59
+ RHO_PSOIL = 0.15
60
+ SIGMA_N = 0.825
61
+ RHO_NSOIL = 0.30
62
+
63
+ # Extinction coefficient for diffuse and scattered diffuse PAR
64
+ kk_Pd = 0.72 # Table A1
65
+
66
+ # Check for None parameters
67
+ parameters = {
68
+ "PARDiff": PARDiff,
69
+ "PARDir": PARDir,
70
+ "NIRDiff": NIRDiff,
71
+ "NIRDir": NIRDir,
72
+ "UV": UV,
73
+ "SZA": SZA,
74
+ "LAI": LAI,
75
+ "CI": CI,
76
+ "albedo_visible": albedo_visible,
77
+ "albedo_NIR": albedo_NIR
78
+ }
79
+
80
+ for param_name, param_value in parameters.items():
81
+ check_distribution(param_value, param_name)
82
+ if param_value is None:
83
+ raise ValueError(f"The parameter '{param_name}' cannot be None.")
84
+
85
+ # Beam radiation extinction coefficient of canopy
86
+ kb = np.where(SZA > 89, 50.0, 0.5 / np.cos(np.radians(SZA))) # Table A1
87
+ check_distribution(kb, "kb")
88
+
89
+ # Extinction coefficient for beam and scattered beam PAR
90
+ kk_Pb = np.where(SZA > 89, 50.0, 0.46 / np.cos(np.radians(SZA))) # Table A1
91
+ check_distribution(kk_Pb, "kk_Pb")
92
+
93
+ # Extinction coefficient for beam and scattered beam NIR
94
+ kk_Nb = kb * np.sqrt(1.0 - SIGMA_N) # Table A1
95
+ check_distribution(kk_Nb, "kk_Nb")
96
+
97
+ # Extinction coefficient for diffuse and scattered diffuse NIR
98
+ kk_Nd = 0.35 * np.sqrt(1.0 - SIGMA_N) # Table A1
99
+ check_distribution(kk_Nd, "kk_Nd")
100
+
101
+ # Sunlit fraction
102
+ fSun = np.clip(1.0 / kb * (1.0 - np.exp(-kb * LAI * CI)) / LAI, 0, 1) # Integration of Eq. (1)
103
+ fSun = np.where(LAI == 0, 0, fSun) # Eq. (1)
104
+ check_distribution(fSun, "fSun")
105
+
106
+ # For simplicity
107
+ L_CI = LAI * CI
108
+ check_distribution(L_CI, "L_CI")
109
+ exp_kk_Pd_L_CI = np.exp(-kk_Pd * L_CI)
110
+ check_distribution(exp_kk_Pd_L_CI, "exp_kk_Pd_L_CI")
111
+ exp_kk_Nd_L_CI = np.exp(-kk_Nd * L_CI)
112
+ check_distribution(exp_kk_Nd_L_CI, "exp_kk_Nd_L_CI")
113
+
114
+ # Total absorbed incoming PAR
115
+ Q_PDn = (1.0 - albedo_visible) * PARDir * (1.0 - np.exp(-kk_Pb * L_CI)) + (1.0 - albedo_visible) * PARDiff * (
116
+ 1.0 - exp_kk_Pd_L_CI) # Eq. (2)
117
+ check_distribution(Q_PDn, "Q_PDn")
118
+
119
+ # Absorbed incoming beam PAR by sunlit leaves
120
+ Q_PbSunDn = PARDir * (1.0 - SIGMA_P) * (1.0 - np.exp(-kb * L_CI)) # Eq. (3)
121
+ check_distribution(Q_PbSunDn, "Q_PbSunDn")
122
+
123
+ # Absorbed incoming diffuse PAR by sunlit leaves
124
+ Q_PdSunDn = PARDiff * (1.0 - albedo_visible) * (1.0 - np.exp(-(kk_Pd + kb) * L_CI)) * kk_Pd / (
125
+ kk_Pd + kb) # Eq. (4)
126
+ check_distribution(Q_PdSunDn, "Q_PdSunDn")
127
+
128
+ # Absorbed incoming scattered PAR by sunlit leaves
129
+ Q_PsSunDn = PARDir * (
130
+ (1.0 - albedo_visible) * (1.0 - np.exp(-(kk_Pb + kb) * L_CI)) * kk_Pb / (kk_Pb + kb) - (1.0 - SIGMA_P) * (
131
+ 1.0 - np.exp(-2.0 * kb * L_CI)) / 2.0) # Eq. (5)
132
+ Q_PsSunDn = np.clip(Q_PsSunDn, 0, None)
133
+ check_distribution(Q_PsSunDn, "Q_PsSunDn")
134
+
135
+ # Absorbed incoming PAR by sunlit leaves
136
+ Q_PSunDn = Q_PbSunDn + Q_PdSunDn + Q_PsSunDn # Eq. (6)
137
+ check_distribution(Q_PSunDn, "Q_PSunDn")
138
+
139
+ # Absorbed incoming PAR by shade leaves
140
+ Q_PShDn = np.clip(Q_PDn - Q_PSunDn, 0, None) # Eq. (7)
141
+ check_distribution(Q_PShDn, "Q_PShDn")
142
+
143
+ # Incoming PAR at soil surface
144
+ I_PSoil = np.clip((1.0 - albedo_visible) * PARDir + (1 - albedo_visible) * PARDiff - (Q_PSunDn + Q_PShDn), 0, None)
145
+ check_distribution(I_PSoil, "I_PSoil")
146
+
147
+ # Absorbed PAR by soil
148
+ APAR_Soil = np.clip((1.0 - RHO_PSOIL) * I_PSoil, 0, None)
149
+ check_distribution(APAR_Soil, "APAR_Soil")
150
+
151
+ # Absorbed outgoing PAR by sunlit leaves
152
+ Q_PSunUp = np.clip(I_PSoil * RHO_PSOIL * exp_kk_Pd_L_CI, 0, None) # Eq. (8)
153
+ check_distribution(Q_PSunUp, "Q_PSunUp")
154
+
155
+ # Absorbed outgoing PAR by shade leaves
156
+ Q_PShUp = np.clip(I_PSoil * RHO_PSOIL * (1 - exp_kk_Pd_L_CI), 0, None) # Eq. (9)
157
+ check_distribution(Q_PShUp, "Q_PShUp")
158
+
159
+ # Total absorbed PAR by sunlit leaves
160
+ APAR_Sun = Q_PSunDn + Q_PSunUp # Eq. (10)
161
+ check_distribution(APAR_Sun, "APAR_Sun")
162
+
163
+ # Total absorbed PAR by shade leaves
164
+ APAR_Sh = Q_PShDn + Q_PShUp # Eq. (11)
165
+ check_distribution(APAR_Sh, "APAR_Sh")
166
+
167
+ # Absorbed incoming NIR by sunlit leaves
168
+ Q_NSunDn = NIRDir * (1.0 - SIGMA_N) * (1.0 - np.exp(-kb * L_CI)) + NIRDiff * (1 - albedo_NIR) * (
169
+ 1.0 - np.exp(-(kk_Nd + kb) * L_CI)) * kk_Nd / (kk_Nd + kb) + NIRDir * (
170
+ (1.0 - albedo_NIR) * (1.0 - np.exp(-(kk_Nb + kb) * L_CI)) * kk_Nb / (kk_Nb + kb) - (
171
+ 1.0 - SIGMA_N) * (1.0 - np.exp(-2.0 * kb * L_CI)) / 2.0) # Eq. (14)
172
+ Q_NSunDn = np.clip(Q_NSunDn, 0, None)
173
+ check_distribution(Q_NSunDn, "Q_NSunDn")
174
+
175
+ # Absorbed incoming NIR by shade leaves
176
+ Q_NShDn = (1.0 - albedo_NIR) * NIRDir * (1.0 - np.exp(-kk_Nb * L_CI)) + (1.0 - albedo_NIR) * NIRDiff * (
177
+ 1.0 - exp_kk_Nd_L_CI) - Q_NSunDn # Eq. (15)
178
+ Q_NShDn = np.clip(Q_NShDn, 0, None)
179
+ check_distribution(Q_NShDn, "Q_NShDn")
180
+
181
+ # Incoming NIR at soil surface
182
+ I_NSoil = (1.0 - albedo_NIR) * NIRDir + (1.0 - albedo_NIR) * NIRDiff - (Q_NSunDn + Q_NShDn)
183
+ I_NSoil = np.clip(I_NSoil, 0, None)
184
+ check_distribution(I_NSoil, "I_NSoil")
185
+
186
+ # Absorbed NIR by soil
187
+ ANIR_Soil = (1.0 - RHO_NSOIL) * I_NSoil
188
+ ANIR_Soil = np.clip(ANIR_Soil, 0, None)
189
+ check_distribution(ANIR_Soil, "ANIR_Soil")
190
+
191
+ # Absorbed outgoing NIR by sunlit leaves
192
+ Q_NSunUp = I_NSoil * RHO_NSOIL * exp_kk_Nd_L_CI # Eq. (16)
193
+ Q_NSunUp = np.clip(Q_NSunUp, 0, None)
194
+ check_distribution(Q_NSunUp, "Q_NSunUp")
195
+
196
+ # Absorbed outgoing NIR by shade leaves
197
+ Q_NShUp = I_NSoil * RHO_NSOIL * (1.0 - exp_kk_Nd_L_CI) # Eq. (17)
198
+ Q_NShUp = np.clip(Q_NShUp, 0, None)
199
+ check_distribution(Q_NShUp, "Q_NShUp")
200
+
201
+ # Total absorbed NIR by sunlit leaves
202
+ ANIR_Sun = Q_NSunDn + Q_NSunUp # Eq. (18)
203
+ check_distribution(ANIR_Sun, "ANIR_Sun")
204
+
205
+ # Total absorbed NIR by shade leaves
206
+ ANIR_Sh = Q_NShDn + Q_NShUp # Eq. (19)
207
+ check_distribution(ANIR_Sh, "ANIR_Sh")
208
+
209
+ # UV
210
+ UVDir = UV * PARDir / (PARDir + PARDiff + 1e-5)
211
+ UVDiff = UV - UVDir
212
+ Q_U = (1.0 - 0.05) * UVDiff * (1.0 - np.exp(-kk_Pb * L_CI)) + (1.0 - 0.05) * UVDiff * (1.0 - exp_kk_Pd_L_CI)
213
+ AUV_Sun = Q_U * fSun
214
+ AUV_Sh = Q_U * (1 - fSun)
215
+ AUV_Soil = (1.0 - 0.05) * UV - Q_U
216
+
217
+ # Ground heat storage
218
+ G = APAR_Soil * 0.28
219
+ check_distribution(G, "G")
220
+
221
+ # Summary
222
+ ASW_Sun = APAR_Sun + ANIR_Sun + AUV_Sun
223
+ ASW_Sun = np.where(LAI == 0, 0, ASW_Sun)
224
+ ASW_Sh = APAR_Sh + ANIR_Sh + AUV_Sh
225
+ ASW_Sh = np.where(LAI == 0, 0, ASW_Sh)
226
+ ASW_Soil = APAR_Soil + ANIR_Soil + AUV_Soil
227
+ APAR_Sun = np.where(LAI == 0, 0, APAR_Sun)
228
+ APAR_Sun = APAR_Sun * 4.56
229
+ APAR_Sh = np.where(LAI == 0, 0, APAR_Sh)
230
+ APAR_Sh = APAR_Sh * 4.56
231
+
232
+ # TODO not sure about these variables: Vcmax25_C3Sun, Vcmax25_C3Sh, Vcmax25_C4Sun, Vcmax25_C4Sh
233
+
234
+ return fSun, APAR_Sun, APAR_Sh, ASW_Sun, ASW_Sh, ASW_Soil, G
Binary file
Binary file