ETo 2.0.0__tar.gz → 2.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ETo
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Class and functions to estimate reference ET
5
5
  Project-URL: Homepage, https://github.com/mullenkamp/ETo
6
6
  Project-URL: Documentation, https://mullenkamp.github.io/ETo/
@@ -24,11 +24,21 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  # ETo
26
26
 
27
- **A Python package for calculating reference evapotranspiration**
27
+ **A Python package for calculating reference and crop evapotranspiration**
28
28
 
29
29
  ---
30
30
 
31
- ETo calculates reference evapotranspiration (ETo) using the [UN-FAO 56 paper](http://www.fao.org/docrep/X0490E/X0490E00.htm) methodology. It estimates missing meteorological parameters from available data and supports both daily and hourly time steps.
31
+ ETo calculates reference evapotranspiration (ETo) and crop evapotranspiration (ETc) using the [UN-FAO 56 paper](http://www.fao.org/docrep/X0490E/X0490E00.htm) methodology. It estimates missing meteorological parameters from available data and supports daily, hourly, and monthly time steps.
32
+
33
+ ## Features
34
+
35
+ - **FAO 56 Penman-Monteith** reference ET (short and tall reference crops)
36
+ - **Hargreaves** simplified daily ET
37
+ - **Crop evapotranspiration** — single Kc, dual Kc (Kcb + Ke), and water stress adjustment (Ks)
38
+ - **Built-in crop coefficients** for 23 major crops (FAO 56 Table 12)
39
+ - **Automatic parameter estimation** with quality tracking
40
+ - **Input validation** with configurable warnings
41
+ - **Derived outputs** — VPD, T_dew (back-calculated), clear-sky radiation
32
42
 
33
43
  ## Documentation
34
44
 
@@ -61,5 +71,13 @@ data = {
61
71
  dates = np.arange('2020-01-01', '2020-01-04', dtype='datetime64[D]')
62
72
 
63
73
  et = ETo(data, freq='D', z_msl=500, lat=-43.6, dates=dates)
74
+
75
+ # Reference ET
64
76
  eto = et.eto_fao() # np.ndarray of ETo in mm
77
+
78
+ # Crop ET (single Kc)
79
+ etc = et.etc(crop='maize_grain', stage='mid') # ETc = Kc × ETo
80
+
81
+ # Tall reference crop (ASCE alfalfa)
82
+ etr = et.eto_fao(ref_crop='tall')
65
83
  ```
eto-2.2.0/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # ETo
2
+
3
+ **A Python package for calculating reference and crop evapotranspiration**
4
+
5
+ ---
6
+
7
+ ETo calculates reference evapotranspiration (ETo) and crop evapotranspiration (ETc) using the [UN-FAO 56 paper](http://www.fao.org/docrep/X0490E/X0490E00.htm) methodology. It estimates missing meteorological parameters from available data and supports daily, hourly, and monthly time steps.
8
+
9
+ ## Features
10
+
11
+ - **FAO 56 Penman-Monteith** reference ET (short and tall reference crops)
12
+ - **Hargreaves** simplified daily ET
13
+ - **Crop evapotranspiration** — single Kc, dual Kc (Kcb + Ke), and water stress adjustment (Ks)
14
+ - **Built-in crop coefficients** for 23 major crops (FAO 56 Table 12)
15
+ - **Automatic parameter estimation** with quality tracking
16
+ - **Input validation** with configurable warnings
17
+ - **Derived outputs** — VPD, T_dew (back-calculated), clear-sky radiation
18
+
19
+ ## Documentation
20
+
21
+ Full documentation is available at [mullenkamp.github.io/ETo](https://mullenkamp.github.io/ETo/).
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install eto
27
+ ```
28
+
29
+ or:
30
+
31
+ ```bash
32
+ conda install -c mullenkamp eto
33
+ ```
34
+
35
+ The only dependency is [NumPy](https://numpy.org/).
36
+
37
+ ## Quick Example
38
+
39
+ ```python
40
+ import numpy as np
41
+ from eto import ETo
42
+
43
+ data = {
44
+ 'T_min': np.array([10.0, 12.0, 8.0]),
45
+ 'T_max': np.array([25.0, 28.0, 22.0]),
46
+ }
47
+ dates = np.arange('2020-01-01', '2020-01-04', dtype='datetime64[D]')
48
+
49
+ et = ETo(data, freq='D', z_msl=500, lat=-43.6, dates=dates)
50
+
51
+ # Reference ET
52
+ eto = et.eto_fao() # np.ndarray of ETo in mm
53
+
54
+ # Crop ET (single Kc)
55
+ etc = et.etc(crop='maize_grain', stage='mid') # ETc = Kc × ETo
56
+
57
+ # Tall reference crop (ASCE alfalfa)
58
+ etr = et.eto_fao(ref_crop='tall')
59
+ ```
@@ -2,4 +2,4 @@ from eto.core import ETo
2
2
  import eto.datasets
3
3
  import eto.methods
4
4
 
5
- __version__ = '2.0.0'
5
+ __version__ = '2.2.0'
@@ -3,9 +3,12 @@
3
3
  Class to estimate reference ET (ETo) from the FAO 56 paper using a minimum of T_min and T_max for daily estimates and T_mean and RH_mean for hourly, but utilizing the maximum number of available met parameters. The function prioritizes the estimation of specific parameters based on the available input data.
4
4
  """
5
5
  import numpy as np
6
+ from eto.util import is_hourly
6
7
  from eto.param_est import param_est
7
8
  from eto.methods.ETo import eto_fao
8
9
  from eto.methods.hargreaves import hargreaves
10
+ from eto.crop_coefficients import etc, etc_adj, kc_adjust
11
+ from eto.methods.dual_kc import etc_dual
9
12
 
10
13
 
11
14
  class ETo(object):
@@ -21,18 +24,20 @@ class ETo(object):
21
24
  T_min, T_max, T_mean, T_dew, RH_min, RH_max, RH_mean, n_sun, U_z,
22
25
  P, e_a). All arrays must have the same length.
23
26
  freq : str
24
- Time frequency: 'D' for daily, 'H' or 'h' for hourly.
27
+ Time frequency: 'D' for daily, 'h' (or 'H') for hourly, 'M' for monthly.
25
28
  day_of_year : np.ndarray of int, or None
26
29
  Day of year (1-366). Required if dates is not provided.
27
30
  hour : np.ndarray of int, or None
28
31
  Hour of day (0-23). Required for hourly frequency if dates is not provided.
29
32
  dates : np.ndarray of datetime64, or None
30
33
  Datetime array. Used to derive day_of_year and hour if they are not provided.
34
+ time_label, rs_rso_init
35
+ Hourly only; see :func:`eto.param_est.param_est`.
31
36
  """
32
37
 
33
38
  def __init__(self, data=None, freq='D', z_msl=None, lat=None, lon=None, TZ_lon=None,
34
39
  z_u=2, K_rs=0.16, a_s=0.25, b_s=0.5, alb=0.23,
35
- day_of_year=None, hour=None, dates=None):
40
+ day_of_year=None, hour=None, dates=None, validate=True, time_label='start', rs_rso_init=None):
36
41
 
37
42
  if data is None:
38
43
  pass
@@ -49,7 +54,7 @@ class ETo(object):
49
54
  if dates is not None:
50
55
  dates = np.asarray(dates)
51
56
  day_of_year = (dates.astype('datetime64[D]') - dates.astype('datetime64[Y]')).astype(int) + 1
52
- if 'h' in freq.lower():
57
+ if is_hourly(freq):
53
58
  hour = (dates - dates.astype('datetime64[D]')).astype('timedelta64[h]').astype(int)
54
59
  elif day_of_year is not None:
55
60
  day_of_year = np.asarray(day_of_year)
@@ -59,7 +64,7 @@ class ETo(object):
59
64
  # Validate temporal array lengths
60
65
  if len(day_of_year) != n:
61
66
  raise ValueError('day_of_year length must match data array length')
62
- if 'h' in freq.lower():
67
+ if is_hourly(freq):
63
68
  if hour is None:
64
69
  raise ValueError('hour array or dates must be provided for hourly frequency')
65
70
  hour = np.asarray(hour)
@@ -67,10 +72,14 @@ class ETo(object):
67
72
  raise ValueError('hour length must match data array length')
68
73
 
69
74
  self.param_est(data, freq, z_msl, lat, lon, TZ_lon, z_u, K_rs, a_s, b_s, alb,
70
- day_of_year=day_of_year, hour=hour)
75
+ day_of_year=day_of_year, hour=hour, validate=validate,
76
+ time_label=time_label, rs_rso_init=rs_rso_init)
71
77
 
72
78
 
73
79
  ### Add in the ETo methods
74
80
  ETo.param_est = param_est
75
81
  ETo.eto_fao = eto_fao
76
82
  ETo.eto_hargreaves = hargreaves
83
+ ETo.etc = etc
84
+ ETo.etc_dual = etc_dual
85
+ ETo.etc_adj = etc_adj
@@ -0,0 +1,123 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Crop coefficient functions for ETc estimation (FAO 56).
4
+ """
5
+ import numpy as np
6
+
7
+
8
+ # FAO 56 Table 12 — single crop coefficients (Kc_ini, Kc_mid, Kc_end)
9
+ KC_TABLE = {
10
+ 'alfalfa': (0.40, 1.20, 1.15),
11
+ 'barley': (0.30, 1.15, 0.25),
12
+ 'beans_dry': (0.40, 1.15, 0.35),
13
+ 'beans_green': (0.50, 1.05, 0.90),
14
+ 'cabbage': (0.70, 1.05, 0.95),
15
+ 'cotton': (0.35, 1.15, 0.70),
16
+ 'grape': (0.30, 0.85, 0.45),
17
+ 'groundnut': (0.40, 1.15, 0.60),
18
+ 'lettuce': (0.70, 1.00, 0.95),
19
+ 'maize_grain': (0.30, 1.20, 0.60),
20
+ 'maize_sweet': (0.30, 1.15, 1.05),
21
+ 'millet': (0.30, 1.00, 0.30),
22
+ 'onion_dry': (0.70, 1.05, 0.75),
23
+ 'peanut': (0.40, 1.15, 0.60),
24
+ 'potato': (0.50, 1.15, 0.75),
25
+ 'rice': (1.05, 1.20, 0.90),
26
+ 'sorghum': (0.30, 1.00, 0.55),
27
+ 'soybean': (0.40, 1.15, 0.50),
28
+ 'sugarcane': (0.40, 1.25, 0.75),
29
+ 'sunflower': (0.35, 1.15, 0.35),
30
+ 'tomato': (0.60, 1.15, 0.80),
31
+ 'wheat_winter': (0.70, 1.15, 0.25),
32
+ 'wheat_spring': (0.30, 1.15, 0.25),
33
+ }
34
+
35
+
36
+ def kc_adjust(Kc, u2, RH_min, h):
37
+ """
38
+ Climate adjustment of Kc for non-standard conditions (FAO 56 Eq 62).
39
+
40
+ Parameters
41
+ ----------
42
+ Kc : float
43
+ Tabulated crop coefficient (Kc_mid or Kc_end).
44
+ u2 : float
45
+ Mean daily wind speed at 2 m (m/s).
46
+ RH_min : float
47
+ Mean minimum relative humidity (%).
48
+ h : float
49
+ Mean plant height (m).
50
+
51
+ Returns
52
+ -------
53
+ float
54
+ Adjusted Kc.
55
+ """
56
+ return Kc + (0.04*(u2 - 2) - 0.004*(RH_min - 45)) * (h/3)**0.3
57
+
58
+
59
+ def etc(self, Kc=None, crop=None, stage=None, **kwargs):
60
+ """
61
+ Crop evapotranspiration ETc = Kc * ETo (FAO 56 Eq 58, single Kc).
62
+
63
+ Parameters
64
+ ----------
65
+ Kc : float or np.ndarray, optional
66
+ Crop coefficient. If not provided, looked up from KC_TABLE.
67
+ crop : str, optional
68
+ Crop name (key in KC_TABLE). Required if Kc is not provided.
69
+ stage : str, optional
70
+ Growth stage: 'ini', 'mid', or 'end'. Required if crop is provided
71
+ without Kc.
72
+ **kwargs
73
+ Passed to eto_fao().
74
+
75
+ Returns
76
+ -------
77
+ np.ndarray
78
+ ETc in mm.
79
+ """
80
+ if Kc is None:
81
+ if crop is None:
82
+ raise ValueError('Either Kc or crop must be provided')
83
+ crop_lower = crop.lower()
84
+ if crop_lower not in KC_TABLE:
85
+ raise ValueError(f'Unknown crop: {crop}. Available: {sorted(KC_TABLE.keys())}')
86
+ if stage is None:
87
+ raise ValueError('stage must be provided when using crop lookup')
88
+ stage_idx = {'ini': 0, 'mid': 1, 'end': 2}
89
+ if stage not in stage_idx:
90
+ raise ValueError(f"stage must be 'ini', 'mid', or 'end', got '{stage}'")
91
+ Kc = KC_TABLE[crop_lower][stage_idx[stage]]
92
+
93
+ return np.asarray(Kc) * self.eto_fao(**kwargs)
94
+
95
+
96
+ def etc_adj(self, Kc, TAW, Dr, p=0.5, **kwargs):
97
+ """
98
+ Adjusted ETc under water stress (FAO 56 Eq 84).
99
+
100
+ ETc_adj = Ks * Kc * ETo
101
+
102
+ Parameters
103
+ ----------
104
+ Kc : float or np.ndarray
105
+ Crop coefficient.
106
+ TAW : float or np.ndarray
107
+ Total available water in root zone (mm).
108
+ Dr : float or np.ndarray
109
+ Root zone depletion (mm).
110
+ p : float
111
+ Depletion fraction for no stress (default 0.5).
112
+ **kwargs
113
+ Passed to eto_fao().
114
+
115
+ Returns
116
+ -------
117
+ np.ndarray
118
+ ETc_adj in mm.
119
+ """
120
+ TAW = np.asarray(TAW, dtype=np.float64)
121
+ Dr = np.asarray(Dr, dtype=np.float64)
122
+ Ks = np.clip((TAW - Dr) / ((1 - p) * TAW), 0, 1)
123
+ return Ks * np.asarray(Kc) * self.eto_fao(**kwargs)
@@ -0,0 +1,61 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Function to estimate reference ET (ETo) from the FAO 56 paper using a minimum of T_min and T_max for daily estimates and T_mean and RH_mean for hourly, but utilizing the maximum number of available met parameters.
4
+ """
5
+ import numpy as np
6
+ from eto.util import is_hourly
7
+
8
+
9
+ def eto_fao(self, max_ETo=15, min_ETo=0, ref_crop='short', decimals=2):
10
+ """
11
+ Function to estimate reference ET (ETo) from the `FAO 56 paper <http://www.fao.org/docrep/X0490E/X0490E00.htm>`_ [1]_ using a minimum of T_min and T_max for daily estimates and T_mean and RH_mean for hourly, but optionally utilising the maximum number of available met parameters.
12
+
13
+ Parameters
14
+ ----------
15
+ max_ETo : float, int or None
16
+ The max realistic value of ETo (mm); higher values become NaN. None disables the check.
17
+ min_ETo : float, int or None
18
+ The min realistic value of ETo (mm); lower values are raised to it. None keeps negative
19
+ values (raw FAO-56, e.g. night-time dew).
20
+ ref_crop : str
21
+ Reference crop type: 'short' (FAO 56 grass) or 'tall' (ASCE alfalfa).
22
+ decimals : int or None
23
+ Decimal places of the result; None returns it unrounded.
24
+
25
+ Returns
26
+ -------
27
+ np.ndarray
28
+ Estimated ETo in mm.
29
+
30
+ References
31
+ ----------
32
+
33
+ .. [1] 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. FAO, Rome, 300(9), D05109.
34
+ """
35
+
36
+ ######
37
+ ## ETo equation — select Cn and Cd based on ref_crop and frequency
38
+ if is_hourly(self.freq):
39
+ if ref_crop == 'tall':
40
+ Cn = 66
41
+ Cd = np.where(self.ts_param['R_n'] > 0, 0.25, 1.7)
42
+ else:
43
+ Cn = 37
44
+ Cd = 0.34
45
+ ETo_FAO = (0.408*self.ts_param['delta']*(self.ts_param['R_n'] - self.ts_param['G']) + self.ts_param['gamma']*Cn/(self.ts_param['T_mean'] + 273)*self.ts_param['U_2']*(self.ts_param['e_mean'] - self.ts_param['e_a']))/(self.ts_param['delta'] + self.ts_param['gamma']*(1 + Cd*self.ts_param['U_2']))
46
+ else:
47
+ if ref_crop == 'tall':
48
+ Cn = 1600
49
+ Cd = 0.38
50
+ else:
51
+ Cn = 900
52
+ Cd = 0.34
53
+ ETo_FAO = (0.408*self.ts_param['delta']*(self.ts_param['R_n'] - self.ts_param['G']) + self.ts_param['gamma']*Cn/(self.ts_param['T_mean'] + 273)*self.ts_param['U_2']*(self.ts_param['e_s'] - self.ts_param['e_a']))/(self.ts_param['delta'] + self.ts_param['gamma']*(1 + Cd*self.ts_param['U_2']))
54
+
55
+ ## Clamp negatives to min_ETo, NaN for suspect highs
56
+ if min_ETo is not None:
57
+ ETo_FAO = np.maximum(ETo_FAO, min_ETo)
58
+ if max_ETo is not None:
59
+ ETo_FAO = np.where(ETo_FAO > max_ETo, np.nan, ETo_FAO)
60
+
61
+ return ETo_FAO if decimals is None else np.round(ETo_FAO, decimals)
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Dual crop coefficient method (FAO 56 Eq 58).
4
+ """
5
+ import numpy as np
6
+
7
+
8
+ def etc_dual(self, Kcb, Ke=None, Kr=None, Kc_max=None, few=None, **kwargs):
9
+ """
10
+ Dual crop coefficient ETc = (Kcb + Ke) * ETo (FAO 56 Eq 58).
11
+
12
+ Parameters
13
+ ----------
14
+ Kcb : float or np.ndarray
15
+ Basal crop coefficient.
16
+ Ke : float or np.ndarray, optional
17
+ Soil evaporation coefficient. If not provided, estimated from
18
+ Kr, Kc_max, few, and Kcb.
19
+ Kr : float or np.ndarray, optional
20
+ Evaporation reduction coefficient (0-1). Required if Ke not provided.
21
+ Kc_max : float or np.ndarray, optional
22
+ Maximum Kc following rain/irrigation. Required if Ke not provided.
23
+ few : float or np.ndarray, optional
24
+ Fraction of soil surface wetted and exposed (0-1). Required if Ke
25
+ not provided.
26
+ **kwargs
27
+ Passed to eto_fao().
28
+
29
+ Returns
30
+ -------
31
+ np.ndarray
32
+ ETc in mm.
33
+ """
34
+ if Ke is None:
35
+ if Kr is None or Kc_max is None or few is None:
36
+ raise ValueError('When Ke is not provided, Kr, Kc_max, and few are required')
37
+ Kcb = np.asarray(Kcb, dtype=np.float64)
38
+ Kr = np.asarray(Kr, dtype=np.float64)
39
+ Kc_max = np.asarray(Kc_max, dtype=np.float64)
40
+ few = np.asarray(few, dtype=np.float64)
41
+ # FAO 56 Eq 71: Ke = Kr * (Kc_max - Kcb), limited by few * Kc_max
42
+ Ke = np.minimum(Kr * (Kc_max - Kcb), few * Kc_max)
43
+
44
+ return (np.asarray(Kcb) + np.asarray(Ke)) * self.eto_fao(**kwargs)
@@ -5,6 +5,7 @@ Created on Sun May 13 09:45:39 2018
5
5
  @author: MichaelEK
6
6
  """
7
7
  import numpy as np
8
+ from eto.util import is_hourly
8
9
 
9
10
 
10
11
  def hargreaves(self, max_ETo=15, min_ETo=0):
@@ -27,13 +28,13 @@ def hargreaves(self, max_ETo=15, min_ETo=0):
27
28
  ######
28
29
  ## ETo equation
29
30
 
30
- if 'h' in self.freq.lower():
31
+ if is_hourly(self.freq):
31
32
  raise ValueError('Hargreaves should not be calculated at time frequencies of less than a day.')
32
33
 
33
34
  ETo_Har = 0.0023*(self.ts_param['T_mean'] + 17.8)*((self.ts_param['T_max'] - self.ts_param['T_min']) **0.5)*self.ts_param['R_a']*0.408
34
35
 
35
- ## Remove extreme values
36
+ ## Clamp negatives to min_ETo, NaN for suspect highs
37
+ ETo_Har = np.maximum(ETo_Har, min_ETo)
36
38
  ETo_Har[ETo_Har > max_ETo] = np.nan
37
- ETo_Har[ETo_Har < min_ETo] = np.nan
38
39
 
39
40
  return np.round(ETo_Har, 2)