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
BESS_JPL/BESS.py ADDED
@@ -0,0 +1,468 @@
1
+ from typing import Union
2
+ from datetime import datetime
3
+ import logging
4
+ import numpy as np
5
+
6
+ import rasters as rt
7
+ from rasters import Raster, RasterGeometry
8
+
9
+ from check_distribution import check_distribution
10
+
11
+ from sun_angles import calculate_SZA_from_DOY_and_hour
12
+
13
+ from koppengeiger import load_koppen_geiger
14
+ from gedi_canopy_height import load_canopy_height
15
+ from FLiESANN import FLiESANN
16
+ from GEOS5FP import GEOS5FP
17
+ from MODISCI import MODISCI
18
+
19
+ from .constants import *
20
+ from .vegetation_conversion import LAI_from_NDVI
21
+ from .canopy_shortwave_radiation import canopy_shortwave_radiation
22
+ from .carbon_water_fluxes import carbon_water_fluxes
23
+ from .meteorology import meteorology, SVP_Pa_from_Ta_K
24
+ from .interpolate_C3_C4 import interpolate_C3_C4
25
+ from .calculate_VCmax import calculate_VCmax
26
+ from .load_NDVI_minimum import load_NDVI_minimum
27
+ from .load_NDVI_maximum import load_NDVI_maximum
28
+ from .load_C4_fraction import load_C4_fraction
29
+ from .load_carbon_uptake_efficiency import load_carbon_uptake_efficiency
30
+ from .load_kn import load_kn
31
+ from .load_peakVCmax_C3 import load_peakVCmax_C3
32
+ from .load_peakVCmax_C4 import load_peakVCmax_C4
33
+ from .load_ball_berry_intercept_C3 import load_ball_berry_intercept_C3
34
+ from .load_ball_berry_slope_C3 import load_ball_berry_slope_C3
35
+ from .load_ball_berry_slope_C4 import load_ball_berry_slope_C4
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ def BESS(
40
+ ST_C: Union[Raster, np.ndarray], # surface temperature in Celsius
41
+ NDVI: Union[Raster, np.ndarray], # NDVI
42
+ albedo: Union[Raster, np.ndarray], # surface albedo
43
+ elevation_km: Union[Raster, np.ndarray], # elevation in kilometers
44
+ geometry: RasterGeometry = None,
45
+ time_UTC: datetime = None,
46
+ hour_of_day: np.ndarray = None,
47
+ day_of_year: np.ndarray = None,
48
+ GEOS5FP_connection: GEOS5FP = None,
49
+ Ta_C: Union[Raster, np.ndarray] = None, # air temperature in Celsius
50
+ RH: Union[Raster, np.ndarray] = None, # relative humidity as a proportion
51
+ NDVI_minimum: Union[Raster, np.ndarray] = None, # minimum NDVI
52
+ NDVI_maximum: Union[Raster, np.ndarray] = None, # maximum NDVI
53
+ Rg: Union[Raster, np.ndarray] = None, # incoming shortwave radiation in W/m^2
54
+ VISdiff: Union[Raster, np.ndarray] = None, # diffuse visible radiation in W/m^2
55
+ VISdir: Union[Raster, np.ndarray] = None, # direct visible radiation in W/m^2
56
+ NIRdiff: Union[Raster, np.ndarray] = None, # diffuse near-infrared radiation in W/m^2
57
+ NIRdir: Union[Raster, np.ndarray] = None, # direct near-infrared radiation in W/m^2
58
+ UV: Union[Raster, np.ndarray] = None, # incoming ultraviolet radiation in W/m^2
59
+ albedo_visible: Union[Raster, np.ndarray] = None, # surface albedo in visible wavelengths (initialized to surface albedo if left as None)
60
+ albedo_NIR: Union[Raster, np.ndarray] = None, # surface albedo in near-infrared wavelengths (initialized to surface albedo if left as None)
61
+ COT: Union[Raster, np.ndarray] = None, # cloud optical thickness
62
+ AOT: Union[Raster, np.ndarray] = None, # aerosol optical thickness
63
+ vapor_gccm: Union[Raster, np.ndarray] = None, # water vapor in g/ccm
64
+ ozone_cm: Union[Raster, np.ndarray] = None, # ozone in cm
65
+ KG_climate: Union[Raster, np.ndarray] = None, # KG climate
66
+ canopy_height_meters: Union[Raster, np.ndarray] = None, # canopy height in meters
67
+ Ca: Union[Raster, np.ndarray] = None, # atmospheric CO2 concentration in ppm
68
+ wind_speed_mps: Union[Raster, np.ndarray] = None, # wind speed in meters per second
69
+ SZA: Union[Raster, np.ndarray] = None, # solar zenith angle in degrees
70
+ canopy_temperature_C: Union[Raster, np.ndarray] = None, # canopy temperature in Celsius (initialized to surface temperature if left as None)
71
+ soil_temperature_C: Union[Raster, np.ndarray] = None, # soil temperature in Celsius (initialized to surface temperature if left as None)
72
+ C4_fraction: Union[Raster, np.ndarray] = None, # fraction of C4 plants
73
+ carbon_uptake_efficiency: Union[Raster, np.ndarray] = None, # intrinsic quantum efficiency for carbon uptake
74
+ kn: np.ndarray = None,
75
+ ball_berry_intercept_C3: np.ndarray = None, # Ball-Berry intercept for C3 plants
76
+ ball_berry_intercept_C4: Union[np.ndarray, float] = BALL_BERRY_INTERCEPT_C4, # Ball-Berry intercept for C4 plants
77
+ ball_berry_slope_C3: np.ndarray = None, # Ball-Berry slope for C3 plants
78
+ ball_berry_slope_C4: np.ndarray = None, # Ball-Berry slope for C4 plants
79
+ peakVCmax_C3: np.ndarray = None, # peak maximum carboxylation rate for C3 plants
80
+ peakVCmax_C4: np.ndarray = None, # peak maximum carboxylation rate for C4 plants
81
+ CI: Union[Raster, np.ndarray] = None,
82
+ resampling: str = RESAMPLING): # clumping index
83
+ if geometry is None and isinstance(ST_C, Raster):
84
+ geometry = ST_C.geometry
85
+
86
+ if (day_of_year is None or hour_of_day is None) and time_UTC is not None and geometry is not None:
87
+ day_of_year = solar_day_of_year_for_area(time_UTC=time_UTC, geometry=geometry)
88
+ hour_of_day = solar_hour_of_day_for_area(time_UTC=time_UTC, geometry=geometry)
89
+
90
+ if time_UTC is None and day_of_year is None and hour_of_day is None:
91
+ raise ValueError("no time given between time_UTC, day_of_year, and hour_of_day")
92
+
93
+ # load air temperature in Celsius if not provided
94
+ if Ta_C is None:
95
+ Ta_C = GEOS5FP_connection.Ta_C(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
96
+
97
+ # load relative humidity if not provided
98
+ if RH is None:
99
+ RH = GEOS5FP_connection.RH(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
100
+
101
+ # load minimum NDVI if not provided
102
+ if NDVI_minimum is None and geometry is not None:
103
+ NDVI_minimum = load_NDVI_minimum(geometry=geometry, resampling=resampling)
104
+
105
+ # load maximum NDVI if not provided
106
+ if NDVI_maximum is None and geometry is not None:
107
+ NDVI_maximum = load_NDVI_maximum(geometry=geometry, resampling=resampling)
108
+
109
+ # load C4 fraction if not provided
110
+ if C4_fraction is None:
111
+ C4_fraction = load_C4_fraction(geometry=geometry, resampling=resampling)
112
+
113
+ # load carbon uptake efficiency if not provided
114
+ if carbon_uptake_efficiency is None:
115
+ carbon_uptake_efficiency = load_carbon_uptake_efficiency(geometry=geometry, resampling=resampling)
116
+
117
+ # load kn if not provided
118
+ if kn is None:
119
+ kn = load_kn(geometry=geometry, resampling=resampling)
120
+
121
+ # load peak VC max for C3 plants if not provided
122
+ if peakVCmax_C3 is None:
123
+ peakVCmax_C3 = load_peakVCmax_C3(geometry=geometry, resampling=resampling)
124
+
125
+ # load peak VC max for C4 plants if not provided
126
+ if peakVCmax_C4 is None:
127
+ peakVCmax_C4 = load_peakVCmax_C4(geometry=geometry, resampling=resampling)
128
+
129
+ # load Ball-Berry slope for C3 plants if not provided
130
+ if ball_berry_slope_C3 is None:
131
+ ball_berry_slope_C3 = load_ball_berry_slope_C3(geometry=geometry, resampling=resampling)
132
+
133
+ # load Ball-Berry slope for C4 plants if not provided
134
+ if ball_berry_slope_C4 is None:
135
+ ball_berry_slope_C4 = load_ball_berry_slope_C4(geometry=geometry, resampling=resampling)
136
+
137
+ # load Ball-Berry intercept for C3 plants if not provided
138
+ if ball_berry_intercept_C3 is None:
139
+ ball_berry_intercept_C3 = load_ball_berry_intercept_C3(geometry=geometry, resampling=resampling)
140
+
141
+ # check if any of the FLiES outputs are not given
142
+ if None in (Rg, VISdiff, VISdir, NIRdiff, NIRdir, UV, albedo_visible, albedo_NIR):
143
+ # load cloud optical thickness if not provided
144
+ if COT is None:
145
+ COT = GEOS5FP_connection.COT(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
146
+
147
+ # load aerosol optical thickness if not provided
148
+ if AOT is None:
149
+ AOT = GEOS5FP_connection.AOT(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
150
+
151
+ ## FIXME fix FLiES interface
152
+
153
+ # run FLiES radiative transfer model
154
+ FLiES_results = FLiESANN(
155
+ day_of_year=day_of_year,
156
+ hour_of_day=hour_of_day,
157
+ albedo=albedo,
158
+ COT=COT,
159
+ AOT=AOT,
160
+ vapor_gccm=vapor_gccm,
161
+ ozone_cm=ozone_cm,
162
+ elevation_km=elevation_km,
163
+ SZA=SZA,
164
+ KG_climate=KG_climate,
165
+ geometry=geometry,
166
+ GEOS5FP_connection=GEOS5FP_connection
167
+ )
168
+
169
+ # extract FLiES outputs
170
+ Rg = FLiES_results["Rg"]
171
+ VISdiff = FLiES_results["VISdiff"]
172
+ VISdir = FLiES_results["VISdir"]
173
+ NIRdiff = FLiES_results["NIRdiff"]
174
+ NIRdir = FLiES_results["NIRdir"]
175
+ UV = FLiES_results["UV"]
176
+ albedo_visible = FLiES_results["VIS"]
177
+ albedo_NIR = FLiES_results["NIR"]
178
+
179
+ # load koppen geiger climate classification if not provided
180
+ if KG_climate is None:
181
+ KG_climate = load_koppen_geiger(geometry=geometry)
182
+
183
+ # load canopy height in meters if not provided
184
+ if canopy_height_meters is None:
185
+ canopy_height_meters = load_canopy_height(geometry=geometry, resampling=resampling)
186
+
187
+ # load CO2 concentration in ppm if not provided
188
+ if Ca is None:
189
+ Ca = GEOS5FP_connection.CO2SC(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
190
+
191
+ # load wind speed in meters per second if not provided
192
+ if wind_speed_mps is None:
193
+ wind_speed_mps = GEOS5FP_connection.wind_speed(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
194
+
195
+ # canopy temperature defaults to surface temperature
196
+ if canopy_temperature_C is None:
197
+ canopy_temperature_C = ST_C
198
+
199
+ # soil temperature defaults to surface temperature
200
+ if soil_temperature_C is None:
201
+ soil_temperature_C = ST_C
202
+
203
+ # visible albedo defaults to surface albedo
204
+ if albedo_visible is None:
205
+ albedo_visible = albedo
206
+
207
+ # near-infrared albedo defaults to surface albedo
208
+ if albedo_NIR is None:
209
+ albedo_NIR = albedo
210
+
211
+ # calculate solar zenith angle if not provided
212
+ if SZA is None:
213
+ SZA = calculate_SZA_from_DOY_and_hour(lat, lon, day_of_year, hour_of_day)
214
+
215
+ if CI is None and geometry is not None:
216
+ modisci = MODISCI()
217
+ CI = modisci.CI(geometry=geometry, resampling=resampling)
218
+
219
+ # canopy height defaults to zero
220
+ canopy_height_meters = np.where(np.isnan(canopy_height_meters), 0, canopy_height_meters)
221
+
222
+ # calculate saturation vapor pressure in Pascal from air temperature in Kelvin
223
+ Ta_K = Ta_C + 273.15
224
+ SVP_Pa = SVP_Pa_from_Ta_K(Ta_K)
225
+
226
+ # calculate actual vapor pressure in Pascal from relative humidity and saturation vapor pressure
227
+ Ea_Pa = RH * SVP_Pa
228
+
229
+ # convert elevation to meters
230
+ elevation_m = elevation_km * 1000
231
+
232
+ latitude = geometry.lat
233
+
234
+ Ps_Pa, VPD_Pa, RH, desTa, ddesTa, gamma, Cp, rhoa, epsa, R, Rc, Rs, SFd, SFd2, DL, Ra, fStress = meteorology(
235
+ day_of_year=day_of_year,
236
+ hour_of_day=hour_of_day,
237
+ latitude=latitude,
238
+ elevation_m=elevation_m,
239
+ SZA=SZA,
240
+ Ta_K=Ta_K,
241
+ Ea_Pa=Ea_Pa,
242
+ Rg=Rg,
243
+ wind_speed_mps=wind_speed_mps,
244
+ canopy_height_meters=canopy_height_meters
245
+ )
246
+
247
+ meteorology_outputs = {
248
+ "Ps_Pa": Ps_Pa,
249
+ "VPD_Pa": VPD_Pa,
250
+ "RH": RH,
251
+ "desTa": desTa,
252
+ "ddesTa": ddesTa,
253
+ "gamma": gamma,
254
+ "Cp": Cp,
255
+ "rhoa": rhoa,
256
+ "epsa": epsa,
257
+ "R": R,
258
+ "Rc": Rc,
259
+ "Rs": Rs,
260
+ "SFd": SFd,
261
+ "SFd2": SFd2,
262
+ "DL": DL,
263
+ "Ra": Ra,
264
+ "fStress": fStress
265
+ }
266
+
267
+ # Check the distribution for each variable
268
+ for var_name, var_value in meteorology_outputs.items():
269
+ check_distribution(var_value, var_name, time_UTC)
270
+
271
+ # convert NDVI to LAI
272
+ LAI = LAI_from_NDVI(NDVI)
273
+ LAI_minimum = LAI_from_NDVI(NDVI_minimum)
274
+ LAI_maximum = LAI_from_NDVI(NDVI_maximum)
275
+
276
+ VCmax_C3_sunlit, VCmax_C4_sunlit, VCmax_C3_shaded, VCmax_C4_shaded = calculate_VCmax(
277
+ LAI=LAI,
278
+ LAI_minimum=LAI_minimum,
279
+ LAI_maximum=LAI_maximum,
280
+ peakVCmax_C3=peakVCmax_C3,
281
+ peakVCmax_C4=peakVCmax_C4,
282
+ SZA=SZA,
283
+ kn=kn
284
+ )
285
+
286
+ # List of variable names and their corresponding values
287
+ VCmax_outputs = {
288
+ "VCmax_C3_sunlit": VCmax_C3_sunlit,
289
+ "VCmax_C4_sunlit": VCmax_C4_sunlit,
290
+ "VCmax_C3_shaded": VCmax_C3_shaded,
291
+ "VCmax_C4_shaded": VCmax_C4_shaded
292
+ }
293
+
294
+ # Check the distribution for each variable
295
+ for var_name, var_value in VCmax_outputs.items():
296
+ check_distribution(var_value, var_name, time_UTC)
297
+
298
+ sunlit_fraction, APAR_sunlit, APAR_shaded, ASW_sunlit, ASW_shaded, ASW_soil, G = canopy_shortwave_radiation(
299
+ PARDiff=VISdiff, # diffuse photosynthetically active radiation in W/m^2
300
+ PARDir=VISdir, # direct photosynthetically active radiation in W/m^2
301
+ NIRDiff=NIRdiff, # diffuse near-infrared radiation in W/m^2
302
+ NIRDir=NIRdir, # direct near-infrared radiation in W/m^2
303
+ UV=UV, # incoming ultraviolet radiation in W/m^2
304
+ SZA=SZA, # solar zenith angle in degrees
305
+ LAI=LAI, # leaf area index
306
+ CI=CI, # clumping index
307
+ albedo_visible=albedo_visible, # surface albedo in visible wavelengths
308
+ albedo_NIR=albedo_NIR # surface albedo in near-infrared wavelengths
309
+ )
310
+
311
+ # List of variable names and their corresponding values
312
+ canopy_radiation_outputs = {
313
+ "sunlit_fraction": sunlit_fraction,
314
+ "APAR_sunlit": APAR_sunlit,
315
+ "APAR_shaded": APAR_shaded,
316
+ "ASW_sunlit": ASW_sunlit,
317
+ "ASW_shaded": ASW_shaded,
318
+ "ASW_soil": ASW_soil,
319
+ "G": G
320
+ }
321
+
322
+ # Check the distribution for each variable
323
+ for var_name, var_value in canopy_radiation_outputs.items():
324
+ check_distribution(var_value, var_name, time_UTC)
325
+
326
+
327
+ canopy_temperature_K = canopy_temperature_C + 273.15
328
+ soil_temperature_K = soil_temperature_C + 273.15
329
+
330
+ GPP_C3, LE_C3, LE_soil_C3, LE_canopy_C3, Rn_C3, Rn_soil_C3, Rn_canopy_C3 = carbon_water_fluxes(
331
+ canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
332
+ soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
333
+ LAI=LAI, # leaf area index
334
+ Ta_K=Ta_K, # air temperature in Kelvin
335
+ APAR_sunlit=APAR_sunlit, # sunlit leaf absorptance of photosynthetically active radiation
336
+ APAR_shaded=APAR_shaded, # shaded leaf absorptance of photosynthetically active radiation
337
+ ASW_sunlit=ASW_sunlit, # sunlit absorbed shortwave radiation
338
+ ASW_shaded=ASW_shaded, # shaded absorbed shortwave radiation
339
+ ASW_soil=ASW_soil, # absorbed shortwave radiation of soil
340
+ Vcmax25_sunlit=VCmax_C3_sunlit, # sunlit maximum carboxylation rate at 25 degrees C
341
+ Vcmax25_shaded=VCmax_C3_shaded, # shaded maximum carboxylation rate at 25 degrees C
342
+ ball_berry_slope=ball_berry_slope_C3, # Ball-Berry slope for C3 photosynthesis
343
+ ball_berry_intercept=ball_berry_intercept_C3, # Ball-Berry intercept for C3 photosynthesis
344
+ sunlit_fraction=sunlit_fraction, # fraction of sunlit leaves
345
+ G=G, # soil heat flux
346
+ SZA=SZA, # solar zenith angle
347
+ Ca=Ca, # atmospheric CO2 concentration
348
+ Ps_Pa=Ps_Pa, # surface pressure in Pascal
349
+ gamma=gamma, # psychrometric constant
350
+ Cp=Cp, # specific heat of air in J/kg/K
351
+ rhoa=rhoa, # density of air in kg/m3
352
+ VPD_Pa=VPD_Pa, # vapor pressure deficit in Pascal
353
+ RH=RH, # relative humidity as a fraction
354
+ desTa=desTa,
355
+ ddesTa=ddesTa,
356
+ epsa=epsa,
357
+ Rc=Rc,
358
+ Rs=Rs,
359
+ carbon_uptake_efficiency=carbon_uptake_efficiency, # intrinsic quantum efficiency for carbon uptake
360
+ fStress=fStress,
361
+ C4_photosynthesis=False # C3 or C4 photosynthesis
362
+ )
363
+
364
+ # List of variable names and their corresponding values
365
+ carbon_water_fluxes_outputs = {
366
+ "GPP_C3": GPP_C3,
367
+ "LE_C3": LE_C3,
368
+ "LE_soil_C3": LE_soil_C3,
369
+ "LE_canopy_C3": LE_canopy_C3,
370
+ "Rn_C3": Rn_C3,
371
+ "Rn_soil_C3": Rn_soil_C3,
372
+ "Rn_canopy_C3": Rn_canopy_C3
373
+ }
374
+
375
+ # Check the distribution for each variable
376
+ for var_name, var_value in carbon_water_fluxes_outputs.items():
377
+ check_distribution(var_value, var_name, time_UTC)
378
+
379
+
380
+ GPP_C4, LE_C4, LE_soil_C4, LE_canopy_C4, Rn_C4, Rn_soil_C4, Rn_canopy_C4 = carbon_water_fluxes(
381
+ canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
382
+ soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
383
+ LAI=LAI, # leaf area index
384
+ Ta_K=Ta_K, # air temperature in Kelvin
385
+ APAR_sunlit=APAR_sunlit, # sunlit leaf absorptance of photosynthetically active radiation
386
+ APAR_shaded=APAR_shaded, # shaded leaf absorptance of photosynthetically active radiation
387
+ ASW_sunlit=ASW_sunlit, # sunlit absorbed shortwave radiation
388
+ ASW_shaded=ASW_shaded, # shaded absorbed shortwave radiation
389
+ ASW_soil=ASW_soil, # absorbed shortwave radiation of soil
390
+ Vcmax25_sunlit=VCmax_C4_sunlit, # sunlit maximum carboxylation rate at 25 degrees C
391
+ Vcmax25_shaded=VCmax_C4_shaded, # shaded maximum carboxylation rate at 25 degrees C
392
+ ball_berry_slope=ball_berry_slope_C4, # Ball-Berry slope for C4 photosynthesis
393
+ ball_berry_intercept=ball_berry_intercept_C4, # Ball-Berry intercept for C4 photosynthesis
394
+ sunlit_fraction=sunlit_fraction, # fraction of sunlit leaves
395
+ G=G, # soil heat flux
396
+ SZA=SZA, # solar zenith angle
397
+ Ca=Ca, # atmospheric CO2 concentration
398
+ Ps_Pa=Ps_Pa, # surface pressure in Pascal
399
+ gamma=gamma, # psychrometric constant
400
+ Cp=Cp, # specific heat of air in J/kg/K
401
+ rhoa=rhoa, # density of air in kg/m3
402
+ VPD_Pa=VPD_Pa, # vapor pressure deficit in Pascal
403
+ RH=RH, # relative humidity as a fraction
404
+ desTa=desTa,
405
+ ddesTa=ddesTa,
406
+ epsa=epsa,
407
+ Rc=Rc,
408
+ Rs=Rs,
409
+ carbon_uptake_efficiency=carbon_uptake_efficiency, # intrinsic quantum efficiency for carbon uptake
410
+ fStress=fStress,
411
+ C4_photosynthesis=True # C3 or C4 photosynthesis
412
+ )
413
+
414
+ # List of variable names and their corresponding values
415
+ carbon_water_fluxes_C4_outputs = {
416
+ "GPP_C4": GPP_C4,
417
+ "LE_C4": LE_C4,
418
+ "LE_soil_C4": LE_soil_C4,
419
+ "LE_canopy_C4": LE_canopy_C4,
420
+ "Rn_C4": Rn_C4,
421
+ "Rn_soil_C4": Rn_soil_C4,
422
+ "Rn_canopy_C4": Rn_canopy_C4
423
+ }
424
+
425
+ # Check the distribution for each variable
426
+ for var_name, var_value in carbon_water_fluxes_C4_outputs.items():
427
+ check_distribution(var_value, var_name, time_UTC)
428
+
429
+ # interpolate C3 and C4 GPP
430
+ ST_K = ST_C + 273.15
431
+ GPP = np.clip(interpolate_C3_C4(GPP_C3, GPP_C4, C4_fraction), 0, 50)
432
+ GPP = np.where(np.isnan(ST_K), np.nan, GPP)
433
+
434
+ # upscale from instantaneous to daily
435
+
436
+ # upscale GPP to daily
437
+ GPP_daily = 1800 * GPP / SFd * 1e-6 * 12 # Eq. (3) in Ryu et al 2008
438
+ GPP_daily = np.where(SFd < 0.01, 0, GPP_daily)
439
+ GPP_daily = np.where(SZA >= 90, 0, GPP_daily)
440
+
441
+ # interpolate C3 and C4 net radiation
442
+ Rn = np.clip(interpolate_C3_C4(Rn_C3, Rn_C4, C4_fraction), 0, 1000)
443
+
444
+ # interpolate C3 and C4 soil net radiation
445
+ Rn_soil = np.clip(interpolate_C3_C4(Rn_soil_C3, Rn_soil_C4, C4_fraction), 0, 1000)
446
+
447
+ # interpolate C3 and C4 canopy net radiation
448
+ Rn_canopy = np.clip(interpolate_C3_C4(Rn_canopy_C3, Rn_canopy_C4, C4_fraction), 0, 1000)
449
+
450
+ # interpolate C3 and C4 latent heat flux
451
+ LE = np.clip(interpolate_C3_C4(LE_C3, LE_C4, C4_fraction), 0, 1000)
452
+
453
+ # interpolate C3 and C4 soil latent heat flux
454
+ LE_soil = np.clip(interpolate_C3_C4(LE_soil_C3, LE_soil_C4, C4_fraction), 0, 1000)
455
+
456
+ # interpolate C3 and C4 canopy latent heat flux
457
+ LE_canopy = np.clip(interpolate_C3_C4(LE_canopy_C3, LE_canopy_C4, C4_fraction), 0, 1000)
458
+
459
+ return {
460
+ "GPP": GPP,
461
+ "GPP_daily": GPP_daily,
462
+ "Rn": Rn,
463
+ "Rn_soil": Rn_soil,
464
+ "Rn_canopy": Rn_canopy,
465
+ "LE": LE,
466
+ "LE_soil": LE_soil,
467
+ "LE_canopy": LE_canopy
468
+ }
BESS_JPL/BESS_JPL.py ADDED
@@ -0,0 +1,23 @@
1
+ from .constants import *
2
+ from .C3_photosynthesis import *
3
+ from .C4_photosynthesis import *
4
+ from .canopy_energy_balance import *
5
+ from .canopy_longwave_radiation import *
6
+ from .canopy_shortwave_radiation import *
7
+ from .carbon_water_fluxes import *
8
+ from .FVC_from_NDVI import FVC_from_NDVI
9
+ from .interpolate_C3_C4 import interpolate_C3_C4
10
+ from .LAI_from_NDVI import LAI_from_NDVI
11
+ from .load_C4_fraction import load_C4_fraction
12
+ from .load_carbon_uptake_efficiency import load_carbon_uptake_efficiency
13
+ from .load_kn import load_kn
14
+ from .load_NDVI_minimum import load_NDVI_minimum
15
+ from .load_NDVI_maximum import load_NDVI_maximum
16
+ from .load_peakVCmax_C3 import load_peakVCmax_C3
17
+ from .load_peakVCmax_C4 import load_peakVCmax_C4
18
+ from .load_ball_berry_intercept_C3 import load_ball_berry_intercept_C3
19
+ from .load_ball_berry_slope_C3 import load_ball_berry_slope_C3
20
+ from .load_ball_berry_slope_C4 import load_ball_berry_slope_C4
21
+ from .meteorology import *
22
+ from .BESS import BESS
23
+ from .soil_energy_balance import *
@@ -0,0 +1,72 @@
1
+ import numpy as np
2
+
3
+
4
+ def calculate_C3_photosynthesis(
5
+ Tf_K: np.ndarray, # leaf temperature in Kelvin
6
+ Ci: np.ndarray, # intercellular CO2 concentration [umol mol-1]
7
+ APAR: np.ndarray, # leaf absorptance to photosynthetically active radiation [umol m-2 s-1]
8
+ Vcmax25: np.ndarray, # maximum carboxylation rate at 25C [umol m-2 s-1]
9
+ Ps_Pa: np.ndarray, # surface pressure in Pascal
10
+ carbon_uptake_efficiency: np.ndarray) -> np.ndarray: # intrinsic quantum efficiency for carbon uptake
11
+ """
12
+ photosynthesis for C3 plants
13
+ Collatz et al., 1991
14
+ https://www.sciencedirect.com/science/article/abs/pii/0168192391900028
15
+ Adapted from Youngryel Ryu's code by Gregory Halverson and Robert Freepartner
16
+ :param Tf_K: leaf temperature in Kelvin
17
+ :param Ci: intercellular CO2 concentration [umol mol-1]
18
+ :param APAR: leaf absorptance to photosynthetically active radiation [umol m-2 s-1]
19
+ :param Vcmax25: maximum carboxylation rate at 25C [umol m-2 s-1]
20
+ :param Ps_Pa: surface pressure in Pascal
21
+ :param carbon_uptake_efficiency: intrinsic quantum efficiency for carbon uptake
22
+ :return: net assimilation [umol m-2 s-1]
23
+ """
24
+ # gas constant
25
+ R = 8.314e-3 # [kJ K-1 mol-1]
26
+
27
+ # calculate oxygen concentration
28
+ O2 = Ps_Pa * 0.21 # [Pa]
29
+
30
+ # convert intercellular CO2 concentration to Pascal
31
+ Pi = Ci * 1e-6 * Ps_Pa # [umol mol-1] -> [Pa]
32
+
33
+ # Temperature correction
34
+ item = (Tf_K - 298.15) / 10
35
+ KC25 = 30 # [Pa]
36
+ KCQ10 = 2.1 # [-]
37
+ KO25 = 30000 # [Pa]
38
+ KOQ10 = 1.2 # [-]
39
+ tao25 = 2600 # [Pa]
40
+ taoQ10 = 0.57 # [-]
41
+ KC = KC25 * KCQ10 ** item # [Pa]
42
+ KO = KO25 * KOQ10 ** item # [Pa]
43
+ K = KC * (1.0 + O2 / KO) # [Pa]
44
+ tao = tao25 * taoQ10 ** item # [Pa]
45
+ GammaS = O2 / (2.0 * tao) # [Pa]
46
+ VcmaxQ10 = 2.4 # [-]
47
+ Vcmax_o = Vcmax25 * VcmaxQ10 ** item # [umol m-2 s-1]
48
+ Vcmax = Vcmax_o / (1.0 + np.exp((-220.0 + 0.703 * Tf_K) / (R * Tf_K))) # [umol m-2 s-1]
49
+ Rd_o = 0.015 * Vcmax # [umol m-2 s-1]
50
+ Rd = Rd_o * 1.0 / (1.0 + np.exp(1.3 * (Tf_K - 273.15 - 55.0))) # [umol m-2 s-1]
51
+
52
+ # Three limiting states
53
+ JC = Vcmax * (Pi - GammaS) / (Pi + K)
54
+ JE = carbon_uptake_efficiency * APAR * (Pi - GammaS) / (Pi + 2.0 * GammaS)
55
+ JS = Vcmax / 2.0
56
+
57
+ # Colimitation (not the case at canopy level according to DePury and Farquhar)
58
+ a = 0.98
59
+ b = -(JC + JE)
60
+ c = JC * JE
61
+ JCE = (-b + np.sign(b) * np.sqrt(b * b - 4.0 * a * c)) / (2.0 * a)
62
+ JCE = np.real(JCE)
63
+ a = 0.95
64
+ b = -(JCE + JS)
65
+ c = JCE * JS
66
+ JCES = (-b + np.sign(b) * np.sqrt(b * b - 4.0 * a * c)) / (2.0 * a)
67
+ JCES = np.real(JCES)
68
+
69
+ # calculate net assimilation
70
+ An = np.clip(JCES - Rd, 0, None)
71
+
72
+ return An
Binary file
Binary file
@@ -0,0 +1,56 @@
1
+ import numpy as np
2
+
3
+
4
+ def calculate_C4_photosynthesis(Tf_K: np.ndarray, Ci: np.ndarray, APAR: np.ndarray, Vcmax25: np.ndarray) -> np.ndarray:
5
+ """
6
+ =============================================================================
7
+ Collatz et al., 1992
8
+
9
+ Module : Photosynthesis for C4 plant
10
+ Input : leaf temperature (Tf) [K],
11
+ : intercellular CO2 concentration (Ci) [umol mol-1],
12
+ : absorbed photosynthetically active radiation (APAR) [umol m-2 s-1],
13
+ : maximum carboxylation rate at 25C (Vcmax25) [umol m-2 s-1].
14
+ Output : net assimilation (An) [umol m-2 s-1].
15
+
16
+
17
+ Conversion from MatLab by Robert Freepartner, JPL/Raytheon/JaDa Systems
18
+ March 2020
19
+
20
+ =============================================================================
21
+ """
22
+ # Temperature correction
23
+ item = (Tf_K - 298.15) / 10.0
24
+ Q10 = 2.0
25
+ k = 0.7 * pow(Q10, item) # [mol m-2 s-1]
26
+ Vcmax_o = Vcmax25 * pow(Q10, item) # [umol m-2 s-1]
27
+ Vcmax = Vcmax_o / (
28
+ (1.0 + np.exp(0.3 * (286.15 - Tf_K))) * (1.0 + np.exp(0.3 * (Tf_K - 309.15)))) # [umol m-2 s-1]
29
+ Rd_o = 0.8 * pow(Q10, item) # [umol m-2 s-1]
30
+ Rd = Rd_o / (1.0 + np.exp(1.3 * (Tf_K - 328.15))) # [umol m-2 s-1]
31
+
32
+ # Three limiting states
33
+ Je = Vcmax # [umol m-2 s-1]
34
+ alf = 0.067 # [mol CO2 mol photons-1]
35
+ Ji = alf * APAR # [umol m-2 s-1]
36
+ ci = Ci * 1e-6 # [umol mol-1] -> [mol CO2 mol CO2-1]
37
+ Jc = ci * k * 1e6 # [umol m-2 s-1]
38
+
39
+ # Colimitation (not the case at canopy level according to DePury and Farquhar)
40
+ a = 0.83
41
+ b = -(Je + Ji)
42
+ c = Je * Ji
43
+ Jei = (-b + np.sign(b) * np.sqrt(b * b - 4.0 * a * c)) / (2.0 * a)
44
+ Jei = np.real(Jei)
45
+ a = 0.93
46
+ b = -(Jei + Jc)
47
+ c = Jei * Jc
48
+ Jeic = (-b + np.sign(b) * np.sqrt(b * b - 4.0 * a * c)) / (2.0 * a)
49
+ Jeic = np.real(Jeic)
50
+
51
+ # Net assimilation
52
+ # An = nanmin(cat(3,Je,Ji,Jc),[],3) - Rd; % [umol m-2 s-1]
53
+ An = np.clip(Jeic - Rd, 0, None)
54
+ # An[An<0.0] = 0.0
55
+
56
+ return An
@@ -0,0 +1,22 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+ import rasters as rt
5
+ from rasters import Raster
6
+
7
+
8
+ def FVC_from_NDVI(NDVI: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
9
+ """
10
+ Convert Normalized Difference Vegetation Index (NDVI) to Fractional Vegetation Cover (FVC).
11
+
12
+ Parameters:
13
+ NDVI (Union[Raster, np.ndarray]): Input NDVI data.
14
+
15
+ Returns:
16
+ Union[Raster, np.ndarray]: Converted FVC data.
17
+ """
18
+ NDVIv = 0.52 # +- 0.03
19
+ NDVIs = 0.04 # +- 0.03
20
+ FVC = rt.clip((NDVI - NDVIs) / (NDVIv - NDVIs), 0.0, 1.0)
21
+
22
+ return FVC
@@ -0,0 +1,29 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+ import rasters as rt
5
+ from rasters import Raster
6
+
7
+ from .constants import KPAR, MIN_FIPAR, MAX_FIPAR, MIN_LAI, MAX_LAI
8
+
9
+
10
+ def LAI_from_NDVI(
11
+ NDVI: Union[Raster, np.ndarray],
12
+ min_fIPAR: float = MIN_FIPAR,
13
+ max_fIPAR: float = MAX_FIPAR,
14
+ min_LAI: float = MIN_LAI,
15
+ max_LAI: float = MAX_LAI) -> Union[Raster, np.ndarray]:
16
+ """
17
+ Convert Normalized Difference Vegetation Index (NDVI) to Leaf Area Index (LAI).
18
+
19
+ Parameters:
20
+ NDVI (Union[Raster, np.ndarray]): Input NDVI data.
21
+
22
+ Returns:
23
+ Union[Raster, np.ndarray]: Converted LAI data.
24
+ """
25
+ fIPAR = rt.clip(NDVI - 0.05, min_fIPAR, max_fIPAR)
26
+ fIPAR = np.where(fIPAR == 0, np.nan, fIPAR)
27
+ LAI = rt.clip(-np.log(1 - fIPAR) * (1 / KPAR), min_LAI, max_LAI)
28
+
29
+ return LAI
Binary file
Binary file
Binary file
Binary file