BESS-JPL 1.26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. BESS_JPL/BESS_JPL.py +54 -0
  2. BESS_JPL/C3_photosynthesis.py +165 -0
  3. BESS_JPL/C4_fraction.jpeg +0 -0
  4. BESS_JPL/C4_fraction.tif +0 -0
  5. BESS_JPL/C4_fraction.tif.aux.xml +11 -0
  6. BESS_JPL/C4_photosynthesis.py +133 -0
  7. BESS_JPL/ECOv002-cal-val-BESS-JPL-GEOS5FP-inputs.csv +1066 -0
  8. BESS_JPL/ECOv002-cal-val-BESS-JPL-inputs.csv +1066 -0
  9. BESS_JPL/ECOv002-cal-val-BESS-JPL-outputs.csv +1066 -0
  10. BESS_JPL/ECOv002-cal-val-FLiESANN-inputs.csv +1066 -0
  11. BESS_JPL/ECOv002-static-tower-BESS-JPL-inputs.csv +122 -0
  12. BESS_JPL/ECOv002_calval_BESS_inputs.py +30 -0
  13. BESS_JPL/ECOv002_static_tower_BESS_inputs.py +19 -0
  14. BESS_JPL/FVC_from_NDVI.py +22 -0
  15. BESS_JPL/LAI_from_NDVI.py +28 -0
  16. BESS_JPL/NDVI_maximum.jpeg +0 -0
  17. BESS_JPL/NDVI_maximum.tif +0 -0
  18. BESS_JPL/NDVI_minimum.jpeg +0 -0
  19. BESS_JPL/NDVI_minimum.tif +0 -0
  20. BESS_JPL/__init__.py +5 -0
  21. BESS_JPL/ball_berry_intercept_C3.jpeg +0 -0
  22. BESS_JPL/ball_berry_intercept_C3.tif +0 -0
  23. BESS_JPL/ball_berry_slope_C3.jpeg +0 -0
  24. BESS_JPL/ball_berry_slope_C3.tif +0 -0
  25. BESS_JPL/ball_berry_slope_C4.jpeg +0 -0
  26. BESS_JPL/ball_berry_slope_C4.tif +0 -0
  27. BESS_JPL/calculate_VCmax.py +90 -0
  28. BESS_JPL/calculate_bulk_aerodynamic_resistance.py +119 -0
  29. BESS_JPL/calculate_friction_velocity.py +111 -0
  30. BESS_JPL/canopy_energy_balance.py +110 -0
  31. BESS_JPL/canopy_longwave_radiation.py +117 -0
  32. BESS_JPL/canopy_shortwave_radiation.py +276 -0
  33. BESS_JPL/carbon_uptake_efficiency.jpeg +0 -0
  34. BESS_JPL/carbon_uptake_efficiency.tif +0 -0
  35. BESS_JPL/carbon_water_fluxes.py +313 -0
  36. BESS_JPL/colors.py +33 -0
  37. BESS_JPL/constants.py +25 -0
  38. BESS_JPL/exceptions.py +3 -0
  39. BESS_JPL/generate_BESS_GEOS5FP_inputs.py +58 -0
  40. BESS_JPL/generate_BESS_inputs_table.py +186 -0
  41. BESS_JPL/generate_input_dataset.py +243 -0
  42. BESS_JPL/generate_output_dataset.py +26 -0
  43. BESS_JPL/interpolate_C3_C4.py +12 -0
  44. BESS_JPL/kn.jpeg +0 -0
  45. BESS_JPL/kn.tif +0 -0
  46. BESS_JPL/load_C4_fraction.py +20 -0
  47. BESS_JPL/load_NDVI_maximum.py +17 -0
  48. BESS_JPL/load_NDVI_minimum.py +17 -0
  49. BESS_JPL/load_ball_berry_intercept_C3.py +10 -0
  50. BESS_JPL/load_ball_berry_slope_C3.py +10 -0
  51. BESS_JPL/load_ball_berry_slope_C4.py +10 -0
  52. BESS_JPL/load_carbon_uptake_efficiency.py +10 -0
  53. BESS_JPL/load_kn.py +10 -0
  54. BESS_JPL/load_peakVCmax_C3.py +12 -0
  55. BESS_JPL/load_peakVCmax_C4.py +12 -0
  56. BESS_JPL/meteorology.py +429 -0
  57. BESS_JPL/model.py +594 -0
  58. BESS_JPL/peakVCmax_C3.jpeg +0 -0
  59. BESS_JPL/peakVCmax_C3.tif +0 -0
  60. BESS_JPL/peakVCmax_C4.jpeg +0 -0
  61. BESS_JPL/peakVCmax_C4.tif +0 -0
  62. BESS_JPL/process_BESS_table.py +365 -0
  63. BESS_JPL/process_paw_and_gao_LE.py +50 -0
  64. BESS_JPL/retrieve_BESS_JPL_GEOS5FP_inputs.py +257 -0
  65. BESS_JPL/retrieve_BESS_inputs.py +279 -0
  66. BESS_JPL/soil_energy_balance.py +35 -0
  67. BESS_JPL/verify.py +127 -0
  68. BESS_JPL/version.py +3 -0
  69. bess_jpl-1.26.0.dist-info/METADATA +102 -0
  70. bess_jpl-1.26.0.dist-info/RECORD +73 -0
  71. bess_jpl-1.26.0.dist-info/WHEEL +5 -0
  72. bess_jpl-1.26.0.dist-info/licenses/LICENSE +201 -0
  73. bess_jpl-1.26.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,279 @@
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 calculate_solar_day_of_year, calculate_solar_hour_of_day
13
+ from koppengeiger import load_koppen_geiger
14
+ from gedi_canopy_height import load_canopy_height, GEDI_DOWNLOAD_DIRECTORY
15
+ from FLiESANN import FLiESANN
16
+ from GEOS5FP import GEOS5FP
17
+ from MODISCI import MODISCI
18
+ from NASADEM import NASADEMConnection
19
+
20
+ from .constants import *
21
+ from .exceptions import *
22
+ from .colors import *
23
+ from .C3_photosynthesis import *
24
+ from .C4_photosynthesis import *
25
+ from .canopy_energy_balance import *
26
+ from .canopy_longwave_radiation import *
27
+ from .canopy_shortwave_radiation import *
28
+ from .carbon_water_fluxes import *
29
+ from .FVC_from_NDVI import *
30
+ from .interpolate_C3_C4 import *
31
+ from .LAI_from_NDVI import *
32
+ from .load_C4_fraction import *
33
+ from .load_carbon_uptake_efficiency import *
34
+ from .load_kn import *
35
+ from .load_NDVI_minimum import *
36
+ from .load_NDVI_maximum import *
37
+ from .load_peakVCmax_C3 import *
38
+ from .load_peakVCmax_C4 import *
39
+ from .load_ball_berry_intercept_C3 import *
40
+ from .load_ball_berry_slope_C3 import *
41
+ from .load_ball_berry_slope_C4 import *
42
+ from .calculate_VCmax import *
43
+ from .meteorology import *
44
+ from .soil_energy_balance import *
45
+ from .retrieve_BESS_JPL_GEOS5FP_inputs import retrieve_BESS_JPL_GEOS5FP_inputs
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ def retrieve_BESS_inputs(ST_C: Union[Raster, np.ndarray], # surface temperature in Celsius
50
+ NDVI: Union[Raster, np.ndarray], # NDVI
51
+ albedo: Union[Raster, np.ndarray], # surface albedo
52
+ geometry: RasterGeometry = None,
53
+ time_UTC: datetime = None,
54
+ hour_of_day: np.ndarray = None,
55
+ day_of_year: np.ndarray = None,
56
+ GEOS5FP_connection: GEOS5FP = None,
57
+ elevation_m: Union[Raster, np.ndarray] = None, # elevation in meters
58
+ Ta_C: Union[Raster, np.ndarray] = None, # air temperature in Celsius
59
+ RH: Union[Raster, np.ndarray] = None, # relative humidity as a proportion
60
+ NDVI_minimum: Union[Raster, np.ndarray] = None, # minimum NDVI
61
+ NDVI_maximum: Union[Raster, np.ndarray] = None, # maximum NDVI
62
+ PAR_albedo: Union[Raster, np.ndarray] = None, # surface albedo in visible wavelengths (initialized to surface albedo if left as None)
63
+ NIR_albedo: Union[Raster, np.ndarray] = None, # surface albedo in near-infrared wavelengths (initialized to surface albedo if left as None)
64
+ COT: Union[Raster, np.ndarray] = None, # cloud optical thickness
65
+ AOT: Union[Raster, np.ndarray] = None, # aerosol optical thickness
66
+ vapor_gccm: Union[Raster, np.ndarray] = None, # water vapor in g/ccm
67
+ ozone_cm: Union[Raster, np.ndarray] = None, # ozone in cm
68
+ KG_climate: Union[Raster, np.ndarray] = None, # KG climate
69
+ canopy_height_meters: Union[Raster, np.ndarray] = None, # canopy height in meters
70
+ Ca: Union[Raster, np.ndarray] = None, # atmospheric CO2 concentration in ppm
71
+ wind_speed_mps: Union[Raster, np.ndarray] = None, # wind speed in meters per second
72
+ SZA_deg: Union[Raster, np.ndarray] = None, # solar zenith angle in degrees
73
+ canopy_temperature_C: Union[Raster, np.ndarray] = None, # canopy temperature in Celsius (initialized to surface temperature if left as None)
74
+ soil_temperature_C: Union[Raster, np.ndarray] = None, # soil temperature in Celsius (initialized to surface temperature if left as None)
75
+ C4_fraction: Union[Raster, np.ndarray] = None, # fraction of C4 plants
76
+ carbon_uptake_efficiency: Union[Raster, np.ndarray] = None, # intrinsic quantum efficiency for carbon uptake
77
+ kn: np.ndarray = None,
78
+ ball_berry_intercept_C3: np.ndarray = None, # Ball-Berry intercept for C3 plants
79
+ ball_berry_intercept_C4: Union[np.ndarray, float] = BALL_BERRY_INTERCEPT_C4, # Ball-Berry intercept for C4 plants
80
+ ball_berry_slope_C3: np.ndarray = None, # Ball-Berry slope for C3 plants
81
+ ball_berry_slope_C4: np.ndarray = None, # Ball-Berry slope for C4 plants
82
+ peakVCmax_C3_μmolm2s1: np.ndarray = None, # peak maximum carboxylation rate for C3 plants
83
+ peakVCmax_C4_μmolm2s1: np.ndarray = None, # peak maximum carboxylation rate for C4 plants
84
+ CI: Union[Raster, np.ndarray] = None,
85
+ C4_fraction_scale_factor: float = C4_FRACTION_SCALE_FACTOR,
86
+ MODISCI_connection: MODISCI = None,
87
+ NASADEM_connection: NASADEMConnection = None,
88
+ resampling: str = RESAMPLING,
89
+ GEDI_download_directory: str = GEDI_DOWNLOAD_DIRECTORY,
90
+ offline_mode: bool = False) -> dict:
91
+ results = {}
92
+
93
+ if (day_of_year is None or hour_of_day is None) and time_UTC is not None and geometry is not None:
94
+ day_of_year = calculate_solar_day_of_year(time_UTC=time_UTC, geometry=geometry)
95
+ hour_of_day = calculate_solar_hour_of_day(time_UTC=time_UTC, geometry=geometry)
96
+
97
+ if time_UTC is None and day_of_year is None and hour_of_day is None:
98
+ raise ValueError("no time given between time_UTC, day_of_year, and hour_of_day")
99
+
100
+ # calculate solar zenith angle if not provided
101
+ if SZA_deg is None:
102
+ SZA_deg = calculate_SZA_from_DOY_and_hour(geometry.lat, geometry.lon, day_of_year, hour_of_day)
103
+
104
+ if isinstance(SZA_deg, np.ndarray):
105
+ # If array contains string representations, convert them first
106
+ if SZA_deg.dtype == object or SZA_deg.dtype.kind in ['U', 'S']:
107
+ # Handle string arrays by converting each element
108
+ # This handles cases like '[71.46303285]' or '71.46303285'
109
+ SZA_deg = np.array([float(str(x).strip('[]')) for x in SZA_deg], dtype=np.float32)
110
+ else:
111
+ # cast SZA_deg numpy array to float32
112
+ SZA_deg = SZA_deg.astype(np.float32)
113
+
114
+ print(type(SZA_deg))
115
+ print(SZA_deg.dtype if isinstance(SZA_deg, np.ndarray) else type(SZA_deg))
116
+
117
+ check_distribution(SZA_deg, "SZA_deg")
118
+ results["SZA_deg"] = SZA_deg
119
+
120
+
121
+ if CI is None and geometry is not None:
122
+ if offline_mode:
123
+ raise MissingOfflineParameter("CI not provided in offline mode")
124
+
125
+ if MODISCI_connection is None:
126
+ MODISCI_connection = MODISCI()
127
+
128
+ CI = MODISCI_connection.CI(geometry=geometry, resampling=resampling)
129
+
130
+ check_distribution(CI, "CI")
131
+ results["CI"] = CI
132
+
133
+ if elevation_m is None and geometry is not None:
134
+ if offline_mode:
135
+ raise MissingOfflineParameter("elevation_m not provided in offline mode")
136
+
137
+ if NASADEM_connection is None:
138
+ NASADEM_connection = NASADEMConnection()
139
+
140
+ elevation_m = NASADEM_connection.elevation_m(geometry=geometry)
141
+
142
+ check_distribution(elevation_m, "elevation_m")
143
+ results["elevation_m"] = elevation_m
144
+
145
+
146
+ # load minimum NDVI if not provided
147
+ if NDVI_minimum is None and geometry is not None:
148
+ NDVI_minimum = load_NDVI_minimum(geometry=geometry, resampling=resampling)
149
+
150
+ check_distribution(NDVI_minimum, "NDVI_minimum")
151
+ results["NDVI_minimum"] = NDVI_minimum
152
+
153
+ # load maximum NDVI if not provided
154
+ if NDVI_maximum is None and geometry is not None:
155
+ NDVI_maximum = load_NDVI_maximum(geometry=geometry, resampling=resampling)
156
+
157
+ check_distribution(NDVI_maximum, "NDVI_maximum")
158
+ results["NDVI_maximum"] = NDVI_maximum
159
+
160
+ # load C4 fraction if not provided
161
+ if C4_fraction is None:
162
+ C4_fraction = load_C4_fraction(
163
+ geometry=geometry,
164
+ resampling=resampling,
165
+ scale_factor=C4_fraction_scale_factor
166
+ )
167
+
168
+ check_distribution(C4_fraction, "C4_fraction")
169
+ results["C4_fraction"] = C4_fraction
170
+
171
+ # load carbon uptake efficiency if not provided
172
+ if carbon_uptake_efficiency is None:
173
+ carbon_uptake_efficiency = load_carbon_uptake_efficiency(geometry=geometry, resampling=resampling)
174
+
175
+ check_distribution(carbon_uptake_efficiency, "carbon_uptake_efficiency")
176
+ results["carbon_uptake_efficiency"] = carbon_uptake_efficiency
177
+
178
+ # load kn if not provided
179
+ if kn is None:
180
+ kn = load_kn(geometry=geometry, resampling=resampling)
181
+
182
+ check_distribution(kn, "kn")
183
+ results["kn"] = kn
184
+
185
+ # load peak VC max for C3 plants if not provided
186
+ if peakVCmax_C3_μmolm2s1 is None:
187
+ peakVCmax_C3_μmolm2s1 = load_peakVCmax_C3(geometry=geometry, resampling=resampling)
188
+
189
+ check_distribution(peakVCmax_C3_μmolm2s1, "peakVCmax_C3_μmolm2s1")
190
+ results["peakVCmax_C3_μmolm2s1"] = peakVCmax_C3_μmolm2s1
191
+
192
+ # load peak VC max for C4 plants if not provided
193
+ if peakVCmax_C4_μmolm2s1 is None:
194
+ peakVCmax_C4_μmolm2s1 = load_peakVCmax_C4(geometry=geometry, resampling=resampling)
195
+
196
+ check_distribution(peakVCmax_C4_μmolm2s1, "peakVCmax_C4_μmolm2s1")
197
+ results["peakVCmax_C4_μmolm2s1"] = peakVCmax_C4_μmolm2s1
198
+
199
+ # load Ball-Berry slope for C3 plants if not provided
200
+ if ball_berry_slope_C3 is None:
201
+ ball_berry_slope_C3 = load_ball_berry_slope_C3(geometry=geometry, resampling=resampling)
202
+
203
+ check_distribution(ball_berry_slope_C3, "ball_berry_slope_C3")
204
+ results["ball_berry_slope_C3"] = ball_berry_slope_C3
205
+
206
+ # load Ball-Berry slope for C4 plants if not provided
207
+ if ball_berry_slope_C4 is None:
208
+ ball_berry_slope_C4 = load_ball_berry_slope_C4(geometry=geometry, resampling=resampling)
209
+
210
+ check_distribution(ball_berry_slope_C4, "ball_berry_slope_C4")
211
+ results["ball_berry_slope_C4"] = ball_berry_slope_C4
212
+
213
+ # load Ball-Berry intercept for C3 plants if not provided
214
+ if ball_berry_intercept_C3 is None:
215
+ ball_berry_intercept_C3 = load_ball_berry_intercept_C3(geometry=geometry, resampling=resampling)
216
+
217
+ check_distribution(ball_berry_intercept_C3, "ball_berry_intercept_C3")
218
+ results["ball_berry_intercept_C3"] = ball_berry_intercept_C3
219
+
220
+ # load koppen geiger climate classification if not provided
221
+ if KG_climate is None:
222
+ KG_climate = load_koppen_geiger(geometry=geometry)
223
+
224
+ check_distribution(np.float32(KG_climate), "KG_climate")
225
+ results["KG_climate"] = KG_climate
226
+
227
+ # load canopy height in meters if not provided
228
+ if canopy_height_meters is None:
229
+ canopy_height_meters = load_canopy_height(
230
+ geometry=geometry,
231
+ resampling=resampling,
232
+ source_directory=GEDI_download_directory
233
+ )
234
+
235
+ # canopy height defaults to zero
236
+ canopy_height_meters = np.where(np.isnan(canopy_height_meters), 0, canopy_height_meters)
237
+
238
+ check_distribution(canopy_height_meters, "canopy_height_meters")
239
+ results["canopy_height_meters"] = canopy_height_meters
240
+
241
+
242
+ # Retrieve GEOS-5 FP inputs if not provided
243
+ GEOS5FP_inputs = retrieve_BESS_JPL_GEOS5FP_inputs(
244
+ time_UTC=time_UTC,
245
+ geometry=geometry,
246
+ albedo=albedo,
247
+ GEOS5FP_connection=GEOS5FP_connection,
248
+ Ta_C=Ta_C,
249
+ RH=RH,
250
+ COT=COT,
251
+ AOT=AOT,
252
+ vapor_gccm=vapor_gccm,
253
+ ozone_cm=ozone_cm,
254
+ PAR_albedo=PAR_albedo,
255
+ NIR_albedo=NIR_albedo,
256
+ Ca=Ca,
257
+ wind_speed_mps=wind_speed_mps,
258
+ resampling=resampling,
259
+ offline_mode=offline_mode
260
+ )
261
+
262
+ # Merge GEOS-5 FP inputs into results dictionary
263
+ results.update(GEOS5FP_inputs)
264
+
265
+ # canopy temperature defaults to surface temperature
266
+ if canopy_temperature_C is None:
267
+ canopy_temperature_C = ST_C
268
+
269
+ check_distribution(canopy_temperature_C, "canopy_temperature_C")
270
+ results["canopy_temperature_C"] = canopy_temperature_C
271
+
272
+ # soil temperature defaults to surface temperature
273
+ if soil_temperature_C is None:
274
+ soil_temperature_C = ST_C
275
+
276
+ check_distribution(soil_temperature_C, "soil_temperature_C")
277
+ results["soil_temperature_C"] = soil_temperature_C
278
+
279
+ return results
@@ -0,0 +1,35 @@
1
+ import numpy as np
2
+
3
+
4
+ def soil_energy_balance(
5
+ Ts_K: np.ndarray,
6
+ Ta_K: np.ndarray,
7
+ G_Wm2: np.ndarray,
8
+ VPD_Pa: np.ndarray,
9
+ RH: np.ndarray,
10
+ gamma: np.ndarray,
11
+ Cp: np.ndarray,
12
+ rhoa: np.ndarray,
13
+ desTa: np.ndarray,
14
+ Rs: np.ndarray,
15
+ ASW_soil_Wm2: np.ndarray,
16
+ ALW_soil_Wm2: np.ndarray,
17
+ Ls: np.ndarray,
18
+ epsa: np.ndarray):
19
+ # Net radiation
20
+ # Rn = Rnet - Rn_Sun - Rn_Sh
21
+ sigma = 5.670373e-8 # [W m-2 K-4] (Wiki)
22
+ Rn_soil_Wm2 = np.clip(ASW_soil_Wm2 + ALW_soil_Wm2 - Ls - 4.0 * epsa * sigma * (Ta_K ** 3) * (Ts_K - Ta_K), 0, None)
23
+ # G = Rn * 0.35
24
+
25
+ # Latent heat
26
+ LE_soil_Wm2 = desTa / (desTa + gamma) * (Rn_soil_Wm2 - G_Wm2) * (RH ** (VPD_Pa / 1000.0)) # (Ryu et al., 2011)
27
+ LE_soil_Wm2 = np.clip(LE_soil_Wm2, 0, Rn_soil_Wm2)
28
+ # Sensible heat
29
+ H_soil_Wm2 = np.clip(Rn_soil_Wm2 - G_Wm2 - LE_soil_Wm2, 0, Rn_soil_Wm2)
30
+
31
+ # Update temperature
32
+ dT = np.clip(Rs / (rhoa * Cp) * H_soil_Wm2, -20, 20)
33
+ Ts_K = Ta_K + dT
34
+
35
+ return Rn_soil_Wm2, LE_soil_Wm2, Ts_K
BESS_JPL/verify.py ADDED
@@ -0,0 +1,127 @@
1
+ def verify() -> bool:
2
+ """
3
+ Verifies the correctness of the BESS model implementation by comparing
4
+ its outputs to a reference dataset.
5
+
6
+ This function loads a known input table and the corresponding expected output table.
7
+ It runs the model on the input data, then compares the resulting outputs to the
8
+ reference outputs for key variables using strict numerical tolerances. If all
9
+ outputs match within tolerance, the function returns True. Otherwise, it prints
10
+ which column failed and returns False.
11
+
12
+ Returns:
13
+ bool: True if all model outputs match the reference outputs within tolerance, False otherwise.
14
+ """
15
+ import pandas as pd
16
+ import numpy as np
17
+ from .ECOv002_calval_BESS_inputs import load_ECOv002_calval_BESS_inputs
18
+ from .process_BESS_table import process_BESS_table
19
+ import os
20
+
21
+ # Load input and output tables
22
+ input_df = load_ECOv002_calval_BESS_inputs()
23
+ module_dir = os.path.dirname(os.path.abspath(__file__))
24
+ output_file_path = os.path.join(module_dir, "ECOv002-cal-val-BESS-JPL-outputs.csv")
25
+ output_df = pd.read_csv(output_file_path)
26
+
27
+ # Check that input dataset contains all required inputs for BESS model
28
+ # This ensures no on-the-fly data retrieval is needed
29
+ required_inputs = [
30
+ # Core inputs
31
+ 'ST_C', 'NDVI', 'albedo', 'time_UTC',
32
+ # Geometry (either geometry or lat/lon)
33
+ # 'geometry' or both 'lat' and 'lon' required - checked separately
34
+ 'day_of_year', 'hour_of_day',
35
+ # Meteorological inputs
36
+ 'Ta_C', # or 'Ta'
37
+ 'RH', 'elevation_m', # or 'elevation_km'
38
+ 'wind_speed_mps',
39
+ # Atmospheric inputs from GEOS5FP
40
+ 'COT', 'AOT', 'vapor_gccm', 'ozone_cm',
41
+ # Note: Ca defaults to 400 ppm if not provided
42
+ # Radiation inputs
43
+ 'PAR_albedo', 'NIR_albedo',
44
+ # Vegetation parameters
45
+ 'NDVI_minimum', 'NDVI_maximum', 'C4_fraction',
46
+ 'carbon_uptake_efficiency', 'kn',
47
+ # Photosynthesis parameters
48
+ 'peakVCmax_C3', 'peakVCmax_C4',
49
+ 'ball_berry_slope_C3', 'ball_berry_slope_C4', 'ball_berry_intercept_C3',
50
+ # Other required inputs
51
+ 'KG_climate', 'CI', 'canopy_height_meters', 'SZA_deg',
52
+ # Note: canopy_temperature_C and soil_temperature_C default to ST_C if not provided
53
+ ]
54
+
55
+ # Check for alternative column names
56
+ missing_inputs = []
57
+ for col in required_inputs:
58
+ # Handle alternative names
59
+ if col == 'Ta_C' and 'Ta_C' not in input_df and 'Ta' not in input_df:
60
+ missing_inputs.append('Ta_C (or Ta)')
61
+ elif col == 'elevation_m' and 'elevation_m' not in input_df and 'elevation_km' not in input_df:
62
+ missing_inputs.append('elevation_m (or elevation_km)')
63
+ elif col not in ['Ta_C', 'elevation_m'] and col not in input_df:
64
+ missing_inputs.append(col)
65
+
66
+ # Check geometry requirement
67
+ has_geometry = 'geometry' in input_df or ('lat' in input_df and 'lon' in input_df)
68
+ if not has_geometry:
69
+ missing_inputs.append('geometry (or lat and lon)')
70
+
71
+ if missing_inputs:
72
+ print("Input verification failed: Missing required inputs for BESS model.")
73
+ print("The following inputs are missing from the input dataset:")
74
+ for inp in missing_inputs:
75
+ print(f" - {inp}")
76
+ print("\nThese inputs must be present to run the model without on-the-fly data retrieval.")
77
+ return False
78
+
79
+ print("Input verification passed: All required BESS inputs are present.")
80
+
81
+ # Run the model on the input table
82
+ model_df = process_BESS_table(input_df, offline_mode=True)
83
+
84
+ # Columns to compare (model outputs)
85
+ output_columns = [
86
+ "G_Wm2",
87
+ "Rn_Wm2",
88
+ "LE_Wm2"
89
+ ]
90
+
91
+ # Compare each output column and collect mismatches
92
+ mismatches = []
93
+ for col in output_columns:
94
+ if col not in model_df or col not in output_df:
95
+ mismatches.append((col, 'missing_column', None))
96
+ continue
97
+ model_vals = model_df[col].values
98
+ ref_vals = output_df[col].values
99
+ # Use numpy allclose for floating point comparison
100
+ # Tolerances account for minor platform/version differences (macOS vs Ubuntu, different numpy versions)
101
+ if not np.allclose(model_vals, ref_vals, rtol=1e-4, atol=1e-7, equal_nan=True):
102
+ # Find indices where values differ
103
+ diffs = np.abs(model_vals - ref_vals)
104
+ max_diff = np.nanmax(diffs)
105
+ idxs = np.where(~np.isclose(model_vals, ref_vals, rtol=1e-4, atol=1e-7, equal_nan=True))[0]
106
+ mismatch_info = {
107
+ 'indices': idxs.tolist(),
108
+ 'model_values': model_vals[idxs].tolist(),
109
+ 'ref_values': ref_vals[idxs].tolist(),
110
+ 'diffs': diffs[idxs].tolist(),
111
+ 'max_diff': float(max_diff)
112
+ }
113
+ mismatches.append((col, 'value_mismatch', mismatch_info))
114
+ if mismatches:
115
+ print("Verification failed. Details:")
116
+ for col, reason, info in mismatches:
117
+ if reason == 'missing_column':
118
+ print(f" Missing column: {col}")
119
+ elif reason == 'value_mismatch':
120
+ print(f" Mismatch in column: {col}")
121
+ print(f" Max difference: {info['max_diff']}")
122
+ print(f" Indices off: {info['indices']}")
123
+ print(f" Model values: {info['model_values']}")
124
+ print(f" Reference values: {info['ref_values']}")
125
+ print(f" Differences: {info['diffs']}")
126
+ return False
127
+ return True
BESS_JPL/version.py ADDED
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("BESS-JPL")
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: BESS-JPL
3
+ Version: 1.26.0
4
+ Summary: Breathing Earth System Simulator (BESS) Gross Primary Production (GPP) and Evapotranspiration (ET) Model Python
5
+ Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>
6
+ Project-URL: Homepage, https://github.com/JPL-Evapotranspiration-Algorithms/BESS-JPL
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: check-distribution>=1.2.0
13
+ Requires-Dist: daylight-evapotranspiration
14
+ Requires-Dist: ECOv002-calval-tables>=1.6.0
15
+ Requires-Dist: FLiESANN>=2.13.0
16
+ Requires-Dist: gedi-canopy-height>=1.1.0
17
+ Requires-Dist: GEOS5FP>=2.19.0
18
+ Requires-Dist: koppengeiger>=1.1.0
19
+ Requires-Dist: MODISCI>=1.4.0
20
+ Requires-Dist: monte-carlo-sensitivity>=1.5.0
21
+ Requires-Dist: NASADEM>=1.3.0
22
+ Requires-Dist: numpy
23
+ Requires-Dist: pytictoc
24
+ Requires-Dist: rasters>=1.9.0
25
+ Requires-Dist: seaborn
26
+ Requires-Dist: solar-apparent-time>=1.5.3
27
+ Provides-Extra: dev
28
+ Requires-Dist: build; extra == "dev"
29
+ Requires-Dist: pytest>=6.0; extra == "dev"
30
+ Requires-Dist: pytest-cov; extra == "dev"
31
+ Requires-Dist: jupyter; extra == "dev"
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: twine; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # Breathing Earth System Simulator (BESS) Model Python Implementation
37
+
38
+ [![CI](https://github.com/JPL-Evapotranspiration-Algorithms/breathing-earth-system-simulator/actions/workflows/ci.yml/badge.svg)](https://github.com/JPL-Evapotranspiration-Algorithms/breathing-earth-system-simulator/actions/workflows/ci.yml)
39
+
40
+ This software package is a Python implementation of the Breathing Earth System Simulator (BESS) model. It was re-implemented in Python by Gregory Halverson at Jet Propulsion Laboratory based on [MATLAB code](https://www.environment.snu.ac.kr/bess-flux) produced by Youngryel Ryu at Seoul University. The BESS model was designed to quantify global gross primary productivity (GPP) and evapotranspiration (ET) using MODIS with a spatial resolution of 1–5 km and a temporal resolution of 8 days. It couples atmospheric and canopy radiative transfer processes with photosynthesis, stomatal conductance, and transpiration models on sunlit and shaded portions of vegetation and soil. An artificial neural network emulator of Hideki Kobayashi's Forest Light Environmental Simulator (FLiES) radiative transfer model is used to estimate incoming solar radiation. This implementation of BESS was designed to process GPP at fine spatial resolution using surface temperature from the ECOsystem Spaceborne Thermal Radiometer Experiment on Space Station (ECOSTRESS) mission and normalized difference vegetation index (NDVI) and albedo from the [Spatial Timeseries for Automated high-Resolution multi-Sensor (STARS) data fusion system](https://github.com/STARS-Data-Fusion). The software was developed as part of a research grant by the NASA Research Opportunities in Space and Earth Sciences (ROSES) program. It was designed for use by the ECOSTRESS mission as a precursor for the Surface Biology and Geology (SBG) mission. However, it may also be useful for general remote sensing and GIS projects in Python. This package can be utilized for remote sensing research in Jupyter notebooks and deployed for operations in data processing pipelines. This software is being released according to the SPD-41 open-science requirements of NASA-funded ROSES projects.
41
+
42
+ Gregory H. Halverson (they/them)<br>
43
+ [gregory.h.halverson@jpl.nasa.gov](mailto:gregory.h.halverson@jpl.nasa.gov)<br>
44
+ Lead developer<br>
45
+ NASA Jet Propulsion Laboratory 329G
46
+
47
+ Youngryel Ryu (he/him)<br>
48
+ [yryu@snu.ac.kr](mailto:yryu@snu.ac.kr)<br>
49
+ BESS algorithm inventor<br>
50
+ Seoul National University
51
+
52
+ Hideki Kobayashi (he/him)<br>
53
+ [hkoba@jamstec.go.jp](mailto:hkoba@jamstec.go.jp)<br>
54
+ FLiES algorithm inventor<br>
55
+ Japan Agency for Marine-Earth Science and Technology
56
+
57
+ Robert Freepartner (he/him)<br>
58
+ [robert.freepartner@jpl.nasa.gov](robert.freepartner@jpl.nasa.gov)<br>
59
+ MATLAB to python translation<br>
60
+ Raytheon
61
+
62
+ Joshua Fisher (he/him)<br>
63
+ [jbfisher@chapman.edu](mailto:jbfisher@chapman.edu)<br>
64
+ Concept development and project management<br>
65
+ Chapman University
66
+
67
+ Kerry Cawse-Nicholson (she/her)<br>
68
+ [kerry-anne.cawse-nicholson@jpl.nasa.gov](mailto:kerry-anne.cawse-nicholson@jpl.nasa.gov)<br>
69
+ Project management<br>
70
+ NASA Jet Propulsion Laboratory 329G
71
+
72
+ Zoe Pierrat (she/her)<br>
73
+ [zoe.a.pierrat@jpl.nasa.gov](mailto:zoe.a.pierrat@jpl.nasa.gov)<br>
74
+ Algorithm maintenance<br>
75
+ NASA Jet Propulsion Laboratory 329G
76
+
77
+ Claire Villanueva-Weeks (she/her)<br>
78
+ [claire.s.villanueva-weeks@jpl.nasa.gov](mailto:claire.s.villanueva-weeks@jpl.nasa.gov)<br>
79
+ Code maintenance<br>
80
+ NASA Jet Propulsion Laboratory 329G
81
+
82
+ ## Installation
83
+
84
+ Use the pip package manager to install this package
85
+
86
+ ```
87
+ pip install breathing-earth-system-simulator
88
+ ```
89
+
90
+ ## References
91
+
92
+ The following scientific references provide detailed information about the BESS model and its underlying algorithms:
93
+
94
+ 1. Ryu, Y., Baldocchi, D. D., Kobayashi, H., van Ingen, C., Li, J., Black, T. A., ... & Ueyama, M. (2011). Integration of MODIS land and atmosphere products with a coupled-process model to estimate gross primary productivity and evapotranspiration from 1 km to global scales. *Remote Sensing of Environment, 115*(8), 1865-1874. https://doi.org/10.1016/j.rse.2011.03.009
95
+
96
+ 2. Kobayashi, H., Ryu, Y., Baldocchi, D. D., Welles, J. M., & Norman, J. M. (2012). On the correct estimation of gap fraction: How to remove scattered radiation in gap fraction measurements? *Agricultural and Forest Meteorology, 160*, 14-25. https://doi.org/10.1016/j.agrformet.2012.03.001
97
+
98
+ 3. Fisher, J. B., Lee, B., Purdy, A. J., Halverson, G. H., Dohlen, M. B., & Tu, K. P. (2020). ECOSTRESS: NASA's next generation mission to measure evapotranspiration from the International Space Station. *Water Resources Research, 56*(4), e2019WR026058. https://doi.org/10.1029/2019WR026058
99
+
100
+ 4. Ryu, Y., Jiang, C., Kobayashi, H., & Detto, M. (2018). Modis-derived global land products of shortwave radiation and diffuse and total photosynthetically active radiation at 5 km resolution from 2000. *Remote Sensing of Environment, 204*, 812-825. https://doi.org/10.1016/j.rse.2017.09.021
101
+
102
+ 5. Kobayashi, H., & Iwabuchi, H. (2008). A coupled 1-D atmosphere and canopy radiative transfer model for an atmosphere with a nonlambertian surface. *Journal of Quantitative Spectroscopy and Radiative Transfer, 109*(17-18), 2955-2970. https://doi.org/10.1016/j.jqsrt.2008.07.008
@@ -0,0 +1,73 @@
1
+ BESS_JPL/BESS_JPL.py,sha256=l5ubRRpPYlZxhmHjsB-88J5_2exgNLB5lvvVVO10fjw,1720
2
+ BESS_JPL/C3_photosynthesis.py,sha256=VmBYwxCK1URhA2_Etb96JXSqubyvuftjyuhHha9QOXQ,6506
3
+ BESS_JPL/C4_fraction.jpeg,sha256=ECaEYWA8MnNd5z0Yq3beeUCR93KtTDxM8MrH-YYon3Y,2746
4
+ BESS_JPL/C4_fraction.tif,sha256=DFS-J08JCjnf0hi_T_dFridAegVTiU3FGyMBjS9yc1w,25446
5
+ BESS_JPL/C4_fraction.tif.aux.xml,sha256=5Ytn5UtZdGWQUKNmg_kiOU2zRQsm82sY7bq-j3TS1QY,358
6
+ BESS_JPL/C4_photosynthesis.py,sha256=NtT-fV8DQbLXMyePGGDSwYadbsDY0ZW3WFGJ9pxxL40,7035
7
+ BESS_JPL/ECOv002-cal-val-BESS-JPL-GEOS5FP-inputs.csv,sha256=aw7YL3S81qu3SUNp3DHG7Q4HtOdOWxBLycEcKUJIBrA,104716
8
+ BESS_JPL/ECOv002-cal-val-BESS-JPL-inputs.csv,sha256=3oQVYY_dSzIyclORmE181gtbVvWcj8fTFR9E_XW9gjQ,1317067
9
+ BESS_JPL/ECOv002-cal-val-BESS-JPL-outputs.csv,sha256=W5cDy2I3X37z3YaY0ehEeprljIOqfGgEC_5NlpSoUVY,2984084
10
+ BESS_JPL/ECOv002-cal-val-FLiESANN-inputs.csv,sha256=AybkhW0EmPGlkoYe_Ph0aoh7TrA9yd0ozJ6cvDwS4fE,946508
11
+ BESS_JPL/ECOv002-static-tower-BESS-JPL-inputs.csv,sha256=uRrCRPfbnsU_a3Otnnpk47gcBpTrS079EZPrB0KNc8s,33603
12
+ BESS_JPL/ECOv002_calval_BESS_inputs.py,sha256=9XTFJHn9EczVNLr2WDjsJ4b5nHZonkxWpS5yEogo2RY,1109
13
+ BESS_JPL/ECOv002_static_tower_BESS_inputs.py,sha256=jvMGMWVo0UnNsGXebrYDVTgJXroH-MTkLj2zYcCYADk,622
14
+ BESS_JPL/FVC_from_NDVI.py,sha256=LYI1ESsrO8UxlPOr_dxJYDeUsrwjVAwrV_OhL-tcAWc,567
15
+ BESS_JPL/LAI_from_NDVI.py,sha256=uwV1voc2q45hmsSHzIhehA3ZHLivzSeoST-ht9ecSIE,783
16
+ BESS_JPL/NDVI_maximum.jpeg,sha256=6kc0-eE_WOU31bUztdjoWsZ3ZmqHAG-ZwS7lMiZyBGA,763946
17
+ BESS_JPL/NDVI_maximum.tif,sha256=yRgdpB-xoVNMveIUapCrnGAwHX7WkH6pVXu4HECuFfc,19664541
18
+ BESS_JPL/NDVI_minimum.jpeg,sha256=AfpRU8PikwExBJ2IdcFKewg6aTcp3gx3y3EQMzeA7vg,644871
19
+ BESS_JPL/NDVI_minimum.tif,sha256=wPV0CFfQSLfiTAEDlwIAI857pA7aw0iqWhVlOAZFsJg,18762242
20
+ BESS_JPL/__init__.py,sha256=UaK3mWbc5jNoA4kNzIWF9h7CQs64O_chKrpuxNkH48o,95
21
+ BESS_JPL/ball_berry_intercept_C3.jpeg,sha256=c8jlRD3i8cnWXQNRUfyNTuObEpptcG6p6kQd2RGMK70,634200
22
+ BESS_JPL/ball_berry_intercept_C3.tif,sha256=O7ATUIKtXcIjkUI2lloTiHAgvRk4gWyWjYGh5Xc_CRY,1026642
23
+ BESS_JPL/ball_berry_slope_C3.jpeg,sha256=KdFUYG4h1nRNb_eK7w-M2kqChJnhbcGmal9Z2Jos1nI,225597
24
+ BESS_JPL/ball_berry_slope_C3.tif,sha256=PEAZ09AMMbDUXi5R4FsyMQGsanLd4Ptzk-pRHnaa-oU,407646
25
+ BESS_JPL/ball_berry_slope_C4.jpeg,sha256=JJJn3C17duBOlEe98PCyycBT3SS-YBhHJpJZYTq4GMw,1009208
26
+ BESS_JPL/ball_berry_slope_C4.tif,sha256=UedoUOnOjBxQ9pAaf5nXgDa8wvKOHVBD3sVToV12aM8,1012948
27
+ BESS_JPL/calculate_VCmax.py,sha256=dQ6uGzgqrv2rAaUlhxuLauWhMSuUGvb9fgS2pRYugLY,4129
28
+ BESS_JPL/calculate_bulk_aerodynamic_resistance.py,sha256=5j0YJGI_dEnMDud4pv0AyOf03hYxv5cJ6m0nbIJtlWU,5668
29
+ BESS_JPL/calculate_friction_velocity.py,sha256=njTyo9H5tXxqS7-VNGOqf5pCqTV3Y5vaNwYEmF1kxsI,4932
30
+ BESS_JPL/canopy_energy_balance.py,sha256=aNnODLouI33GkVrqmNaviWGzyPvV0DgnbV4iQncKsw4,4945
31
+ BESS_JPL/canopy_longwave_radiation.py,sha256=Hv5TUJ1O_PBIYRmx8kn6Ake8cyKEL9h0cfN4wsC0UgM,5436
32
+ BESS_JPL/canopy_shortwave_radiation.py,sha256=WmzH6birUGkT4O0q7eG1Eo4ed3xi9LVKTydIf9r8J_s,13280
33
+ BESS_JPL/carbon_uptake_efficiency.jpeg,sha256=wz2cLk_4-DRdF71xR2c-cx1MsE0CH-EDWjs2pDJcRF0,704540
34
+ BESS_JPL/carbon_uptake_efficiency.tif,sha256=Nnc8h2ImU6_sip0WzDh3q0BRoBe-CfCbwzDtYo5BSog,819269
35
+ BESS_JPL/carbon_water_fluxes.py,sha256=hfTug6k7ch_i2k-HoICVJZTJW310Uc5esK258qaEN-M,16758
36
+ BESS_JPL/colors.py,sha256=vRZoazJwgSa6v3sC678GoyVPRUxVfYg1JMibzI3jHiw,635
37
+ BESS_JPL/constants.py,sha256=LYtSoFHA8zaerW4BOSY8mRcKvqpf3foIX5NX8xwVVFM,688
38
+ BESS_JPL/exceptions.py,sha256=KpYl0RC4FtfmXrMHpRhVb6InwbrALwh3WfZIfbdbpgA,132
39
+ BESS_JPL/generate_BESS_GEOS5FP_inputs.py,sha256=7J5Cv6t6fZZbpETOquTKWf604dvLC1vePE3y8almUK8,1685
40
+ BESS_JPL/generate_BESS_inputs_table.py,sha256=QqYUG98bWXLoTp5TqBfDPI8xXoOu9kEQG04g7oirkBI,9763
41
+ BESS_JPL/generate_input_dataset.py,sha256=4YbPdKvcY69PQVjTDKVb5pYvt7DbMLSzAZcsRIQFY1E,11768
42
+ BESS_JPL/generate_output_dataset.py,sha256=tz7pM3JF2fI1HqKWT3ub1P40VQYzOLoT-qrTV4zeIOI,839
43
+ BESS_JPL/interpolate_C3_C4.py,sha256=adIyk03Yt6SJ_jFCUm6ivK9QpZYw10C3wJKpx1O56Pk,394
44
+ BESS_JPL/kn.jpeg,sha256=wsI40jabT9_iPpQmOxd3GZzfAN7oZ2DubiARmULFUS0,854097
45
+ BESS_JPL/kn.tif,sha256=nZAIAms7LRDVsadxa4z8yRqOudWu3AjJSm6l1T3ywuY,819988
46
+ BESS_JPL/load_C4_fraction.py,sha256=qemgzsyDVKvzvql1p_N7g23L8KYvbLw-qxjWlUC3p_Q,624
47
+ BESS_JPL/load_NDVI_maximum.py,sha256=HMOvChb9CWItkDpGCZtZ9Ode2N4Q_MllmnOgiAt5yzQ,528
48
+ BESS_JPL/load_NDVI_minimum.py,sha256=ed_qmkNCIy-H7Q5POA4mSlUvYUTknKiz31ioXZGpbvU,528
49
+ BESS_JPL/load_ball_berry_intercept_C3.py,sha256=6auXZHej127rhUCFXe7vdEg9C4CosFSHLTcdqKs06Ls,388
50
+ BESS_JPL/load_ball_berry_slope_C3.py,sha256=9nmbdkGnJ7UWXwktpkE8rL1LGA1vi71YZdzpme04P84,380
51
+ BESS_JPL/load_ball_berry_slope_C4.py,sha256=lsKwrkbChEm3sspUgAXjm86Q4VBirwFOZStiO9NuGx8,380
52
+ BESS_JPL/load_carbon_uptake_efficiency.py,sha256=RbWxLbqHnxiHw7XHyFBuWRToc8hDjUR6EUUntiJfe0M,390
53
+ BESS_JPL/load_kn.py,sha256=39RkQkkdmey0IPInziJI0kSiI8alRk5Hz42pzm-qihU,346
54
+ BESS_JPL/load_peakVCmax_C3.py,sha256=A-Wg4PjPgogfpRsAMo6ZNEXM7G3KbnF7ZRV3k_jTtEA,401
55
+ BESS_JPL/load_peakVCmax_C4.py,sha256=iVmJTitkMkB4AjfjanAi3yv1lWO4sSF6J7cjuRmaCwU,401
56
+ BESS_JPL/meteorology.py,sha256=B7tguKu_1H0bWKJI0YQpSZzk9cXS-wc6W4OIe_5igds,14799
57
+ BESS_JPL/model.py,sha256=r4fpvRngARgkQsDmbNVPH7KeC_lHYQiwYheNOI0nanE,25838
58
+ BESS_JPL/peakVCmax_C3.jpeg,sha256=nVvwLx8JyRhtm5lWRW93HLz0mInshEdOCJ1tAdcFqa8,1006133
59
+ BESS_JPL/peakVCmax_C3.tif,sha256=ax6wCOPc_ovgJJl9YRjPWNY13OCGlzs2djXqwob_U7A,1583598
60
+ BESS_JPL/peakVCmax_C4.jpeg,sha256=s7dhpcD573KW9Se4mejDeSzbSHqPtQY2EL6KJKt7ZIo,961497
61
+ BESS_JPL/peakVCmax_C4.tif,sha256=EST4_yy-HHYmazIv--RePL_bhLejMWql6hoV9EE38ok,1556928
62
+ BESS_JPL/process_BESS_table.py,sha256=U3SMtCEESm7bAu3Vaccdy_MnL2l2Er7LdbZnbryYBkI,13039
63
+ BESS_JPL/process_paw_and_gao_LE.py,sha256=e-hMowIWC-aORGOyJU-omGsFk0Pp8YmTjl5wwQiOXOQ,1760
64
+ BESS_JPL/retrieve_BESS_JPL_GEOS5FP_inputs.py,sha256=mS6IcXELzj2j8fHAAVwregI-FU3g_9l4tolu24x1_l8,10651
65
+ BESS_JPL/retrieve_BESS_inputs.py,sha256=VubZ3ui6ZYnLGpjDjgrcwVRRobeEslCG6fpIPIzzK6A,11924
66
+ BESS_JPL/soil_energy_balance.py,sha256=u5YMywVQPard8DSutBuI37WfyDcCtm64YwxaHZDm-rs,1095
67
+ BESS_JPL/verify.py,sha256=RvGVvaQ68WOfA42KwdZCKOBnydBri4dQfpcYCwDA01M,5628
68
+ BESS_JPL/version.py,sha256=oPRGJspBmrZU7gLv4L_3WPX3aGwee0inKgfzcyvkXcc,74
69
+ bess_jpl-1.26.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
70
+ bess_jpl-1.26.0.dist-info/METADATA,sha256=TfruStH4jfszvGX0-wjbRnDKv6BIueGhK2PaThb0UFU,6543
71
+ bess_jpl-1.26.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
72
+ bess_jpl-1.26.0.dist-info/top_level.txt,sha256=GaKnzt-BBktYn1o-w4Qzh_jHxse4Y3ACOxZQrB2ufhc,9
73
+ bess_jpl-1.26.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+