PM-JPL 1.2.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 PM-JPL might be problematic. Click here for more details.

Files changed (44) hide show
  1. PMJPL/MCD12C1/MCD12C1.py +10 -0
  2. PMJPL/MCD12C1/__init__.py +1 -0
  3. PMJPL/PMJPL.py +406 -0
  4. PMJPL/SEBAL/SEBAL.py +45 -0
  5. PMJPL/SEBAL/__init__.py +1 -0
  6. PMJPL/VPD_factor.py +26 -0
  7. PMJPL/__init__.py +9 -0
  8. PMJPL/canopy_aerodynamic_resistance.py +31 -0
  9. PMJPL/canopy_conductance.py +36 -0
  10. PMJPL/constants.py +34 -0
  11. PMJPL/correctance_factor.py +17 -0
  12. PMJPL/downscaling/__init__.py +1 -0
  13. PMJPL/downscaling/downscaling.py +271 -0
  14. PMJPL/downscaling/linear_downscale.py +71 -0
  15. PMJPL/evapotranspiration_conversion/__init__.py +1 -0
  16. PMJPL/evapotranspiration_conversion/evapotranspiration_conversion.py +80 -0
  17. PMJPL/fwet.py +21 -0
  18. PMJPL/interception.py +41 -0
  19. PMJPL/meteorology_conversion/__init__.py +1 -0
  20. PMJPL/meteorology_conversion/meteorology_conversion.py +123 -0
  21. PMJPL/parameters.py +41 -0
  22. PMJPL/penman_monteith/__init__.py +1 -0
  23. PMJPL/penman_monteith/penman_monteith.py +20 -0
  24. PMJPL/potential_soil_evaporation.py +48 -0
  25. PMJPL/priestley_taylor/__init__.py +1 -0
  26. PMJPL/priestley_taylor/priestley_taylor.py +27 -0
  27. PMJPL/santanello/__init__.py +1 -0
  28. PMJPL/santanello/santanello.py +46 -0
  29. PMJPL/soil_heat_flux/__init__.py +1 -0
  30. PMJPL/soil_heat_flux/soil_heat_flux.py +62 -0
  31. PMJPL/soil_moisture_constraint.py +19 -0
  32. PMJPL/tmin_factor.py +45 -0
  33. PMJPL/transpiration.py +44 -0
  34. PMJPL/vegetation_conversion/__init__.py +1 -0
  35. PMJPL/vegetation_conversion/vegetation_conversion.py +47 -0
  36. PMJPL/verma_net_radiation/__init__.py +1 -0
  37. PMJPL/verma_net_radiation/verma_net_radiation.py +108 -0
  38. PMJPL/wet_canopy_resistance.py +21 -0
  39. PMJPL/wet_soil_evaporation.py +36 -0
  40. pm_jpl-1.2.0.dist-info/METADATA +84 -0
  41. pm_jpl-1.2.0.dist-info/RECORD +44 -0
  42. pm_jpl-1.2.0.dist-info/WHEEL +5 -0
  43. pm_jpl-1.2.0.dist-info/licenses/LICENSE +201 -0
  44. pm_jpl-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ from os.path import join, abspath, dirname
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster, RasterGeometry
5
+
6
+ def load_MCD12C1_IGBP(geometry: RasterGeometry = None) -> Raster:
7
+ filename = join(abspath(dirname(__file__)), "MCD12C1.A2019001.006.2020220162300.tif")
8
+ image = rt.Raster.open(filename, geometry=geometry, resampling="nearest")
9
+
10
+ return image
@@ -0,0 +1 @@
1
+ from .MCD12C1 import *
PMJPL/PMJPL.py ADDED
@@ -0,0 +1,406 @@
1
+ """
2
+ MOD16 model of evapotranspiration
3
+
4
+ This implementation follows the MOD16 Version 1.5 Collection 6 algorithm described in the MOD16 user's guide.
5
+ https://landweb.nascom.nasa.gov/QA_WWW/forPage/user_guide/MOD16UsersGuide2016.pdf
6
+
7
+ 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.
8
+ """
9
+ import logging
10
+ from typing import Callable, Dict, Union
11
+ from datetime import datetime
12
+ from os.path import join, abspath, dirname, expanduser
13
+
14
+ import numpy as np
15
+ import pandas as pd
16
+ from numpy import where, nan, exp, array, isnan, logical_and, clip, float32
17
+ import warnings
18
+
19
+ import rasters as rt
20
+
21
+ from rasters import Raster, RasterGrid, RasterGeometry
22
+
23
+ from .MCD12C1.MCD12C1 import load_MCD12C1_IGBP
24
+ from .parameters import MOD16_parameter_from_IGBP
25
+ from .evapotranspiration_conversion.evapotranspiration_conversion import lambda_Jkg_from_Ta_C
26
+ from .meteorology_conversion.meteorology_conversion import SVP_Pa_from_Ta_C, calculate_specific_heat, calculate_surface_pressure, celcius_to_kelvin
27
+ from .penman_monteith.penman_monteith import calculate_gamma
28
+ from .priestley_taylor.priestley_taylor import delta_Pa_from_Ta_C, delta_kPa_from_Ta_C
29
+
30
+ from .soil_heat_flux import calculate_soil_heat_flux
31
+ from .evapotranspiration_conversion import daily_ET_from_daily_LE
32
+ from .meteorology_conversion import kelvin_to_celsius, calculate_specific_humidity, calculate_air_density
33
+ from .vegetation_conversion.vegetation_conversion import FVC_from_NDVI, LAI_from_NDVI
34
+
35
+ from .constants import *
36
+
37
+ from .fwet import calculate_fwet
38
+ from .soil_moisture_constraint import calculate_fSM
39
+ from .tmin_factor import calculate_tmin_factor
40
+ from .correctance_factor import calculate_rcorr
41
+ from .VPD_factor import calculate_VPD_factor
42
+
43
+ from .canopy_conductance import calculate_canopy_conductance
44
+
45
+ from .wet_canopy_resistance import calculate_wet_canopy_resistance
46
+ from .canopy_aerodynamic_resistance import calculate_rtotc
47
+
48
+ from .wet_soil_evaporation import calculate_wet_soil_evaporation
49
+ from .potential_soil_evaporation import calculate_potential_soil_evaporation
50
+ from .interception import calculate_interception
51
+ from .transpiration import calculate_transpiration
52
+
53
+ __author__ = 'Qiaozhen Mu, Maosheng Zhao, Steven W. Running, Gregory Halverson'
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ DEFAULT_WORKING_DIRECTORY = "."
58
+ DEFAULT_MOD16_INTERMEDIATE = "MOD16_intermediate"
59
+
60
+ DEFAULT_OUTPUT_VARIABLES = [
61
+ 'LEi',
62
+ 'LEc',
63
+ 'LEs',
64
+ 'LE',
65
+ 'LE_daily',
66
+ 'ET_daily_kg'
67
+ ]
68
+
69
+ def PMJPL(
70
+ Rn: Union[Raster, np.ndarray],
71
+ G: Union[Raster, np.ndarray],
72
+ NDVI: Union[Raster, np.ndarray],
73
+ Ta_C: Union[Raster, np.ndarray],
74
+ Tmin_C: Union[Raster, np.ndarray],
75
+ RH: Union[Raster, np.ndarray],
76
+ IGBP: Union[Raster, np.ndarray],
77
+ Ps_Pa: Union[Raster, np.ndarray] = None,
78
+ elevation_m: Union[Raster, np.ndarray] = None,
79
+ delta_Pa: Union[Raster, np.ndarray] = None,
80
+ gamma_Jkg: Union[Raster, np.ndarray, float] = None) -> Dict[str, Raster]:
81
+ results = {}
82
+
83
+ # calculate leaf area index if it's not given
84
+ if LAI is None:
85
+ # calculate leaf area index from NDVI
86
+ LAI = LAI_from_NDVI(NDVI)
87
+
88
+ # calculate fraction of vegetation cover if it's not given
89
+ if FVC is None:
90
+ # calculate fraction of vegetation cover from NDVI
91
+ FVC = FVC_from_NDVI(NDVI)
92
+
93
+ # calculate surface air pressure if it's not given
94
+ if Ps_Pa is None:
95
+ # calculate surface air pressure is Pascal
96
+ Ps_Pa = calculate_surface_pressure(elevation_m=elevation_m, Ta_C=Ta_C)
97
+
98
+ # calculate Penman-Monteith/Priestley-Taylor delta term if it's not given
99
+ if delta_Pa is None:
100
+ # calculate Penman-Monteith/Priestley-Taylor delta term in Pascal per degree Celsius
101
+ delta_Pa = delta_Pa_from_Ta_C(Ta_C)
102
+
103
+ # calculate latent heat of vaporization if it's not given
104
+ if lambda_Jkg is None:
105
+ # calculate latent heat of vaporization in Joules per kilogram
106
+ lambda_Jkg = lambda_Jkg_from_Ta_C(Ta_C)
107
+
108
+ logger.info("calculating PM-MOD meteorology")
109
+
110
+ # calculate air temperature in Kelvin
111
+ Ta_K = celcius_to_kelvin(Ta_C)
112
+
113
+ # calculate saturation vapor pressure in Pascal from air temperature in Celsius
114
+ SVP_Pa = SVP_Pa_from_Ta_C(Ta_C)
115
+
116
+ # calculate vapor pressure in Pascal from releative humidity and saturation vapor pressure
117
+ Ea_Pa = RH * SVP_Pa
118
+
119
+ # specific humidity of air
120
+ # as a ratio of kilograms of water to kilograms of air and water
121
+ # from surface pressure and actual water vapor pressure
122
+ specific_humidity = calculate_specific_humidity(Ea_Pa, Ps_Pa)
123
+ results['specific_humidity'] = specific_humidity
124
+
125
+ # calculate air density (rho) in kilograms per cubic meter
126
+ rho_kgm3 = calculate_air_density(Ps_Pa, Ta_K, specific_humidity)
127
+ results["rho_kgm3"] = rho_kgm3
128
+
129
+ # calculate specific heat capacity of the air (Cp)
130
+ # in joules per kilogram per kelvin
131
+ # from specific heat of water vapor (CPW)
132
+ # and specific heat of dry air (CPD)
133
+ Cp_Jkg = calculate_specific_heat(specific_humidity)
134
+ results["Cp"] = Cp_Jkg
135
+
136
+ # calculate delta term if it's not given
137
+ if delta_Pa is None:
138
+ # slope of saturation vapor pressure curve in Pascal per degree
139
+ delta_Pa = delta_Pa_from_Ta_C(Ta_C)
140
+
141
+ results["delta_Pa"] = delta_Pa
142
+
143
+ # calculate gamma term if it's not given
144
+ if gamma_Jkg is None:
145
+ # calculate psychrometric gamma in Joules per kilogram
146
+ gamma_Jkg = calculate_gamma(
147
+ Ta_C=Ta_C,
148
+ Ps_Pa=Ps_Pa,
149
+ Cp_Jkg=Cp_Jkg
150
+ )
151
+
152
+ # vapor pressure deficit in Pascal
153
+ VPD_Pa = rt.clip(SVP_Pa - Ea_Pa, 0.0, None)
154
+
155
+ # calculate relative surface wetness (fwet)
156
+ # from relative humidity
157
+ fwet = calculate_fwet(RH)
158
+ results['fwet'] = fwet
159
+
160
+ logger.info("calculating PM-MOD resistances")
161
+
162
+ # query leaf conductance to sensible heat (gl_sh) in seconds per meter
163
+ gl_sh = MOD16_parameter_from_IGBP(
164
+ variable="gl_sh",
165
+ IGBP=IGBP
166
+ )
167
+
168
+ results['gl_sh'] = gl_sh
169
+
170
+ # calculate wet canopy resistance to sensible heat (rhc) in seconds per meter
171
+ # from leaf conductance to sensible heat (gl_sh), LAI, and relative surface wetness (fwet)
172
+ rhc = calculate_wet_canopy_resistance(gl_sh, LAI, fwet)
173
+ results['rhc'] = rhc
174
+
175
+ # calculate resistance to radiative heat transfer through air (rrc)
176
+ rrc = float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3.0))
177
+ results['rrc'] = rrc
178
+
179
+ # calculate aerodynamic resistance (rhrc)
180
+ # in seconds per meter
181
+ # from wet canopy resistance to sensible heat
182
+ # and resistance to radiative heat transfer through air
183
+ rhrc = float32((rhc * rrc) / (rhc + rrc))
184
+ results['rhrc'] = rhrc
185
+
186
+ # calculate leaf conductance to evaporated water vapor (gl_e_wv)
187
+ gl_e_wv = MOD16_parameter_from_IGBP(
188
+ variable="gl_e_wv",
189
+ IGBP=IGBP
190
+ )
191
+
192
+ results['gl_e_wv'] = gl_e_wv
193
+
194
+ rvc = calculate_wet_canopy_resistance(gl_e_wv, LAI, fwet)
195
+ results['rvc'] = rvc
196
+
197
+ # caluclate available radiation to the canopy (Ac)
198
+ # in watts per square meter
199
+ # this is the same as net radiation to the canopy in PT-JPL
200
+ Ac = Rn * FVC
201
+ results['Ac'] = Ac
202
+
203
+ # calculate wet latent heat flux (LEi)
204
+ # in watts per square meter
205
+ LEi = calculate_interception(
206
+ delta_Pa=delta_Pa,
207
+ Ac=Ac,
208
+ rho=rho_kgm3,
209
+ Cp=Cp_Jkg,
210
+ VPD_Pa=VPD_Pa,
211
+ FVC=FVC,
212
+ rhrc=rhrc,
213
+ fwet=fwet,
214
+ rvc=rvc,
215
+ gamma_Jkg=gamma_Jkg
216
+ )
217
+
218
+ results['LEi'] = LEi
219
+
220
+ # calculate correctance factor (rcorr)
221
+ # for stomatal and cuticular conductances
222
+ # from surface pressure and air temperature
223
+ rcorr = calculate_rcorr(Ps_Pa, Ta_K)
224
+ results['rcorr'] = rcorr
225
+
226
+ # query biome-specific mean potential stomatal conductance per unit leaf area
227
+ CL = MOD16_parameter_from_IGBP(
228
+ variable="CL",
229
+ IGBP=IGBP
230
+ )
231
+
232
+ results['CL'] = CL
233
+
234
+ # query open minimum temperature by land-cover
235
+ tmin_open = MOD16_parameter_from_IGBP(
236
+ variable="tmin_open",
237
+ IGBP=IGBP
238
+ )
239
+
240
+ results['tmin_open'] = tmin_open
241
+
242
+ # query closed minimum temperature by land-cover
243
+ tmin_close = MOD16_parameter_from_IGBP(
244
+ variable="tmin_close",
245
+ IGBP=IGBP
246
+ )
247
+
248
+ results['tmin_close'] = tmin_close
249
+
250
+ # calculate minimum temperature factor for stomatal conductance
251
+ mTmin = calculate_tmin_factor(Tmin_C, tmin_open, tmin_close, IGBP)
252
+ results['mTmin'] = mTmin
253
+
254
+ # query open vapor pressure deficit by land-cover
255
+ VPD_open = MOD16_parameter_from_IGBP(
256
+ variable="VPD_open",
257
+ IGBP=IGBP
258
+ )
259
+
260
+ results['vpd_open'] = VPD_open
261
+
262
+ # query closed vapor pressure deficit by land-cover
263
+ VPD_close = MOD16_parameter_from_IGBP(
264
+ variable="VPD_close",
265
+ IGBP=IGBP
266
+ )
267
+
268
+ results['vpd_close'] = VPD_close
269
+
270
+ # calculate vapor pressure deficit factor for stomatal conductance
271
+ mVPD = calculate_VPD_factor(VPD_open, VPD_close, VPD_Pa)
272
+ results['mVPD'] = mVPD
273
+
274
+ # calculate stomatal conductance (gs1)
275
+ gs1 = CL * mTmin * mVPD * rcorr
276
+ results['gs1'] = gs1
277
+
278
+ # correct cuticular conductance constant to leaf cuticular conductance (Gcu) using correction factor (rcorr)
279
+ Gcu = CUTICULAR_CONDUCTANCE * rcorr
280
+ results['Gcu'] = Gcu
281
+
282
+ # calculate canopy conductance
283
+ # equivalent to g_canopy
284
+ Cc = calculate_canopy_conductance(LAI, fwet, gl_sh, gs1, Gcu)
285
+ results['Cc'] = Cc
286
+
287
+ # calculate surface resistance to evapotranspiration as inverse of canopy conductance (Cc)
288
+ rs = rt.clip(1.0 / Cc, 0.0, MAX_RESISTANCE)
289
+ results['rs'] = rs
290
+
291
+ # calculate convective heat transfer as inverse of leaf conductance to sensible heat (gl_sh)
292
+ rh = 1.0 / gl_sh
293
+ results['rh'] = rs
294
+
295
+ # calculate radiative heat transfer (rr)
296
+ rr = rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3)
297
+ results['rr'] = rr
298
+
299
+ # calculate parallel resistance (ra)
300
+ # MOD16 user guide is not clear about what to call this
301
+ ra = (rh * rr) / (rh + rr)
302
+ results["ra"] = ra
303
+
304
+ # calculate transpiration
305
+ LEc = calculate_transpiration(
306
+ delta_Pa=delta_Pa,
307
+ Ac=Ac,
308
+ rho=rho_kgm3,
309
+ Cp_Jkg=Cp_Jkg,
310
+ VPD_Pa=VPD_Pa,
311
+ FVC=FVC,
312
+ ra=ra,
313
+ fwet=fwet,
314
+ rs=rs,
315
+ gamma_Jkg=gamma_Jkg
316
+ )
317
+
318
+ results['LEc'] = LEc
319
+
320
+ # soil evaporation
321
+
322
+ # query aerodynamic resistant constraints from land-cover
323
+ rbl_max = MOD16_parameter_from_IGBP(
324
+ variable="rbl_max",
325
+ IGBP=IGBP
326
+ )
327
+
328
+ results['rbl_max'] = rbl_max
329
+
330
+ rbl_min = MOD16_parameter_from_IGBP(
331
+ variable="rbl_min",
332
+ IGBP=IGBP
333
+ )
334
+
335
+ results['rbl_min'] = rbl_min
336
+
337
+ rtotc = calculate_rtotc(VPD_Pa, VPD_open, VPD_close, rbl_max, rbl_min)
338
+ results['rtotc'] = rtotc
339
+
340
+ # calculate total aerodynamic resistance
341
+ # by applying correction to total canopy resistance
342
+ rtot = rcorr * rtotc
343
+ results['rtot'] = rtot
344
+
345
+ # calculate resistance to radiative heat transfer through air
346
+ rrs = float32(rho_kgm3 * Cp_Jkg / (4.0 * SIGMA * Ta_K ** 3))
347
+ results['rrs'] = rrs
348
+
349
+ # calculate aerodynamic resistance at the soil surface
350
+ ras = (rtot * rrs) / (rtot + rrs)
351
+ results['ras'] = ras
352
+
353
+ # calculate available radiation at the soil
354
+ Asoil = rt.clip((1.0 - FVC) * Rn - G, 0.0, None)
355
+ results['Asoil'] = Asoil
356
+
357
+ # separate wet soil evaporation and potential soil evaporation
358
+
359
+ # calculate wet soil evaporation
360
+ LE_soil_wet = calculate_wet_soil_evaporation(
361
+ delta_Pa=delta_Pa,
362
+ Asoil=Asoil,
363
+ rho=rho_kgm3,
364
+ Cp_Jkg=Cp_Jkg,
365
+ FVC=FVC,
366
+ VPD_Pa=VPD_Pa,
367
+ ras=ras,
368
+ fwet=fwet,
369
+ rtot=rtot,
370
+ gamma_Jkg=gamma_Jkg
371
+ )
372
+
373
+ results['LE_soil_wet'] = LE_soil_wet
374
+
375
+ # calculate potential soil evaporation
376
+ LE_soil_pot = calculate_potential_soil_evaporation(
377
+ delta_Pa=delta_Pa,
378
+ Asoil=Asoil,
379
+ rho=rho_kgm3,
380
+ Cp_Jkg=Cp_Jkg,
381
+ FVC=FVC,
382
+ VPD_Pa=VPD_Pa,
383
+ ras=ras,
384
+ fwet=fwet,
385
+ rtot=rtot,
386
+ gamma_Jkg=gamma_Jkg
387
+ )
388
+
389
+ results['LE_soil_pot'] = LE_soil_pot
390
+
391
+ # calculate soil moisture constraint
392
+ fSM = calculate_fSM(RH, VPD_Pa)
393
+ results['fSM'] = fSM
394
+
395
+ # calculate soil evaporation
396
+ LEs = rt.clip(LE_soil_wet + LE_soil_pot * fSM, 0.0, None)
397
+
398
+ # fill soil evaporation with zero
399
+ LEs = rt.where(isnan(LEs), 0.0, LEs)
400
+ results['LEs'] = LEs
401
+
402
+ # sum partitions into total latent heat flux
403
+ LE = rt.clip(LEi + LEc + LEs, 0.0, Rn)
404
+ results['LE'] = LE
405
+
406
+ return results
PMJPL/SEBAL/SEBAL.py ADDED
@@ -0,0 +1,45 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import rasters as rt
4
+
5
+ def calculate_soil_heat_flux(Rn: np.ndarray, ST_C: np.ndarray, NDVI: np.ndarray, albedo: np.ndarray) -> np.ndarray:
6
+ """
7
+ This function calculates the soil heat flux (G) in the Surface Energy Balance Algorithm for Land (SEBAL) model.
8
+ The formula used in the function is a simplification of the more complex relationship between these variables in the energy balance at the surface.
9
+
10
+ Parameters:
11
+ Rn (np.ndarray): Net radiation at the surface.
12
+ ST_C (np.ndarray): Surface temperature in Celsius.
13
+ NDVI (np.ndarray): Normalized Difference Vegetation Index, indicating the presence and condition of vegetation.
14
+ albedo (np.ndarray): Measure of the diffuse reflection of solar radiation.
15
+
16
+ Returns:
17
+ np.ndarray: The soil heat flux (G), a key component in the energy balance.
18
+
19
+ Reference:
20
+ "Evapotranspiration Estimation Based on Remote Sensing and the SEBAL Model in the Bosten Lake Basin of China" [^1^][1]
21
+ """
22
+ # Empirical coefficients used in the calculation
23
+ coeff1 = 0.0038
24
+ coeff2 = 0.0074
25
+
26
+ # Vegetation cover correction factor
27
+ NDVI_correction = 1 - 0.98 * NDVI ** 4
28
+
29
+ # Calculation of the soil heat flux (G)
30
+ G = Rn * ST_C * (coeff1 + coeff2 * albedo) * NDVI_correction
31
+
32
+ G = rt.clip(G, 0, None)
33
+
34
+ return G
35
+
36
+ def process_SEBAL_G_table(input_df: pd.DataFrame) -> pd.DataFrame:
37
+ Rn = input_df.Rn
38
+ ST_C = input_df.ST_C
39
+ NDVI = input_df.NDVI
40
+ albedo = input_df.albedo
41
+ G = calculate_soil_heat_flux(Rn=Rn, ST_C=ST_C, NDVI=NDVI, albedo=albedo)
42
+ output_df = input_df.copy()
43
+ output_df["G"] = G
44
+
45
+ return output_df
@@ -0,0 +1 @@
1
+ from .SEBAL import *
PMJPL/VPD_factor.py ADDED
@@ -0,0 +1,26 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster
5
+
6
+ def calculate_VPD_factor(
7
+ VPD_open: Union[Raster, np.ndarray],
8
+ VPD_close: Union[Raster, np.ndarray],
9
+ VPD: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
10
+ """
11
+ Calculate the VPD factor based on the open and closed vapor pressure deficit
12
+
13
+ Parameters:
14
+ VPD_open (Union[Raster, np.ndarray]): open vapor pressure deficit
15
+ VPD_close (Union[Raster, np.ndarray]): closed vapor pressure deficit
16
+ VPD (Union[Raster, np.ndarray]): vapor pressure deficit
17
+
18
+ Returns:
19
+ Union[Raster, np.ndarray]: VPD factor
20
+ """
21
+ # calculate VPD factor using queried open and closed VPD
22
+ mVPD = rt.where(VPD <= VPD_open, 1.0, np.nan)
23
+ mVPD = rt.where(rt.logical_and(VPD_open < VPD, VPD < VPD_close), (VPD_close - VPD) / (VPD_close - VPD_open), mVPD)
24
+ mVPD = rt.where(VPD >= VPD_close, 0.0, mVPD)
25
+
26
+ return mVPD
PMJPL/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ from .PMJPL import *
2
+
3
+ from os.path import join, abspath, dirname
4
+
5
+ with open(join(abspath(dirname(__file__)), "version.txt")) as f:
6
+ version = f.read()
7
+
8
+ __version__ = version
9
+ __author__ = "Gregory H. Halverson"
@@ -0,0 +1,31 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster
5
+
6
+ def calculate_rtotc(
7
+ VPD: Union[Raster, np.ndarray],
8
+ vpd_open: Union[Raster, np.ndarray],
9
+ vpd_close: Union[Raster, np.ndarray],
10
+ rbl_max: Union[Raster, np.ndarray],
11
+ rbl_min: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
12
+ """
13
+ calculates total aerodynamic resistance to the canopy
14
+ from vapor pressure deficit and biome-specific constraints.
15
+ :param VPD: vapor pressure deficit in Pascal
16
+ :param vpd_open: vapor pressure deficit when stomata are open in Pascal
17
+ :param vpd_close: vapor pressure deficit when stomata are closed in Pascal
18
+ :param rbl_max: maximum boundary layer resistance in seconds per meter
19
+ :param rbl_min: minimum boundary layer resistance in seconds per meter
20
+ :return: aerodynamic resistance to the canopy in seconds per meter
21
+ """
22
+ rtotc = rt.where(VPD <= vpd_open, rbl_max, np.nan)
23
+ rtotc = rt.where(VPD >= vpd_close, rbl_min, rtotc)
24
+
25
+ rtotc = rt.where(
26
+ rt.logical_and(vpd_open < VPD, VPD < vpd_close),
27
+ rbl_min + (rbl_max - rbl_min) * (vpd_close - VPD) / (vpd_close - vpd_open),
28
+ rtotc
29
+ )
30
+
31
+ return rtotc
@@ -0,0 +1,36 @@
1
+ import numpy as np
2
+ from typing import Union
3
+ from .constants import MAX_RESISTANCE
4
+ import rasters as rt
5
+ from rasters import Raster
6
+
7
+ def calculate_canopy_conductance(
8
+ LAI: Union[Raster, np.ndarray],
9
+ fwet: Union[Raster, np.ndarray],
10
+ gl_sh: Union[Raster, np.ndarray],
11
+ gs1: Union[Raster, np.ndarray],
12
+ Gcu: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
13
+ """
14
+ calculate canopy conductance (Cc)
15
+ Canopy conductance (Cc) to transpired water vapor per unit LAI is derived from stomatal
16
+ and cuticular conductances in parallel with each other, and both in series with leaf boundary layer
17
+ conductance (Thornton, 1998; Running & Kimball, 2005). In the case of plant transpiration,
18
+ surface conductance is equivalent to the canopy conductance (Cc), and hence surface resistance
19
+ (rs) is the inverse of canopy conductance (Cc).
20
+ :param LAI: leaf-area index
21
+ :param fwet: relative surface wetness
22
+ :param gl_sh: leaf boundary layer conductance
23
+ :param gs1: stomatal conductance
24
+ :param Gcu: cuticular conductance
25
+ :return: canopy conductance
26
+ """
27
+ # noinspection PyTypeChecker
28
+ Cc = rt.where(
29
+ rt.logical_and(LAI > 0.0, (1.0 - fwet) > 0.0),
30
+ gl_sh * (gs1 + Gcu) / (gs1 + gl_sh + Gcu) * LAI * (1.0 - fwet),
31
+ 0.0
32
+ )
33
+
34
+ Cc = rt.clip(Cc, 1.0 / MAX_RESISTANCE, None)
35
+
36
+ return Cc
PMJPL/constants.py ADDED
@@ -0,0 +1,34 @@
1
+ from .priestley_taylor import GAMMA_PA
2
+
3
+ # TODO need to defend picking arbitrary maximum to avoid extreme values
4
+ MIN_RESISTANCE = 0.0
5
+ MAX_RESISTANCE = 2000.0
6
+
7
+ # gas constant for dry air in joules per kilogram per kelvin
8
+ RD = 286.9
9
+
10
+ # gas constant for moist air in joules per kilogram per kelvin
11
+ RW = 461.5
12
+
13
+ # specific heat of water vapor in joules per kilogram per kelvin
14
+ CPW = 1846.0
15
+
16
+ # specific heat of dry air in joules per kilogram per kelvin
17
+ CPD = 1005.0
18
+
19
+ # psychrometric constant in Pascal per kelvin
20
+ # GAMMA = 67.0
21
+
22
+ # Stefan Boltzmann constant
23
+ SIGMA = 5.678e-8
24
+
25
+ # cuticular conductance in meters per second
26
+ CUTICULAR_CONDUCTANCE = 0.00001
27
+
28
+ # VPD factor in Pascal for soil moisture constraint
29
+ # MOD16 uses 200, but PT-JPL uses 1000
30
+ BETA = 200
31
+
32
+ RH_THRESHOLD = 0.7
33
+
34
+ MIN_FWET = 0.0001
@@ -0,0 +1,17 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster
5
+
6
+ def calculate_rcorr(
7
+ Ps_Pa: Union[Raster, np.ndarray],
8
+ Ta_K: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
9
+ """
10
+ calculates correctance factor (rcorr)
11
+ for stomatal and cuticular conductances
12
+ from surface pressure and air temperature.
13
+ :param Ps_Pa: surface pressure in Pascal
14
+ :param Ta_K: near-surface air temperature in kelvin
15
+ :return: correctance factor (rcorr)
16
+ """
17
+ return 1.0 / ((101300.0 / Ps_Pa) * (Ta_K / 293.15) ** 1.75)
@@ -0,0 +1 @@
1
+ from .downscaling import *