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,313 @@
1
+ from os.path import join, abspath, dirname
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import rasters as rt
6
+ from rasters import Raster, RasterGeometry
7
+ from .C3_photosynthesis import calculate_C3_photosynthesis
8
+ from .C4_photosynthesis import calculate_C4_photosynthesis
9
+ from .canopy_longwave_radiation import canopy_longwave_radiation
10
+ from .canopy_energy_balance import canopy_energy_balance
11
+ from .soil_energy_balance import soil_energy_balance
12
+
13
+ PASSES = 1
14
+
15
+ def carbon_water_fluxes(
16
+ canopy_temperature_K: np.ndarray, # canopy temperature in Kelvin
17
+ soil_temperature_K: np.ndarray, # soil temperature in Kelvin
18
+ LAI: np.ndarray, # leaf area index
19
+ Ta_K: np.ndarray, # air temperature in Kelvin
20
+ APAR_sunlit_μmolm2s1: np.ndarray, # sunlit leaf absorptance to photosynthetically active radiation [μmol m⁻² s⁻¹]
21
+ APAR_shaded_μmolm2s1: np.ndarray, # shaded leaf absorptance to photosynthetically active radiation [μmol m⁻² s⁻¹]
22
+ ASW_sunlit_Wm2: np.ndarray, # sunlit absorbed shortwave radiation [W m⁻²]
23
+ ASW_shaded_Wm2: np.ndarray, # shaded absorbed shortwave radiation [W m⁻²]
24
+ ASW_soil_Wm2: np.ndarray, # absorbed shortwave radiation in soil [W m⁻²]
25
+ Vcmax25_sunlit: np.ndarray, # sunlit maximum carboxylation rate at 25°C [μmol m⁻² s⁻¹]
26
+ Vcmax25_shaded: np.ndarray, # shaded maximum carboxylation rate at 25°C [μmol m⁻² s⁻¹]
27
+ ball_berry_slope: np.ndarray, # Ball-Berry slope
28
+ ball_berry_intercept: Union[np.ndarray, float], # Ball-Berry intercept
29
+ sunlit_fraction: np.ndarray, # sunlit fraction of canopy
30
+ G_Wm2: np.ndarray, # soil heat flux [W m⁻²]
31
+ SZA_deg: np.ndarray, # solar zenith angle in degrees
32
+ Ca: np.ndarray, # atmospheric CO2 concentration [μmol mol⁻¹]
33
+ Ps_Pa: np.ndarray, # surface pressure [Pa]
34
+ gamma: np.ndarray, # psychrometric constant [Pa K⁻¹]
35
+ Cp: np.ndarray, # specific heat capacity of air [J kg⁻¹ K⁻¹]
36
+ rhoa: np.ndarray, # air density [kg m⁻³]
37
+ VPD_Pa: np.ndarray, # vapor pressure deficit [Pa]
38
+ RH: np.ndarray, # relative humidity as a fraction
39
+ desTa: np.ndarray, # 1st derivative of saturated vapor pressure [Pa K⁻¹]
40
+ ddesTa: np.ndarray, # 2nd derivative of saturated vapor pressure [Pa K⁻²]
41
+ epsa: np.ndarray, # clear-sky emissivity [-]
42
+ Rc: np.ndarray, # canopy resistance [s m⁻¹]
43
+ Rs: np.ndarray, # soil resistance [s m⁻¹]
44
+ carbon_uptake_efficiency: np.ndarray, # intrinsic quantum efficiency of carbon uptake [-]
45
+ fStress: np.ndarray, # stress factor [-]
46
+ C4_photosynthesis: bool, # C3 or C4 photosynthesis
47
+ passes: int = PASSES): # number of iterations
48
+ """
49
+ Calculate carbon and water fluxes for a canopy-soil system.
50
+
51
+ Parameters:
52
+ canopy_temperature_K: Canopy temperature [K].
53
+ soil_temperature_K: Soil temperature [K].
54
+ LAI: Leaf area index [-].
55
+ Ta_K: Air temperature [K].
56
+ APAR_sunlit: Sunlit leaf absorptance to photosynthetically active radiation [μmol m⁻² s⁻¹].
57
+ APAR_shaded: Shaded leaf absorptance to photosynthetically active radiation [μmol m⁻² s⁻¹].
58
+ ASW_sunlit_Wm2: Sunlit absorbed shortwave radiation [W m⁻²].
59
+ ASW_shaded: Shaded absorbed shortwave radiation [W m⁻²].
60
+ ASW_soil_Wm2: Absorbed shortwave radiation in soil [W m⁻²].
61
+ Vcmax25_sunlit: Sunlit maximum carboxylation rate at 25°C [μmol m⁻² s⁻¹].
62
+ Vcmax25_shaded: Shaded maximum carboxylation rate at 25°C [μmol m⁻² s⁻¹].
63
+ ball_berry_slope: Ball-Berry slope [-].
64
+ ball_berry_intercept: Ball-Berry intercept [-].
65
+ sunlit_fraction: Sunlit fraction of canopy [-].
66
+ G: Soil heat flux [W m⁻²].
67
+ SZA: Solar zenith angle [degrees].
68
+ Ca: Atmospheric CO2 concentration [μmol mol⁻¹].
69
+ Ps_Pa: Surface pressure [Pa].
70
+ gamma: Psychrometric constant [Pa K⁻¹].
71
+ Cp: Specific heat capacity of air [J kg⁻¹ K⁻¹].
72
+ rhoa: Air density [kg m⁻³].
73
+ VPD_Pa: Vapor pressure deficit [Pa].
74
+ RH: Relative humidity as a fraction [-].
75
+ desTa: 1st derivative of saturated vapor pressure [Pa K⁻¹].
76
+ ddesTa: 2nd derivative of saturated vapor pressure [Pa K⁻²].
77
+ epsa: Clear-sky emissivity [-].
78
+ Rc: Canopy resistance [s m⁻¹].
79
+ Rs: Soil resistance [s m⁻¹].
80
+ carbon_uptake_efficiency: Intrinsic quantum efficiency of carbon uptake [-].
81
+ fStress: Stress factor [-].
82
+ C4_photosynthesis: Whether to use C4 photosynthesis (True) or C3 (False).
83
+ passes: Number of iterations.
84
+
85
+ Returns:
86
+ GPP: Gross primary productivity [μmol m⁻² s⁻¹].
87
+ LE: Latent heat flux [W m⁻²].
88
+ LE_soil: Soil latent heat flux [W m⁻²].
89
+ LE_canopy: Canopy latent heat flux [W m⁻²].
90
+ Rn: Net radiation [W m⁻²].
91
+ Rn_soil: Soil net radiation [W m⁻²].
92
+ Rn_canopy: Canopy net radiation [W m⁻²].
93
+ """
94
+
95
+ # carbon = 4 if C4_photosynthesis else 3
96
+ GPP_max = 50 if C4_photosynthesis else 40
97
+
98
+ # this model originally initialized soil and canopy temperature to air temperature
99
+ Tf_sunlit_K = canopy_temperature_K
100
+ Tf_shaded_K = canopy_temperature_K
101
+ Tf_K = canopy_temperature_K
102
+ Ts_K = soil_temperature_K
103
+
104
+ # initialize intercellular CO2 concentration to atmospheric CO2 concentration depending on C3 or C4 photosynthesis
105
+ chi = 0.4 if C4_photosynthesis else 0.7
106
+ Ci_sunlit = Ca * chi
107
+ Ci_shaded = Ca * chi
108
+
109
+ ball_berry_intercept = ball_berry_intercept * fStress
110
+
111
+ epsf = 0.98
112
+ epss = 0.96
113
+
114
+ # initialize sunlit partition (overwritten when iterations process)
115
+
116
+ # initialize sunlit net assimilation rate to zero
117
+ net_assimilation_sunlit_μmolm2s1 = Tf_sunlit_K * 0
118
+
119
+ # initialize sunlit net radiation to zero
120
+ Rn_sunlit_Wm2 = Tf_sunlit_K * 0
121
+
122
+ # initialize sunlit latent heat flux to zero
123
+ LE_sunlit_Wm2 = Tf_sunlit_K * 0
124
+
125
+ # initialize sunlit sensible heat flux to zero
126
+ H_sunlit_Wm2 = Tf_sunlit_K * 0
127
+
128
+ # initialize shaded partition (overwritten when iterations process)
129
+
130
+ # initialize shaded net assimilation rate to zero
131
+ net_assimilation_shade_μmolm2s1 = Tf_shaded_K * 0
132
+
133
+ # initialize shaded net radiation to zero
134
+ Rn_shaded_Wm2 = Tf_shaded_K * 0
135
+
136
+ # initialize shaded latent heat flux to zero
137
+ LE_shaded_Wm2 = Tf_shaded_K * 0
138
+
139
+ # initialize shaded sensible heat flux to zero
140
+ H_shaded_Wm2 = Tf_shaded_K * 0
141
+
142
+ # initialize soil partition (overwritten when iterations process)
143
+
144
+ # initialize soil net radiation to zero
145
+ Rn_soil_Wm2 = Ts_K * 0
146
+
147
+ # initialize soil latent heat flux to zero
148
+ LE_soil_Wm2 = Ts_K * 0
149
+
150
+ # Iteration
151
+ for iter in range(1, passes + 1):
152
+
153
+ # Longwave radiation
154
+ # CLR:[ALW_Sun, ALW_shaded, ALW_Soil, Ls, La]
155
+ ALW_sunlit_Wm2, ALW_shaded_Wm2, ALW_soil_Wm2, Ls, La, Lf = canopy_longwave_radiation(
156
+ LAI=LAI, # leaf area index (LAI) [-]
157
+ SZA=SZA_deg, # solar zenith angle (degrees)
158
+ Ts_K=Ts_K, # soil temperature (Ts) [K]
159
+ Tf_K=Tf_K, # foliage temperature (Tf) [K]
160
+ Ta_K=Ta_K, # air temperature (Ta) [K]
161
+ epsa=epsa, # clear-sky emissivity (epsa) [-]
162
+ epsf=epsf, # foliage emissivity (epsf) [-]
163
+ epss=epss # soil emissivity (epss) [-],
164
+ )
165
+
166
+ # calculate sunlit photosynthesis
167
+ if C4_photosynthesis:
168
+ # calculate sunlit photosynthesis for C4 plants
169
+ photosynthesis_sunlit_C4_results = calculate_C4_photosynthesis(
170
+ Tf_K=Tf_sunlit_K, # sunlit leaf temperature (Tf) [K]
171
+ Ci_μmol_per_mol=Ci_sunlit, # sunlit intercellular CO2 concentration (Ci) [umol mol-1]
172
+ APAR_μmolm2s1=APAR_sunlit_μmolm2s1, # sunlit leaf absorptance to photosynthetically active radiation [umol m-2 s-1]
173
+ Vcmax25_μmolm2s1=Vcmax25_sunlit # sunlit maximum carboxylation rate at 25C (Vcmax25) [umol m-2 s-1]
174
+ )
175
+
176
+ net_assimilation_sunlit_μmolm2s1 = photosynthesis_sunlit_C4_results['net_assimilation_C4_μmolm2s1']
177
+ else:
178
+ # calculate sunlit photosynthesis for C3 plants
179
+ photosynthesis_sunlit_C3_results = calculate_C3_photosynthesis(
180
+ Tf_K=Tf_sunlit_K, # sunlit leaf temperature (Tf) [K]
181
+ Ci=Ci_sunlit, # sunlit intercellular CO2 concentration (Ci) [umol mol-1]
182
+ APAR_μmolm2s1=APAR_sunlit_μmolm2s1, # sunlit leaf absorptance to photosynthetically active radiation [umol m-2 s-1]
183
+ Vcmax25=Vcmax25_sunlit, # sunlit maximum carboxylation rate at 25C (Vcmax25) [umol m-2 s-1]
184
+ Ps_Pa=Ps_Pa, # surface pressure (Ps) [Pa]
185
+ carbon_uptake_efficiency=carbon_uptake_efficiency # intrinsic quantum efficiency for carbon uptake
186
+ )
187
+
188
+ net_assimilation_sunlit_μmolm2s1 = photosynthesis_sunlit_C3_results['net_assimilation_C3_μmolm2s1']
189
+
190
+ # calculate sunlit energy balance
191
+ Rn_sunlit_new_Wm2, LE_sunlit_new_Wm2, H_sunlit_new_Wm2, Tf_sunlit_new_K, gs2_sunlit_new, Ci_sunlit_new = canopy_energy_balance(
192
+ An=net_assimilation_sunlit_μmolm2s1, # net assimulation (An) [umol m-2 s-1]
193
+ ASW_Wm2=ASW_sunlit_Wm2, # total absorbed shortwave radiation by sunlit canopy (ASW) [W/m^2]
194
+ ALW_Wm2=ALW_sunlit_Wm2, # total absorbed longwave radiation by sunlit canopy (ALW) [W/m^2]
195
+ Tf_K=Tf_sunlit_K, # sunlit leaf temperature (Tf) [K]
196
+ Ps_Pa=Ps_Pa, # surface pressure (Ps) [Pa]
197
+ Ca=Ca, # ambient CO2 concentration (Ca) [umol mol-1]
198
+ Ta_K=Ta_K, # air temperature (Ta) [K]
199
+ RH=RH, # relative humidity (RH) [-]
200
+ VPD_Pa=VPD_Pa, # water vapour deficit (VPD) [Pa]
201
+ desTa=desTa, # 1st derivative of saturated vapour pressure (desTa)
202
+ ddesTa=ddesTa, # 2nd derivative of saturated vapour pressure (ddesTa)
203
+ gamma=gamma, # psychrometric constant (gamma) [pa K-1]
204
+ Cp=Cp, # specific heat of air at constant pressure (Cp) [J kg-1 K-1]
205
+ rhoa=rhoa, # air density (rhoa) [kg m-3]
206
+ Rc=Rc, # TODO is this Ra or Rc in Ball-Berry?
207
+ ball_berry_slope=ball_berry_slope, # Ball-Berry slope (m) [-]
208
+ ball_berry_intercept=ball_berry_intercept, # Ball-Berry intercept (b0) [-]
209
+ C4_photosynthesis=C4_photosynthesis # process for C4 plants instead of C3
210
+ )
211
+
212
+ # filter in sunlit energy balance estimates
213
+ Rn_sunlit_Wm2 = np.where(np.isnan(Rn_sunlit_new_Wm2), Rn_sunlit_Wm2, Rn_sunlit_new_Wm2)
214
+ LE_sunlit_Wm2 = np.where(np.isnan(LE_sunlit_new_Wm2), LE_sunlit_Wm2, LE_sunlit_new_Wm2)
215
+ H_sunlit_Wm2 = np.where(np.isnan(H_sunlit_new_Wm2), H_sunlit_Wm2, H_sunlit_new_Wm2)
216
+ Tf_sunlit_K = np.where(np.isnan(Tf_sunlit_new_K), Tf_sunlit_K, Tf_sunlit_new_K)
217
+ Ci_sunlit = np.where(np.isnan(Ci_sunlit_new), Ci_sunlit, Ci_sunlit_new)
218
+
219
+ # Photosynthesis (shade)
220
+ if C4_photosynthesis:
221
+ photosynthesis_shade_C4_results = calculate_C4_photosynthesis(
222
+ Tf_K=Tf_shaded_K, # shaded leaf temperature (Tf) [K]
223
+ Ci_μmol_per_mol=Ci_shaded, # shaded intercellular CO2 concentration (Ci) [umol mol-1]
224
+ APAR_μmolm2s1=APAR_shaded_μmolm2s1, # shaded absorbed photosynthetically active radiation (APAR) [umol m-2 s-1]
225
+ Vcmax25_μmolm2s1=Vcmax25_shaded # shaded maximum carboxylation rate at 25C (Vcmax25) [umol m-2 s-1]
226
+ )
227
+
228
+ net_assimilation_shade_μmolm2s1 = photosynthesis_shade_C4_results['net_assimilation_C4_μmolm2s1']
229
+ else:
230
+ photosynthesis_shade_C3_results = calculate_C3_photosynthesis(
231
+ Tf_K=Tf_shaded_K, # shaded leaf temperature (Tf) [K]
232
+ Ci=Ci_shaded, # shaed intercellular CO2 concentration (Ci) [umol mol-1]
233
+ APAR_μmolm2s1=APAR_shaded_μmolm2s1, # shaded absorbed photosynthetically active radiation (APAR) [umol m-2 s-1]
234
+ Vcmax25=Vcmax25_shaded, # shaded maximum carboxylation rate at 25C (Vcmax25) [umol m-2 s-1]
235
+ Ps_Pa=Ps_Pa, # surface pressure (Ps) [Pa]
236
+ carbon_uptake_efficiency=carbon_uptake_efficiency # intrinsic quantum efficiency for carbon uptake
237
+ )
238
+
239
+ net_assimilation_shade_μmolm2s1 = photosynthesis_shade_C3_results['net_assimilation_C3_μmolm2s1']
240
+
241
+ # calculated shaded energy balance
242
+ Rn_shaded_new, LE_shaded_new, H_shaded_new, Tf_K_shaded_new, gs2_shaded_new, Ci_shaded_new = canopy_energy_balance(
243
+ An=net_assimilation_shade_μmolm2s1, # net assimulation (An) [umol m-2 s-1]
244
+ ASW_Wm2=ASW_shaded_Wm2, # total absorbed shortwave radiation by shaded canopy (ASW) [umol m-2 s-1]
245
+ ALW_Wm2=ALW_shaded_Wm2, # total absorbed longwave radiation by shaded canopy (ALW) [umol m-2 s-1]
246
+ Tf_K=Tf_shaded_K, # shaded leaf temperature (Tf) [K]
247
+ Ps_Pa=Ps_Pa, # surface pressure (Ps) [Pa]
248
+ Ca=Ca, # ambient CO2 concentration (Ca) [umol mol-1]
249
+ Ta_K=Ta_K, # air temperature (Ta) [K]
250
+ RH=RH, # relative humidity as a fraction
251
+ VPD_Pa=VPD_Pa, # water vapour deficit (VPD) [Pa]
252
+ desTa=desTa, # 1st derivative of saturated vapour pressure (desTa)
253
+ ddesTa=ddesTa, # 2nd derivative of saturated vapour pressure (ddesTa)
254
+ gamma=gamma, # psychrometric constant (gamma) [pa K-1]
255
+ Cp=Cp, # specific heat of air (Cp) [J kg-1 K-1]
256
+ rhoa=rhoa, # air density (rhoa) [kg m-3]
257
+ Rc=Rc,
258
+ ball_berry_slope=ball_berry_slope, # Ball-Berry slope (m) [-]
259
+ ball_berry_intercept=ball_berry_intercept, # Ball-Berry intercept (b0) [-]
260
+ C4_photosynthesis=C4_photosynthesis # process for C4 plants instead of C3
261
+ )
262
+
263
+ # filter in shaded energy balance estimates
264
+ Rn_shaded_Wm2 = np.where(np.isnan(Rn_shaded_new), Rn_shaded_Wm2, Rn_shaded_new)
265
+ LE_shaded_Wm2 = np.where(np.isnan(LE_shaded_new), LE_shaded_Wm2, LE_shaded_new)
266
+ H_shaded_Wm2 = np.where(np.isnan(H_shaded_new), H_shaded_Wm2, H_shaded_new)
267
+ Tf_shaded_K = np.where(np.isnan(Tf_K_shaded_new), Tf_shaded_K, Tf_K_shaded_new)
268
+ Ci_shaded = np.where(np.isnan(Ci_shaded_new), Ci_shaded, Ci_shaded_new)
269
+
270
+ # calculate soil energy balance
271
+ Rn_soil_new, LE_soil_new, Ts_K_soil_new = soil_energy_balance(
272
+ Ts_K=Ts_K, # soil temperature in Kelvin
273
+ Ta_K=Ta_K, # air temperature in Kelvin
274
+ G_Wm2=G_Wm2, # soil heat flux (G) [W m-2]
275
+ VPD_Pa=VPD_Pa, # water vapour deficit in Pascal
276
+ RH=RH, # relative humidity as a fraction
277
+ gamma=gamma, # psychrometric constant (gamma) [pa K-1]
278
+ Cp=Cp, # specific heat of air (Cp) [J kg-1 K-1]
279
+ rhoa=rhoa, # air density (rhoa) [kg m-3]
280
+ desTa=desTa,
281
+ Rs=Rs,
282
+ ASW_soil_Wm2=ASW_soil_Wm2, # total absorbed shortwave radiation by soil (ASW) [umol m-2 s-1]
283
+ ALW_soil_Wm2=ALW_soil_Wm2, # total absorbed longwave radiation by soil (ALW) [umol m-2 s-1]
284
+ Ls=Ls,
285
+ epsa=epsa
286
+ )
287
+
288
+ # filter in soil energy balance estimates
289
+ # where new estimates are missing, retain the prior estimates
290
+ Rn_soil_Wm2 = np.where(np.isnan(Rn_soil_new), Rn_soil_Wm2, Rn_soil_new)
291
+ LE_soil_Wm2 = np.where(np.isnan(LE_soil_new), LE_soil_Wm2, LE_soil_new)
292
+ Ts_K = np.where(np.isnan(Ts_K_soil_new), Ts_K, Ts_K_soil_new)
293
+
294
+ # combine sunlit and shaded foliage temperatures
295
+ Tf_K_new = (((Tf_sunlit_K ** 4) * sunlit_fraction + (Tf_shaded_K ** 4) * (1 - sunlit_fraction)) ** 0.25)
296
+ Tf_K = np.where(np.isnan(Tf_K_new), Tf_K, Tf_K_new)
297
+
298
+ # calculate canopy latent heat flux
299
+ LE_canopy_Wm2 = np.clip(LE_sunlit_Wm2 + LE_shaded_Wm2, 0, 1000)
300
+
301
+ # calculate latent heat flux
302
+ LE_Wm2 = np.clip(LE_sunlit_Wm2 + LE_shaded_Wm2 + LE_soil_Wm2, 0, 1000) # [W m-2]
303
+
304
+ # calculate gross primary productivity
305
+ GPP = np.clip(net_assimilation_sunlit_μmolm2s1 + net_assimilation_shade_μmolm2s1, 0, GPP_max) # [umol m-2 s-1]
306
+
307
+ # calculate canopy net radiation
308
+ Rn_canopy_Wm2 = np.clip(Rn_sunlit_Wm2 + Rn_shaded_Wm2, 0, None)
309
+
310
+ # calculate net radiation
311
+ Rn_Wm2 = np.clip(Rn_sunlit_Wm2 + Rn_shaded_Wm2 + Rn_soil_Wm2, 0, 1000) # [W m-2]
312
+
313
+ return GPP, LE_Wm2, LE_soil_Wm2, LE_canopy_Wm2, Rn_Wm2, Rn_soil_Wm2, Rn_canopy_Wm2
BESS_JPL/colors.py ADDED
@@ -0,0 +1,33 @@
1
+ from matplotlib.colors import LinearSegmentedColormap
2
+
3
+ GPP_COLORMAP = LinearSegmentedColormap.from_list(
4
+ name="GPP",
5
+ colors=[
6
+ "#000000",
7
+ "#bdae08",
8
+ "#325e32",
9
+ "#a6ff01",
10
+ "#00ff00"
11
+ ]
12
+ )
13
+
14
+ ET_COLORMAP = LinearSegmentedColormap.from_list("ET", [
15
+ "#f6e8c3",
16
+ "#d8b365",
17
+ "#99974a",
18
+ "#53792d",
19
+ "#6bdfd2",
20
+ "#1839c5"
21
+ ])
22
+
23
+ NDVI_COLORMAP_ABSOLUTE = LinearSegmentedColormap.from_list(
24
+ name="NDVI",
25
+ colors=[
26
+ (0, "#0000ff"),
27
+ (0.4, "#000000"),
28
+ (0.5, "#745d1a"),
29
+ (0.6, "#e1dea2"),
30
+ (0.8, "#45ff01"),
31
+ (1, "#325e32")
32
+ ]
33
+ )
BESS_JPL/constants.py ADDED
@@ -0,0 +1,25 @@
1
+ A = 0.3
2
+ KPAR = 0.5
3
+ MIN_FIPAR = 0.0
4
+ MAX_FIPAR = 1.0
5
+ MIN_LAI = 0.0
6
+ MAX_LAI = 10.0
7
+ BALL_BERRY_INTERCEPT_C4 = 0.04
8
+ RESAMPLING = "cubic"
9
+
10
+ # Default scale factor for C4 fraction
11
+ C4_FRACTION_SCALE_FACTOR = 0.01
12
+
13
+ UPSCALE_TO_DAYLIGHT = True
14
+
15
+ # GEOS-5 FP variables retrieved by the model
16
+ GEOS5FP_INPUTS = [
17
+ "Ta_C", # Air temperature (°C)
18
+ "RH", # Relative humidity (fraction)
19
+ "COT", # Cloud optical thickness
20
+ "AOT", # Aerosol optical thickness
21
+ "PAR_albedo", # Visible direct beam albedo
22
+ "NIR_albedo", # Near-infrared direct beam albedo
23
+ "Ca", # Atmospheric CO₂ concentration (ppm)
24
+ "wind_speed_mps" # Wind speed (m/s)
25
+ ]
BESS_JPL/exceptions.py ADDED
@@ -0,0 +1,3 @@
1
+ class MissingOfflineParameter(Exception):
2
+ """Exception raised when a required parameter is missing in offline mode."""
3
+ pass
@@ -0,0 +1,58 @@
1
+ from os.path import dirname, join
2
+ import logging
3
+ import sys
4
+
5
+ # Allow running as script while preserving package imports
6
+ if __name__ == "__main__" and __package__ is None:
7
+ sys.path.insert(0, dirname(dirname(__file__)))
8
+ __package__ = "BESS_JPL"
9
+
10
+ from ECOv002_calval_tables import load_times_locations, load_calval_table
11
+ from GEOS5FP import GEOS5FP
12
+ from BESS_JPL import GEOS5FP_INPUTS
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ def generate_BESS_GEOS5FP_inputs(
17
+ filename: str = None,
18
+ update_package_data: bool = True,
19
+ sample_size: int = None) -> None:
20
+ logger.info("Generating BESS-JPL GEOS-5 FP input table:")
21
+
22
+ for item in GEOS5FP_INPUTS:
23
+ logger.info(f" - {item}")
24
+
25
+ # Load sample times and locations
26
+ targets_df = load_times_locations()
27
+ calval_table_df = load_calval_table()
28
+
29
+ if sample_size is not None:
30
+ targets_df = targets_df.sample(n=sample_size).reset_index(drop=True)
31
+
32
+ # Create GEOS5FP connection
33
+ GEOS5FP_connection = GEOS5FP()
34
+
35
+ target_variables = [
36
+ variable
37
+ for variable
38
+ in GEOS5FP_INPUTS
39
+ if variable not in calval_table_df.columns
40
+ ]
41
+
42
+ # Query for FLiESANN GEOS5FP input variables
43
+ results_df = GEOS5FP_connection.query(
44
+ target_variables=target_variables,
45
+ targets_df=targets_df
46
+ )
47
+
48
+ if update_package_data and not sample_size:
49
+ if filename is None:
50
+ filename = join(dirname(__file__), "ECOv002-cal-val-BESS-JPL-GEOS5FP-inputs.csv")
51
+
52
+ results_df.to_csv(filename, index=False)
53
+
54
+ return results_df
55
+
56
+ if __name__ == "__main__":
57
+ logging.basicConfig(level=logging.INFO)
58
+ generate_BESS_GEOS5FP_inputs()
@@ -0,0 +1,186 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import rasters as rt
6
+ from dateutil import parser
7
+ from pandas import DataFrame
8
+ from rasters import MultiPoint, WGS84
9
+ from shapely.geometry import Point
10
+ from GEOS5FP import GEOS5FP
11
+ from MODISCI import MODISCI
12
+ from NASADEM import NASADEMConnection
13
+ from gedi_canopy_height import GEDI_DOWNLOAD_DIRECTORY
14
+ from .retrieve_BESS_inputs import retrieve_BESS_inputs
15
+ from .constants import C4_FRACTION_SCALE_FACTOR, RESAMPLING
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def generate_BESS_inputs_table(
20
+ input_df: DataFrame,
21
+ GEOS5FP_connection: GEOS5FP = None,
22
+ MODISCI_connection: MODISCI = None,
23
+ NASADEM_connection: NASADEMConnection = None,
24
+ C4_fraction_scale_factor: float = C4_FRACTION_SCALE_FACTOR,
25
+ resampling: str = RESAMPLING,
26
+ GEDI_download_directory: str = GEDI_DOWNLOAD_DIRECTORY) -> DataFrame:
27
+ """
28
+ Generates a DataFrame of BESS-JPL inputs by retrieving atmospheric, vegetation, and static data.
29
+
30
+ This is a simple wrapper around retrieve_BESS_inputs that handles DataFrame
31
+ input/output and geometry parsing.
32
+
33
+ Parameters:
34
+ input_df (pd.DataFrame): A DataFrame containing the following columns:
35
+ - time_UTC (str or datetime): Time in UTC.
36
+ - geometry (str or shapely.geometry.Point) or (lat, lon): Spatial coordinates.
37
+ If "geometry" is a string, it should be in WKT format (e.g., "POINT (lon lat)").
38
+ - ST_C (float): Surface temperature in Celsius.
39
+ - NDVI (float): Normalized Difference Vegetation Index.
40
+ - albedo (float): Surface albedo.
41
+ - Ta_C (float, optional): Air temperature in Celsius.
42
+ - RH (float, optional): Relative humidity as a proportion.
43
+ - elevation_m (float, optional): Elevation in meters.
44
+ - NDVI_minimum (float, optional): Minimum NDVI.
45
+ - NDVI_maximum (float, optional): Maximum NDVI.
46
+ - COT (float, optional): Cloud optical thickness.
47
+ - AOT (float, optional): Aerosol optical thickness.
48
+ - vapor_gccm (float, optional): Water vapor in grams per cubic centimeter.
49
+ - ozone_cm (float, optional): Ozone concentration in centimeters.
50
+ - KG or KG_climate (str, optional): Köppen-Geiger climate classification.
51
+ - canopy_height_meters (float, optional): Canopy height in meters.
52
+ - Ca (float, optional): Atmospheric CO2 concentration in ppm.
53
+ - wind_speed_mps (float, optional): Wind speed in meters per second.
54
+ - SZA_deg (float, optional): Solar zenith angle in degrees.
55
+ - canopy_temperature_C (float, optional): Canopy temperature in Celsius.
56
+ - soil_temperature_C (float, optional): Soil temperature in Celsius.
57
+ - C4_fraction (float, optional): Fraction of C4 plants.
58
+ - carbon_uptake_efficiency (float, optional): Intrinsic quantum efficiency for carbon uptake.
59
+ - kn (float, optional): Nitrogen decay coefficient.
60
+ - ball_berry_intercept_C3 (float, optional): Ball-Berry intercept for C3 plants.
61
+ - ball_berry_slope_C3 (float, optional): Ball-Berry slope for C3 plants.
62
+ - ball_berry_slope_C4 (float, optional): Ball-Berry slope for C4 plants.
63
+ - peakVCmax_C3_μmolm2s1 (float, optional): Peak maximum carboxylation rate for C3 plants.
64
+ - peakVCmax_C4_μmolm2s1 (float, optional): Peak maximum carboxylation rate for C4 plants.
65
+ - CI (float, optional): Clumping index.
66
+ - PAR_albedo (float, optional): Surface albedo in visible wavelengths.
67
+ - NIR_albedo (float, optional): Surface albedo in near-infrared wavelengths.
68
+ - day_of_year (float, optional): Day of year.
69
+ - hour_of_day (float, optional): Hour of day.
70
+ GEOS5FP_connection (GEOS5FP, optional): Connection object for GEOS-5 FP data.
71
+ MODISCI_connection (MODISCI, optional): Connection object for MODIS clumping index data.
72
+ NASADEM_connection (NASADEMConnection, optional): Connection object for NASADEM data.
73
+ C4_fraction_scale_factor (float, optional): Scale factor for C4 fraction adjustment.
74
+ resampling (str, optional): Resampling method for data processing.
75
+ GEDI_download_directory (str, optional): Directory for GEDI canopy height data downloads.
76
+
77
+ Returns:
78
+ pd.DataFrame: A DataFrame with the same structure as the input, but with additional columns
79
+ containing all BESS-JPL input variables that were retrieved or calculated.
80
+
81
+ Raises:
82
+ KeyError: If required columns ("geometry" or "lat" and "lon", "time_UTC", "ST_C", "NDVI", "albedo") are missing.
83
+ """
84
+ def ensure_geometry(row):
85
+ if "geometry" in row:
86
+ if isinstance(row.geometry, str):
87
+ s = row.geometry.strip()
88
+ if s.startswith("POINT"):
89
+ coords = s.replace("POINT", "").replace("(", "").replace(")", "").strip().split()
90
+ return Point(float(coords[0]), float(coords[1]))
91
+ elif "," in s:
92
+ coords = [float(c) for c in s.split(",")]
93
+ return Point(coords[0], coords[1])
94
+ else:
95
+ coords = [float(c) for c in s.split()]
96
+ return Point(coords[0], coords[1])
97
+ return row.geometry
98
+
99
+ logger.info("started generating BESS inputs table")
100
+
101
+ # Ensure geometry column is properly formatted
102
+ input_df = input_df.copy()
103
+ input_df["geometry"] = input_df.apply(ensure_geometry, axis=1)
104
+
105
+ # Prepare output DataFrame
106
+ output_df = input_df.copy()
107
+
108
+ # Prepare geometries
109
+ if "geometry" in input_df.columns:
110
+ geometries = MultiPoint([(geom.x, geom.y) for geom in input_df.geometry], crs=WGS84)
111
+ elif "lat" in input_df.columns and "lon" in input_df.columns:
112
+ geometries = MultiPoint([(lon, lat) for lon, lat in zip(input_df.lon, input_df.lat)], crs=WGS84)
113
+ else:
114
+ raise KeyError("Input DataFrame must contain either 'geometry' or both 'lat' and 'lon' columns.")
115
+
116
+ # Convert time column to datetime
117
+ times_UTC = pd.to_datetime(input_df.time_UTC)
118
+
119
+ logger.info(f"generating inputs for {len(input_df)} rows")
120
+
121
+ # Helper function to get column values or None if column doesn't exist
122
+ def get_column_or_none(df, col_name, default_col_name=None):
123
+ if col_name in df.columns:
124
+ return df[col_name].values
125
+ elif default_col_name and default_col_name in df.columns:
126
+ return df[default_col_name].values
127
+ else:
128
+ return None
129
+
130
+ # Retrieve all inputs at once using vectorized retrieve_BESS_inputs call
131
+ BESS_inputs = retrieve_BESS_inputs(
132
+ ST_C=get_column_or_none(input_df, "ST_C"),
133
+ NDVI=get_column_or_none(input_df, "NDVI"),
134
+ albedo=get_column_or_none(input_df, "albedo"),
135
+ geometry=geometries,
136
+ time_UTC=times_UTC,
137
+ hour_of_day=get_column_or_none(input_df, "hour_of_day"),
138
+ day_of_year=get_column_or_none(input_df, "day_of_year"),
139
+ GEOS5FP_connection=GEOS5FP_connection,
140
+ elevation_m=get_column_or_none(input_df, "elevation_m"),
141
+ Ta_C=get_column_or_none(input_df, "Ta_C"),
142
+ RH=get_column_or_none(input_df, "RH"),
143
+ NDVI_minimum=get_column_or_none(input_df, "NDVI_minimum"),
144
+ NDVI_maximum=get_column_or_none(input_df, "NDVI_maximum"),
145
+ PAR_albedo=get_column_or_none(input_df, "PAR_albedo"),
146
+ NIR_albedo=get_column_or_none(input_df, "NIR_albedo"),
147
+ COT=get_column_or_none(input_df, "COT"),
148
+ AOT=get_column_or_none(input_df, "AOT"),
149
+ vapor_gccm=get_column_or_none(input_df, "vapor_gccm"),
150
+ ozone_cm=get_column_or_none(input_df, "ozone_cm"),
151
+ KG_climate=get_column_or_none(input_df, "KG_climate", "KG"),
152
+ canopy_height_meters=get_column_or_none(input_df, "canopy_height_meters"),
153
+ Ca=get_column_or_none(input_df, "Ca"),
154
+ wind_speed_mps=get_column_or_none(input_df, "wind_speed_mps"),
155
+ SZA_deg=get_column_or_none(input_df, "SZA_deg", "SZA"),
156
+ canopy_temperature_C=get_column_or_none(input_df, "canopy_temperature_C"),
157
+ soil_temperature_C=get_column_or_none(input_df, "soil_temperature_C"),
158
+ C4_fraction=get_column_or_none(input_df, "C4_fraction"),
159
+ carbon_uptake_efficiency=get_column_or_none(input_df, "carbon_uptake_efficiency"),
160
+ kn=get_column_or_none(input_df, "kn"),
161
+ ball_berry_intercept_C3=get_column_or_none(input_df, "ball_berry_intercept_C3"),
162
+ ball_berry_slope_C3=get_column_or_none(input_df, "ball_berry_slope_C3"),
163
+ ball_berry_slope_C4=get_column_or_none(input_df, "ball_berry_slope_C4"),
164
+ peakVCmax_C3_μmolm2s1=get_column_or_none(input_df, "peakVCmax_C3_μmolm2s1", "peakVCmax_C3"),
165
+ peakVCmax_C4_μmolm2s1=get_column_or_none(input_df, "peakVCmax_C4_μmolm2s1", "peakVCmax_C4"),
166
+ CI=get_column_or_none(input_df, "CI"),
167
+ C4_fraction_scale_factor=C4_fraction_scale_factor,
168
+ MODISCI_connection=MODISCI_connection,
169
+ NASADEM_connection=NASADEM_connection,
170
+ resampling=resampling,
171
+ GEDI_download_directory=GEDI_download_directory
172
+ )
173
+
174
+ # Add retrieved inputs to the output DataFrame
175
+ for key, values in BESS_inputs.items():
176
+ # Skip values with mismatched lengths
177
+ if hasattr(values, '__len__') and not isinstance(values, str):
178
+ if len(values) != len(output_df):
179
+ logger.warning(f"Skipping {key}: length mismatch ({len(values)} != {len(output_df)})")
180
+ continue
181
+ logger.info(f"Adding {key} to output DataFrame (type: {type(values)}, length: {len(values) if hasattr(values, '__len__') and not isinstance(values, str) else 'N/A'})")
182
+ output_df[key] = values
183
+
184
+ logger.info("completed generating BESS inputs table")
185
+
186
+ return output_df