Skip to content

Commit 41a1357

Browse files
reedmaxwellReedclaudesmithsg84
authored
Add CLM ET formulation improvements: PFT photosynthesis, canopy interception, Medlyn stomata (parflow#712)
## Summary Fixes CLM3-era ET suppression by addressing four high-priority gaps identified in the gap analysis (Jefferson et al. 2017, Lawrence et al. 2019): - **PFT-dependent photosynthesis:** Vcmax 39–62 for forests (vs hardcoded 33), C4 pathway for grasses/crops, with improved stomata (dark respiration, smooth colimitation, niter=5). Activated automatically when `vcmx25` is present in `drv_vegp.dat`. - **Configurable canopy interception:** `InterceptionFpiMax` (default 0.25) and `FwetExponent` (default 0.6667) keys for CLM3 scheme tuning. - **CLM5 tanh canopy interception:** `InterceptionScheme=CLM5Tanh` with `InterceptionTanhAlpha` replaces CLM3's 0.25 ceiling with `fpi = alpha * tanh(LAI+SAI)`, approaching 1.0 for LAI>2. - **Medlyn stomatal conductance:** `StomataScheme=Medlyn` uses VPD-based conductance (Medlyn et al. 2011) with PFT-dependent `g1_medlyn` values in `drv_vegp.dat`. All changes are backward-compatible: default key values and existing `drv_vegp.dat` files reproduce original CLM3 behavior bit-for-bit. ## New PF Keys (5) | Key | Default | Description | |-----|---------|-------------| | `Solver.CLM.InterceptionFpiMax` | 0.25 | Max interception fraction (CLM3 scheme) | | `Solver.CLM.FwetExponent` | 0.6667 | Wet canopy fraction exponent | | `Solver.CLM.StomataScheme` | BallBerry | `BallBerry` or `Medlyn` | | `Solver.CLM.InterceptionScheme` | CLM3 | `CLM3` or `CLM5Tanh` | | `Solver.CLM.InterceptionTanhAlpha` | 1.0 | CLM5 tanh scaling coefficient | ## New drv_vegp.dat parameters (7) `vcmx25`, `c3psn`, `mp`, `bp`, `qe25`, `folnmx`, `g1_medlyn` — provided via `drv_vegp_pft.dat` Presence of `vcmx25` in vegp auto-sets `photosyn_custom=.true.` → activates improved stomata physics (Rd, smooth colimitation, niter=5). Old vegp files without these params → original CLM3 defaults → bit-for-bit. ## Backward compatibility - `photosyn_custom` boolean flag in `drv_readvegpf.F90` gates improved stomata (not `isnan()`) - Guarded fpi/fwet expressions use original Fortran literals at default key values - Original CLM3 stomata loop preserved verbatim in separate code path - `InterceptionScheme` defaults to `CLM3` → existing fpi code path unchanged - New struct fields at END of `clm1d` to preserve existing field offsets ## Commits 1. `c0001aaf` — Add CLM ET formulation improvements: PFT photosynthesis, canopy interception, Medlyn stomata 2. `9e09d14f` — Fix Black formatting in clm_et_interception.py 3. `58065fa3` — Fix uninitialized folnvt on custom photosynthesis path 4. `f272dfda` — Add CLM5 tanh canopy interception scheme (InterceptionScheme key) 5. `b2969dbb` — Fix Uncrustify formatting in parflow_proto_f.h ## References - Jefferson, J.L., Maxwell, R.M., and Constantine, P.G. (2017). Exploring the sensitivity of photosynthesis and stomatal resistance parameters in a land surface model. *J. Hydrometeorol.* 18, 897–915. - Lawrence, D.M., et al. (2019). The Community Land Model version 5. *JAMES*, 11, 4245–4287. - Medlyn, B.E., et al. (2011). Reconciling the optimal and empirical approaches to modelling stomatal conductance. *Global Change Biol.* 17, 2134–2144. - Collatz, G.J., et al. (1991). Physiological and environmental regulation of stomatal conductance, photosynthesis and transpiration. *Agric. For. Meteorol.* 54, 107–136. ## Test plan - [x] All existing CLM tests pass bit-for-bit (clm, clm_snow_defaults, 8 snow tests) - [x] 4 new CI tests: `clm_et_pft_photosynthesis`, `clm_et_interception`, `clm_et_medlyn`, `clm_et_tanh_interception` - [x] Validated against 8 AmeriFlux tower sites (latent heat flux) - [x] Uncrustify (C) and Black (Python) formatting verified - [x] CI pipeline passes on Linux 🤖 Generated with [Claude Code](https://claude.com/claude-code) Create at: parflow/parflow@master...feature/clm_et --------- Co-authored-by: Reed <rmaxwell@mines.edu> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Smith <smith84@llnl.gov>
1 parent 3454f34 commit 41a1357

104 files changed

Lines changed: 2240 additions & 69 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/user_manual/keys.rst

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6839,6 +6839,88 @@ diurnal artifacts in fractional snow cover. Typical range 48-96 hours.
68396839
pfset Solver.CLM.FracSnoAvgWindow 72.0 ## TCL syntax
68406840
<runname>.Solver.CLM.FracSnoAvgWindow = 72.0 ## Python syntax
68416841

6842+
**ET Formulation Improvements**
6843+
6844+
These keys improve CLM's evapotranspiration calculations. Default values
6845+
reproduce original CLM3 behavior for backward compatibility. PFT-dependent
6846+
photosynthesis parameters (vcmx25, c3psn, mp, bp, qe25, folnmx) can be
6847+
provided in drv_vegp.dat; when detected, improved stomata physics
6848+
(dark respiration, smooth colimitation, niter=5) activates automatically.
6849+
6850+
*double* **Solver.CLM.InterceptionFpiMax** 0.25 Maximum interception
6851+
fraction coefficient. CLM3 default 0.25 caps canopy interception at 25%
6852+
even for dense canopies. CLM5 uses higher values.
6853+
Formula: fpi = InterceptionFpiMax * (1 - exp(-0.5*(LAI+SAI)))
6854+
6855+
.. container:: list
6856+
6857+
::
6858+
6859+
pfset Solver.CLM.InterceptionFpiMax 0.25 ## TCL syntax
6860+
<runname>.Solver.CLM.InterceptionFpiMax = 0.25 ## Python syntax
6861+
6862+
*double* **Solver.CLM.FwetExponent** 0.6667 Power-law exponent for wet
6863+
canopy fraction. CLM default 2/3. Lower values keep the canopy wet longer,
6864+
increasing wet canopy evaporation.
6865+
Formula: fwet = (h2ocan/(dewmx*LAI))^FwetExponent
6866+
6867+
.. container:: list
6868+
6869+
::
6870+
6871+
pfset Solver.CLM.FwetExponent 0.6667 ## TCL syntax
6872+
<runname>.Solver.CLM.FwetExponent = 0.6667 ## Python syntax
6873+
6874+
*string* **Solver.CLM.StomataScheme** BallBerry Selects the stomatal
6875+
conductance model.
6876+
6877+
**BallBerry**:
6878+
CLM3 default. Uses relative humidity in the conductance equation.
6879+
6880+
**Medlyn**:
6881+
Medlyn et al. (2011) model. Uses vapor pressure deficit (VPD) instead
6882+
of relative humidity. Requires g1_medlyn parameter in drv_vegp.dat for
6883+
PFT-dependent slope values.
6884+
6885+
.. container:: list
6886+
6887+
::
6888+
6889+
pfset Solver.CLM.StomataScheme BallBerry ## TCL syntax
6890+
<runname>.Solver.CLM.StomataScheme = "BallBerry" ## Python syntax
6891+
6892+
*string* **Solver.CLM.InterceptionScheme** CLM3 Selects the canopy
6893+
interception scheme.
6894+
6895+
**CLM3**:
6896+
CLM3 default exponential formulation.
6897+
Formula: fpi = InterceptionFpiMax * (1 - exp(-0.5*(LAI+SAI)))
6898+
Asymptotes to InterceptionFpiMax (default 0.25) regardless of LAI.
6899+
6900+
**CLM5Tanh**:
6901+
CLM5 tanh formulation (Lawrence et al. 2019).
6902+
Formula: fpi = InterceptionTanhAlpha * tanh(LAI+SAI)
6903+
Approaches InterceptionTanhAlpha (default 1.0) for LAI > 2, correcting
6904+
the CLM3 structural low bias in canopy interception for dense canopies.
6905+
6906+
.. container:: list
6907+
6908+
::
6909+
6910+
pfset Solver.CLM.InterceptionScheme CLM3 ## TCL syntax
6911+
<runname>.Solver.CLM.InterceptionScheme = "CLM3" ## Python syntax
6912+
6913+
*double* **Solver.CLM.InterceptionTanhAlpha** 1.0 Scaling coefficient for
6914+
CLM5 tanh interception scheme. Formula: fpi = alpha * tanh(LAI+SAI).
6915+
Default 1.0 matches CLM5. Only used when InterceptionScheme is CLM5Tanh.
6916+
6917+
.. container:: list
6918+
6919+
::
6920+
6921+
pfset Solver.CLM.InterceptionTanhAlpha 1.0 ## TCL syntax
6922+
<runname>.Solver.CLM.InterceptionTanhAlpha = 1.0 ## Python syntax
6923+
68426924

68436925
.. _ParFlow NetCDF4 Parallel I/O:
68446926

pf-keys/definitions/solver.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,72 @@ Solver:
987987
max_value: 1.0
988988
RequiresModule: CLM
989989

990+
# ET formulation improvements @RMM 2026
991+
InterceptionFpiMax:
992+
help: >
993+
[Type: double] Maximum interception fraction coefficient. CLM3 default
994+
0.25 caps canopy interception at 25% even for dense canopies. CLM5 uses
995+
higher values. Formula: fpi = InterceptionFpiMax * (1 - exp(-0.5*(LAI+SAI)))
996+
default: 0.25
997+
domains:
998+
DoubleValue:
999+
min_value: 0.05
1000+
max_value: 1.0
1001+
RequiresModule: CLM
1002+
1003+
FwetExponent:
1004+
help: >
1005+
[Type: double] Power-law exponent for wet canopy fraction. CLM default
1006+
2/3 (0.6667). Lower values keep the canopy wet longer, increasing wet
1007+
canopy evaporation. Formula: fwet = (h2ocan/(dewmx*LAI))^FwetExponent
1008+
default: 0.6667
1009+
domains:
1010+
DoubleValue:
1011+
min_value: 0.3
1012+
max_value: 1.0
1013+
RequiresModule: CLM
1014+
1015+
StomataScheme:
1016+
help: >
1017+
[Type: string] Stomatal conductance model. BallBerry uses relative
1018+
humidity (CLM3 default). Medlyn uses vapor pressure deficit and is
1019+
recommended for modern applications. Reference: Medlyn et al. (2011)
1020+
Global Change Biology.
1021+
default: BallBerry
1022+
domains:
1023+
EnumDomain:
1024+
enum_list:
1025+
- BallBerry
1026+
- Medlyn
1027+
RequiresModule: CLM
1028+
1029+
InterceptionScheme:
1030+
help: >
1031+
[Type: string] Canopy interception scheme. CLM3 uses exponential
1032+
formulation capped at InterceptionFpiMax (default 0.25). CLM5Tanh
1033+
uses tanh(LAI+SAI) scaled by InterceptionTanhAlpha, which approaches
1034+
1.0 for LAI>2 and corrects the CLM3 structural low bias.
1035+
Reference: Lawrence et al. (2019) JAMES.
1036+
default: CLM3
1037+
domains:
1038+
EnumDomain:
1039+
enum_list:
1040+
- CLM3
1041+
- CLM5Tanh
1042+
RequiresModule: CLM
1043+
1044+
InterceptionTanhAlpha:
1045+
help: >
1046+
[Type: double] Scaling coefficient for CLM5 tanh interception scheme.
1047+
Formula: fpi = alpha * tanh(LAI+SAI). Default 1.0 matches CLM5.
1048+
Only used when InterceptionScheme is CLM5Tanh.
1049+
default: 1.0
1050+
domains:
1051+
DoubleValue:
1052+
min_value: 0.1
1053+
max_value: 1.0
1054+
RequiresModule: CLM
1055+
9901056
# -----------------------------------------------------------------------------
9911057
# CLM input variables to write drv_clmin.dat (Input) and drv_vegp.dat (VegParams) files
9921058
# -----------------------------------------------------------------------------

pfsimulator/clm/clm.F90

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult
2020
frac_sno_typepf,frac_sno_roughnesspf,frac_sno_roughness_minpf,frac_sno_roughness_maxpf, &
2121
frac_sno_gamma_szapf,frac_sno_tau_szapf, &
2222
snowage_tau0_vispf,snowage_tau0_nirpf,snowage_grain_growth_vispf,snowage_grain_growth_nirpf, &
23-
snowage_dirt_soot_vispf,snowage_dirt_soot_nirpf,snowage_reset_factorpf)
23+
snowage_dirt_soot_vispf,snowage_dirt_soot_nirpf,snowage_reset_factorpf, &
24+
interception_fpi_maxpf,fwet_exponentpf,stomata_schemepf, &
25+
interception_schemepf,interception_tanh_alphapf)
2426

