PM-JPL 1.2.1__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. PMJPL/ECOv002-cal-val-PM-JPL-inputs.csv +1048 -0
  2. PMJPL/ECOv002-cal-val-PM-JPL-outputs.csv +1048 -0
  3. PMJPL/ECOv002-static-tower-PM-JPL-inputs.csv +122 -0
  4. PMJPL/ECOv002_calval_PMJPL_inputs.py +19 -0
  5. PMJPL/ECOv002_static_tower_PMJPL_inputs.py +19 -0
  6. PMJPL/PMJPL.py +45 -378
  7. PMJPL/{parameters.py → PMJPL_parameter_from_IGBP.py} +22 -7
  8. PMJPL/VPD_factor.py +1 -1
  9. PMJPL/__init__.py +1 -6
  10. PMJPL/calculate_gamma.py +128 -0
  11. PMJPL/canopy_aerodynamic_resistance.py +151 -18
  12. PMJPL/canopy_conductance.py +71 -15
  13. PMJPL/closed_minimum_temperature.py +11 -0
  14. PMJPL/closed_vapor_pressure_deficit.py +11 -0
  15. PMJPL/constants.py +6 -1
  16. PMJPL/correctance_factor.py +56 -7
  17. PMJPL/generate_PMJPL_inputs.py +263 -0
  18. PMJPL/interception.py +1 -3
  19. PMJPL/leaf_conductance_to_evaporated_water.py +11 -0
  20. PMJPL/leaf_conductance_to_sensible_heat.py +58 -0
  21. PMJPL/maximum_boundary_layer_resistance.py +11 -0
  22. PMJPL/minimum_boundary_layer_resistance.py +11 -0
  23. PMJPL/{tmin_factor.py → minimum_temperature_factor.py} +2 -2
  24. PMJPL/mod16.csv +19 -0
  25. PMJPL/model.py +690 -0
  26. PMJPL/open_minimum_temperature.py +11 -0
  27. PMJPL/open_vapor_pressure_deficit.py +11 -0
  28. PMJPL/potential_soil_evaporation.py +2 -2
  29. PMJPL/potential_stomatal_conductance.py +11 -0
  30. PMJPL/process_PMJPL_table.py +208 -0
  31. PMJPL/process_daily_ET_table.py +40 -0
  32. PMJPL/transpiration.py +4 -4
  33. PMJPL/verify.py +77 -0
  34. PMJPL/version.py +4 -0
  35. PMJPL/wet_canopy_resistance.py +1 -0
  36. PMJPL/wet_soil_evaporation.py +4 -4
  37. {pm_jpl-1.2.1.dist-info → pm_jpl-1.7.0.dist-info}/METADATA +18 -21
  38. pm_jpl-1.7.0.dist-info/RECORD +42 -0
  39. {pm_jpl-1.2.1.dist-info → pm_jpl-1.7.0.dist-info}/WHEEL +1 -1
  40. PMJPL/MCD12C1/MCD12C1.py +0 -10
  41. PMJPL/MCD12C1/__init__.py +0 -1
  42. PMJPL/SEBAL/SEBAL.py +0 -45
  43. PMJPL/SEBAL/__init__.py +0 -1
  44. PMJPL/downscaling/__init__.py +0 -1
  45. PMJPL/downscaling/downscaling.py +0 -271
  46. PMJPL/downscaling/linear_downscale.py +0 -71
  47. PMJPL/evapotranspiration_conversion/__init__.py +0 -1
  48. PMJPL/evapotranspiration_conversion/evapotranspiration_conversion.py +0 -80
  49. PMJPL/fwet.py +0 -21
  50. PMJPL/meteorology_conversion/__init__.py +0 -1
  51. PMJPL/meteorology_conversion/meteorology_conversion.py +0 -123
  52. PMJPL/penman_monteith/__init__.py +0 -1
  53. PMJPL/penman_monteith/penman_monteith.py +0 -20
  54. PMJPL/priestley_taylor/__init__.py +0 -1
  55. PMJPL/priestley_taylor/priestley_taylor.py +0 -27
  56. PMJPL/santanello/__init__.py +0 -1
  57. PMJPL/santanello/santanello.py +0 -46
  58. PMJPL/soil_heat_flux/__init__.py +0 -1
  59. PMJPL/soil_heat_flux/soil_heat_flux.py +0 -62
  60. PMJPL/vegetation_conversion/__init__.py +0 -1
  61. PMJPL/vegetation_conversion/vegetation_conversion.py +0 -47
  62. PMJPL/verma_net_radiation/__init__.py +0 -1
  63. PMJPL/verma_net_radiation/verma_net_radiation.py +0 -108
  64. pm_jpl-1.2.1.dist-info/RECORD +0 -44
  65. {pm_jpl-1.2.1.dist-info → pm_jpl-1.7.0.dist-info}/licenses/LICENSE +0 -0
  66. {pm_jpl-1.2.1.dist-info → pm_jpl-1.7.0.dist-info}/top_level.txt +0 -0
