BESS-JPL 1.9.1__py3-none-any.whl → 1.10.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
@@ -41,481 +41,4 @@ from .load_ball_berry_slope_C4 import *
41
41
  from .calculate_VCmax import *
42
42
  from .meteorology import *
43
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
- geometry: RasterGeometry = None,
52
- time_UTC: datetime = None,
53
- hour_of_day: np.ndarray = None,
54
- day_of_year: np.ndarray = None,
55
- GEOS5FP_connection: GEOS5FP = None,
56
- elevation_km: Union[Raster, np.ndarray] = None, # elevation in kilometers
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
- "G": G,
518
- "LE": LE,
519
- "LE_soil": LE_soil,
520
- "LE_canopy": LE_canopy
521
- }
44
+ from .model import *
BESS_JPL/model.py ADDED
@@ -0,0 +1,520 @@
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
+
21
+ from .constants 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
+
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
+ geometry: RasterGeometry = None,
52
+ time_UTC: datetime = None,
53
+ hour_of_day: np.ndarray = None,
54
+ day_of_year: np.ndarray = None,
55
+ GEOS5FP_connection: GEOS5FP = None,
56
+ elevation_km: Union[Raster, np.ndarray] = None, # elevation in kilometers
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
+ }
@@ -0,0 +1,55 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ import rasters as rt
5
+ from dateutil import parser
6
+ from pandas import DataFrame
7
+
8
+ from .model import BESS_JPL
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ def process_BESS_table(input_df: DataFrame) -> DataFrame:
13
+ ST_C = np.array(input_df.ST_C).astype(np.float64)
14
+ NDVI = np.array(input_df.NDVI).astype(np.float64)
15
+
16
+ NDVI = np.where(NDVI > 0.06, NDVI, np.nan).astype(np.float64)
17
+
18
+ albedo = np.array(input_df.albedo).astype(np.float64)
19
+
20
+ if "Ta_C" in input_df:
21
+ Ta_C = np.array(input_df.Ta_C).astype(np.float64)
22
+ elif "Ta" in input_df:
23
+ Ta_C = np.array(input_df.Ta).astype(np.float64)
24
+
25
+ RH = np.array(input_df.RH).astype(np.float64)
26
+ # Rn = np.array(input_df.Rn).astype(np.float64)
27
+ # Topt = np.array(input_df.Topt).astype(np.float64)
28
+ # fAPARmax = np.array(input_df.fAPARmax).astype(np.float64)
29
+
30
+ # fAPARmax = np.where(fAPARmax == 0, np.nan, fAPARmax).astype(np.float64)
31
+
32
+ # if "G" in input_df:
33
+ # G = np.array(input_df.G).astype(np.float64)
34
+ # else:
35
+ # G = calculate_SEBAL_soil_heat_flux(
36
+ # Rn=Rn,
37
+ # ST_C=ST_C,
38
+ # NDVI=NDVI,
39
+ # albedo=albedo
40
+ # ).astype(np.float64)
41
+
42
+ results = BESS_JPL(
43
+ ST_C=ST_C,
44
+ albedo=albedo,
45
+ NDVI=NDVI,
46
+ Ta_C=Ta_C,
47
+ RH=RH
48
+ )
49
+
50
+ output_df = input_df.copy()
51
+
52
+ for key, value in results.items():
53
+ output_df[key] = value
54
+
55
+ return output_df
BESS_JPL/version.txt CHANGED
@@ -1 +1 @@
1
- 1.9.1
1
+ 1.10.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BESS-JPL
3
- Version: 1.9.1
3
+ Version: 1.10.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
@@ -1,4 +1,4 @@
1
- BESS_JPL/BESS_JPL.py,sha256=jF3Gb7IexnXlaj-Y0Obi0_ftGpnKv1IcitYxyvoMDBo,22311
1
+ BESS_JPL/BESS_JPL.py,sha256=CTby93_HwhONe2NOlt-8DuqDH7jL9mAc3euuzbouANg,1365
2
2
  BESS_JPL/C3_photosynthesis.py,sha256=-ormDycaWttCOoYJdOoOV_zlM6n0nxkgXxj7fyH_mIs,2760
