Skip to content

Commit 20a8c8b

Browse files
Merge pull request #30 from gregory-halverson/main
checking distribution of intermediate values
2 parents b127644 + 49d0280 commit 20a8c8b

File tree

4 files changed

+159
-6
lines changed

4 files changed

+159
-6
lines changed

breathing_earth_system_simulator/BESS.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
from .load_ball_berry_slope_C3 import load_ball_berry_slope_C3
3232
from .load_ball_berry_slope_C4 import load_ball_berry_slope_C4
3333

34-
GEOS5FP_DOWNLOAD_DIRECTORY = "~/data/GEOS5FP_download"
35-
36-
## TODO automatic acquisition of SRTM for default elevation
34+
from .check_distribution import check_distribution
3735

3836
def BESS(
3937
ST_C: Union[Raster, np.ndarray], # surface temperature in Celsius
@@ -78,7 +76,6 @@ def BESS(
7876
peakVCmax_C3: np.ndarray = None, # peak maximum carboxylation rate for C3 plants
7977
peakVCmax_C4: np.ndarray = None, # peak maximum carboxylation rate for C4 plants
8078
CI: Union[Raster, np.ndarray] = None,
81-
GEOS5FP_download_directory: str = GEOS5FP_DOWNLOAD_DIRECTORY,
8279
resampling: str = RESAMPLING): # clumping index
8380
if geometry is None and isinstance(ST_C, Raster):
8481
geometry = ST_C.geometry
@@ -244,6 +241,30 @@ def BESS(
244241
canopy_height_meters=canopy_height_meters
245242
)
246243

244+
meteorology_outputs = {
245+
"Ps_Pa": Ps_Pa,
246+
"VPD_Pa": VPD_Pa,
247+
"RH": RH,
248+
"desTa": desTa,
249+
"ddesTa": ddesTa,
250+
"gamma": gamma,
251+
"Cp": Cp,
252+
"rhoa": rhoa,
253+
"epsa": epsa,
254+
"R": R,
255+
"Rc": Rc,
256+
"Rs": Rs,
257+
"SFd": SFd,
258+
"SFd2": SFd2,
259+
"DL": DL,
260+
"Ra": Ra,
261+
"fStress": fStress
262+
}
263+
264+
# Check the distribution for each variable
265+
for var_name, var_value in meteorology_outputs.items():
266+
check_distribution(var_value, var_name, time_UTC)
267+
247268
# convert NDVI to LAI
248269
LAI = LAI_from_NDVI(NDVI)
249270
LAI_minimum = LAI_from_NDVI(NDVI_minimum)
@@ -259,6 +280,18 @@ def BESS(
259280
kn=kn
260281
)
261282

283+
# List of variable names and their corresponding values
284+
VCmax_outputs = {
285+
"VCmax_C3_sunlit": VCmax_C3_sunlit,
286+
"VCmax_C4_sunlit": VCmax_C4_sunlit,
287+
"VCmax_C3_shaded": VCmax_C3_shaded,
288+
"VCmax_C4_shaded": VCmax_C4_shaded
289+
}
290+
291+
# Check the distribution for each variable
292+
for var_name, var_value in VCmax_outputs.items():
293+
check_distribution(var_value, var_name, time_UTC)
294+
262295
sunlit_fraction, APAR_sunlit, APAR_shaded, ASW_sunlit, ASW_shaded, ASW_soil, G = canopy_shortwave_radiation(
263296
PARDiff=VISdiff, # diffuse photosynthetically active radiation in W/m^2
264297
PARDir=VISdir, # direct photosynthetically active radiation in W/m^2
@@ -272,6 +305,22 @@ def BESS(
272305
albedo_NIR=albedo_NIR # surface albedo in near-infrared wavelengths
273306
)
274307

308+
# List of variable names and their corresponding values
309+
canopy_radiation_outputs = {
310+
"sunlit_fraction": sunlit_fraction,
311+
"APAR_sunlit": APAR_sunlit,
312+
"APAR_shaded": APAR_shaded,
313+
"ASW_sunlit": ASW_sunlit,
314+
"ASW_shaded": ASW_shaded,
315+
"ASW_soil": ASW_soil,
316+
"G": G
317+
}
318+
319+
# Check the distribution for each variable
320+
for var_name, var_value in canopy_radiation_outputs.items():
321+
check_distribution(var_value, var_name, time_UTC)
322+
323+
275324
canopy_temperature_K = canopy_temperature_C + 273.15
276325
soil_temperature_K = soil_temperature_C + 273.15
277326

@@ -309,6 +358,22 @@ def BESS(
309358
C4_photosynthesis=False # C3 or C4 photosynthesis
310359
)
311360

361+
# List of variable names and their corresponding values
362+
carbon_water_fluxes_outputs = {
363+
"GPP_C3": GPP_C3,
364+
"LE_C3": LE_C3,
365+
"LE_soil_C3": LE_soil_C3,
366+
"LE_canopy_C3": LE_canopy_C3,
367+
"Rn_C3": Rn_C3,
368+
"Rn_soil_C3": Rn_soil_C3,
369+
"Rn_canopy_C3": Rn_canopy_C3
370+
}
371+
372+
# Check the distribution for each variable
373+
for var_name, var_value in carbon_water_fluxes_outputs.items():
374+
check_distribution(var_value, var_name, time_UTC)
375+
376+
312377
GPP_C4, LE_C4, LE_soil_C4, LE_canopy_C4, Rn_C4, Rn_soil_C4, Rn_canopy_C4 = carbon_water_fluxes(
313378
canopy_temperature_K=canopy_temperature_K, # canopy temperature in Kelvin
314379
soil_temperature_K=soil_temperature_K, # soil temperature in Kelvin
@@ -343,6 +408,21 @@ def BESS(
343408
C4_photosynthesis=True # C3 or C4 photosynthesis
344409
)
345410

411+
# List of variable names and their corresponding values
412+
carbon_water_fluxes_C4_outputs = {
413+
"GPP_C4": GPP_C4,
414+
"LE_C4": LE_C4,
415+
"LE_soil_C4": LE_soil_C4,
416+
"LE_canopy_C4": LE_canopy_C4,
417+
"Rn_C4": Rn_C4,
418+
"Rn_soil_C4": Rn_soil_C4,
419+
"Rn_canopy_C4": Rn_canopy_C4
420+
}
421+
422+
# Check the distribution for each variable
423+
for var_name, var_value in carbon_water_fluxes_C4_outputs.items():
424+
check_distribution(var_value, var_name, time_UTC)
425+
346426
# interpolate C3 and C4 GPP
347427
ST_K = ST_C + 273.15
348428
GPP = np.clip(interpolate_C3_C4(GPP_C3, GPP_C4, C4_fraction), 0, 50)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import Union
2+
from rasters import Raster
3+
from datetime import date
4+
import numpy as np
5+
from logging import getLogger
6+
import colored_logging as cl
7+
8+
logger = getLogger(__name__)
9+
10+
class BlankOutputError(Exception):
11+
pass
12+
13+
def check_distribution(
14+
image: Raster,
15+
variable: str,
16+
date_UTC: Union[date, str],
17+
target: str = None):
18+
unique = np.unique(image)
19+
nan_proportion = np.count_nonzero(np.isnan(image)) / np.size(image)
20+
21+
target_message = f" at {cl.place(target)}" if target else ""
22+
23+
if len(unique) < 10:
24+
logger.info(f"variable {cl.name(variable)} ({image.dtype}) on {cl.time(f'{date_UTC:%Y-%m-%d}')}{target_message} with {cl.val(unique)} unique values")
25+
26+
for value in unique:
27+
if np.isnan(value):
28+
count = np.count_nonzero(np.isnan(image))
29+
else:
30+
count = np.count_nonzero(image == value)
31+
32+
if value == 0 or np.isnan(value):
33+
logger.info(f"* {cl.colored(value, 'red')}: {cl.colored(count, 'red')}")
34+
else:
35+
logger.info(f"* {cl.val(value)}: {cl.val(count)}")
36+
else:
37+
minimum = np.nanmin(image)
38+
39+
if minimum < 0:
40+
minimum_string = cl.colored(f"{minimum:0.3f}", "red")
41+
else:
42+
minimum_string = cl.val(f"{minimum:0.3f}")
43+
44+
maximum = np.nanmax(image)
45+
46+
if maximum <= 0:
47+
maximum_string = cl.colored(f"{maximum:0.3f}", "red")
48+
else:
49+
maximum_string = cl.val(f"{maximum:0.3f}")
50+
51+
if nan_proportion > 0.5:
52+
nan_proportion_string = cl.colored(f"{(nan_proportion * 100):0.2f}%", "yellow")
53+
elif nan_proportion == 1:
54+
nan_proportion_string = cl.colored(f"{(nan_proportion * 100):0.2f}%", "red")
55+
else:
56+
nan_proportion_string = cl.val(f"{(nan_proportion * 100):0.2f}%")
57+
58+
message = "variable " + cl.name(variable) + \
59+
" on " + cl.time(f"{date_UTC:%Y-%m-%d}") + \
60+
target_message + \
61+
" min: " + minimum_string + \
62+
" mean: " + cl.val(f"{np.nanmean(image):0.3f}") + \
63+
" max: " + maximum_string + \
64+
" nan: " + nan_proportion_string + f" ({cl.val(image.nodata)})"
65+
66+
if np.all(image == 0):
67+
message += " all zeros"
68+
logger.warning(message)
69+
else:
70+
logger.info(message)
71+
72+
if nan_proportion == 1:
73+
raise BlankOutputError(f"variable {variable} on {date_UTC:%Y-%m-%d}{target_message} is a blank image")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.5.0
1+
1.5.1

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = ["setuptools>=60", "setuptools-scm>=8.0", "wheel"]
33

44
[project]
55
name = "breathing-earth-system-simulator"
6-
version = "1.5.0"
6+
version = "1.5.1"
77
description = "Breathing Earth System Simulator (BESS) Gross Primary Production (GPP) and Evapotranspiration (ET) Model Python"
88
readme = "README.md"
99
authors = [

0 commit comments

Comments
 (0)