@@ -25,7 +25,8 @@ new features (e.g., alternative snow/runoff/soil-water formulations, richer irri
2525scheduling, and new remote-sensing proxies for ET and ` Kcb ` ).
2626
2727While this software has been completely rewritten, it was originally based on a fork
28- of [ et-demands] ( https://github.com/WSWUP/et-demands ) ; shoutout to Dr. Richard Allen, Chris Pearson, Charles Morton, Blake Minor,
28+ of [ et-demands] ( https://github.com/WSWUP/et-demands ) ; shoutout to Dr. Richard Allen, Chris Pearson, Charles Morton,
29+ Blake Minor,
2930Thomas Ott, Dr. Justin Huntington, and others who've contributed to that project over the years.
3031For a detailed comparison of the two codebases, see [ ET-Demands Comparison] ( et-demands-compare.md ) .
3132
@@ -86,7 +87,14 @@ the actual sequence differs for physical correctness:
8687
8788## Algorithm Steps
8889
89- ### Step 1: Basal Crop Coefficient from NDVI
90+ ** Note:** Many of the ** FAO-56 Modifications** below are based on qualitative
91+ and quantitative analysis of five eddy covariance flux towers in the Western US:
92+ US-FPe (Fort Peck, MT), S2 (Crane, OR), US-Blo (Sierra Nevada, CA), MR (Muddy
93+ River, NV), and ALARC2_Smith6 (Yuma, AZ). The other hundreds of flux towers
94+ were held out of the testing process and used to assess model generalization
95+ and validation metrics.
96+
97+ ### Step 1: Basal Crop Coefficient from NDVI (FAO-56 Modification)
9098
9199We compute the basal crop coefficient (Kcb) from NDVI using a sigmoid
92100relationship:
@@ -97,12 +105,33 @@ Kcb = Kc_max / (1 + exp(-ndvi_k × (NDVI - ndvi_0)))
97105
98106The exponent is clipped to [ -20, 20] to prevent numerical overflow.
99107
100- - Higher NDVI indicates greater vegetation vigor and higher transpiration
101- potential
102- - The inflection point ` ndvi_0 ` controls where the transition from bare to
103- vegetated occurs
104- - The steepness ` ndvi_k ` controls how sharp that transition is
105- - At ` NDVI = ndvi_0 ` , ` Kcb = Kc_max / 2 `
108+ ** Parameter interpretation:**
109+
110+ - ` ndvi_0 ` (inflection point): The NDVI value at which Kcb = Kc_max / 2.
111+ This controls when transpiration "turns on" as vegetation develops.
112+ - ` ndvi_k ` (steepness): How sharply Kcb transitions from low to high values
113+ around the inflection point. Higher values create a more step-like response.
114+ - ` kc_max ` (ceiling): Maximum basal crop coefficient at full canopy.
115+
116+ ** Land-cover dependence of ndvi_0:**
117+
118+ At calibration test sites, we found that optimal ` ndvi_0 ` varies substantially
119+ by vegetation type:
120+
121+ | Land Cover | Calibrated ndvi_0 | Interpretation |
122+ | ------------------------| -------------------| ----------------------------------------------------------|
123+ | Grassland (US-FPe) | 0.14 | Sparse cover already transpires; sigmoid activates early |
124+ | Irrigated alfalfa (S2) | 0.58 | Transpiration delayed until substantial canopy closure |
125+
126+ This reflects phenology: grasslands transpire proportionally to any greenness,
127+ while dense crops require substantial canopy development before reaching
128+ maximum transpiration rates.
129+
130+ ![ Sigmoid response curves for grassland and irrigated alfalfa] ( images/sigmoid_comparison.png )
131+
132+ * Figure 1: Calibrated NDVI→Kcb sigmoid curves for two flux tower sites. The
133+ inflection point (dot) marks where Kcb = Kc_max/2. Grassland (US-FPe) activates
134+ at low NDVI, while irrigated alfalfa (S2) requires near-full canopy.*
106135
107136** Parameters** : ` ndvi_k ` (slope), ` ndvi_0 ` (inflection point),
108137` kc_max ` (ceiling)
@@ -112,14 +141,16 @@ The exponent is clipped to [-20, 20] to prevent numerical overflow.
112141We partition precipitation into rain or snow and compute snowmelt:
113142
114143** Partitioning** :
144+
115145- If T_avg < 1°C: precipitation falls as snow
116146- If T_avg ≥ 1°C: precipitation falls as rain
117147
118148** Albedo evolution** :
149+
119150- Fresh snowfall (> 3 mm) resets albedo to 0.98
120151- Albedo decays exponentially with two rates:
121- - With some snowfall (0-3 mm): decay rate = 0.12
122- - With no snowfall: decay rate = 0.05 (slower aging)
152+ - With some snowfall (0-3 mm): decay rate = 0.12
153+ - With no snowfall: decay rate = 0.05 (slower aging)
123154- Decay formula: ` albedo = albedo_min + (albedo_prev - albedo_min) × exp(-decay_rate) `
124155- Minimum albedo for old snow: 0.45
125156
@@ -142,6 +173,7 @@ Two methods are available, selected via `runoff_process`:
142173** Curve Number (CN) Method** (` runoff_process = "cn" ` ):
143174
144175We adjust CN for antecedent moisture based on surface layer depletion:
176+
145177- Wet conditions (low depletion): CN → CN_III (higher runoff)
146178- Dry conditions (high depletion): CN → CN_I (lower runoff)
147179
@@ -236,6 +268,7 @@ Ks = 1 when Dr ≤ RAW
236268```
237269
238270Where:
271+
239272- ` TAW = AWC × zr ` (total available water)
240273- ` RAW = MAD × TAW ` (readily available water)
241274- ` MAD ` is the management allowed depletion fraction
@@ -292,6 +325,7 @@ modeling, and the practical necessity to provide the user with a good estimate o
292325E/T partitioning in our water use models.
293326
294327Where:
328+
295329- Transpiration component: ` T = Ks × Kcb × fc × ETref `
296330- Evaporation component: ` E = Ke × ETref `
297331
@@ -327,6 +361,7 @@ rise when the root zone is depleted. Each field is evaluated independently
327361based on its own conditions.
328362
329363** Conditions for subsidy** (all must be met for a given field):
364+
3303651 . ` gw_status = True ` (field has groundwater connection)
3313662 . ` f_sub > 0.2 ` (sufficient subsidy fraction)
3323673 . ` depl_root > RAW ` (root zone is depleted beyond threshold)
@@ -377,14 +412,17 @@ versus natural precipitation. This enables **consumptive use accounting**
377412for water rights management.
378413
379414** Tracked quantities:**
415+
380416- ` irr_frac_root ` : Irrigation fraction in root zone [ 0, 1]
381417- ` irr_frac_l3 ` : Irrigation fraction in layer 3 [ 0, 1]
382418
383419** Derived outputs:**
420+
384421- ` et_irr = eta × irr_frac_root ` : ET from irrigation water (consumptive use)
385422- ` dperc_irr = dperc × irr_frac_root ` : Deep percolation of irrigation water (return flow)
386423
387424** Fraction mixing logic:**
425+
388426- When irrigation is applied (90% to root zone), it mixes with existing soil water
389427- When water transfers between root zone and layer 3 (root growth/recession),
390428 the irrigation fraction transfers proportionally
@@ -397,11 +435,11 @@ This feature is implemented in `kernels/irrigation_tracking.py`.
397435
398436The model tracks water in three conceptual layers:
399437
400- | Layer | Symbol | Description | Depth |
401- | ------------| ---------| ---------------------------------- | - ---------------|
402- | Surface | Ze | Evaporation source layer | ~ 0.1-0.15 m |
403- | Root zone | zr | Transpiration source, dynamic | 0.1 to zr_max |
404- | Below-root | Layer 3 | Storage reservoir below roots | zr_max - zr |
438+ | Layer | Symbol | Description | Depth |
439+ | ------------| ---------| -------------------------------| ---------------|
440+ | Surface | Ze | Evaporation source layer | ~ 0.1-0.15 m |
441+ | Root zone | zr | Transpiration source, dynamic | 0.1 to zr_max |
442+ | Below-root | Layer 3 | Storage reservoir below roots | zr_max - zr |
405443
406444- ** Surface layer** : Controls evaporation availability via ` depl_ze `
407445- ** Root zone** : Primary transpiration source, depth varies with crop
@@ -411,42 +449,42 @@ The model tracks water in three conceptual layers:
411449
412450## Key State Variables
413451
414- | Variable | Description | Units |
415- | -------------| ------------------------------------------| -------|
416- | ` depl_root ` | Root zone depletion (0 = field capacity) | mm |
417- | ` depl_ze ` | Surface layer depletion | mm |
418- | ` daw3 ` | Available water in layer 3 | mm |
419- | ` swe ` | Snow water equivalent | mm |
420- | ` zr ` | Current root depth | m |
421- | ` ks ` | Water stress coefficient (damped) | - |
422- | ` kr ` | Evaporation reduction coefficient | - |
423- | ` albedo ` | Snow albedo | - |
424- | ` irr_frac_root ` | Irrigation fraction in root zone | - |
425- | ` irr_frac_l3 ` | Irrigation fraction in layer 3 | - |
452+ | Variable | Description | Units |
453+ | ----------------- | ------------------------------------------| -------|
454+ | ` depl_root ` | Root zone depletion (0 = field capacity) | mm |
455+ | ` depl_ze ` | Surface layer depletion | mm |
456+ | ` daw3 ` | Available water in layer 3 | mm |
457+ | ` swe ` | Snow water equivalent | mm |
458+ | ` zr ` | Current root depth | m |
459+ | ` ks ` | Water stress coefficient (damped) | - |
460+ | ` kr ` | Evaporation reduction coefficient | - |
461+ | ` albedo ` | Snow albedo | - |
462+ | ` irr_frac_root ` | Irrigation fraction in root zone | - |
463+ | ` irr_frac_l3 ` | Irrigation fraction in layer 3 | - |
426464
427465## Tunable Coefficients
428466
429467### Calibration Parameters (adjustable via PEST++)
430468
431- | Parameter | Description | Typical Range |
432- | ----------------| ---------------------------------- | ---------------|
433- | ` ndvi_k ` | Sigmoid steepness for NDVI→Kcb | 4-10 |
434- | ` ndvi_0 ` | Sigmoid inflection point NDVI | 0.1-0.7 |
435- | ` swe_alpha ` | Radiation melt coefficient | -0.5-1.0 |
436- | ` swe_beta ` | Degree-day melt factor | 0.5-2.5 |
437- | ` kr_damp ` | Kr damping factor | 0.1-0.5 |
438- | ` ks_damp ` | Ks damping factor | 0.1-0.5 |
439- | ` max_irr_rate ` | Maximum irrigation rate | 15-40 mm/day |
469+ | Parameter | Description | Typical Range |
470+ | ----------------| --------------------------------| ---------------|
471+ | ` ndvi_k ` | Sigmoid steepness for NDVI→Kcb | 4-10 |
472+ | ` ndvi_0 ` | Sigmoid inflection point NDVI | 0.1-0.7 |
473+ | ` swe_alpha ` | Radiation melt coefficient | -0.5-1.0 |
474+ | ` swe_beta ` | Degree-day melt factor | 0.5-2.5 |
475+ | ` kr_damp ` | Kr damping factor | 0.1-0.5 |
476+ | ` ks_damp ` | Ks damping factor | 0.1-0.5 |
477+ | ` max_irr_rate ` | Maximum irrigation rate | 15-40 mm/day |
440478
441479### Field Properties (derived from data)
442480
443- | Property | Description | Source |
444- | ---------------------| ------------------------------| ----------------------------- |
445- | ` awc ` | Available water capacity | Soil surveys (gSSURGO) |
446- | ` p_depletion ` (MAD) | Management allowed depletion | Calibration/literature |
447- | ` ke_max ` | Maximum Ke | 90th %ile ETf, NDVI < 0.3 |
448- | ` f_sub ` | Groundwater subsidy fraction | ETa/PPT ratio analysis |
449- | ` zr_max ` | Maximum root depth | Land cover type |
481+ | Property | Description | Source |
482+ | ---------------------| ------------------------------| ---------------------------|
483+ | ` awc ` | Available water capacity | Soil surveys (gSSURGO) |
484+ | ` p_depletion ` (MAD) | Management allowed depletion | Calibration/literature |
485+ | ` ke_max ` | Maximum Ke | 90th %ile ETf, NDVI < 0.3 |
486+ | ` f_sub ` | Groundwater subsidy fraction | ETa/PPT ratio analysis |
487+ | ` zr_max ` | Maximum root depth | Land cover type |
450488
451489### MAD Parameter: Dual Role
452490
@@ -459,6 +497,7 @@ two purposes:
459497 ` depl_root > RAW `
460498
461499** Implications** :
500+
462501- Higher MAD → irrigation triggered later → less frequent irrigation
463502- Higher MAD → stress begins later → less stress-induced ET reduction
464503- For rainfed fields, MAD controls only stress sensitivity
@@ -470,44 +509,44 @@ and crop stress tolerance.
470509
471510### Daily Arrays (shape: n_days × n_fields)
472511
473- | Output | Description | Units |
474- | -------------| ----------------------------------| - --------|
475- | ` eta ` | Actual evapotranspiration | mm/day |
476- | ` etf ` | ET fraction (ETa/ETref) | - |
477- | ` kcb ` | Basal crop coefficient | - |
478- | ` ke ` | Evaporation coefficient | - |
479- | ` ks ` | Water stress coefficient | - |
480- | ` kr ` | Evaporation reduction coefficient| - |
481- | ` runoff ` | Surface runoff | mm |
482- | ` rain ` | Liquid precipitation | mm |
483- | ` melt ` | Snowmelt | mm |
484- | ` swe ` | Snow water equivalent | mm |
485- | ` depl_root ` | Root zone depletion | mm |
486- | ` dperc ` | Deep percolation | mm |
487- | ` irr_sim ` | Simulated irrigation | mm |
488- | ` gw_sim ` | Groundwater subsidy | mm |
489- | ` et_irr ` | ET from irrigation water | mm |
490- | ` dperc_irr ` | Deep percolation of irr. water | mm |
491- | ` irr_frac_root ` | Irrigation fraction in root zone | - |
492- | ` irr_frac_l3 ` | Irrigation fraction in layer 3 | - |
512+ | Output | Description | Units |
513+ | ----------------- | ----------------------------------- | --------|
514+ | ` eta ` | Actual evapotranspiration | mm/day |
515+ | ` etf ` | ET fraction (ETa/ETref) | - |
516+ | ` kcb ` | Basal crop coefficient | - |
517+ | ` ke ` | Evaporation coefficient | - |
518+ | ` ks ` | Water stress coefficient | - |
519+ | ` kr ` | Evaporation reduction coefficient | - |
520+ | ` runoff ` | Surface runoff | mm |
521+ | ` rain ` | Liquid precipitation | mm |
522+ | ` melt ` | Snowmelt | mm |
523+ | ` swe ` | Snow water equivalent | mm |
524+ | ` depl_root ` | Root zone depletion | mm |
525+ | ` dperc ` | Deep percolation | mm |
526+ | ` irr_sim ` | Simulated irrigation | mm |
527+ | ` gw_sim ` | Groundwater subsidy | mm |
528+ | ` et_irr ` | ET from irrigation water | mm |
529+ | ` dperc_irr ` | Deep percolation of irr. water | mm |
530+ | ` irr_frac_root ` | Irrigation fraction in root zone | - |
531+ | ` irr_frac_l3 ` | Irrigation fraction in layer 3 | - |
493532
494533## Source Code Reference
495534
496- | File | Section Coverage |
497- | -----------------------------| -----------------------------------------|
498- | ` loop.py ` | Daily iteration, kernel orchestration |
499- | ` kernels/crop_coefficient.py ` | Step 1 (NDVI → Kcb sigmoid) |
500- | ` kernels/snow.py ` | Step 2 (Snow partitioning, albedo, melt)|
501- | ` kernels/runoff.py ` | Step 3 (CN and infiltration-excess) |
502- | ` kernels/cover.py ` | Step 4 (Fractional cover, few) |
503- | ` kernels/evaporation.py ` | Step 6 (Kr, Ke coefficients) |
504- | ` kernels/transpiration.py ` | Step 7 (Ks stress coefficient) |
505- | ` kernels/water_balance.py ` | Step 8 (Actual ET, deep percolation) |
506- | ` kernels/irrigation.py ` | Steps 9-10 (Irrigation, groundwater) |
507- | ` kernels/irrigation_tracking.py ` | Irrigation fraction tracking |
508- | ` kernels/root_growth.py ` | Step 5, 11 (Root depth, redistribution) |
509- | ` state.py ` | State variable containers |
510- | ` input.py ` | Input data structures (HDF5 container) |
535+ | File | Section Coverage |
536+ | ---------------------------------- | - -----------------------------------------|
537+ | ` loop.py ` | Daily iteration, kernel orchestration |
538+ | ` kernels/crop_coefficient.py ` | Step 1 (NDVI → Kcb sigmoid) |
539+ | ` kernels/snow.py ` | Step 2 (Snow partitioning, albedo, melt) |
540+ | ` kernels/runoff.py ` | Step 3 (CN and infiltration-excess) |
541+ | ` kernels/cover.py ` | Step 4 (Fractional cover, few) |
542+ | ` kernels/evaporation.py ` | Step 6 (Kr, Ke coefficients) |
543+ | ` kernels/transpiration.py ` | Step 7 (Ks stress coefficient) |
544+ | ` kernels/water_balance.py ` | Step 8 (Actual ET, deep percolation) |
545+ | ` kernels/irrigation.py ` | Steps 9-10 (Irrigation, groundwater) |
546+ | ` kernels/irrigation_tracking.py ` | Irrigation fraction tracking |
547+ | ` kernels/root_growth.py ` | Step 5, 11 (Root depth, redistribution) |
548+ | ` state.py ` | State variable containers |
549+ | ` input.py ` | Input data structures (HDF5 container) |
511550
512551## References
513552
0 commit comments