PMJPL/model.py ADDED
@@ -0,0 +1,690 @@
1
+
2
+ """
3
+ MOD16 model of evapotranspiration
4
+
5
+ This implementation follows the MOD16 Version 1.5 Collection 6 algorithm described in the MOD16 user's guide.
6
+ https://landweb.nascom.nasa.gov/QA_WWW/forPage/user_guide/MOD16UsersGuide2016.pdf
7
+
8
+ Developed by Gregory Halverson in the Jet Propulsion Laboratory Year-Round Internship Program (Columbus Technologies and Services), in coordination with the ECOSTRESS mission and master's thesis studies at California State University, Northridge.
9
+ """
10
+
11
+ # All imports moved to the top
12
+ import logging
13
+ from typing import Dict, Union
14
+ from datetime import datetime
15
+
16
+ import numpy as np
17
+ import rasters as rt
18
+ from rasters import Raster, RasterGrid, RasterGeometry, VectorGeometry
19
+
20
+ from check_distribution import check_distribution
21
+ from GEOS5FP import GEOS5FP
22
+ from NASADEM import NASADEM
23
+ from verma_net_radiation import verma_net_radiation
24
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
25
+ from MCD12C1_2019_v006 import load_MCD12C1_IGBP
26
+
27
+ from carlson_leaf_area_index import carlson_leaf_area_index
28
+ from carlson_fractional_vegetation_cover import carlson_fractional_vegetation_cover
29
+ from carlson_leaf_area_index import carlson_leaf_area_index
30
+
31
+ from daylight_evapotranspiration import lambda_Jkg_from_Ta_C, daylight_ET_from_instantaneous_LE
32
+
33
+ from meteorology_conversion import SVP_Pa_from_Ta_C
34
+ from meteorology_conversion import calculate_air_density
35
+ from meteorology_conversion import calculate_specific_heat
36
+ from meteorology_conversion import calculate_specific_humidity
37
+ from meteorology_conversion import calculate_surface_pressure
38
+ from meteorology_conversion import celcius_to_kelvin
39
+
40
+ from priestley_taylor import delta_Pa_from_Ta_C
41
+ from PTJPL import calculate_relative_surface_wetness
42
+ from PTJPL import RH_THRESHOLD, MIN_FWET
43
+
44
+ from .constants import *
45
+ from .PMJPL_parameter_from_IGBP import PMJPL_parameter_from_IGBP
46
+ from .calculate_gamma import calculate_gamma
47
+ from .soil_moisture_constraint import calculate_fSM
48
+ from .minimum_temperature_factor import Tmin_factor
49
+ from .correctance_factor import calculate_correctance_factor
50
+ from .VPD_factor import calculate_VPD_factor
51
+ from .canopy_conductance import calculate_canopy_conductance
52
+ from .wet_canopy_resistance import calculate_wet_canopy_resistance
53
+ from .canopy_aerodynamic_resistance import calculate_canopy_aerodynamic_resistance
54
+ from .wet_soil_evaporation import calculate_wet_soil_evaporation
55
+ from .potential_soil_evaporation import calculate_potential_soil_evaporation
56
+ from .interception import calculate_interception
57
+ from .transpiration import calculate_transpiration
58
+ from .leaf_conductance_to_sensible_heat import leaf_conductance_to_sensible_heat
59
+ from .potential_stomatal_conductance import potential_stomatal_conductance
60
+ from .open_minimum_temperature import open_minimum_temperature
61
+ from .closed_minimum_temperature import closed_minimum_temperature
62
+ from .open_vapor_pressure_deficit import open_vapor_pressure_deficit
63
+ from .closed_vapor_pressure_deficit import closed_vapor_pressure_deficit
64
+ from .leaf_conductance_to_evaporated_water import leaf_conductance_to_evaporated_water
65
+ from .maximum_boundary_layer_resistance import maximum_boundary_layer_resistance
66
+ from .minimum_boundary_layer_resistance import minimum_boundary_layer_resistance
67
+
68
+ __author__ = 'Qiaozhen Mu, Maosheng Zhao, Steven W. Running, Gregory Halverson'
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+ def PMJPL(
73
+ NDVI: Union[Raster, np.ndarray],
74
+ ST_C: Union[Raster, np.ndarray] = None,
75
+ emissivity: Union[Raster, np.ndarray] = None,
76
+ albedo: Union[Raster, np.ndarray] = None,
77
+ Rn_Wm2: Union[Raster, np.ndarray] = None,
78
+ G_Wm2: Union[Raster, np.ndarray] = None,
79
+ SWin_Wm2: Union[Raster, np.ndarray] = None,
80
+ Ta_C: Union[Raster, np.ndarray] = None,
81
+ Tmin_C: Union[Raster, np.ndarray] = None,
82
+ RH: Union[Raster, np.ndarray] = None,
83
+ IGBP: Union[Raster, np.ndarray] = None,
84
+ FVC: Union[Raster, np.ndarray] = None,
85
+ geometry: RasterGeometry = None,
86
+ time_UTC: datetime = None,
87
+ GEOS5FP_connection: GEOS5FP = None,
88
+ resampling: str = "nearest",
89
+ Ps_Pa: Union[Raster, np.ndarray] = None,
90
+ elevation_km: Union[Raster, np.ndarray] = None,
91
+ delta_Pa: Union[Raster, np.ndarray] = None,
92
+ lambda_Jkg: Union[Raster, np.ndarray] = None,
93
+ gamma_Jkg: Union[Raster, np.ndarray, float] = None,
94
+ gl_sh: Union[Raster, np.ndarray] = None,
95
+ CL: Union[Raster, np.ndarray] = None,
96
+ Tmin_open: Union[Raster, np.ndarray] = None,
97
+ Tmin_closed: Union[Raster, np.ndarray] = None,
98
+ VPD_open: Union[Raster, np.ndarray] = None,
99
+ VPD_closed: Union[Raster, np.ndarray] = None,
100
+ gl_e_wv: Union[Raster, np.ndarray] = None,
101
+ RBL_max: Union[Raster, np.ndarray] = None,
102
+ RBL_min: Union[Raster, np.ndarray] = None,
103
+ RH_threshold: float = RH_THRESHOLD,
104
+ min_fwet: float = MIN_FWET,
105
+ IGBP_upsampling_resolution_meters: float = IGBP_UPSAMPLING_RESOLUTION_METERS,
106
+ upscale_to_daylight: bool = False,
107
+ Rn_daylight_Wm2: Union[Raster, np.ndarray] = None,
108
+ day_of_year: np.ndarray = None,
109
+ regenerate_net_radiation: bool = False
110
+ ) -> Dict[str, Raster]:
111
+ """
112
+ MOD16 Penman-Monteith Evapotranspiration Model (Version 1.5, Collection 6)
113
+
114
+ Implements the MOD16 algorithm for partitioning daylight latent heat flux (LE) into canopy transpiration, soil evaporation, and wet canopy evaporation, following Mu et al. (2011) and the MOD16 User Guide (2016).
115
+
116
+ Scientific Overview:
117
+ -------------------
118
+ This function estimates daylight evapotranspiration (ET) and its components using satellite-derived vegetation indices, land cover, and meteorological data. It applies the Penman-Monteith equation, partitioning energy and resistances according to biophysical and meteorological constraints. The model is designed for gridded raster inputs but supports numpy arrays for flexibility.
119
+
120
+ Key Steps:
121
+ - Retrieves or computes all necessary meteorological and surface variables (temperature, humidity, pressure, radiation, NDVI, LAI, FVC, etc.).
122
+ - Calculates net radiation (Verma et al., 1989), soil heat flux (SEBAL; Bastiaanssen et al., 1998), and meteorological properties (Allen et al., 1998).
123
+ - Computes resistances and conductances for canopy and soil evaporation, including biome-specific and environmental constraints (Mu et al., 2011; Monteith, 1965).
124
+ - Partitions LE into wet canopy evaporation, transpiration, and soil evaporation, applying soil moisture and surface wetness constraints.
125
+
126
+ Parameters
127
+ ----------
128
+ NDVI : Raster or np.ndarray
129
+ Normalized Difference Vegetation Index.
130
+ ST_C : Raster or np.ndarray, optional
131
+ Surface temperature (Celsius).
132
+ emissivity : Raster or np.ndarray, optional
133
+ Surface emissivity.
134
+ albedo : Raster or np.ndarray, optional
135
+ Surface albedo.
136
+ Rn_Wm2 : Raster or np.ndarray, optional
137
+ Net radiation (W/m^2).
138
+ G_Wm2 : Raster or np.ndarray, optional
139
+ Soil heat flux (W/m^2).
140
+ SWin_Wm2 : Raster or np.ndarray, optional
141
+ Incoming shortwave radiation (W/m^2).
142
+ Ta_C : Raster or np.ndarray, optional
143
+ Air temperature (Celsius).
144
+ Tmin_C : Raster or np.ndarray, optional
145
+ Minimum air temperature (Celsius).
146
+ RH : Raster or np.ndarray, optional
147
+ Relative humidity (fraction).
148
+ IGBP : Raster or np.ndarray, optional
149
+ Land cover classification (IGBP).
150
+ FVC : Raster or np.ndarray, optional
151
+ Fractional vegetation cover.
152
+ geometry : RasterGeometry, optional
153
+ Spatial geometry for raster data.
154
+ time_UTC : datetime, optional
155
+ Timestamp for meteorological data.
156
+ GEOS5FP_connection : GEOS5FP, optional
157
+ Meteorological data source.
158
+ resampling : str, optional
159
+ Resampling method for raster data.
160
+ Ps_Pa : Raster or np.ndarray, optional
161
+ Surface pressure (Pa).
162
+ elevation_km : Raster or np.ndarray, optional
163
+ Elevation (km).
164
+ delta_Pa : Raster or np.ndarray, optional
165
+ Slope of saturation vapor pressure curve (Pa/°C).
166
+ lambda_Jkg : Raster or np.ndarray, optional
167
+ Latent heat of vaporization (J/kg).
168
+ gamma_Jkg : Raster, np.ndarray, or float, optional
169
+ Psychrometric constant (J/kg).
170
+ gl_sh : Raster or np.ndarray, optional
171
+ Leaf conductance to sensible heat.
172
+ CL : Raster or np.ndarray, optional
173
+ Potential stomatal conductance.
174
+ Tmin_open, Tmin_closed : Raster or np.ndarray, optional
175
+ Open/closed minimum temperature by land cover.
176
+ VPD_open, VPD_closed : Raster or np.ndarray, optional
177
+ Open/closed vapor pressure deficit by land cover.
178
+ gl_e_wv : Raster or np.ndarray, optional
179
+ Leaf conductance to evaporated water vapor.
180
+ RBL_max, RBL_min : Raster or np.ndarray, optional
181
+ Maximum/minimum boundary layer resistance.
182
+ RH_threshold : float, optional
183
+ RH threshold for surface wetness.
184
+ min_fwet : float, optional
185
+ Minimum surface wetness.
186
+ IGBP_upsampling_resolution_meters : float, optional
187
+ Resolution for upsampling IGBP data.
188
+ upscale_to_daylight : bool, optional
189
+ Whether to upscale instantaneous values to daylight estimates.
190
+ regenerate_net_radiation : bool, optional
191
+ Whether to regenerate net radiation from surface components even if Rn_Wm2 is provided.
192
+
193
+ Returns
194
+ -------
195
+ Dict[str, Raster]
196
+ Dictionary of output rasters, including:
197
+ - 'LEi_Wm2': Wet canopy evaporation (W/m^2)
198
+ - 'LEc_Wm2': Canopy transpiration (W/m^2)
199
+ - 'LEs': Soil evaporation (W/m^2)
200
+ - 'LE_Wm2': Total latent heat flux (W/m^2)
201
+ - Additional intermediate variables (e.g., resistances, conductances, meteorological properties)
202
+
203
+ References
204
+ ----------
205
+ Mu, Q., Zhao, M., & Running, S. W. (2011). Improvements to a MODIS global terrestrial evapotranspiration algorithm. Remote Sensing of Environment, 115(8), 1781-1800. https://doi.org/10.1016/j.rse.2011.02.019
206
+ MOD16 User Guide (2016): https://landweb.nascom.nasa.gov/QA_WWW/forPage/user_guide/MOD16UsersGuide2016.pdf
207
+ Allen, R. G., Pereira, L. S., Raes, D., & Smith, M. (1998). Crop evapotranspiration—Guidelines for computing crop water requirements—FAO Irrigation and drainage paper 56.
208
+ Monteith, J. L. (1965). Evaporation and environment. Symposia of the Society for Experimental Biology, 19, 205-234.
209
+ Carlson, T. N., & Ripley, D. A. (1997). On the relation between NDVI, fractional vegetation cover, and leaf area index. Remote Sensing of Environment, 62(3), 241-252.
210
+ Bastiaanssen, W. G. M., et al. (1998). A remote sensing surface energy balance algorithm for land (SEBAL). Journal of Hydrology, 212-213, 198-212.
211
+ Verma, S. B., Rosenberg, N. J., & Blad, B. L. (1989). Microclimate, evapotranspiration, and water status of maize under shelterbelt and non-shelterbelt conditions. Agricultural and Forest Meteorology, 46(1), 21-34.
212
+ """
213
+ results = {}
214
+
215
+ if geometry is None and isinstance(NDVI, Raster):
216
+ geometry = NDVI.geometry
217
+
218
+ if GEOS5FP_connection is None:
219
+ GEOS5FP_connection = GEOS5FP()
220
+
221
+ if Ta_C is None and geometry is not None and time_UTC is not None:
222
+ Ta_C = GEOS5FP_connection.Ta_C(
223
+ time_UTC=time_UTC,
224
+ geometry=geometry,
225
+ resampling=resampling
226
+ )
227
+
228
+ if Ta_C is None:
229
+ raise ValueError("air temperature (Ta_C) not given")
230
+
231
+ check_distribution(Ta_C, "Ta_C")
232
+
233
+ if Tmin_C is None and geometry is not None and time_UTC is not None:
234
+ Tmin_K = GEOS5FP_connection.Tmin_K(
235
+ time_UTC=time_UTC,
236
+ geometry=geometry,
237
+ resampling=resampling
238
+ )
239
+
240
+ Tmin_C = Tmin_K - 273.15
241
+
242
+ if Tmin_C is None:
243
+ raise ValueError("minimum temperature (Tmin_C) not given")
244
+
245
+ check_distribution(Tmin_C, "Tmin_C")
246
+
247
+ if RH is None and geometry is not None and time_UTC is not None:
248
+ RH = GEOS5FP_connection.RH(
249
+ time_UTC=time_UTC,
250
+ geometry=geometry,
251
+ resampling=resampling
252
+ )
253
+
254
+ if RH is None:
255
+ raise ValueError("relative humidity (RH) not given")
256
+
257
+ check_distribution(RH, "RH")
258
+
259
+ if elevation_km is None and geometry is not None:
260
+ elevation_km = NASADEM.elevation_km(geometry=geometry)
261
+
262
+ check_distribution(elevation_km, "elevation_km")
263
+
264
+ elevation_m = elevation_km * 1000.0
265
+
266
+ if IGBP is None and geometry is not None:
267
+ if isinstance(geometry, VectorGeometry):
268
+ IGBP_geometry = geometry
269
+ elif isinstance(geometry, RasterGeometry):
270
+ IGBP_geometry = geometry.UTM(IGBP_upsampling_resolution_meters)
271
+ else:
272
+ raise ValueError(f"invalid geometry type for IGBP retrieval: {type(geometry)}")
273
+
274
+ IGBP = load_MCD12C1_IGBP(geometry=IGBP_geometry)
275
+
276
+ check_distribution(np.float32(IGBP), "IGBP")
277
+
278
+ if regenerate_net_radiation or (Rn_Wm2 is None and albedo is not None and ST_C is not None and emissivity is not None):
279
+ if SWin_Wm2 is None and geometry is not None and time_UTC is not None:
280
+ logger.info("retrieving shortwave radiation (SWin_Wm2) from GEOS-5 FP")
281
+ SWin_Wm2 = GEOS5FP_connection.SWin(
282
+ time_UTC=time_UTC,
283
+ geometry=geometry,
284
+ resampling=resampling
285
+ )
286
+ elif SWin_Wm2 is not None:
287
+ logger.info("using given shortwave radiation (SWin_Wm2)")
288
+
289
+ if upscale_to_daylight:
290
+ logger.info("running Verma net radiation with daylight upscaling")
291
+ else:
292
+ logger.info("running instantaneous Verma net radiation")
293
+
294
+ Rn_results = verma_net_radiation(
295
+ SWin_Wm2=SWin_Wm2,
296
+ albedo=albedo,
297
+ ST_C=ST_C,
298
+ emissivity=emissivity,
299
+ Ta_C=Ta_C,
300
+ RH=RH,
301
+ upscale_to_daylight=upscale_to_daylight,
302
+ )
303
+
304
+ Rn_Wm2 = Rn_results["Rn_Wm2"]
305
+
306
+ if "Rn_daylight_Wm2" in Rn_results:
307
+ Rn_daylight_Wm2 = Rn_results["Rn_daylight_Wm2"]
308
+
309
+ elif Rn_Wm2 is not None:
310
+ logger.info("using given net radiation (Rn_Wm2) for PM-JPL processing")
311
+
312
+ if Rn_Wm2 is None:
313
+ missing_vars = []
314
+ if albedo is None:
315
+ missing_vars.append('albedo')
316
+ if ST_C is None:
317
+ missing_vars.append('ST_C')
318
+ if emissivity is None:
319
+ missing_vars.append('emissivity')
320
+ if missing_vars:
321
+ raise ValueError(f"net radiation (Rn_Wm2) not given, and missing required variables to calculate: {', '.join(missing_vars)}")
322
+ else:
323
+ raise ValueError("net radiation (Rn_Wm2) not given and cannot be calculated")
324
+
325
+ check_distribution(Rn_Wm2, "Rn_Wm2")
326
+
327
+ if G_Wm2 is None and Rn_Wm2 is not None and ST_C is not None and NDVI is not None and albedo is not None:
328
+ G_Wm2 = calculate_SEBAL_soil_heat_flux(
329
+ Rn=Rn_Wm2,
330
+ ST_C=ST_C,
331
+ NDVI=NDVI,
332
+ albedo=albedo
333
+ )
334
+
335
+ if G_Wm2 is None:
336
+ raise ValueError("soil heat flux (G) not given")
337
+
338
+ check_distribution(G_Wm2, "G_Wm2")
339
+ results["G_Wm2"] = G_Wm2
340
+
341
+ LAI = carlson_leaf_area_index(NDVI)
342
+
343
+ # calculate fraction of vegetation cover if it's not given
344
+ if FVC is None:
345
+ # calculate fraction of vegetation cover from NDVI
346
+ FVC = carlson_fractional_vegetation_cover(NDVI)
347
+
348
+ # calculate surface air pressure if it's not given
349
+ if Ps_Pa is None:
350
+ # calculate surface air pressure is Pascal
351
+ Ps_Pa = calculate_surface_pressure(elevation_m=elevation_m, Ta_C=Ta_C)
352
+
353
+ # calculate Penman-Monteith/Priestley-Taylor delta term if it's not given
354
+ if delta_Pa is None:
355
+ # calculate Penman-Monteith/Priestley-Taylor delta term in Pascal per degree Celsius
356
+ delta_Pa = delta_Pa_from_Ta_C(Ta_C)
357
+
358
+ # calculate latent heat of vaporization if it's not given
359
+ if lambda_Jkg is None:
360
+ # calculate latent heat of vaporization in Joules per kilogram
361
+ lambda_Jkg = lambda_Jkg_from_Ta_C(Ta_C)
362
+
363
+ logger.info("calculating PM-MOD meteorology")
364
+
365
+ # calculate air temperature in Kelvin
366
+ Ta_K = celcius_to_kelvin(Ta_C)
367
+
368
+ # calculate saturation vapor pressure in Pascal from air temperature in Celsius
369
+ SVP_Pa = SVP_Pa_from_Ta_C(Ta_C)
370
+
371
+ # calculate vapor pressure in Pascal from releative humidity and saturation vapor pressure
372
+ Ea_Pa = RH * SVP_Pa
373
+
374
+ # specific humidity of air
375
+ # as a ratio of kilograms of water to kilograms of air and water
376
+ # from surface pressure and actual water vapor pressure
377
+ specific_humidity = calculate_specific_humidity(Ea_Pa, Ps_Pa)
378
+ check_distribution(specific_humidity, "specific_humidity")
379
+ results['specific_humidity'] = specific_humidity
380
+
381
+ # calculate air density (rho) in kilograms per cubic meter
382
+ rho_kgm3 = calculate_air_density(Ps_Pa, Ta_K, specific_humidity)
383
+ check_distribution(rho_kgm3, "rho_kgm3")
384
+ results["rho_kgm3"] = rho_kgm3
385
+
386
+ # calculate specific heat capacity of the air (Cp)
387
+ # in joules per kilogram per kelvin
388
+ # from specific heat of water vapor (CPW)
389
+ # and specific heat of dry air (CPD)
390
+ Cp_Jkg = calculate_specific_heat(specific_humidity)
391
+ check_distribution(Cp_Jkg, "Cp_Jkg")
392
+ results["Cp"] = Cp_Jkg
393
+
394
+ # calculate delta term if it's not given
395
+ if delta_Pa is None:
396
+ # slope of saturation vapor pressure curve in Pascal per degree
397
+ delta_Pa = delta_Pa_from_Ta_C(Ta_C)
398
+
399
+ check_distribution(delta_Pa, "delta_Pa")
400
+ results["delta_Pa"] = delta_Pa
401
+
402
+ # calculate gamma term if it's not given
403
+ if gamma_Jkg is None:
404
+ # calculate psychrometric gamma in Joules per kilogram
405
+ gamma_Jkg = calculate_gamma(
406
+ Ta_C=Ta_C,
407
+ Ps_Pa=Ps_Pa,
408
+ Cp_Jkg=Cp_Jkg
409
+ )
410
+
411
+ # vapor pressure deficit in Pascal
412
+ VPD_Pa = rt.clip(SVP_Pa - Ea_Pa, 0.0, None)
413
+
414
+ # calculate relative surface wetness (fwet)
415
+ # from relative humidity
416
+ fwet = calculate_relative_surface_wetness(
417
+ RH=RH,
418
+ RH_threshold=RH_threshold,
419
+ min_fwet=min_fwet
420
+ )
421
+
422
+ check_distribution(fwet, "fwet")
423
+ results['fwet'] = fwet
424
+
425
+ logger.info("calculating PM-MOD resistances")
426
+
427
+ # query leaf conductance to sensible heat (gl_sh) in seconds per meter
428
+ if gl_sh is None:
429
+ gl_sh = leaf_conductance_to_sensible_heat(IGBP, geometry)
430
+
431
+ check_distribution(gl_sh, "gl_sh")
432
+ results['gl_sh'] = gl_sh
433
+
434
+ # calculate wet canopy resistance to sensible heat (rhc) in seconds per meter
435
+ rhc = calculate_wet_canopy_resistance(gl_sh, LAI, fwet)
436
+ check_distribution(rhc, "rhc")
437
+ results['rhc'] = rhc
438
+
439
+ # calculate resistance to radiative heat transfer through air (rrc)
440
+ rrc = np.float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3.0))
441
+ check_distribution(rrc, "rrc")
442
+ results['rrc'] = rrc
443
+
444
+ # calculate aerodynamic resistance (rhrc)
445
+ rhrc = np.float32((rhc * rrc) / (rhc + rrc))
446
+ check_distribution(rhrc, "rhrc")
447
+ results['rhrc'] = rhrc
448
+
449
+ # calculate leaf conductance to evaporated water vapor (gl_e_wv)
450
+ if gl_e_wv is None:
451
+ gl_e_wv = leaf_conductance_to_evaporated_water(IGBP, geometry, IGBP_upsampling_resolution_meters)
452
+
453
+ check_distribution(gl_e_wv, "gl_e_wv")
454
+ results['gl_e_wv'] = gl_e_wv
455
+
456
+ rvc = calculate_wet_canopy_resistance(gl_e_wv, LAI, fwet)
457
+ check_distribution(rvc, "rvc")
458
+ results['rvc'] = rvc
459
+
460
+ # caluclate available radiation to the canopy (Ac)
461
+ Ac = Rn_Wm2 * FVC
462
+ check_distribution(Ac, "Ac")
463
+ results['Ac'] = Ac
464
+
465
+ # calculate wet latent heat flux (LEi)
466
+ LE_interception_Wm2 = calculate_interception(
467
+ delta_Pa=delta_Pa,
468
+ Ac=Ac,
469
+ rho=rho_kgm3,
470
+ Cp=Cp_Jkg,
471
+ VPD_Pa=VPD_Pa,
472
+ FVC=FVC,
473
+ rhrc=rhrc,
474
+ fwet=fwet,
475
+ rvc=rvc,
476
+ )
477
+
478
+ check_distribution(LE_interception_Wm2, "LE_interception_Wm2")
479
+ results['LE_interception_Wm2'] = LE_interception_Wm2
480
+
481
+ # calculate correctance factor (rcorr)
482
+ rcorr = calculate_correctance_factor(Ps_Pa, Ta_K)
483
+ check_distribution(rcorr, "rcorr")
484
+ results['rcorr'] = rcorr
485
+
486
+ # biome-specific mean potential stomatal conductance per unit leaf area
487
+ if CL is None:
488
+ CL = potential_stomatal_conductance(IGBP, geometry, IGBP_upsampling_resolution_meters)
489
+
490
+ check_distribution(CL, "CL")
491
+ results['CL'] = CL
492
+
493
+ # open minimum temperature by land-cover
494
+ if Tmin_open is None:
495
+ Tmin_open = open_minimum_temperature(IGBP, geometry, IGBP_upsampling_resolution_meters)
496
+
497
+ check_distribution(Tmin_open, "Tmin_open")
498
+ results['Tmin_open'] = Tmin_open
499
+
500
+ # closed minimum temperature by land-cover
501
+ if Tmin_closed is None:
502
+ Tmin_closed = closed_minimum_temperature(IGBP, geometry, IGBP_upsampling_resolution_meters)
503
+
504
+ check_distribution(Tmin_closed, "Tmin_closed")
505
+ results['Tmin_closed'] = Tmin_closed
506
+
507
+ check_distribution(Tmin_C, "Tmin_C")
508
+
509
+ # minimum temperature factor for stomatal conductance
510
+ mTmin = Tmin_factor(Tmin_C, Tmin_open, Tmin_closed)
511
+ check_distribution(mTmin, "mTmin")
512
+ results['mTmin'] = mTmin
513
+
514
+ # open vapor pressure deficit by land-cover
515
+ if VPD_open is None:
516
+ VPD_open = open_vapor_pressure_deficit(IGBP, geometry, IGBP_upsampling_resolution_meters)
517
+
518
+ check_distribution(VPD_open, "VPD_open")
519
+ results['VPD_open'] = VPD_open
520
+
521
+ # closed vapor pressure deficit by land-cover
522
+ if VPD_closed is None:
523
+ VPD_closed = closed_vapor_pressure_deficit(IGBP, geometry, IGBP_upsampling_resolution_meters)
524
+
525
+ check_distribution(VPD_closed, "VPD_closed")
526
+ results['VPD_closed'] = VPD_closed
527
+
528
+ # vapor pressure deficit factor for stomatal conductance
529
+ mVPD = calculate_VPD_factor(VPD_open, VPD_closed, VPD_Pa)
530
+ check_distribution(mVPD, "mVPD")
531
+ results['mVPD'] = mVPD
532
+
533
+ # stomatal conductance (gs1)
534
+ gs1 = CL * mTmin * mVPD * rcorr
535
+ check_distribution(gs1, "gs1")
536
+ results['gs1'] = gs1
537
+
538
+ # correct cuticular conductance constant to leaf cuticular conductance (Gcu) using correction factor (rcorr)
539
+ Gcu = CUTICULAR_CONDUCTANCE * rcorr
540
+ check_distribution(Gcu, "Gcu")
541
+ results['Gcu'] = Gcu
542
+
543
+ # canopy conductance
544
+ Cc = calculate_canopy_conductance(LAI, fwet, gl_sh, gs1, Gcu)
545
+ check_distribution(Cc, "Cc")
546
+ results['Cc'] = Cc
547
+
548
+ # surface resistance to evapotranspiration as inverse of canopy conductance (Cc)
549
+ rs = rt.clip(1.0 / Cc, 0.0, MAX_RESISTANCE)
550
+ check_distribution(rs, "rs")
551
+ results['rs'] = rs
552
+
553
+ # convective heat transfer as inverse of leaf conductance to sensible heat (gl_sh)
554
+ rh = 1.0 / gl_sh
555
+ check_distribution(rs, "rh")
556
+ results['rh'] = rs
557
+
558
+ # radiative heat transfer (rr)
559
+ rr = rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3)
560
+ check_distribution(rr, "rr")
561
+ results['rr'] = rr
562
+
563
+ # parallel resistance (ra)
564
+ ra = (rh * rr) / (rh + rr)
565
+ check_distribution(ra, "ra")
566
+ results["ra"] = ra
567
+
568
+ # transpiration
569
+ LE_canopy_Wm2 = calculate_transpiration(
570
+ delta_Pa=delta_Pa,
571
+ Ac=Ac,
572
+ rho_kgm3=rho_kgm3,
573
+ Cp_Jkg=Cp_Jkg,
574
+ VPD_Pa=VPD_Pa,
575
+ FVC=FVC,
576
+ ra=ra,
577
+ fwet=fwet,
578
+ rs=rs,
579
+ )
580
+
581
+ check_distribution(LE_canopy_Wm2, "LE_canopy_Wm2")
582
+ results['LE_canopy_Wm2'] = LE_canopy_Wm2
583
+
584
+ # soil evaporation
585
+ # aerodynamic resistant constraints from land-cover
586
+ if RBL_max is None:
587
+ RBL_max = maximum_boundary_layer_resistance(IGBP, geometry, IGBP_upsampling_resolution_meters)
588
+
589
+ check_distribution(RBL_max, "RBL_max")
590
+ results['RBL_max'] = RBL_max
591
+
592
+ if RBL_min is None:
593
+ RBL_min = minimum_boundary_layer_resistance(IGBP, geometry, IGBP_upsampling_resolution_meters)
594
+
595
+ check_distribution(RBL_min, "RBL_min")
596
+ results['RBL_min'] = RBL_min
597
+
598
+ # canopy aerodynamic resistance in seconds per meter
599
+ rtotc = calculate_canopy_aerodynamic_resistance(VPD_Pa, VPD_open, VPD_closed, RBL_max, RBL_min)
600
+ check_distribution(rtotc, "rtotc")
601
+ results['rtotc'] = rtotc
602
+
603
+ # total aerodynamic resistance
604
+ rtot = rcorr * rtotc
605
+ check_distribution(rtot, "rtot")
606
+ results['rtot'] = rtot
607
+
608
+ # resistance to radiative heat transfer through air
609
+ rrs = np.float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3))
610
+ check_distribution(rrs, "rrs")
611
+ results['rrs'] = rrs
612
+
613
+ # aerodynamic resistance at the soil surface
614
+ ras = (rtot * rrs) / (rtot + rrs)
615
+ check_distribution(ras, "ras")
616
+ results['ras'] = ras
617
+
618
+ # available radiation at the soil
619
+ Asoil = rt.clip((1.0 - FVC) * Rn_Wm2 - G_Wm2, 0.0, None)
620
+ check_distribution(Asoil, "Asoil")
621
+ results['Asoil'] = Asoil
622
+
623
+ # separate wet soil evaporation and potential soil evaporation
624
+ # wet soil evaporation
625
+ wet_soil_evaporation_Wm2 = calculate_wet_soil_evaporation(
626
+ delta_Pa=delta_Pa,
627
+ Asoil=Asoil,
628
+ rho_kgm3=rho_kgm3,
629
+ Cp_Jkg=Cp_Jkg,
630
+ FVC=FVC,
631
+ VPD_Pa=VPD_Pa,
632
+ ras=ras,
633
+ fwet=fwet,
634
+ rtot=rtot,
635
+ )
636
+
637
+ check_distribution(wet_soil_evaporation_Wm2, "wet_soil_evaporation_Wm2")
638
+ results['wet_soil_evaporation_Wm2'] = wet_soil_evaporation_Wm2
639
+
640
+ # potential soil evaporation
641
+ potential_soil_evaporation_Wm2 = calculate_potential_soil_evaporation(
642
+ delta_Pa=delta_Pa,
643
+ Asoil=Asoil,
644
+ rho=rho_kgm3,
645
+ Cp_Jkg=Cp_Jkg,
646
+ FVC=FVC,
647
+ VPD_Pa=VPD_Pa,
648
+ ras=ras,
649
+ fwet=fwet,
650
+ rtot=rtot,
651
+ )
652
+
653
+ check_distribution(potential_soil_evaporation_Wm2, "potential_soil_evaporation_Wm2")
654
+ results['potential_soil_evaporation_Wm2'] = potential_soil_evaporation_Wm2
655
+
656
+ # soil moisture constraint
657
+ fSM = calculate_fSM(RH, VPD_Pa)
658
+ check_distribution(fSM, "fSM")
659
+ results['fSM'] = fSM
660
+
661
+ # soil evaporation
662
+ LE_soil_Wm2 = rt.clip(wet_soil_evaporation_Wm2 + potential_soil_evaporation_Wm2 * fSM, 0.0, None)
663
+ LE_soil_Wm2 = rt.where(np.isnan(LE_soil_Wm2), 0.0, LE_soil_Wm2)
664
+ check_distribution(LE_soil_Wm2, "LE_soil_Wm2")
665
+ results['LE_soil_Wm2'] = LE_soil_Wm2
666
+
667
+ # sum partitions into total latent heat flux
668
+ LE_Wm2 = rt.clip(LE_interception_Wm2 + LE_canopy_Wm2 + LE_soil_Wm2, 0.0, Rn_Wm2)
669
+ check_distribution(LE_Wm2, "LE_Wm2")
670
+ results['LE_Wm2'] = LE_Wm2
671
+
672
+ # --- daylight Upscaling Option ---
673
+ if upscale_to_daylight and time_UTC is not None:
674
+ logger.info("started daylight ET upscaling (PMJPL)")
675
+
676
+ # Use new upscaling function from daylight_evapotranspiration
677
+ daylight_results = daylight_ET_from_instantaneous_LE(
678
+ LE_instantaneous_Wm2=LE_Wm2,
679
+ Rn_instantaneous_Wm2=Rn_Wm2,
680
+ G_instantaneous_Wm2=G_Wm2,
681
+ day_of_year=day_of_year,
682
+ time_UTC=time_UTC,
683
+ geometry=geometry
684
+ )
685
+ # Add all returned daylight results to output
686
+ results.update(daylight_results)
687
+
688
+ logger.info("completed daylight ET upscaling (PMJPL)")
689
+
690
+ return results
@@ -0,0 +1,11 @@
1
+ from .PMJPL_parameter_from_IGBP import PMJPL_parameter_from_IGBP
2
+ from rasters import Raster, RasterGeometry, VectorGeometry
3
+ from typing import Union
4
+
5
+ def open_minimum_temperature(IGBP: Union[Raster, int], geometry=None, IGBP_upsampling_resolution_meters=None):
6
+ return PMJPL_parameter_from_IGBP(
7
+ variable="Tmin_open",
8
+ IGBP=IGBP,
9
+ geometry=geometry,
10
+ IGBP_upsampling_resolution_meters=IGBP_upsampling_resolution_meters
11
+ )