BESS-JPL 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of BESS-JPL might be problematic. Click here for more details.

BESS_JPL/BESS_JPL.py CHANGED
@@ -1,3 +1,23 @@
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
+ from solar_apparent_time import solar_day_of_year_for_area, solar_hour_of_day_for_area
13
+
14
+ from koppengeiger import load_koppen_geiger
15
+ from gedi_canopy_height import load_canopy_height
16
+ from FLiESANN import FLiESANN
17
+ from GEOS5FP import GEOS5FP
18
+ from MODISCI import MODISCI
19
+ from NASADEM import NASADEM
20
+
1
21
  from .constants import *
2
22
  from .C3_photosynthesis import *
3
23
  from .C4_photosynthesis import *
@@ -5,19 +25,496 @@ from .canopy_energy_balance import *
5
25
  from .canopy_longwave_radiation import *
6
26
  from .canopy_shortwave_radiation import *
7
27
  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
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 *
21
42
  from .meteorology import *
22
- from .BESS import BESS
23
43
  from .soil_energy_balance import *
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ def BESS_JPL(
48
+ ST_C: Union[Raster, np.ndarray], # surface temperature in Celsius
49
+ NDVI: Union[Raster, np.ndarray], # NDVI
50
+ albedo: Union[Raster, np.ndarray], # surface albedo
51
+ elevation_km: Union[Raster, np.ndarray] = None, # elevation in kilometers
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
+ Ta_C: Union[Raster, np.ndarray] = None, # air temperature in Celsius
58
+ RH: Union[Raster, np.ndarray] = None, # relative humidity as a proportion
59
+ NDVI_minimum: Union[Raster, np.ndarray] = None, # minimum NDVI
60
+ NDVI_maximum: Union[Raster, np.ndarray] = None, # maximum NDVI
61
+ Rg: Union[Raster, np.ndarray] = None, # incoming shortwave radiation in W/m^2
62
+ VISdiff: Union[Raster, np.ndarray] = None, # diffuse visible radiation in W/m^2
63
+ VISdir: Union[Raster, np.ndarray] = None, # direct visible radiation in W/m^2
64
+ NIRdiff: Union[Raster, np.ndarray] = None, # diffuse near-infrared radiation in W/m^2
65
+ NIRdir: Union[Raster, np.ndarray] = None, # direct near-infrared radiation in W/m^2
66
+ UV: Union[Raster, np.ndarray] = None, # incoming ultraviolet radiation in W/m^2
67
+ albedo_visible: Union[Raster, np.ndarray] = None, # surface albedo in visible wavelengths (initialized to surface albedo if left as None)
68
+ albedo_NIR: Union[Raster, np.ndarray] = None, # surface albedo in near-infrared wavelengths (initialized to surface albedo if left as None)
69
+ COT: Union[Raster, np.ndarray] = None, # cloud optical thickness
70
+ AOT: Union[Raster, np.ndarray] = None, # aerosol optical thickness
71
+ vapor_gccm: Union[Raster, np.ndarray] = None, # water vapor in g/ccm
72
+ ozone_cm: Union[Raster, np.ndarray] = None, # ozone in cm
73
+ KG_climate: Union[Raster, np.ndarray] = None, # KG climate
74
+ canopy_height_meters: Union[Raster, np.ndarray] = None, # canopy height in meters
75
+ Ca: Union[Raster, np.ndarray] = None, # atmospheric CO2 concentration in ppm
76
+ wind_speed_mps: Union[Raster, np.ndarray] = None, # wind speed in meters per second
77
+ SZA: Union[Raster, np.ndarray] = None, # solar zenith angle in degrees
78
+ canopy_temperature_C: Union[Raster, np.ndarray] = None, # canopy temperature in Celsius (initialized to surface temperature if left as None)
79
+ soil_temperature_C: Union[Raster, np.ndarray] = None, # soil temperature in Celsius (initialized to surface temperature if left as None)
80
+ C4_fraction: Union[Raster, np.ndarray] = None, # fraction of C4 plants
81
+ carbon_uptake_efficiency: Union[Raster, np.ndarray] = None, # intrinsic quantum efficiency for carbon uptake
82
+ kn: np.ndarray = None,
83
+ ball_berry_intercept_C3: np.ndarray = None, # Ball-Berry intercept for C3 plants
84
+ ball_berry_intercept_C4: Union[np.ndarray, float] = BALL_BERRY_INTERCEPT_C4, # Ball-Berry intercept for C4 plants
85
+ ball_berry_slope_C3: np.ndarray = None, # Ball-Berry slope for C3 plants
86
+ ball_berry_slope_C4: np.ndarray = None, # Ball-Berry slope for C4 plants
87
+ peakVCmax_C3: np.ndarray = None, # peak maximum carboxylation rate for C3 plants
88
+ peakVCmax_C4: np.ndarray = None, # peak maximum carboxylation rate for C4 plants
89
+ CI: Union[Raster, np.ndarray] = None,
90
+ resampling: str = RESAMPLING): # clumping index
91
+ if geometry is None and isinstance(ST_C, Raster):
92
+ geometry = ST_C.geometry
93
+
94
+ if GEOS5FP_connection is None:
95
+ GEOS5FP_connection = GEOS5FP()
96
+
97
+ if (day_of_year is None or hour_of_day is None) and time_UTC is not None and geometry is not None:
98
+ day_of_year = solar_day_of_year_for_area(time_UTC=time_UTC, geometry=geometry)
99
+ hour_of_day = solar_hour_of_day_for_area(time_UTC=time_UTC, geometry=geometry)
100
+
101
+ if time_UTC is None and day_of_year is None and hour_of_day is None:
102
+ raise ValueError("no time given between time_UTC, day_of_year, and hour_of_day")
103
+
104
+ if elevation_km is None and geometry is not None:
105
+ elevation_km = NASADEM.elevation_km(geometry=geometry)
106
+
107
+ # load air temperature in Celsius if not provided
108
+ if Ta_C is None:
109
+ Ta_C = GEOS5FP_connection.Ta_C(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
110
+
111
+ # load relative humidity if not provided
112
+ if RH is None:
113
+ RH = GEOS5FP_connection.RH(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
114
+
115
+ # load minimum NDVI if not provided
116
+ if NDVI_minimum is None and geometry is not None:
117
+ NDVI_minimum = load_NDVI_minimum(geometry=geometry, resampling=resampling)
118
+
119
+ # load maximum NDVI if not provided
120
+ if NDVI_maximum is None and geometry is not None:
121
+ NDVI_maximum = load_NDVI_maximum(geometry=geometry, resampling=resampling)
122
+
123
+ # load C4 fraction if not provided
124
+ if C4_fraction is None:
125
+ C4_fraction = load_C4_fraction(geometry=geometry, resampling=resampling)
126
+
127
+ # load carbon uptake efficiency if not provided
128
+ if carbon_uptake_efficiency is None:
129
+ carbon_uptake_efficiency = load_carbon_uptake_efficiency(geometry=geometry, resampling=resampling)
130
+
131
+ # load kn if not provided
132
+ if kn is None:
133
+ kn = load_kn(geometry=geometry, resampling=resampling)
134
+
135
+ # load peak VC max for C3 plants if not provided
136
+ if peakVCmax_C3 is None:
137
+ peakVCmax_C3 = load_peakVCmax_C3(geometry=geometry, resampling=resampling)
138
+
139
+ # load peak VC max for C4 plants if not provided
140
+ if peakVCmax_C4 is None:
141
+ peakVCmax_C4 = load_peakVCmax_C4(geometry=geometry, resampling=resampling)
142
+
143
+ # load Ball-Berry slope for C3 plants if not provided
144
+ if ball_berry_slope_C3 is None:
145
+ ball_berry_slope_C3 = load_ball_berry_slope_C3(geometry=geometry, resampling=resampling)
146
+
147
+ # load Ball-Berry slope for C4 plants if not provided
148
+ if ball_berry_slope_C4 is None:
149
+ ball_berry_slope_C4 = load_ball_berry_slope_C4(geometry=geometry, resampling=resampling)
150
+
151
+ # load Ball-Berry intercept for C3 plants if not provided
152
+ if ball_berry_intercept_C3 is None:
153
+ ball_berry_intercept_C3 = load_ball_berry_intercept_C3(geometry=geometry, resampling=resampling)
154
+
155
+ # Create a dictionary of variables to check
156
+ variables_to_check = {
157
+ "Rg": Rg,
158
+ "VISdiff": VISdiff,
159
+ "VISdir": VISdir,
160
+ "NIRdiff": NIRdiff,
161
+ "NIRdir": NIRdir,
162
+ "UV": UV,
163
+ "albedo_visible": albedo_visible,
164
+ "albedo_NIR": albedo_NIR
165
+ }
166
+
167
+ # Check for None values and size mismatches
168
+ reference_size = None
169
+ for name, var in variables_to_check.items():
170
+ if var is None:
171
+ logger.warning(f"Variable '{name}' is None.")
172
+ else:
173
+ # Get the size of the variable if it's a numpy array
174
+ size = var.shape if isinstance(var, np.ndarray) else None
175
+ if reference_size is None:
176
+ reference_size = size # Set the first non-None size as the reference
177
+ elif size != reference_size:
178
+ logger.warning(f"Variable '{name}' has a different size: {size} (expected: {reference_size}).")
179
+
180
+ # check if any of the FLiES outputs are not given
181
+ if None in (Rg, VISdiff, VISdir, NIRdiff, NIRdir, UV, albedo_visible, albedo_NIR):
182
+ # load cloud optical thickness if not provided
183
+ if COT is None:
184
+ COT = GEOS5FP_connection.COT(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
185
+
186
+ # load aerosol optical thickness if not provided
187
+ if AOT is None:
188
+ AOT = GEOS5FP_connection.AOT(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
189
+
190
+ ## FIXME fix FLiES interface
191
+
192
+ # run FLiES radiative transfer model
193
+ FLiES_results = FLiESANN(
194
+ time_UTC=time_UTC,
195
+ day_of_year=day_of_year,
196
+ hour_of_day=hour_of_day,
197
+ geometry=geometry,
198
+ albedo=albedo,
199
+ COT=COT,
200
+ AOT=AOT,
201
+ vapor_gccm=vapor_gccm,
202
+ ozone_cm=ozone_cm,
203
+ elevation_km=elevation_km,
204
+ SZA=SZA,
205
+ KG_climate=KG_climate,
206
+ GEOS5FP_connection=GEOS5FP_connection
207
+ )
208
+
209
+ # extract FLiES outputs
210
+ Rg = FLiES_results["Rg"]
211
+ VISdiff = FLiES_results["VISdiff"]
212
+ VISdir = FLiES_results["VISdir"]
213
+ NIRdiff = FLiES_results["NIRdiff"]
214
+ NIRdir = FLiES_results["NIRdir"]
215
+ UV = FLiES_results["UV"]
216
+ # albedo_visible = FLiES_results["VIS"]
217
+ # albedo_NIR = FLiES_results["NIR"]
218
+ albedo_NWP = GEOS5FP_connection.ALBEDO(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
219
+ RVIS_NWP = GEOS5FP_connection.ALBVISDR(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
220
+ albedo_visible = rt.clip(albedo * (RVIS_NWP / albedo_NWP), 0, 1)
221
+ check_distribution(albedo_visible, "RVIS")
222
+ RNIR_NWP = GEOS5FP_connection.ALBNIRDR(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
223
+ albedo_NIR = rt.clip(albedo * (RNIR_NWP / albedo_NWP), 0, 1)
224
+ check_distribution(albedo_NIR, "RNIR")
225
+ PARDir = VISdir
226
+ check_distribution(PARDir, "PARDir")
227
+ else:
228
+ logger.info("using given FLiES output as BESS parameters")
229
+
230
+ # load koppen geiger climate classification if not provided
231
+ if KG_climate is None:
232
+ KG_climate = load_koppen_geiger(geometry=geometry)
233
+
234
+ # load canopy height in meters if not provided
235
+ if canopy_height_meters is None:
236
+ canopy_height_meters = load_canopy_height(geometry=geometry, resampling=resampling)
237
+
238
+ # load CO2 concentration in ppm if not provided
239
+ if Ca is None:
240
+ Ca = GEOS5FP_connection.CO2SC(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
241
+
242
+ # load wind speed in meters per second if not provided
243
+ if wind_speed_mps is None:
244
+ wind_speed_mps = GEOS5FP_connection.wind_speed(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
245
+
246
+ # canopy temperature defaults to surface temperature
247
+ if canopy_temperature_C is None:
248
+ canopy_temperature_C = ST_C
249
+
250
+ # soil temperature defaults to surface temperature
251
+ if soil_temperature_C is None:
252
+ soil_temperature_C = ST_C
253
+
254
+ # visible albedo defaults to surface albedo
255
+ if albedo_visible is None:
256
+ albedo_visible = albedo
257
+
258
+ # near-infrared albedo defaults to surface albedo
259
+ if albedo_NIR is None:
260
+ albedo_NIR = albedo
261
+
262
+ # calculate solar zenith angle if not provided
263
+ if SZA is None:
264
+ SZA = calculate_SZA_from_DOY_and_hour(geometry.lat, geometry.lon, day_of_year, hour_of_day)
265
+
266
+ if CI is None and geometry is not None:
267
+ modisci = MODISCI()
268
+ CI = modisci.CI(geometry=geometry, resampling=resampling)
269
+
270
+ # canopy height defaults to zero
271
+ canopy_height_meters = np.where(np.isnan(canopy_height_meters), 0, canopy_height_meters)
272
+
273
+ # calculate saturation vapor pressure in Pascal from air temperature in Kelvin
274
+ Ta_K = Ta_C + 273.15
275
+ SVP_Pa = SVP_Pa_from_Ta_K(Ta_K)
276
+
277
+ # calculate actual vapor pressure in Pascal from relative humidity and saturation vapor pressure
278
+ Ea_Pa = RH * SVP_Pa
279
+
280
+ # convert elevation to meters
281
+ elevation_m = elevation_km * 1000
282
+
283
+ latitude = geometry.lat
284
+
285
+ Ps_Pa, VPD_Pa, RH, desTa, ddesTa, gamma, Cp, rhoa, epsa, R, Rc, Rs, SFd, SFd2, DL, Ra, fStress = meteorology(
286
+ day_of_year=day_of_year,
287
+ hour_of_day=hour_of_day,
288
+ latitude=latitude,
289
+ elevation_m=elevation_m,
290
+ SZA=SZA,
291
+ Ta_K=Ta_K,
292
+ Ea_Pa=Ea_Pa,
293
+ Rg=Rg,
294
+ wind_speed_mps=wind_speed_mps,
295
+ canopy_height_meters=canopy_height_meters
296
+ )
297
+
298
+ meteorology_outputs = {
299
+ "Ps_Pa": Ps_Pa,
300
+ "VPD_Pa": VPD_Pa,
301
+ "RH": RH,
302
+ "desTa": desTa,
303
+ "ddesTa": ddesTa,
304
+ "gamma": gamma,
305
+ "Cp": Cp,
306
+ "rhoa": rhoa,
307
+ "epsa": epsa,
308
+ "R": R,
309
+ "Rc": Rc,
310
+ "Rs": Rs,
311
+ "SFd": SFd,
312
+ "SFd2": SFd2,
313
+ "DL": DL,
314
+ "Ra": Ra,
315
+ "fStress": fStress
316
+ }
317
+
318
+ # Check the distribution for each variable
319
+ for var_name, var_value in meteorology_outputs.items():
320
+ check_distribution(var_value, var_name, time_UTC)
321
+
322
+ # convert NDVI to LAI
323
+ LAI = LAI_from_NDVI(NDVI)
324
+ LAI_minimum = LAI_from_NDVI(NDVI_minimum)
325
+ LAI_maximum = LAI_from_NDVI(NDVI_maximum)
326
+
327
+ VCmax_C3_sunlit, VCmax_C4_sunlit, VCmax_C3_shaded, VCmax_C4_shaded = calculate_VCmax(
328
+ LAI=LAI,
329
+ LAI_minimum=LAI_minimum,
330
+ LAI_maximum=LAI_maximum,
331
+ peakVCmax_C3=peakVCmax_C3,
332
+ peakVCmax_C4=peakVCmax_C4,
333
+ SZA=SZA,
334
+ kn=kn
335
+ )
336
+
337
+ # List of variable names and their corresponding values
338
+ VCmax_outputs = {
339
+ "VCmax_C3_sunlit": VCmax_C3_sunlit,
340
+ "VCmax_C4_sunlit": VCmax_C4_sunlit,
341
+ "VCmax_C3_shaded": VCmax_C3_shaded,
342
+ "VCmax_C4_shaded": VCmax_C4_shaded
343
+ }
344
+
345
+ # Check the distribution for each variable
346
+ for var_name, var_value in VCmax_outputs.items():
347
+ check_distribution(var_value, var_name, time_UTC)
348
+
349
+ sunlit_fraction, APAR_sunlit, APAR_shaded, ASW_sunlit, ASW_shaded, ASW_soil, G = canopy_shortwave_radiation(
350
+ PARDiff=VISdiff, # diffuse photosynthetically active radiation in W/m^2
351
+ PARDir=VISdir, # direct photosynthetically active radiation in W/m^2
352
+ NIRDiff=NIRdiff, # diffuse near-infrared radiation in W/m^2
353
+ NIRDir=NIRdir, # direct near-infrared radiation in W/m^2
354
+ UV=UV, # incoming ultraviolet radiation in W/m^2
355
+ SZA=SZA, # solar zenith angle in degrees
356
+ LAI=LAI, # leaf area index
357
+ CI=CI, # clumping index
358
+ albedo_visible=albedo_visible, # surface albedo in visible wavelengths
359
+ albedo_NIR=albedo_NIR # surface albedo in near-infrared wavelengths
360
+ )
361
+
362
+ # List of variable names and their corresponding values
363
+ canopy_radiation_outputs = {
364
+ "sunlit_fraction": sunlit_fraction,
365
+ "APAR_sunlit": APAR_sunlit,
366
+ "APAR_shaded": APAR_shaded,
367
+ "ASW_sunlit": ASW_sunlit,
368
+ "ASW_shaded": ASW_shaded,
369
+ "ASW_soil": ASW_soil,
370
+ "G": G
371
+ }
372
+
373
+ # Check the distribution for each variable
374
+ for var_name, var_value in canopy_radiation_outputs.items():
375
+ check_distribution(var_value, var_name, time_UTC)
376
+
377
+ canopy_temperature_K = canopy_temperature_C + 273.15
378
+ soil_temperature_K = soil_temperature_C + 273.15
379
+
380
+ GPP_C3, LE_C3, LE_soil_C3, LE_canopy_C3, Rn_C3, Rn_soil_C3, Rn_canopy_C3 = 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_C3_sunlit, # sunlit maximum carboxylation rate at 25 degrees C
391
+ Vcmax25_shaded=VCmax_C3_shaded, # shaded maximum carboxylation rate at 25 degrees C
392
+ ball_berry_slope=ball_berry_slope_C3, # Ball-Berry slope for C3 photosynthesis
393
+ ball_berry_intercept=ball_berry_intercept_C3, # Ball-Berry intercept for C3 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=False # C3 or C4 photosynthesis
412
+ )
413
+
414
+ # List of variable names and their corresponding values
415
+ carbon_water_fluxes_outputs = {
416
+ "GPP_C3": GPP_C3,
417
+ "LE_C3": LE_C3,
418
+ "LE_soil_C3": LE_soil_C3,
419
+ "LE_canopy_C3": LE_canopy_C3,
420
+ "Rn_C3": Rn_C3,
421
+ "Rn_soil_C3": Rn_soil_C3,
422
+ "Rn_canopy_C3": Rn_canopy_C3
423
+ }
424
+
425
+ # Check the distribution for each variable
426
+ for var_name, var_value in carbon_water_fluxes_outputs.items():
427
+ check_distribution(var_value, var_name, time_UTC)
428
+
429
+ GPP_C4, LE_C4, LE_soil_C4, LE_canopy_C4, Rn_C4, Rn_soil_C4, Rn_canopy_C4 = carbon_water_fluxes(
430
+ canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
431
+ soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
432
+ LAI=LAI, # leaf area index
433
+ Ta_K=Ta_K, # air temperature in Kelvin
434
+ APAR_sunlit=APAR_sunlit, # sunlit leaf absorptance of photosynthetically active radiation
435
+ APAR_shaded=APAR_shaded, # shaded leaf absorptance of photosynthetically active radiation
436
+ ASW_sunlit=ASW_sunlit, # sunlit absorbed shortwave radiation
437
+ ASW_shaded=ASW_shaded, # shaded absorbed shortwave radiation
438
+ ASW_soil=ASW_soil, # absorbed shortwave radiation of soil
439
+ Vcmax25_sunlit=VCmax_C4_sunlit, # sunlit maximum carboxylation rate at 25 degrees C
440
+ Vcmax25_shaded=VCmax_C4_shaded, # shaded maximum carboxylation rate at 25 degrees C
441
+ ball_berry_slope=ball_berry_slope_C4, # Ball-Berry slope for C4 photosynthesis
442
+ ball_berry_intercept=ball_berry_intercept_C4, # Ball-Berry intercept for C4 photosynthesis
443
+ sunlit_fraction=sunlit_fraction, # fraction of sunlit leaves
444
+ G=G, # soil heat flux
445
+ SZA=SZA, # solar zenith angle
446
+ Ca=Ca, # atmospheric CO2 concentration
447
+ Ps_Pa=Ps_Pa, # surface pressure in Pascal
448
+ gamma=gamma, # psychrometric constant
449
+ Cp=Cp, # specific heat of air in J/kg/K
450
+ rhoa=rhoa, # density of air in kg/m3
451
+ VPD_Pa=VPD_Pa, # vapor pressure deficit in Pascal
452
+ RH=RH, # relative humidity as a fraction
453
+ desTa=desTa,
454
+ ddesTa=ddesTa,
455
+ epsa=epsa,
456
+ Rc=Rc,
457
+ Rs=Rs,
458
+ carbon_uptake_efficiency=carbon_uptake_efficiency, # intrinsic quantum efficiency for carbon uptake
459
+ fStress=fStress,
460
+ C4_photosynthesis=True # C3 or C4 photosynthesis
461
+ )
462
+
463
+ # List of variable names and their corresponding values
464
+ carbon_water_fluxes_C4_outputs = {
465
+ "GPP_C4": GPP_C4,
466
+ "LE_C4": LE_C4,
467
+ "LE_soil_C4": LE_soil_C4,
468
+ "LE_canopy_C4": LE_canopy_C4,
469
+ "Rn_C4": Rn_C4,
470
+ "Rn_soil_C4": Rn_soil_C4,
471
+ "Rn_canopy_C4": Rn_canopy_C4
472
+ }
473
+
474
+ # Check the distribution for each variable
475
+ for var_name, var_value in carbon_water_fluxes_C4_outputs.items():
476
+ check_distribution(var_value, var_name, time_UTC)
477
+
478
+ # interpolate C3 and C4 GPP
479
+ ST_K = ST_C + 273.15
480
+ GPP = np.clip(interpolate_C3_C4(GPP_C3, GPP_C4, C4_fraction), 0, 50)
481
+ GPP = np.where(np.isnan(ST_K), np.nan, GPP)
482
+
483
+ if isinstance(geometry, RasterGeometry):
484
+ GPP = Raster(GPP, geometry=geometry)
485
+
486
+ # upscale from instantaneous to daily
487
+
488
+ # upscale GPP to daily
489
+ GPP_daily = 1800 * GPP / SFd * 1e-6 * 12 # Eq. (3) in Ryu et al 2008
490
+ GPP_daily = np.where(SFd < 0.01, 0, GPP_daily)
491
+ GPP_daily = np.where(SZA >= 90, 0, GPP_daily)
492
+
493
+ # interpolate C3 and C4 net radiation
494
+ Rn = np.clip(interpolate_C3_C4(Rn_C3, Rn_C4, C4_fraction), 0, 1000)
495
+
496
+ # interpolate C3 and C4 soil net radiation
497
+ Rn_soil = np.clip(interpolate_C3_C4(Rn_soil_C3, Rn_soil_C4, C4_fraction), 0, 1000)
498
+
499
+ # interpolate C3 and C4 canopy net radiation
500
+ Rn_canopy = np.clip(interpolate_C3_C4(Rn_canopy_C3, Rn_canopy_C4, C4_fraction), 0, 1000)
501
+
502
+ # interpolate C3 and C4 latent heat flux
503
+ LE = np.clip(interpolate_C3_C4(LE_C3, LE_C4, C4_fraction), 0, 1000)
504
+
505
+ # interpolate C3 and C4 soil latent heat flux
506
+ LE_soil = np.clip(interpolate_C3_C4(LE_soil_C3, LE_soil_C4, C4_fraction), 0, 1000)
507
+
508
+ # interpolate C3 and C4 canopy latent heat flux
509
+ LE_canopy = np.clip(interpolate_C3_C4(LE_canopy_C3, LE_canopy_C4, C4_fraction), 0, 1000)
510
+
511
+ return {
512
+ "GPP": GPP,
513
+ "GPP_daily": GPP_daily,
514
+ "Rn": Rn,
515
+ "Rn_soil": Rn_soil,
516
+ "Rn_canopy": Rn_canopy,
517
+ "LE": LE,
518
+ "LE_soil": LE_soil,
519
+ "LE_canopy": LE_canopy
520
+ }
BESS_JPL/version.txt CHANGED
@@ -1 +1 @@
1
- 1.6.0
1
+ 1.7.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BESS-JPL
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Breathing Earth System Simulator (BESS) Gross Primary Production (GPP) and Evapotranspiration (ET) Model Python
5
5
  Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>
6
6
  Project-URL: Homepage, https://github.com/JPL-Evapotranspiration-Algorithms/BESS-JPL
@@ -10,13 +10,15 @@ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: check-distribution
13
- Requires-Dist: FLiESANN>=1.4.1
13
+ Requires-Dist: FLiESANN>=1.4.2
14
14
  Requires-Dist: gedi-canopy-height>=1.1.0
15
15
  Requires-Dist: GEOS5FP>=1.1.1
16
16
  Requires-Dist: koppengeiger>=1.0.4
17
17
  Requires-Dist: MODISCI>=1.3.0
18
+ Requires-Dist: NASADEM
18
19
  Requires-Dist: numpy
19
20
  Requires-Dist: rasters
21
+ Requires-Dist: solar-apparent-time>=1.3.2
20
22
  Provides-Extra: dev
21
23
  Requires-Dist: build; extra == "dev"
22
24
  Requires-Dist: pytest>=6.0; extra == "dev"
@@ -1,5 +1,4 @@
1
- BESS_JPL/BESS.py,sha256=gyX7Hn3bq7plpbhM7X_noRtrtEKBVhwfEnaAuCVeowQ,20289
2
- BESS_JPL/BESS_JPL.py,sha256=VBIgeaqXngpRUjXI0ZYjkkPKMM_VmLsYYPAZPyq4yR4,1004
1
+ BESS_JPL/BESS_JPL.py,sha256=ziycbcQ65bLB9s5CUP2J1CIAC-uvrrWCzdPJaC-8qZs,22295
3
2
  BESS_JPL/C3_photosynthesis.py,sha256=-ormDycaWttCOoYJdOoOV_zlM6n0nxkgXxj7fyH_mIs,2760
4
3
  BESS_JPL/C4_fraction.jpeg,sha256=ECaEYWA8MnNd5z0Yq3beeUCR93KtTDxM8MrH-YYon3Y,2746
5
4
  BESS_JPL/C4_fraction.tif,sha256=DFS-J08JCjnf0hi_T_dFridAegVTiU3FGyMBjS9yc1w,25446
@@ -44,13 +43,13 @@ BESS_JPL/peakVCmax_C3.tif,sha256=ax6wCOPc_ovgJJl9YRjPWNY13OCGlzs2djXqwob_U7A,158
44
43
  BESS_JPL/peakVCmax_C4.jpeg,sha256=s7dhpcD573KW9Se4mejDeSzbSHqPtQY2EL6KJKt7ZIo,961497
45
44
  BESS_JPL/peakVCmax_C4.tif,sha256=EST4_yy-HHYmazIv--RePL_bhLejMWql6hoV9EE38ok,1556928
46
45
  BESS_JPL/soil_energy_balance.py,sha256=5TKDVkKYJ8jhjuiwbRva_03fi_0gTM0IAKbSm4WhWnY,944
47
- BESS_JPL/version.txt,sha256=ViNosgpkvpVlG7gka3Qg9_zlVTjf-Zmu-qeGYqFNDTk,5
46
+ BESS_JPL/version.txt,sha256=LBlAk30Td33A8sWokJIf8T6YuRqenUQ6QmUdjQ2LRI0,5
48
47
  BESS_JPL/SZA/__init__.py,sha256=crvPY6xMapE42hEd78jJWvW0NRfyzairRy75goNd6co,18
49
48
  BESS_JPL/SZA/daylight_hours.py,sha256=QkYUvq6-6JwNcErtg_WKQB93MAbwFokL9gSzrTwJVC4,2480
50
49
  BESS_JPL/vegetation_conversion/__init__.py,sha256=8gnz0yK_euCV0TtVGDrSGaVfx4g0EJCG2J68ppuA5oc,37
51
50
  BESS_JPL/vegetation_conversion/vegetation_conversion.py,sha256=PYus-c7zA-POa8gRDm7WzwLimWS-wKk1Mr6jkh590EU,2411
52
- bess_jpl-1.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
53
- bess_jpl-1.6.0.dist-info/METADATA,sha256=q31kyo8Gj-WvCe-gzpvOPypaGf04Fewcm5VxcyzMeBM,6274
54
- bess_jpl-1.6.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
55
- bess_jpl-1.6.0.dist-info/top_level.txt,sha256=GaKnzt-BBktYn1o-w4Qzh_jHxse4Y3ACOxZQrB2ufhc,9
56
- bess_jpl-1.6.0.dist-info/RECORD,,
51
+ bess_jpl-1.7.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
52
+ bess_jpl-1.7.0.dist-info/METADATA,sha256=Q5NcGaYCyZ9KgAbzdEztGh9fpIXO_sl8ZnBoM3CmeBw,6339
53
+ bess_jpl-1.7.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
54
+ bess_jpl-1.7.0.dist-info/top_level.txt,sha256=GaKnzt-BBktYn1o-w4Qzh_jHxse4Y3ACOxZQrB2ufhc,9
55
+ bess_jpl-1.7.0.dist-info/RECORD,,
BESS_JPL/BESS.py DELETED
@@ -1,468 +0,0 @@
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
- }