2527
!=========================================================================
2628
!
27-
! CLMCLMCLMCLMCLMCLMCLMCLMCL A community developed and sponsored, freely
29+
! CLMCLMCLMCLMCLMCLMCLMCLMCL A community developed and sponsored, freely
2830
! L M available land surface process model.
2931
! M --COMMON LAND MODEL-- C
3032
! C L CLM WEB INFO: http://clm.gsfc.nasa.gov
@@ -206,6 +208,13 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult
206208
real(r8) :: snowage_dirt_soot_nirpf ! NIR dirt/soot factor [-]
207209
real(r8) :: snowage_reset_factorpf ! fresh snow reset factor [-]
208210

211+
! ET formulation improvements @RMM 2026
212+
real(r8) :: interception_fpi_maxpf ! max interception fraction coeff [-]
213+
real(r8) :: fwet_exponentpf ! power-law exponent for wet canopy [-]
214+
integer :: stomata_schemepf ! stomatal model: 0=BallBerry, 1=Medlyn
215+
integer :: interception_schemepf ! interception: 0=CLM3, 1=CLM5Tanh
216+
real(r8) :: interception_tanh_alphapf ! CLM5 tanh scaling coeff [-]
217+
209218
! local indices & counters
210219
integer :: i,j,k,k1,j1,l1 ! indices for local looping
211220
integer :: bj,bl ! indices for local looping !BH
@@ -596,6 +605,13 @@ subroutine clm_lsm(pressure,saturation,evap_trans,top,bottom,porosity,pf_dz_mult
596605
clm(t)%snowage_dirt_soot_nir = snowage_dirt_soot_nirpf
597606
clm(t)%snowage_reset_factor = snowage_reset_factorpf
598607

608+
! for ET formulation improvements @RMM 2026
609+
clm(t)%interception_fpi_max = interception_fpi_maxpf
610+
clm(t)%fwet_exponent = fwet_exponentpf
611+
clm(t)%stomata_scheme = stomata_schemepf
612+
clm(t)%interception_scheme = interception_schemepf
613+
clm(t)%interception_tanh_alpha = interception_tanh_alphapf
614+
599615
! for irrigation
600616
clm(t)%irr_type = irr_typepf
601617
clm(t)%irr_cycle = irr_cyclepf

pfsimulator/clm/clm_hydro_canopy.F90

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,17 @@ subroutine clm_hydro_canopy (clm)
111111

112112
! Direct throughfall
113113

114-
fpi = 0.25d0*(1. - exp(-0.5*(clm%elai + clm%esai)))
114+
if (clm%interception_scheme == 1) then
115+
! CLM5 tanh interception (Lawrence et al. 2019)
116+
fpi = clm%interception_tanh_alpha &
117+
* tanh(clm%elai + clm%esai)
118+
else if (clm%interception_fpi_max /= 0.25d0) then
119+
fpi = clm%interception_fpi_max &
120+
* (1.d0 - exp(-0.5d0*(clm%elai + clm%esai)))
121+
else
122+
! Original CLM3 expression for bit-for-bit backward compat
123+
fpi = 0.25d0*(1. - exp(-0.5*(clm%elai + clm%esai)))
124+
endif
115125
qflx_through = prcp*(1.-fpi)*clm%frac_veg_nosno
116126

117127
! Water storage of intercepted precipitation and dew
@@ -313,7 +323,11 @@ subroutine clm_hydro_canopy (clm)
313323
if (clm%h2ocan > 0. .and. clm%frac_veg_nosno == 1) then
314324
vegt = clm%frac_veg_nosno*(clm%elai + clm%esai)
315325
dewmxi = 1.0/clm%dewmx
316-
clm%fwet = ((dewmxi/vegt)*clm%h2ocan)**.666666666666
326+
if (clm%fwet_exponent /= 0.6667d0) then
327+
clm%fwet = ((dewmxi/vegt)*clm%h2ocan)**clm%fwet_exponent
328+
else
329+
clm%fwet = ((dewmxi/vegt)*clm%h2ocan)**.666666666666
330+
endif
317331
clm%fwet = min(clm%fwet,dble(1.0)) ! Check for maximum limit of fwet
318332
clm%fdry = (1.-clm%fwet)*clm%elai/(clm%elai+clm%esai)
319333
else

0 commit comments

Comments
 (0)