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.
- BESS_JPL/BESS.py +468 -0
- BESS_JPL/BESS_JPL.py +23 -0
- BESS_JPL/C3_photosynthesis.py +72 -0
- BESS_JPL/C4_fraction.jpeg +0 -0
- BESS_JPL/C4_fraction.tif +0 -0
- BESS_JPL/C4_photosynthesis.py +56 -0
- BESS_JPL/FVC_from_NDVI.py +22 -0
- BESS_JPL/LAI_from_NDVI.py +29 -0
- BESS_JPL/NDVI_maximum.jpeg +0 -0
- BESS_JPL/NDVI_maximum.tif +0 -0
- BESS_JPL/NDVI_minimum.jpeg +0 -0
- BESS_JPL/NDVI_minimum.tif +0 -0
- BESS_JPL/SZA/__init__.py +1 -0
- BESS_JPL/SZA/daylight_hours.py +74 -0
- BESS_JPL/__init__.py +10 -0
- BESS_JPL/ball_berry_intercept_C3.jpeg +0 -0
- BESS_JPL/ball_berry_intercept_C3.tif +0 -0
- BESS_JPL/ball_berry_slope_C3.jpeg +0 -0
- BESS_JPL/ball_berry_slope_C3.tif +0 -0
- BESS_JPL/ball_berry_slope_C4.jpeg +0 -0
- BESS_JPL/ball_berry_slope_C4.tif +0 -0
- BESS_JPL/calculate_VCmax.py +54 -0
- BESS_JPL/canopy_energy_balance.py +146 -0
- BESS_JPL/canopy_longwave_radiation.py +92 -0
- BESS_JPL/canopy_shortwave_radiation.py +234 -0
- BESS_JPL/carbon_uptake_efficiency.jpeg +0 -0
- BESS_JPL/carbon_uptake_efficiency.tif +0 -0
- BESS_JPL/carbon_water_fluxes.py +277 -0
- BESS_JPL/constants.py +8 -0
- BESS_JPL/interpolate_C3_C4.py +12 -0
- BESS_JPL/kn.jpeg +0 -0
- BESS_JPL/kn.tif +0 -0
- BESS_JPL/load_C4_fraction.py +10 -0
- BESS_JPL/load_NDVI_maximum.py +10 -0
- BESS_JPL/load_NDVI_minimum.py +10 -0
- BESS_JPL/load_ball_berry_intercept_C3.py +10 -0
- BESS_JPL/load_ball_berry_slope_C3.py +10 -0
- BESS_JPL/load_ball_berry_slope_C4.py +10 -0
- BESS_JPL/load_carbon_uptake_efficiency.py +10 -0
- BESS_JPL/load_kn.py +10 -0
- BESS_JPL/load_peakVCmax_C3.py +10 -0
- BESS_JPL/load_peakVCmax_C4.py +10 -0
- BESS_JPL/meteorology.py +204 -0
- BESS_JPL/peakVCmax_C3.jpeg +0 -0
- BESS_JPL/peakVCmax_C3.tif +0 -0
- BESS_JPL/peakVCmax_C4.jpeg +0 -0
- BESS_JPL/peakVCmax_C4.tif +0 -0
- BESS_JPL/soil_energy_balance.py +35 -0
- BESS_JPL/vegetation_conversion/__init__.py +1 -0
- BESS_JPL/vegetation_conversion/vegetation_conversion.py +71 -0
- BESS_JPL/version.txt +1 -0
- bess_jpl-1.6.0.dist-info/METADATA +95 -0
- bess_jpl-1.6.0.dist-info/RECORD +56 -0
- bess_jpl-1.6.0.dist-info/WHEEL +5 -0
- bess_jpl-1.6.0.dist-info/licenses/LICENSE +201 -0
- bess_jpl-1.6.0.dist-info/top_level.txt +1 -0
BESS_JPL/SZA/__init__.py
ADDED
|
@@ -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
|
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
|