BESS-JPL 1.26.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.
Files changed (73) hide show
  1. BESS_JPL/BESS_JPL.py +54 -0
  2. BESS_JPL/C3_photosynthesis.py +165 -0
  3. BESS_JPL/C4_fraction.jpeg +0 -0
  4. BESS_JPL/C4_fraction.tif +0 -0
  5. BESS_JPL/C4_fraction.tif.aux.xml +11 -0
  6. BESS_JPL/C4_photosynthesis.py +133 -0
  7. BESS_JPL/ECOv002-cal-val-BESS-JPL-GEOS5FP-inputs.csv +1066 -0
  8. BESS_JPL/ECOv002-cal-val-BESS-JPL-inputs.csv +1066 -0
  9. BESS_JPL/ECOv002-cal-val-BESS-JPL-outputs.csv +1066 -0
  10. BESS_JPL/ECOv002-cal-val-FLiESANN-inputs.csv +1066 -0
  11. BESS_JPL/ECOv002-static-tower-BESS-JPL-inputs.csv +122 -0
  12. BESS_JPL/ECOv002_calval_BESS_inputs.py +30 -0
  13. BESS_JPL/ECOv002_static_tower_BESS_inputs.py +19 -0
  14. BESS_JPL/FVC_from_NDVI.py +22 -0
  15. BESS_JPL/LAI_from_NDVI.py +28 -0
  16. BESS_JPL/NDVI_maximum.jpeg +0 -0
  17. BESS_JPL/NDVI_maximum.tif +0 -0
  18. BESS_JPL/NDVI_minimum.jpeg +0 -0
  19. BESS_JPL/NDVI_minimum.tif +0 -0
  20. BESS_JPL/__init__.py +5 -0
  21. BESS_JPL/ball_berry_intercept_C3.jpeg +0 -0
  22. BESS_JPL/ball_berry_intercept_C3.tif +0 -0
  23. BESS_JPL/ball_berry_slope_C3.jpeg +0 -0
  24. BESS_JPL/ball_berry_slope_C3.tif +0 -0
  25. BESS_JPL/ball_berry_slope_C4.jpeg +0 -0
  26. BESS_JPL/ball_berry_slope_C4.tif +0 -0
  27. BESS_JPL/calculate_VCmax.py +90 -0
  28. BESS_JPL/calculate_bulk_aerodynamic_resistance.py +119 -0
  29. BESS_JPL/calculate_friction_velocity.py +111 -0
  30. BESS_JPL/canopy_energy_balance.py +110 -0
  31. BESS_JPL/canopy_longwave_radiation.py +117 -0
  32. BESS_JPL/canopy_shortwave_radiation.py +276 -0
  33. BESS_JPL/carbon_uptake_efficiency.jpeg +0 -0
  34. BESS_JPL/carbon_uptake_efficiency.tif +0 -0
  35. BESS_JPL/carbon_water_fluxes.py +313 -0
  36. BESS_JPL/colors.py +33 -0
  37. BESS_JPL/constants.py +25 -0
  38. BESS_JPL/exceptions.py +3 -0
  39. BESS_JPL/generate_BESS_GEOS5FP_inputs.py +58 -0
  40. BESS_JPL/generate_BESS_inputs_table.py +186 -0
  41. BESS_JPL/generate_input_dataset.py +243 -0
  42. BESS_JPL/generate_output_dataset.py +26 -0
  43. BESS_JPL/interpolate_C3_C4.py +12 -0
  44. BESS_JPL/kn.jpeg +0 -0
  45. BESS_JPL/kn.tif +0 -0
  46. BESS_JPL/load_C4_fraction.py +20 -0
  47. BESS_JPL/load_NDVI_maximum.py +17 -0
  48. BESS_JPL/load_NDVI_minimum.py +17 -0
  49. BESS_JPL/load_ball_berry_intercept_C3.py +10 -0
  50. BESS_JPL/load_ball_berry_slope_C3.py +10 -0
  51. BESS_JPL/load_ball_berry_slope_C4.py +10 -0
  52. BESS_JPL/load_carbon_uptake_efficiency.py +10 -0
  53. BESS_JPL/load_kn.py +10 -0
  54. BESS_JPL/load_peakVCmax_C3.py +12 -0
  55. BESS_JPL/load_peakVCmax_C4.py +12 -0
  56. BESS_JPL/meteorology.py +429 -0
  57. BESS_JPL/model.py +594 -0
  58. BESS_JPL/peakVCmax_C3.jpeg +0 -0
  59. BESS_JPL/peakVCmax_C3.tif +0 -0
  60. BESS_JPL/peakVCmax_C4.jpeg +0 -0
  61. BESS_JPL/peakVCmax_C4.tif +0 -0
  62. BESS_JPL/process_BESS_table.py +365 -0
  63. BESS_JPL/process_paw_and_gao_LE.py +50 -0
  64. BESS_JPL/retrieve_BESS_JPL_GEOS5FP_inputs.py +257 -0
  65. BESS_JPL/retrieve_BESS_inputs.py +279 -0
  66. BESS_JPL/soil_energy_balance.py +35 -0
  67. BESS_JPL/verify.py +127 -0
  68. BESS_JPL/version.py +3 -0
  69. bess_jpl-1.26.0.dist-info/METADATA +102 -0
  70. bess_jpl-1.26.0.dist-info/RECORD +73 -0
  71. bess_jpl-1.26.0.dist-info/WHEEL +5 -0
  72. bess_jpl-1.26.0.dist-info/licenses/LICENSE +201 -0
  73. bess_jpl-1.26.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,110 @@
