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
BESS_JPL/model.py ADDED
@@ -0,0 +1,594 @@
1
+ from typing import Union
2
+ from datetime import datetime
3
+ import logging
4
+ import numpy as np
5
+ from pytictoc import TicToc
6
+
7
+ from rasters import Raster, RasterGeometry
8
+
9
+ from check_distribution import check_distribution
10
+
11
+ from solar_apparent_time import calculate_solar_day_of_year, calculate_solar_hour_of_day
12
+ from gedi_canopy_height import GEDI_DOWNLOAD_DIRECTORY
13
+ from FLiESANN import FLiESANN
14
+ from GEOS5FP import GEOS5FP
15
+ from MODISCI import MODISCI
16
+ from NASADEM import NASADEMConnection
17
+
18
+ from daylight_evapotranspiration import daylight_ET_from_instantaneous_LE
19
+
20
+ from .constants import *
21
+ from .colors import *
22
+ from .C3_photosynthesis import *
23
+ from .C4_photosynthesis import *
24
+ from .canopy_energy_balance import *
25
+ from .canopy_longwave_radiation import *
26
+ from .canopy_shortwave_radiation import *
27
+ from .carbon_water_fluxes import *
28
+ from .FVC_from_NDVI import *
29
+ from .interpolate_C3_C4 import *
30
+ from .LAI_from_NDVI import *
31
+ from .load_C4_fraction import *
32
+ from .load_carbon_uptake_efficiency import *
33
+ from .load_kn import *
34
+ from .load_NDVI_minimum import *
35
+ from .load_NDVI_maximum import *
36
+ from .load_peakVCmax_C3 import *
37
+ from .load_peakVCmax_C4 import *
38
+ from .load_ball_berry_intercept_C3 import *
39
+ from .load_ball_berry_slope_C3 import *
40
+ from .load_ball_berry_slope_C4 import *
41
+ from .calculate_VCmax import *
42
+ from .meteorology import *
43
+ from .soil_energy_balance import *
44
+ from .retrieve_BESS_inputs import retrieve_BESS_inputs
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+ def BESS_JPL(
49
+ ST_C: Union[Raster, np.ndarray], # surface temperature in Celsius
50
+ NDVI: Union[Raster, np.ndarray], # NDVI
51
+ albedo: Union[Raster, np.ndarray], # surface albedo
52
+ geometry: RasterGeometry = None,
53
+ time_UTC: datetime = None,
54
+ hour_of_day: np.ndarray = None,
55
+ day_of_year: np.ndarray = None,
56
+ GEOS5FP_connection: GEOS5FP = None,
57
+ elevation_m: Union[Raster, np.ndarray] = None, # elevation in meters
58
+ Ta_C: Union[Raster, np.ndarray] = None, # air temperature in Celsius
59
+ RH: Union[Raster, np.ndarray] = None, # relative humidity as a proportion
60
+ NDVI_minimum: Union[Raster, np.ndarray] = None, # minimum NDVI
61
+ NDVI_maximum: Union[Raster, np.ndarray] = None, # maximum NDVI
62
+ SWin_Wm2: Union[Raster, np.ndarray] = None, # incoming shortwave radiation in W/m^2
63
+ PAR_diffuse_Wm2: Union[Raster, np.ndarray] = None, # diffuse visible radiation in W/m^2
64
+ PAR_direct_Wm2: Union[Raster, np.ndarray] = None, # direct visible radiation in W/m^2
65
+ NIR_diffuse_Wm2: Union[Raster, np.ndarray] = None, # diffuse near-infrared radiation in W/m^2
66
+ NIR_direct_Wm2: Union[Raster, np.ndarray] = None, # direct near-infrared radiation in W/m^2
67
+ UV_Wm2: Union[Raster, np.ndarray] = None, # incoming ultraviolet radiation in W/m^2
68
+ PAR_albedo: Union[Raster, np.ndarray] = None, # surface albedo in visible wavelengths (initialized to surface albedo if left as None)
69
+ NIR_albedo: Union[Raster, np.ndarray] = None, # surface albedo in near-infrared wavelengths (initialized to surface albedo if left as None)
70
+ COT: Union[Raster, np.ndarray] = None, # cloud optical thickness
71
+ AOT: Union[Raster, np.ndarray] = None, # aerosol optical thickness
72
+ vapor_gccm: Union[Raster, np.ndarray] = None, # water vapor in g/ccm
73
+ ozone_cm: Union[Raster, np.ndarray] = None, # ozone in cm
74
+ KG_climate: Union[Raster, np.ndarray] = None, # KG climate
75
+ canopy_height_meters: Union[Raster, np.ndarray] = None, # canopy height in meters
76
+ Ca: Union[Raster, np.ndarray] = None, # atmospheric CO2 concentration in ppm
77
+ wind_speed_mps: Union[Raster, np.ndarray] = None, # wind speed in meters per second
78
+ SZA_deg: Union[Raster, np.ndarray] = None, # solar zenith angle in degrees
79
+ canopy_temperature_C: Union[Raster, np.ndarray] = None, # canopy temperature in Celsius (initialized to surface temperature if left as None)
80
+ soil_temperature_C: Union[Raster, np.ndarray] = None, # soil temperature in Celsius (initialized to surface temperature if left as None)
81
+ C4_fraction: Union[Raster, np.ndarray] = None, # fraction of C4 plants
82
+ carbon_uptake_efficiency: Union[Raster, np.ndarray] = None, # intrinsic quantum efficiency for carbon uptake
83
+ kn: np.ndarray = None,
84
+ ball_berry_intercept_C3: np.ndarray = None, # Ball-Berry intercept for C3 plants
85
+ ball_berry_intercept_C4: Union[np.ndarray, float] = BALL_BERRY_INTERCEPT_C4, # Ball-Berry intercept for C4 plants
86
+ ball_berry_slope_C3: np.ndarray = None, # Ball-Berry slope for C3 plants
87
+ ball_berry_slope_C4: np.ndarray = None, # Ball-Berry slope for C4 plants
88
+ peakVCmax_C3_μmolm2s1: np.ndarray = None, # peak maximum carboxylation rate for C3 plants
89
+ peakVCmax_C4_μmolm2s1: np.ndarray = None, # peak maximum carboxylation rate for C4 plants
90
+ CI: Union[Raster, np.ndarray] = None,
91
+ C4_fraction_scale_factor: float = C4_FRACTION_SCALE_FACTOR,
92
+ MODISCI_connection: MODISCI = None,
93
+ NASADEM_connection: NASADEMConnection = None,
94
+ upscale_to_daylight: bool = UPSCALE_TO_DAYLIGHT,
95
+ resampling: str = RESAMPLING,
96
+ GEDI_download_directory: str = GEDI_DOWNLOAD_DIRECTORY,
97
+ offline_mode: bool = False) -> dict:
98
+ """
99
+ Breathing Earth System Simulator (BESS) model for estimating gross primary productivity (GPP)
100
+ and evapotranspiration (ET) using coupled atmospheric and canopy radiative transfer processes.
101
+ ...
102
+ """
103
+ # Initialize results dictionary to collect all inputs and outputs
104
+ results = {}
105
+
106
+ if geometry is None and isinstance(ST_C, Raster):
107
+ geometry = ST_C.geometry
108
+
109
+ if GEOS5FP_connection is None:
110
+ GEOS5FP_connection = GEOS5FP()
111
+
112
+ if (day_of_year is None or hour_of_day is None) and time_UTC is not None and geometry is not None:
113
+ day_of_year = calculate_solar_day_of_year(time_UTC=time_UTC, geometry=geometry)
114
+ hour_of_day = calculate_solar_hour_of_day(time_UTC=time_UTC, geometry=geometry)
115
+
116
+ if time_UTC is None and day_of_year is None and hour_of_day is None:
117
+ raise ValueError("no time given between time_UTC, day_of_year, and hour_of_day")
118
+
119
+ # Add primary inputs to results
120
+ results["ST_C"] = ST_C
121
+ results["NDVI"] = NDVI
122
+ results["albedo"] = albedo
123
+ results["geometry"] = geometry
124
+ results["time_UTC"] = time_UTC
125
+ results["day_of_year"] = day_of_year
126
+ results["hour_of_day"] = hour_of_day
127
+
128
+ BESS_inputs_dict = retrieve_BESS_inputs(
129
+ ST_C=ST_C,
130
+ NDVI=NDVI,
131
+ albedo=albedo,
132
+ geometry=geometry,
133
+ time_UTC=time_UTC,
134
+ hour_of_day=hour_of_day,
135
+ day_of_year=day_of_year,
136
+ GEOS5FP_connection=GEOS5FP_connection,
137
+ elevation_m=elevation_m,
138
+ Ta_C=Ta_C,
139
+ RH=RH,
140
+ NDVI_minimum=NDVI_minimum,
141
+ NDVI_maximum=NDVI_maximum,
142
+ PAR_albedo=PAR_albedo,
143
+ NIR_albedo=NIR_albedo,
144
+ COT=COT,
145
+ AOT=AOT,
146
+ vapor_gccm=vapor_gccm,
147
+ ozone_cm=ozone_cm,
148
+ KG_climate=KG_climate,
149
+ canopy_height_meters=canopy_height_meters,
150
+ Ca=Ca,
151
+ wind_speed_mps=wind_speed_mps,
152
+ SZA_deg=SZA_deg,
153
+ canopy_temperature_C=canopy_temperature_C,
154
+ soil_temperature_C=soil_temperature_C,
155
+ C4_fraction=C4_fraction,
156
+ carbon_uptake_efficiency=carbon_uptake_efficiency,
157
+ kn=kn,
158
+ ball_berry_intercept_C3=ball_berry_intercept_C3,
159
+ ball_berry_intercept_C4=ball_berry_intercept_C4,
160
+ ball_berry_slope_C3=ball_berry_slope_C3,
161
+ ball_berry_slope_C4=ball_berry_slope_C4,
162
+ peakVCmax_C3_μmolm2s1=peakVCmax_C3_μmolm2s1,
163
+ peakVCmax_C4_μmolm2s1=peakVCmax_C4_μmolm2s1,
164
+ CI=CI,
165
+ C4_fraction_scale_factor=C4_fraction_scale_factor,
166
+ MODISCI_connection=MODISCI_connection,
167
+ NASADEM_connection=NASADEM_connection,
168
+ resampling=resampling,
169
+ GEDI_download_directory=GEDI_download_directory,
170
+ offline_mode=offline_mode
171
+ )
172
+
173
+ # Extract all variables from the resulting dictionary
174
+ CI = BESS_inputs_dict["CI"]
175
+ elevation_m = BESS_inputs_dict["elevation_m"]
176
+ NDVI_minimum = BESS_inputs_dict["NDVI_minimum"]
177
+ NDVI_maximum = BESS_inputs_dict["NDVI_maximum"]
178
+ C4_fraction = BESS_inputs_dict["C4_fraction"]
179
+ carbon_uptake_efficiency = BESS_inputs_dict["carbon_uptake_efficiency"]
180
+ kn = BESS_inputs_dict["kn"]
181
+ peakVCmax_C3_μmolm2s1 = BESS_inputs_dict["peakVCmax_C3_μmolm2s1"]
182
+ peakVCmax_C4_μmolm2s1 = BESS_inputs_dict["peakVCmax_C4_μmolm2s1"]
183
+ ball_berry_slope_C3 = BESS_inputs_dict["ball_berry_slope_C3"]
184
+ ball_berry_slope_C4 = BESS_inputs_dict["ball_berry_slope_C4"]
185
+ ball_berry_intercept_C3 = BESS_inputs_dict["ball_berry_intercept_C3"]
186
+ KG_climate = BESS_inputs_dict["KG_climate"]
187
+ canopy_height_meters = BESS_inputs_dict["canopy_height_meters"]
188
+ canopy_temperature_C = BESS_inputs_dict["canopy_temperature_C"]
189
+ soil_temperature_C = BESS_inputs_dict["soil_temperature_C"]
190
+ SZA_deg = BESS_inputs_dict["SZA_deg"]
191
+
192
+ # Variables from GEOS5FP_inputs (merged via results.update())
193
+ Ta_C = BESS_inputs_dict["Ta_C"]
194
+ RH = BESS_inputs_dict["RH"]
195
+ COT = BESS_inputs_dict["COT"]
196
+ AOT = BESS_inputs_dict["AOT"]
197
+ vapor_gccm = BESS_inputs_dict["vapor_gccm"]
198
+ ozone_cm = BESS_inputs_dict["ozone_cm"]
199
+ PAR_albedo = BESS_inputs_dict["PAR_albedo"]
200
+ NIR_albedo = BESS_inputs_dict["NIR_albedo"]
201
+ Ca = BESS_inputs_dict["Ca"]
202
+ wind_speed_mps = BESS_inputs_dict["wind_speed_mps"]
203
+
204
+ # Add all BESS inputs to results
205
+ results.update(BESS_inputs_dict)
206
+
207
+ # Create a dictionary of variables to check
208
+ variables_to_check = {
209
+ "SWin_Wm2": SWin_Wm2,
210
+ "PAR_diffuse_Wm2": PAR_diffuse_Wm2,
211
+ "PAR_direct_Wm2": PAR_direct_Wm2,
212
+ "NIR_diffuse_Wm2": NIR_diffuse_Wm2,
213
+ "NIR_direct_Wm2": NIR_direct_Wm2,
214
+ "UV_Wm2": UV_Wm2,
215
+ "PAR_albedo": PAR_albedo,
216
+ "NIR_albedo": NIR_albedo
217
+ }
218
+
219
+ # Check for None values and size mismatches
220
+ reference_size = None
221
+
222
+ for name, var in variables_to_check.items():
223
+ if var is None:
224
+ logger.warning(f"Variable '{name}' is None.")
225
+ else:
226
+ # Get the size of the variable if it's a numpy array
227
+ size = var.shape if isinstance(var, np.ndarray) else None
228
+ if reference_size is None:
229
+ reference_size = size # Set the first non-None size as the reference
230
+ elif size != reference_size:
231
+ logger.warning(f"Variable '{name}' has a different size: {size} (expected: {reference_size}).")
232
+
233
+ # check if any of the FLiES outputs are not given
234
+ FLiES_variables = [SWin_Wm2, PAR_diffuse_Wm2, PAR_direct_Wm2, NIR_diffuse_Wm2, NIR_direct_Wm2, UV_Wm2, PAR_albedo, NIR_albedo]
235
+ FLiES_variables_missing = False
236
+
237
+ for variable in FLiES_variables:
238
+ if variable is None:
239
+ FLiES_variables_missing = True
240
+
241
+ if FLiES_variables_missing:
242
+ # run FLiES radiative transfer model
243
+ FLiES_results = FLiESANN(
244
+ time_UTC=time_UTC,
245
+ day_of_year=day_of_year,
246
+ hour_of_day=hour_of_day,
247
+ geometry=geometry,
248
+ albedo=albedo,
249
+ COT=COT,
250
+ AOT=AOT,
251
+ vapor_gccm=vapor_gccm,
252
+ ozone_cm=ozone_cm,
253
+ elevation_m=elevation_m,
254
+ SZA_deg=SZA_deg,
255
+ KG_climate=KG_climate,
256
+ GEOS5FP_connection=GEOS5FP_connection
257
+ )
258
+
259
+ # extract FLiES outputs
260
+ SWin_Wm2 = FLiES_results["SWin_Wm2"]
261
+ PAR_diffuse_Wm2 = FLiES_results["PAR_diffuse_Wm2"]
262
+ PAR_direct_Wm2 = FLiES_results["PAR_direct_Wm2"]
263
+ NIR_diffuse_Wm2 = FLiES_results["NIR_diffuse_Wm2"]
264
+ NIR_direct_Wm2 = FLiES_results["NIR_direct_Wm2"]
265
+ UV_Wm2 = FLiES_results["UV_Wm2"]
266
+ # albedo_visible = FLiES_results["VIS"]
267
+ # albedo_NIR = FLiES_results["NIR"]
268
+
269
+ check_distribution(PAR_direct_Wm2, "PAR_direct_Wm2")
270
+ else:
271
+ logger.info("using given FLiES output as BESS parameters")
272
+
273
+ # Add radiation inputs to results
274
+ results["SWin_Wm2"] = SWin_Wm2
275
+ results["PAR_diffuse_Wm2"] = PAR_diffuse_Wm2
276
+ results["PAR_direct_Wm2"] = PAR_direct_Wm2
277
+ results["NIR_diffuse_Wm2"] = NIR_diffuse_Wm2
278
+ results["NIR_direct_Wm2"] = NIR_direct_Wm2
279
+ results["UV_Wm2"] = UV_Wm2
280
+
281
+ # calculate saturation vapor pressure in Pascal from air temperature in Kelvin
282
+ Ta_K = Ta_C + 273.15
283
+ SVP_Pa = SVP_Pa_from_Ta_K(Ta_K)
284
+
285
+ # calculate actual vapor pressure in Pascal from relative humidity and saturation vapor pressure
286
+ Ea_Pa = RH * SVP_Pa
287
+
288
+ latitude = geometry.lat
289
+
290
+ meteorology_results = meteorology(
291
+ day_of_year=day_of_year,
292
+ hour_of_day=hour_of_day,
293
+ latitude=latitude,
294
+ elevation_m=elevation_m,
295
+ SZA=SZA_deg,
296
+ Ta_K=Ta_K,
297
+ Ea_Pa=Ea_Pa,
298
+ Rg_Wm2=SWin_Wm2,
299
+ wind_speed_mps=wind_speed_mps,
300
+ canopy_height_meters=canopy_height_meters
301
+ )
302
+
303
+ # Extract all variables from the dictionary returned by meteorology
304
+ Ps_Pa = meteorology_results["Ps_Pa"]
305
+ VPD_Pa = meteorology_results["VPD_Pa"]
306
+ RH = meteorology_results["RH"]
307
+ desTa = meteorology_results["desTa"]
308
+ ddesTa = meteorology_results["ddesTa"]
309
+ gamma = meteorology_results["gamma"]
310
+ Cp = meteorology_results["Cp"]
311
+ rhoa = meteorology_results["rhoa"]
312
+ epsa = meteorology_results["epsa"]
313
+ R = meteorology_results["R"]
314
+ Rc = meteorology_results["Rc"]
315
+ Rs = meteorology_results["Rs"]
316
+ SFd = meteorology_results["SFd"]
317
+ SFd2 = meteorology_results["SFd2"]
318
+ DL = meteorology_results["DL"]
319
+ Ra = meteorology_results["Ra"]
320
+ fStress = meteorology_results["fStress"]
321
+
322
+ # Check the distribution for each variable
323
+ for var_name, var_value in meteorology_results.items():
324
+ check_distribution(var_value, var_name)
325
+
326
+ # Add meteorology results to output
327
+ results.update(meteorology_results)
328
+ results["Ta_K"] = Ta_K
329
+ results["SVP_Pa"] = SVP_Pa
330
+ results["Ea_Pa"] = Ea_Pa
331
+
332
+ # convert NDVI to LAI
333
+ LAI = LAI_from_NDVI(NDVI)
334
+ LAI_minimum = LAI_from_NDVI(NDVI_minimum)
335
+ LAI_maximum = LAI_from_NDVI(NDVI_maximum)
336
+
337
+ VCmax_results = calculate_VCmax(
338
+ LAI=LAI,
339
+ LAI_minimum=LAI_minimum,
340
+ LAI_maximum=LAI_maximum,
341
+ peakVCmax_C3_μmolm2s1=peakVCmax_C3_μmolm2s1,
342
+ peakVCmax_C4_μmolm2s1=peakVCmax_C4_μmolm2s1,
343
+ SZA_deg=SZA_deg,
344
+ kn=kn
345
+ )
346
+
347
+ VCmax_C3_sunlit_μmolm2s1 = VCmax_results["VCmax_C3_sunlit_μmolm2s1"]
348
+ VCmax_C4_sunlit_μmolm2s1 = VCmax_results["VCmax_C4_sunlit_μmolm2s1"]
349
+ VCmax_C3_shaded_μmolm2s1 = VCmax_results["VCmax_C3_shaded_μmolm2s1"]
350
+ VCmax_C4_shaded_μmolm2s1 = VCmax_results["VCmax_C4_shaded_μmolm2s1"]
351
+
352
+ # Check the distribution for each variable
353
+ for var_name, var_value in VCmax_results.items():
354
+ check_distribution(var_value, var_name)
355
+
356
+ # Add LAI and VCmax results to output
357
+ results["LAI"] = LAI
358
+ results["LAI_minimum"] = LAI_minimum
359
+ results["LAI_maximum"] = LAI_maximum
360
+ results.update(VCmax_results)
361
+
362
+ canopy_shortwave_radiation_results = canopy_shortwave_radiation(
363
+ PAR_diffuse_Wm2=PAR_diffuse_Wm2, # diffuse photosynthetically active radiation in W/m^2
364
+ PAR_direct_Wm2=PAR_direct_Wm2, # direct photosynthetically active radiation in W/m^2
365
+ NIR_diffuse_Wm2=NIR_diffuse_Wm2, # diffuse near-infrared radiation in W/m^2
366
+ NIR_direct_Wm2=NIR_direct_Wm2, # direct near-infrared radiation in W/m^2
367
+ UV_Wm2=UV_Wm2, # incoming ultraviolet radiation in W/m^2
368
+ SZA_deg=SZA_deg, # solar zenith angle in degrees
369
+ LAI=LAI, # leaf area index
370
+ CI=CI, # clumping index
371
+ albedo_visible=PAR_albedo, # surface albedo in visible wavelengths
372
+ albedo_NIR=NIR_albedo # surface albedo in near-infrared wavelengths
373
+ )
374
+
375
+ # Check the distribution for each variable
376
+ for var_name, var_value in canopy_shortwave_radiation_results.items():
377
+ check_distribution(var_value, var_name)
378
+
379
+ # Extract values from the dictionary
380
+ sunlit_fraction = canopy_shortwave_radiation_results["fSun"]
381
+ APAR_sunlit_μmolm2s1 = canopy_shortwave_radiation_results["APAR_sunlit_μmolm2s1"]
382
+ APAR_shade_μmolm2s1 = canopy_shortwave_radiation_results["APAR_shade_μmolm2s1"]
383
+ ASW_sunlit_Wm2 = canopy_shortwave_radiation_results["ASW_sunlit_Wm2"]
384
+ ASW_shade_Wm2 = canopy_shortwave_radiation_results["ASW_shade_Wm2"]
385
+ ASW_soil_Wm2 = canopy_shortwave_radiation_results["ASW_soil_Wm2"]
386
+ G_Wm2 = canopy_shortwave_radiation_results["G_Wm2"]
387
+
388
+ # Add canopy shortwave radiation results to output
389
+ results.update(canopy_shortwave_radiation_results)
390
+
391
+ # convert canopy temperature from Celsius to Kelvin
392
+ canopy_temperature_K = canopy_temperature_C + 273.15
393
+
394
+ # convert soil temperature from Celsius to Kelvin
395
+ soil_temperature_K = soil_temperature_C + 273.15
396
+
397
+ GPP_C3, LE_C3, LE_soil_C3, LE_canopy_C3, Rn_C3, Rn_soil_C3, Rn_canopy_C3 = carbon_water_fluxes(
398
+ canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
399
+ soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
400
+ LAI=LAI, # leaf area index
401
+ Ta_K=Ta_K, # air temperature in Kelvin
402
+ APAR_sunlit_μmolm2s1=APAR_sunlit_μmolm2s1, # sunlit leaf absorptance of photosynthetically active radiation
403
+ APAR_shaded_μmolm2s1=APAR_shade_μmolm2s1, # shaded leaf absorptance of photosynthetically active radiation
404
+ ASW_sunlit_Wm2=ASW_sunlit_Wm2, # sunlit absorbed shortwave radiation
405
+ ASW_shaded_Wm2=ASW_shade_Wm2, # shaded absorbed shortwave radiation
406
+ ASW_soil_Wm2=ASW_soil_Wm2, # absorbed shortwave radiation of soil
407
+ Vcmax25_sunlit=VCmax_C3_sunlit_μmolm2s1, # sunlit maximum carboxylation rate at 25 degrees C
408
+ Vcmax25_shaded=VCmax_C3_shaded_μmolm2s1, # shaded maximum carboxylation rate at 25 degrees C
409
+ ball_berry_slope=ball_berry_slope_C3, # Ball-Berry slope for C3 photosynthesis
410
+ ball_berry_intercept=ball_berry_intercept_C3, # Ball-Berry intercept for C3 photosynthesis
411
+ sunlit_fraction=sunlit_fraction, # fraction of sunlit leaves
412
+ G_Wm2=G_Wm2, # soil heat flux
413
+ SZA_deg=SZA_deg, # solar zenith angle
414
+ Ca=Ca, # atmospheric CO2 concentration
415
+ Ps_Pa=Ps_Pa, # surface pressure in Pascal
416
+ gamma=gamma, # psychrometric constant
417
+ Cp=Cp, # specific heat of air in J/kg/K
418
+ rhoa=rhoa, # density of air in kg/m3
419
+ VPD_Pa=VPD_Pa, # vapor pressure deficit in Pascal
420
+ RH=RH, # relative humidity as a fraction
421
+ desTa=desTa,
422
+ ddesTa=ddesTa,
423
+ epsa=epsa,
424
+ Rc=Rc,
425
+ Rs=Rs,
426
+ carbon_uptake_efficiency=carbon_uptake_efficiency, # intrinsic quantum efficiency for carbon uptake
427
+ fStress=fStress,
428
+ C4_photosynthesis=False # C3 or C4 photosynthesis
429
+ )
430
+
431
+ # List of variable names and their corresponding values
432
+ carbon_water_fluxes_outputs = {
433
+ "GPP_C3": GPP_C3,
434
+ "LE_C3": LE_C3,
435
+ "LE_soil_C3": LE_soil_C3,
436
+ "LE_canopy_C3": LE_canopy_C3,
437
+ "Rn_C3": Rn_C3,
438
+ "Rn_soil_C3": Rn_soil_C3,
439
+ "Rn_canopy_C3": Rn_canopy_C3
440
+ }
441
+
442
+ # Check the distribution for each variable
443
+ for var_name, var_value in carbon_water_fluxes_outputs.items():
444
+ check_distribution(var_value, var_name)
445
+
446
+ # Add temperature conversions and C3 results to output
447
+ results["canopy_temperature_K"] = canopy_temperature_K
448
+ results["soil_temperature_K"] = soil_temperature_K
449
+ results.update(carbon_water_fluxes_outputs)
450
+
451
+ GPP_C4, LE_C4, LE_soil_C4, LE_canopy_C4, Rn_C4, Rn_soil_C4, Rn_canopy_C4 = carbon_water_fluxes(
452
+ canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
453
+ soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
454
+ LAI=LAI, # leaf area index
455
+ Ta_K=Ta_K, # air temperature in Kelvin
456
+ APAR_sunlit_μmolm2s1=APAR_sunlit_μmolm2s1, # sunlit leaf absorptance of photosynthetically active radiation
457
+ APAR_shaded_μmolm2s1=APAR_shade_μmolm2s1, # shaded leaf absorptance of photosynthetically active radiation
458
+ ASW_sunlit_Wm2=ASW_sunlit_Wm2, # sunlit absorbed shortwave radiation
459
+ ASW_shaded_Wm2=ASW_shade_Wm2, # shaded absorbed shortwave radiation
460
+ ASW_soil_Wm2=ASW_soil_Wm2, # absorbed shortwave radiation of soil
461
+ Vcmax25_sunlit=VCmax_C4_sunlit_μmolm2s1, # sunlit maximum carboxylation rate at 25 degrees C
462
+ Vcmax25_shaded=VCmax_C4_shaded_μmolm2s1, # shaded maximum carboxylation rate at 25 degrees C
463
+ ball_berry_slope=ball_berry_slope_C4, # Ball-Berry slope for C4 photosynthesis
464
+ ball_berry_intercept=ball_berry_intercept_C4, # Ball-Berry intercept for C4 photosynthesis
465
+ sunlit_fraction=sunlit_fraction, # fraction of sunlit leaves
466
+ G_Wm2=G_Wm2, # soil heat flux
467
+ SZA_deg=SZA_deg, # solar zenith angle
468
+ Ca=Ca, # atmospheric CO2 concentration
469
+ Ps_Pa=Ps_Pa, # surface pressure in Pascal
470
+ gamma=gamma, # psychrometric constant
471
+ Cp=Cp, # specific heat of air in J/kg/K
472
+ rhoa=rhoa, # density of air in kg/m3
473
+ VPD_Pa=VPD_Pa, # vapor pressure deficit in Pascal
474
+ RH=RH, # relative humidity as a fraction
475
+ desTa=desTa,
476
+ ddesTa=ddesTa,
477
+ epsa=epsa,
478
+ Rc=Rc,
479
+ Rs=Rs,
480
+ carbon_uptake_efficiency=carbon_uptake_efficiency, # intrinsic quantum efficiency for carbon uptake
481
+ fStress=fStress,
482
+ C4_photosynthesis=True # C3 or C4 photosynthesis
483
+ )
484
+
485
+ # List of variable names and their corresponding values
486
+ carbon_water_fluxes_C4_outputs = {
487
+ "GPP_C4": GPP_C4,
488
+ "LE_C4": LE_C4,
489
+ "LE_soil_C4": LE_soil_C4,
490
+ "LE_canopy_C4": LE_canopy_C4,
491
+ "Rn_C4": Rn_C4,
492
+ "Rn_soil_C4": Rn_soil_C4,
493
+ "Rn_canopy_C4": Rn_canopy_C4
494
+ }
495
+
496
+ # Check the distribution for each variable
497
+ for var_name, var_value in carbon_water_fluxes_C4_outputs.items():
498
+ check_distribution(var_value, var_name)
499
+
500
+ # Add C4 results to output
501
+ results.update(carbon_water_fluxes_C4_outputs)
502
+
503
+ # interpolate C3 and C4 GPP
504
+ ST_K = ST_C + 273.15
505
+ results["ST_K"] = ST_K
506
+ GPP = np.clip(interpolate_C3_C4(GPP_C3, GPP_C4, C4_fraction), 0, 50)
507
+ GPP = np.where(np.isnan(ST_K), np.nan, GPP)
508
+
509
+ if isinstance(geometry, RasterGeometry):
510
+ GPP = Raster(GPP, geometry=geometry)
511
+ GPP.cmap = GPP_COLORMAP
512
+
513
+ # upscale from instantaneous to daily
514
+
515
+ # upscale GPP to daily
516
+ GPP_daily = 1800 * GPP / SFd * 1e-6 * 12 # Eq. (3) in Ryu et al 2008
517
+ GPP_daily = np.where(SFd < 0.01, 0, GPP_daily)
518
+ GPP_daily = np.where(SZA_deg >= 90, 0, GPP_daily)
519
+
520
+ if isinstance(geometry, RasterGeometry):
521
+ GPP_daily = Raster(GPP_daily, geometry=geometry)
522
+ GPP_daily.cmap = GPP_COLORMAP
523
+
524
+ # interpolate C3 and C4 net radiation
525
+ Rn_Wm2 = np.clip(interpolate_C3_C4(Rn_C3, Rn_C4, C4_fraction), 0, None)
526
+
527
+ if isinstance(geometry, RasterGeometry):
528
+ Rn_Wm2 = Raster(Rn_Wm2, geometry=geometry)
529
+
530
+ # interpolate C3 and C4 soil net radiation
531
+ Rn_soil_Wm2 = np.clip(interpolate_C3_C4(Rn_soil_C3, Rn_soil_C4, C4_fraction), 0, Rn_Wm2)
532
+
533
+ if isinstance(geometry, RasterGeometry):
534
+ Rn_soil_Wm2 = Raster(Rn_soil_Wm2, geometry=geometry)
535
+
536
+ # interpolate C3 and C4 canopy net radiation
537
+ Rn_canopy_Wm2 = np.clip(interpolate_C3_C4(Rn_canopy_C3, Rn_canopy_C4, C4_fraction), 0, Rn_Wm2)
538
+
539
+ if isinstance(geometry, RasterGeometry):
540
+ Rn_canopy_Wm2 = Raster(Rn_canopy_Wm2, geometry=geometry)
541
+
542
+ # interpolate C3 and C4 latent heat flux
543
+ LE_Wm2 = np.clip(interpolate_C3_C4(LE_C3, LE_C4, C4_fraction), 0, Rn_Wm2)
544
+
545
+ if isinstance(geometry, RasterGeometry):
546
+ LE_Wm2 = Raster(LE_Wm2, geometry=geometry)
547
+ LE_Wm2.cmap = ET_COLORMAP
548
+
549
+ # interpolate C3 and C4 soil latent heat flux
550
+ LE_soil_Wm2 = np.clip(interpolate_C3_C4(LE_soil_C3, LE_soil_C4, C4_fraction), 0, LE_Wm2)
551
+
552
+ if isinstance(geometry, RasterGeometry):
553
+ LE_soil_Wm2 = Raster(LE_soil_Wm2, geometry=geometry)
554
+ LE_soil_Wm2.cmap = ET_COLORMAP
555
+
556
+ # interpolate C3 and C4 canopy latent heat flux
557
+ LE_canopy_Wm2 = np.clip(interpolate_C3_C4(LE_canopy_C3, LE_canopy_C4, C4_fraction), 0, LE_Wm2)
558
+
559
+ if isinstance(geometry, RasterGeometry):
560
+ LE_canopy_Wm2 = Raster(LE_canopy_Wm2, geometry=geometry)
561
+ LE_canopy_Wm2.cmap = ET_COLORMAP
562
+
563
+ # Add final interpolated outputs to results
564
+ results["GPP"] = GPP
565
+ results["GPP_daily"] = GPP_daily
566
+ results["Rn_Wm2"] = Rn_Wm2
567
+ results["Rn_soil_Wm2"] = Rn_soil_Wm2
568
+ results["Rn_canopy_Wm2"] = Rn_canopy_Wm2
569
+ results["LE_Wm2"] = LE_Wm2
570
+ results["LE_soil_Wm2"] = LE_soil_Wm2
571
+ results["LE_canopy_Wm2"] = LE_canopy_Wm2
572
+ # G_Wm2 already added via canopy_shortwave_radiation_results
573
+
574
+ if upscale_to_daylight and time_UTC is not None:
575
+ logger.info("started daylight ET upscaling")
576
+ t_et = TicToc()
577
+ t_et.tic()
578
+
579
+ # Use new upscaling function from daylight_evapotranspiration
580
+ daylight_results = daylight_ET_from_instantaneous_LE(
581
+ LE_instantaneous_Wm2=LE_Wm2,
582
+ Rn_instantaneous_Wm2=Rn_Wm2,
583
+ G_instantaneous_Wm2=G_Wm2,
584
+ day_of_year=day_of_year,
585
+ time_UTC=time_UTC,
586
+ geometry=geometry
587
+ )
588
+ # Add all returned daylight results to output
589
+ results.update(daylight_results)
590
+
591
+ elapsed_et = t_et.tocvalue()
592
+ logger.info(f"completed daylight ET upscaling (elapsed: {elapsed_et:.2f} seconds)")
593
+
594
+ return results
Binary file
Binary file
Binary file
Binary file