Skip to content

Commit 08d1301

Browse files
committed
Work on sigmoid docs.
1 parent a1e9463 commit 08d1301

2 files changed

Lines changed: 117 additions & 78 deletions

File tree

docs/algorithm_description.md

Lines changed: 117 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ new features (e.g., alternative snow/runoff/soil-water formulations, richer irri
2525
scheduling, and new remote-sensing proxies for ET and `Kcb`).
2626

2727
While 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,
2930
Thomas Ott, Dr. Justin Huntington, and others who've contributed to that project over the years.
3031
For 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

9199
We compute the basal crop coefficient (Kcb) from NDVI using a sigmoid
92100
relationship:
@@ -97,12 +105,33 @@ Kcb = Kc_max / (1 + exp(-ndvi_k × (NDVI - ndvi_0)))
97105

98106
The 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.
112141
We 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

144175
We 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

238270
Where:
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
292325
E/T partitioning in our water use models.
293326

294327
Where:
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
327361
based on its own conditions.
328362

329363
**Conditions for subsidy** (all must be met for a given field):
364+
330365
1. `gw_status = True` (field has groundwater connection)
331366
2. `f_sub > 0.2` (sufficient subsidy fraction)
332367
3. `depl_root > RAW` (root zone is depleted beyond threshold)
@@ -377,14 +412,17 @@ versus natural precipitation. This enables **consumptive use accounting**
377412
for 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

398436
The 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

docs/images/sigmoid_comparison.png

115 KB
Loading

0 commit comments

Comments
 (0)