1
+ import numpy as np
2
+
3
+ from .process_paw_and_gao_LE import process_paw_and_gao_LE
4
+
5
+ def canopy_energy_balance(
6
+ An: np.ndarray, # net assimulation (An) [umol m-2 s-1],
7
+ ASW_Wm2: np.ndarray, # total absorbed shortwave radiation by sunlit/shade canopy (ASW) [umol m-2 s-1],
8
+ ALW_Wm2: np.ndarray, # total absorbed longwave radiation by sunlit/shade canopy (ALW) [umol m-2 s-1],
9
+ Tf_K: np.ndarray, # leaf temperature in Kelvin
10
+ Ps_Pa: np.ndarray, # surface pressure in Pascal
11
+ Ca: np.ndarray, # ambient CO2 concentration [umol mol-1],
12
+ Ta_K: np.ndarray, # air temperature in Kelvin
13
+ RH: np.ndarray, # relative humidity as a fraction
14
+ VPD_Pa: np.ndarray, # vapor pressure deficit in Pascal
15
+ desTa: np.ndarray, # 1st derivative of saturated vapour pressure
16
+ ddesTa: np.ndarray, # 2nd derivative of saturated vapour pressure
17
+ gamma: np.ndarray, # psychrometric constant [pa K-1],
18
+ Cp: np.ndarray, # specific heat of air [J kg-1 K-1],
19
+ rhoa: np.ndarray, # air density [kg m-3],
20
+ Rc: np.ndarray, # aerodynamic resistance [s m-1],
21
+ ball_berry_slope: np.ndarray, # Ball-Berry slope [-],
22
+ ball_berry_intercept: np.ndarray, # Ball-Berry intercept [-].
23
+ C4_photosynthesis: bool): # flag for C4 photosynthesis
24
+ """
25
+ =============================================================================
26
+
27
+ Module : Canopy energy balance
28
+ Input : net assimulation (An) [umol m-2 s-1],
29
+ : total absorbed shortwave radiation by sunlit/shade canopy (ASW) [umol m-2 s-1],
30
+ : total absorbed longwave radiation by sunlit/shade canopy (ALW) [umol m-2 s-1],
31
+ : sunlit/shade leaf temperature (Tf) [K],
32
+ : surface pressure (Ps) [Pa],
33
+ : ambient CO2 concentration (Ca) [umol mol-1],
34
+ : air temperature (Ta) [K],
35
+ : relative humidity (RH) [-],
36
+ : water vapour deficit (VPD) [Pa],
37
+ : 1st derivative of saturated vapour pressure (desTa),
38
+ : 2nd derivative of saturated vapour pressure (ddesTa),
39
+ : psychrometric constant (gamma) [pa K-1],
40
+ : air density (rhoa) [kg m-3],
41
+ : aerodynamic resistance (ra) [s m-1],
42
+ : Ball-Berry slope (m) [-],
43
+ : Ball-Berry intercept (b0) [-].
44
+ Output : sunlit/shade canopy net radiation (Rn) [W m-2],
45
+ : sunlit/shade canopy latent heat (LE) [W m-2],
46
+ : sunlit/shade canopy sensible heat (H) [W m-2],
47
+ : sunlit/shade leaf temperature (Tl) [K],
48
+ : stomatal resistance to vapour transfer from cell to leaf surface (rs) [s m-1],
49
+ : inter-cellular CO2 concentration (Ci) [umol mol-1].
50
+ References : Paw U, K. T., & Gao, W. (1988). Applications of solutions to non-linear energy budget equations.
51
+ Agricultural and Forest Meteorology, 43(2), 121�145. doi:10.1016/0168-1923(88)90087-1
52
+
53
+
54
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
55
+ March 2020
56
+
57
+ =============================================================================
58
+ """
59
+ # Convert factor
60
+ cf = 0.446 * (273.15 / Tf_K) * (Ps_Pa / 101325.0)
61
+
62
+ # Stefan_Boltzmann_constant
63
+ sigma = 5.670373e-8 # [W m-2 K-4] (Wiki)
64
+
65
+ # Ball-Berry stomatal conductance
66
+ # https://onlinelibrary.wiley.com/doi/full/10.1111/pce.12990
67
+ gs1 = ball_berry_slope * RH * An / Ca + ball_berry_intercept # [mol m-2 s-1]
68
+
69
+ # intercellular CO2 concentration
70
+ Ci = Ca - 1.6 * An / gs1 # [umol./mol]
71
+
72
+ # constrain intercellular CO2 concentration based on ambient CO2 concentration differently depending on C3 or C4 photosynthesis
73
+ Ci = np.clip(Ci, 0.2 * Ca, 0.6 * Ca) if C4_photosynthesis else np.clip(Ci, 0.5 * Ca, 0.9 * Ca)
74
+
75
+ # Stomatal resistance to vapour transfer from cell to leaf surface
76
+ rs = 1.0 / (gs1 / cf * 1e-2) # [s m-1]
77
+
78
+ # Stomatal H2O conductance
79
+ gs2 = 1.0 / rs # [m s-1]
80
+
81
+ # Canopy net radiation
82
+ Rn = np.clip(ASW_Wm2 + ALW_Wm2 - 4.0 * 0.98 * sigma * (Ta_K ** 3) * (Tf_K - Ta_K), 0, None)
83
+
84
+ # TODO explore options for alternate latent heat flux models in the BESS canopy energy balance calculation
85
+
86
+ # caluclate latent heat flux using Paw and Gao (1988)
87
+ # https://www.sciencedirect.com/science/article/abs/pii/0168192388900871
88
+ LE = process_paw_and_gao_LE(
89
+ Rn=Rn,
90
+ Ta_C=Ta_K - 273.15,
91
+ VPD_Pa=VPD_Pa,
92
+ Cp=Cp,
93
+ rhoa=rhoa,
94
+ gamma=gamma,
95
+ Rc=Rc,
96
+ rs=rs,
97
+ desTa=desTa,
98
+ ddesTa=ddesTa
99
+ )
100
+
101
+ # update sensible heat flux
102
+ H = np.clip(Rn - LE, 0, Rn)
103
+
104
+ # update difference between air and canopy temperature
105
+ dT = np.clip(Rc / (rhoa * Cp) * H, -20, 20) # Eq. (6)
106
+
107
+ # update canopy temperature in Kelvin
108
+ Tf_K = Ta_K + dT
109
+
110
+ return Rn, LE, H, Tf_K, gs2, Ci
@@ -0,0 +1,117 @@
1
+ import numpy as np
2
+
3
+ def canopy_longwave_radiation(
4
+ LAI: np.ndarray,
5
+ SZA: np.ndarray,
6
+ Ts_K: np.ndarray,
7
+ Tf_K: np.ndarray,
8
+ Ta_K: np.ndarray,
9
+ epsa: np.ndarray,
10
+ epsf: float,
11
+ epss: float,
12
+ ALW_min: float = None,
13
+ intermediate_min: float = None,
14
+ intermediate_max: float = None):
15
+ """
16
+ =============================================================================
17
+
18
+ Module : Canopy longwave radiation transfer
19
+ Description: This function calculates the absorbed longwave radiation by sunlit leaves, shaded leaves, and soil within a vegetative canopy. It also computes the longwave radiation flux densities from air, soil, and foliage. The calculations are based on radiative transfer principles and empirical coefficients derived from literature.
20
+
21
+ Inputs:
22
+ LAI (np.ndarray): Leaf Area Index [-], a dimensionless measure of leaf area per unit ground area.
23
+ SZA (np.ndarray): Solar Zenith Angle [degrees], the angle between the sun's rays and the vertical.
24
+ Ts_K (np.ndarray): Soil temperature [K].
25
+ Tf_K (np.ndarray): Foliage temperature [K].
26
+ Ta_K (np.ndarray): Air temperature [K].
27
+ epsa (np.ndarray): Clear-sky emissivity [-].
28
+ epsf (float): Foliage emissivity [-].
29
+ epss (float): Soil emissivity [-].
30
+ ALW_min (float, optional): Minimum threshold for absorbed longwave radiation.
31
+ intermediate_min (float, optional): Minimum threshold for intermediate calculations.
32
+ intermediate_max (float, optional): Maximum threshold for intermediate calculations.
33
+
34
+ Outputs:
35
+ ALW_sunlit (np.ndarray): Absorbed longwave radiation by sunlit leaves [W/m^2].
36
+ ALW_shaded (np.ndarray): Absorbed longwave radiation by shaded leaves [W/m^2].
37
+ ALW_soil (np.ndarray): Absorbed longwave radiation by soil [W/m^2].
38
+ Ls (np.ndarray): Longwave radiation flux density from soil [W/m^2].
39
+ La (np.ndarray): Longwave radiation flux density from air [W/m^2].
40
+ Lf (np.ndarray): Longwave radiation flux density from foliage [W/m^2].
41
+
42
+ References:
43
+ - Ryu, Y., Baldocchi, D. D., Kobayashi, H., et al. (2011). Integration of MODIS land and atmosphere products with a coupled-process model to estimate gross primary productivity and evapotranspiration. Remote Sensing of Environment, 115(8), 1866-1875.
44
+ - Wang, Y., Law, R. M., Davies, H. L., McGregor, J. L., & Abramowitz, G. (2006). The CSIRO Atmosphere Biosphere Land Exchange (CABLE) model for use in climate models and as an offline model. Geoscientific Model Development.
45
+
46
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
47
+ March 2020
48
+
49
+ =============================================================================
50
+ """
51
+ # Clip SZA to a maximum of 89 degrees to avoid extreme values
52
+ SZA = np.clip(SZA, None, 89)
53
+
54
+ # Extinction coefficient for beam radiation (kb) based on solar zenith angle
55
+ # Derived from Table A1 in Ryu et al. (2011)
56
+ kb = 0.5 / np.cos(SZA * np.pi / 180.0)
57
+
58
+ # Extinction coefficient for longwave radiation (kd)
59
+ # Empirical value from Table A1 in Ryu et al. (2011)
60
+ kd = 0.78
61
+
62
+ # Stefan-Boltzmann constant [W m^-2 K^-4]
63
+ sigma = 5.670373e-8
64
+
65
+ # Longwave radiation flux densities from air, soil, and foliage
66
+ # Calculated using the Stefan-Boltzmann law: L = eps * sigma * T^4
67
+ La = np.clip(epsa * sigma * Ta_K ** 4, 0, None) # Air
68
+ Ls = np.clip(epss * sigma * Ts_K ** 4, 0, None) # Soil
69
+ Lf = np.clip(epsf * sigma * Tf_K ** 4, 0, None) # Foliage
70
+
71
+ # Precompute kd * LAI for efficiency
72
+ kd_LAI = kd * LAI
73
+
74
+ # Difference in longwave radiation flux densities between soil and foliage
75
+ soil_leaf_difference = Ls - Lf
76
+
77
+ if intermediate_min is not None:
78
+ soil_leaf_difference = np.clip(soil_leaf_difference, intermediate_min, None)
79
+
80
+ # Difference in longwave radiation flux densities between air and foliage
81
+ air_leaf_difference = La - Lf
82
+
83
+ if intermediate_min is not None:
84
+ air_leaf_difference = np.clip(air_leaf_difference, intermediate_min, intermediate_max)
85
+
86
+ # Absorbed longwave radiation by sunlit leaves
87
+ # Numerator and denominator derived from Eq. (44) in Wang et al. (2006)
88
+ numerator = soil_leaf_difference * kd * (np.exp(-kd_LAI) - np.exp(-kb * LAI)) / (kd - kb)
89
+ numerator += kd * air_leaf_difference * (1.0 - np.exp(-(kb + kd) * LAI))
90
+ denominator = kd + kb
91
+
92
+ if ALW_min is not None:
93
+ numerator = np.clip(numerator, ALW_min, None)
94
+
95
+ ALW_sunlit = numerator / denominator
96
+
97
+ # Combined longwave radiation flux densities from soil, air, and foliage
98
+ soil_air_leaf = Ls + La - 2 * Lf
99
+
100
+ if intermediate_min is not None or intermediate_max is not None:
101
+ soil_air_leaf = np.clip(soil_air_leaf, intermediate_min, intermediate_max)
102
+
103
+ # Absorbed longwave radiation by shaded leaves
104
+ # Derived from Eq. (45) in Wang et al. (2006)
105
+ ALW_shaded = (1.0 - np.exp(-kd_LAI)) * soil_air_leaf - ALW_sunlit
106
+
107
+ if ALW_min is not None:
108
+ ALW_shaded = np.clip(ALW_shaded, ALW_min, None)
109
+
110
+ # Absorbed longwave radiation by soil
111
+ # Derived from Eq. (41) in Wang et al. (2006)
112
+ ALW_soil = (1.0 - np.exp(-kd_LAI)) * Lf + np.exp(-kd_LAI) * La
113
+
114
+ if ALW_min is not None:
115
+ ALW_soil = np.clip(ALW_soil, ALW_min, None)
116
+
117
+ return ALW_sunlit, ALW_shaded, ALW_soil, Ls, La, Lf
@@ -0,0 +1,276 @@
1
+ import numpy as np
2
+
3
+ from check_distribution import check_distribution
4
+
5
+
6
+ def canopy_shortwave_radiation(
7
+ PAR_diffuse_Wm2: np.ndarray,
8
+ PAR_direct_Wm2: np.ndarray,
9
+ NIR_diffuse_Wm2: np.ndarray,
10
+ NIR_direct_Wm2: np.ndarray,
11
+ UV_Wm2: np.ndarray,
12
+ SZA_deg: 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
+ : ultraviolet radiation (UV) [W m-2],
26
+ : solar zenith angle (SZA) [degree],
27
+ : leaf area index (LAI) [-],
28
+ : clumping index (CI) [-],
29
+ : visible albedo (albedo_visible) [-],
30
+ : NIR albedo (albedo_NIR) [-].
31
+ Output : fraction of sunlit canopy (fSun) [-],
32
+ : total absorbed PAR by sunlit leaves (APAR_Sun) [μmol m-2 s-1],
33
+ : total absorbed PAR by shade leaves (APAR_Sh) [μmol m-2 s-1],
34
+ : total absorbed shortwave radiation by sunlit leaves (ASW_Sun) [W m-2],
35
+ : total absorbed shortwave radiation by shade leaves (ASW_Sh) [W m-2],
36
+ : total absorbed shortwave radiation by soil (ASW_Soil) [W m-2],
37
+ : ground heat storage (G) [W m-2].
38
+ References : Ryu, Y., Baldocchi, D. D., Kobayashi, H., Van Ingen, C., Li, J., Black, T. A., Beringer, J.,
39
+ Van Gorsel, E., Knohl, A., Law, B. E., & Roupsard, O. (2011).
40
+
41
+ Integration of MODIS land and atmosphere products with a coupled-process model
42
+ to estimate gross primary productivity and evapotranspiration from 1 km to global scales.
43
+ Global Biogeochemical Cycles, 25(GB4017), 1-24. doi:10.1029/2011GB004053.1.
44
+
45
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
46
+ March 2020
47
+
48
+ =============================================================================
49
+ """
50
+ # Leaf scattering coefficients and soil reflectance (Sellers 1985)
51
+ SIGMA_P = 0.175
52
+ RHO_PSOIL = 0.15
53
+ SIGMA_N = 0.825
54
+ RHO_NSOIL = 0.30
55
+
56
+ # Extinction coefficient for diffuse and scattered diffuse PAR
57
+ kk_Pd = 0.72 # Table A1
58
+
59
+ # Check for None parameters
60
+ parameters = {
61
+ "PARDiff": PAR_diffuse_Wm2,
62
+ "PARDir": PAR_direct_Wm2,
63
+ "NIRDiff": NIR_diffuse_Wm2,
64
+ "NIRDir": NIR_direct_Wm2,
65
+ "UV": UV_Wm2,
66
+ "SZA": SZA_deg,
67
+ "LAI": LAI,
68
+ "CI": CI,
69
+ "albedo_visible": albedo_visible,
70
+ "albedo_NIR": albedo_NIR
71
+ }
72
+
73
+ for param_name, param_value in parameters.items():
74
+ check_distribution(param_value, param_name)
75
+ if param_value is None:
76
+ raise ValueError(f"The parameter '{param_name}' cannot be None.")
77
+
78
+ # Beam radiation extinction coefficient of canopy
79
+ kb = np.where(SZA_deg > 89, 50.0, 0.5 / np.cos(np.radians(SZA_deg))) # Table A1
80
+ check_distribution(kb, "kb")
81
+
82
+ # Extinction coefficient for beam and scattered beam PAR
83
+ kk_Pb = np.where(SZA_deg > 89, 50.0, 0.46 / np.cos(np.radians(SZA_deg))) # Table A1
84
+ check_distribution(kk_Pb, "kk_Pb")
85
+
86
+ # Extinction coefficient for beam and scattered beam NIR
87
+ kk_Nb = kb * np.sqrt(1.0 - SIGMA_N) # Table A1
88
+ check_distribution(kk_Nb, "kk_Nb")
89
+
90
+ # Extinction coefficient for diffuse and scattered diffuse NIR
91
+ kk_Nd = 0.35 * np.sqrt(1.0 - SIGMA_N) # Table A1
92
+ check_distribution(kk_Nd, "kk_Nd")
93
+
94
+ # Sunlit fraction
95
+ fSun = np.clip(1.0 / kb * (1.0 - np.exp(-kb * LAI * CI)) / LAI, 0, 1) # Integration of Eq. (1)
96
+ fSun = np.where(LAI == 0, 0, fSun) # Eq. (1)
97
+ check_distribution(fSun, "fSun")
98
+
99
+ # For simplicity
100
+ L_CI = LAI * CI
101
+ check_distribution(L_CI, "L_CI")
102
+ exp_kk_Pd_L_CI = np.exp(-kk_Pd * L_CI)
103
+ check_distribution(exp_kk_Pd_L_CI, "exp_kk_Pd_L_CI")
104
+ exp_kk_Nd_L_CI = np.exp(-kk_Nd * L_CI)
105
+ check_distribution(exp_kk_Nd_L_CI, "exp_kk_Nd_L_CI")
106
+
107
+ # Total absorbed incoming PAR
108
+ APARin_Wm2 = (1.0 - albedo_visible) * PAR_direct_Wm2 * (1.0 - np.exp(-kk_Pb * L_CI)) + (1.0 - albedo_visible) * PAR_diffuse_Wm2 * (
109
+ 1.0 - exp_kk_Pd_L_CI) # Eq. (2)
110
+ check_distribution(APARin_Wm2, "APARin_Wm2")
111
+
112
+ # Absorbed incoming beam PAR by sunlit leaves
113
+ APARin_sunlit_Wm2 = PAR_direct_Wm2 * (1.0 - SIGMA_P) * (1.0 - np.exp(-kb * L_CI)) # Eq. (3)
114
+ check_distribution(APARin_sunlit_Wm2, "APARin_sunlit_Wm2")
115
+
116
+ # Absorbed incoming diffuse PAR by sunlit leaves
117
+ APARin_diffuse_sunlit_Wm2 = PAR_diffuse_Wm2 * (1.0 - albedo_visible) * (1.0 - np.exp(-(kk_Pd + kb) * L_CI)) * kk_Pd / (
118
+ kk_Pd + kb) # Eq. (4)
119
+ check_distribution(APARin_diffuse_sunlit_Wm2, "APARin_diffuse_Wm2")
120
+
121
+ # Absorbed incoming scattered PAR by sunlit leaves
122
+ APARin_scattered_sunlit_Wm2 = PAR_direct_Wm2 * (
123
+ (1.0 - albedo_visible) * (1.0 - np.exp(-(kk_Pb + kb) * L_CI)) * kk_Pb / (kk_Pb + kb) - (1.0 - SIGMA_P) * (
124
+ 1.0 - np.exp(-2.0 * kb * L_CI)) / 2.0) # Eq. (5)
125
+ APARin_scattered_sunlit_Wm2 = np.clip(APARin_scattered_sunlit_Wm2, 0, None)
126
+ check_distribution(APARin_scattered_sunlit_Wm2, "APARin_scattered_sunlit_Wm2")
127
+
128
+ # Absorbed incoming PAR by sunlit leaves
129
+ APARin_sunlit_Wm2 = APARin_sunlit_Wm2 + APARin_diffuse_sunlit_Wm2 + APARin_scattered_sunlit_Wm2 # Eq. (6)
130
+ check_distribution(APARin_sunlit_Wm2, "APARin_sunlit_Wm2")
131
+
132
+ # Absorbed incoming PAR by shade leaves
133
+ APARin_shade_Wm2 = np.clip(APARin_Wm2 - APARin_sunlit_Wm2, 0, None) # Eq. (7)
134
+ check_distribution(APARin_shade_Wm2, "APARin_shade_Wm2")
135
+
136
+ # Incoming PAR at soil surface
137
+ PARin_soil_Wm2 = np.clip((1.0 - albedo_visible) * PAR_direct_Wm2 + (1 - albedo_visible) * PAR_diffuse_Wm2 - (APARin_sunlit_Wm2 + APARin_shade_Wm2), 0, None)
138
+ check_distribution(PARin_soil_Wm2, "PARin_soil_Wm2")
139
+
140
+ # Absorbed PAR by soil
141
+ APAR_soil_Wm2 = np.clip((1.0 - RHO_PSOIL) * PARin_soil_Wm2, 0, None)
142
+ check_distribution(APAR_soil_Wm2, "APAR_Soil")
143
+
144
+ # Absorbed outgoing PAR by sunlit leaves
145
+ APARout_sunlit_Wm2 = np.clip(PARin_soil_Wm2 * RHO_PSOIL * exp_kk_Pd_L_CI, 0, None) # Eq. (8)
146
+ check_distribution(APARout_sunlit_Wm2, "APARout_sunlit_Wm2")
147
+
148
+ # Absorbed outgoing PAR by shade leaves
149
+ APARout_shade_Wm2 = np.clip(PARin_soil_Wm2 * RHO_PSOIL * (1 - exp_kk_Pd_L_CI), 0, None) # Eq. (9)
150
+ check_distribution(APARout_shade_Wm2, "APARout_shade_Wm2")
151
+
152
+ # Total absorbed PAR by sunlit leaves
153
+ APAR_sunlit_Wm2 = APARin_sunlit_Wm2 + APARout_sunlit_Wm2 # Eq. (10)
154
+ check_distribution(APAR_sunlit_Wm2, "APAR_sunlit_Wm2")
155
+
156
+ # Total absorbed PAR by shade leaves
157
+ APAR_shade_Wm2 = APARin_shade_Wm2 + APARout_shade_Wm2 # Eq. (11)
158
+ check_distribution(APAR_shade_Wm2, "APAR_shade_Wm2")
159
+
160
+ # Absorbed incoming NIR by sunlit leaves
161
+ ANIRin_sunlit_Wm2 = NIR_direct_Wm2 * (1.0 - SIGMA_N) * (1.0 - np.exp(-kb * L_CI)) + NIR_diffuse_Wm2 * (1 - albedo_NIR) * (
162
+ 1.0 - np.exp(-(kk_Nd + kb) * L_CI)) * kk_Nd / (kk_Nd + kb) + NIR_direct_Wm2 * (
163
+ (1.0 - albedo_NIR) * (1.0 - np.exp(-(kk_Nb + kb) * L_CI)) * kk_Nb / (kk_Nb + kb) - (
164
+ 1.0 - SIGMA_N) * (1.0 - np.exp(-2.0 * kb * L_CI)) / 2.0) # Eq. (14)
165
+ ANIRin_sunlit_Wm2 = np.clip(ANIRin_sunlit_Wm2, 0, None)
166
+ check_distribution(ANIRin_sunlit_Wm2, "ANIRin_sunlit_Wm2")
167
+
168
+ # Absorbed incoming NIR by shade leaves
169
+ ANIRin_shade_Wm2 = (1.0 - albedo_NIR) * NIR_direct_Wm2 * (1.0 - np.exp(-kk_Nb * L_CI)) + (1.0 - albedo_NIR) * NIR_diffuse_Wm2 * (
170
+ 1.0 - exp_kk_Nd_L_CI) - ANIRin_sunlit_Wm2 # Eq. (15)
171
+ ANIRin_shade_Wm2 = np.clip(ANIRin_shade_Wm2, 0, None)
172
+ check_distribution(ANIRin_shade_Wm2, "ANIRin_shade_Wm2")
173
+
174
+ # Incoming NIR at soil surface
175
+ NIRin_soil_Wm2 = (1.0 - albedo_NIR) * NIR_direct_Wm2 + (1.0 - albedo_NIR) * NIR_diffuse_Wm2 - (ANIRin_sunlit_Wm2 + ANIRin_shade_Wm2)
176
+ NIRin_soil_Wm2 = np.clip(NIRin_soil_Wm2, 0, None)
177
+ check_distribution(NIRin_soil_Wm2, "NIRin_soil_Wm2")
178
+
179
+ # Absorbed NIR by soil
180
+ ANIR_soil_Wm2 = (1.0 - RHO_NSOIL) * NIRin_soil_Wm2
181
+ ANIR_soil_Wm2 = np.clip(ANIR_soil_Wm2, 0, None)
182
+ check_distribution(ANIR_soil_Wm2, "ANIR_soil_Wm2")
183
+
184
+ # Absorbed outgoing NIR by sunlit leaves
185
+ ANIRout_sunlit_Wm2 = NIRin_soil_Wm2 * RHO_NSOIL * exp_kk_Nd_L_CI # Eq. (16)
186
+ ANIRout_sunlit_Wm2 = np.clip(ANIRout_sunlit_Wm2, 0, None)
187
+ check_distribution(ANIRout_sunlit_Wm2, "ANIRout_sunlit_Wm2")
188
+
189
+ # Absorbed outgoing NIR by shade leaves
190
+ ANIRout_Wm2 = NIRin_soil_Wm2 * RHO_NSOIL * (1.0 - exp_kk_Nd_L_CI) # Eq. (17)
191
+ ANIRout_Wm2 = np.clip(ANIRout_Wm2, 0, None)
192
+ check_distribution(ANIRout_Wm2, "ANIRout_Wm2")
193
+
194
+ # Total absorbed NIR by sunlit leaves
195
+ ANIR_sunlit_Wm2 = ANIRin_sunlit_Wm2 + ANIRout_sunlit_Wm2 # Eq. (18)
196
+ check_distribution(ANIR_sunlit_Wm2, "ANIR_sunlit_Wm2")
197
+
198
+ # Total absorbed NIR by shade leaves
199
+ ANIR_shade_Wm2 = ANIRin_shade_Wm2 + ANIRout_Wm2 # Eq. (19)
200
+ check_distribution(ANIR_shade_Wm2, "ANIR_shade_Wm2")
201
+
202
+ # Direct UV radiation (W/m²), calculated as the proportion of total UV radiation that corresponds to direct PAR radiation.
203
+ # This represents the UV radiation coming directly from the sun without scattering.
204
+ UV_direct_Wm2 = UV_Wm2 * PAR_direct_Wm2 / (PAR_direct_Wm2 + PAR_diffuse_Wm2 + 1e-5)
205
+
206
+ # Diffuse UV radiation (W/m²), calculated as the remaining portion of total UV radiation after subtracting the direct component.
207
+ # This represents the UV radiation scattered in the atmosphere before reaching the canopy.
208
+ UV_diffuse_Wm2 = UV_Wm2 - UV_direct_Wm2
209
+
210
+ # Total absorbed UV radiation by the canopy (W/m²), combining contributions from diffuse UV radiation.
211
+ AUV_Wm2 = (1.0 - 0.05) * UV_diffuse_Wm2 * (1.0 - np.exp(-kk_Pb * L_CI)) + (1.0 - 0.05) * UV_diffuse_Wm2 * (1.0 - exp_kk_Pd_L_CI)
212
+
213
+ # Absorbed UV radiation by sunlit leaves (W/m²), calculated as the product of total absorbed UV radiation and the sunlit fraction.
214
+ # This represents the portion of UV radiation absorbed by the sunlit canopy.
215
+ AUV_sunlit_Wm2 = AUV_Wm2 * fSun
216
+
217
+ # Absorbed UV radiation by shaded leaves (W/m²), calculated as the product of total absorbed UV radiation and the shaded fraction.
218
+ # This represents the portion of UV radiation absorbed by the shaded canopy.
219
+ AUV_shade_Wm2 = AUV_Wm2 * (1 - fSun)
220
+
221
+ # Absorbed UV radiation by the soil (W/m²), calculated as the remaining UV radiation after accounting for absorption by the canopy.
222
+ # This represents the portion of UV radiation absorbed by the ground surface.
223
+ AUV_soil_Wm2 = (1.0 - 0.05) * UV_Wm2 - AUV_Wm2
224
+
225
+ # Ground heat storage
226
+ G_Wm2 = APAR_soil_Wm2 * 0.28
227
+ check_distribution(G_Wm2, "G_Wm2")
228
+
229
+ # Summary
230
+ # Total absorbed shortwave radiation by sunlit leaves (ASW_Sun_Wm2) [W/m^2].
231
+ # Calculated as the sum of absorbed PAR, NIR, and UV radiation by sunlit leaves.
232
+ ASW_sunlit_Wm2 = APAR_sunlit_Wm2 + ANIR_sunlit_Wm2 + AUV_sunlit_Wm2
233
+ ASW_sunlit_Wm2 = np.where(LAI == 0, 0, ASW_sunlit_Wm2)
234
+ check_distribution(ASW_sunlit_Wm2, "ASW_sunlit_Wm2")
235
+
236
+ # Total absorbed shortwave radiation by shaded leaves (ASW_Sh_Wm2) [W/m^2].
237
+ # Calculated as the sum of absorbed PAR, NIR, and UV radiation by shaded leaves.
238
+ ASW_shade_Wm2 = APAR_shade_Wm2 + ANIR_shade_Wm2 + AUV_shade_Wm2
239
+ ASW_shade_Wm2 = np.where(LAI == 0, 0, ASW_shade_Wm2)
240
+ check_distribution(ASW_shade_Wm2, "ASW_shade_Wm2")
241
+
242
+ # Total absorbed shortwave radiation by soil (ASW_Soil_Wm2) [W/m^2].
243
+ # Calculated as the sum of absorbed PAR, NIR, and UV radiation by the soil.
244
+ ASW_soil_Wm2 = APAR_soil_Wm2 + ANIR_soil_Wm2 + AUV_soil_Wm2
245
+ check_distribution(ASW_soil_Wm2, "ASW_soil_Wm2")
246
+
247
+ # Total absorbed PAR by sunlit leaves (APAR_Sun_Wm2) [W/m^2].
248
+ # Adjusted to zero where LAI is zero.
249
+ APAR_sunlit_Wm2 = np.where(LAI == 0, 0, APAR_sunlit_Wm2)
250
+ check_distribution(APAR_sunlit_Wm2, "APAR_sunlit_Wm2")
251
+
252
+ # Total absorbed PAR by sunlit leaves (APAR_Sun_μmolm2s1) [μmol/m^2/s].
253
+ # Converted from APAR_Sun_Wm2 using a factor of 4.56.
254
+ APAR_sunlit_μmolm2s1 = APAR_sunlit_Wm2 * 4.56
255
+ check_distribution(APAR_sunlit_μmolm2s1, "APAR_sunlit_μmolm2s1")
256
+
257
+ # Total absorbed PAR by shaded leaves (APAR_Sh_Wm2) [W/m^2].
258
+ # Adjusted to zero where LAI is zero.
259
+ APAR_shade_Wm2 = np.where(LAI == 0, 0, APAR_shade_Wm2)
260
+ check_distribution(APAR_shade_Wm2, "APAR_shade_Wm2")
261
+
262
+ # Total absorbed PAR by shaded leaves (APAR_Sh_μmolm2s1) [μmol/m^2/s].
263
+ # Converted from APAR_Sh_Wm2 using a factor of 4.56.
264
+ APAR_shade_μmolm2s1 = APAR_shade_Wm2 * 4.56
265
+ check_distribution(APAR_shade_μmolm2s1, "APAR_shade_μmolm2s1")
266
+
267
+ # Return values as a dictionary
268
+ return {
269
+ "fSun": fSun,
270
+ "APAR_sunlit_μmolm2s1": APAR_sunlit_μmolm2s1,
271
+ "APAR_shade_μmolm2s1": APAR_shade_μmolm2s1,
272
+ "ASW_sunlit_Wm2": ASW_sunlit_Wm2,
273
+ "ASW_shade_Wm2": ASW_shade_Wm2,
274
+ "ASW_soil_Wm2": ASW_soil_Wm2,
275
+ "G_Wm2": G_Wm2
276
+ }
Binary file
Binary file