3
3
  BESS_JPL/C4_fraction.jpeg,sha256=ECaEYWA8MnNd5z0Yq3beeUCR93KtTDxM8MrH-YYon3Y,2746
4
4
  BESS_JPL/C4_fraction.tif,sha256=DFS-J08JCjnf0hi_T_dFridAegVTiU3FGyMBjS9yc1w,25446
@@ -38,14 +38,16 @@ BESS_JPL/load_kn.py,sha256=_IE7w_VfahubfqjvgE6F6HCO0SizcpMu104_7ZXOiaw,345
38
38
  BESS_JPL/load_peakVCmax_C3.py,sha256=ISaCyaN81F3CyYbYwUqvUfQ_vjn4_jCpt1_x_b4fY5o,365
39
39
  BESS_JPL/load_peakVCmax_C4.py,sha256=8vtjEiJKCqSK04DfTs8Zgn-rWwGucmeeLMzblLT1U-Y,365
40
40
  BESS_JPL/meteorology.py,sha256=xIPQnIk9wGJPy8esJyxn35d6c_7UyiNuWTJXDnuSwf8,6995
41
+ BESS_JPL/model.py,sha256=sJ_CkZ98h-5HDpAGUJnyTpUODDlTTgc_8s5Ib8Kz-90,22295
41
42
  BESS_JPL/peakVCmax_C3.jpeg,sha256=nVvwLx8JyRhtm5lWRW93HLz0mInshEdOCJ1tAdcFqa8,1006133
42
43
  BESS_JPL/peakVCmax_C3.tif,sha256=ax6wCOPc_ovgJJl9YRjPWNY13OCGlzs2djXqwob_U7A,1583598
43
44
  BESS_JPL/peakVCmax_C4.jpeg,sha256=s7dhpcD573KW9Se4mejDeSzbSHqPtQY2EL6KJKt7ZIo,961497
44
45
  BESS_JPL/peakVCmax_C4.tif,sha256=EST4_yy-HHYmazIv--RePL_bhLejMWql6hoV9EE38ok,1556928
46
+ BESS_JPL/process_BESS_table.py,sha256=TYsDEZNELGdUc0xkB1MRQIDkVonNL5d49q7Zl1iaJvo,1472
45
47
  BESS_JPL/soil_energy_balance.py,sha256=5TKDVkKYJ8jhjuiwbRva_03fi_0gTM0IAKbSm4WhWnY,944
46
- BESS_JPL/version.txt,sha256=thUM_WBxjdCBZZHPmmx_PbRGHQXBJc6xTXXQlaqtiCo,5
47
- bess_jpl-1.9.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
48
- bess_jpl-1.9.1.dist-info/METADATA,sha256=l_zL0KfrgtnKhv8m4dZi_QUBcv9-Qb1kw0ARPfAtifQ,6339
49
- bess_jpl-1.9.1.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
50
- bess_jpl-1.9.1.dist-info/top_level.txt,sha256=GaKnzt-BBktYn1o-w4Qzh_jHxse4Y3ACOxZQrB2ufhc,9
51
- bess_jpl-1.9.1.dist-info/RECORD,,
48
+ BESS_JPL/version.txt,sha256=6R3uO0EpIuVteI11fNMOqq6SsIRjI6vbme61i5z-MME,6
49
+ bess_jpl-1.10.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
50
+ bess_jpl-1.10.0.dist-info/METADATA,sha256=PM7zyAhRude_y8P-nnEQTsPB_Y5WaOD8xKmT2oHr3wg,6340
51
+ bess_jpl-1.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ bess_jpl-1.10.0.dist-info/top_level.txt,sha256=GaKnzt-BBktYn1o-w4Qzh_jHxse4Y3ACOxZQrB2ufhc,9
53
+ bess_jpl-1.10.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5