Skip to content

Commit 891b028

Browse files
review fixes for plant hydraulics refactor (#1713)
1 parent 1bd42d9 commit 891b028

File tree

4 files changed

+48
-39
lines changed

4 files changed

+48
-39
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ ClimaLand.jl Release Notes
33

44
main
55
-----
6+
- ![breaking change][badge-💥breaking] Refactor `PlantHydraulicsModel` to a
7+
single above-ground compartment. The `PlantHydraulics` submodule has been
8+
merged into `Canopy`; the `n_stem`, `n_leaf`, `h_stem`, and `h_leaf` kwargs
9+
of `PlantHydraulicsModel{FT}(domain, toml_dict; ...)` are removed, and the
10+
canopy height now lives in the biomass component (`canopy.biomass.height`).
11+
`Y.canopy.hydraulics.ϑ_l` and `p.canopy.hydraulics.ψ` are now scalar fields
12+
rather than per-compartment tuples (drop `.:1` when accessing them).
613

714
v1.7.0
815
-----

docs/src/standalone/pages/vegetation/plant_hydraulics/prognostic.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Plant Hydraulics
22

3-
Water loss during day-time transpiration drives plants to draw water from the soil by roots and transport it through the stem to leaves. The plant hydraulics code solves for the volumetric water content in the above ground plant material, $\theta_{l}$).
3+
Water loss during day-time transpiration drives plants to draw water from the soil by roots and transport it through the stem to leaves. The plant hydraulics code solves for the volumetric water content in the above ground plant material, $\theta_{l}$.
44

55
The volume flux of water $q$ (m/s) is given by Darcy's law as
66
```math
@@ -33,19 +33,23 @@ Then we have:
3333
```
3434
where $v$ now represents the volume of water in that compartment (of a bulk plant) per unit ground area.
3535

36-
We also need to convert from the variable $v$ to $\psi$, in order to compute root extraction with the soil. To do so, we can convert $v$ to the volumetric water content, and from $\theta$ to $\psi$ using the retention curve. To convert, let the volume of water per area of compartment be $V_{w}$, and $H$ the typical ``length" of the compartment. Then e.g.,
36+
We also need to convert from the variable $v$ to $\psi$, in order to compute root extraction with the soil. To do so, we can convert $v$ to the volumetric water content, and from $\theta$ to $\psi$ using the retention curve. To convert, let the volume of water per area of ground be $V$, and $H_{canopy}$ the canopy height. Then,
3737
```math
3838
\begin{equation}
39-
\theta=\frac{V}{A_{ground}} \times \frac{A_{ground}}{A_{leaf}} \times \frac{1}{H_{canopy}} = \frac{v_{stem}}{H_{canopy} \times LAI }.
39+
\theta=\frac{V}{A_{ground}} \times \frac{A_{ground}}{A_{leaf}} \times \frac{1}{H_{canopy}} = \frac{v}{H_{canopy} \times LAI }.
4040
\end{equation}
4141
```
4242
Substituting in the volumetric water content, we have
4343
```math
4444
\begin{equation}
45-
\frac{d \theta}{dt} = \frac{q_{roots}RAI - \tau LAI}{H_{canopy} LAI},
46-
\end{equaton}
45+
\frac{d \theta}{dt} = \frac{q_{roots}RAI - \tau LAI}{H_{canopy} LAI}.
46+
\end{equation}
4747
```
4848

49+
In the code, the root cross-sectional factor is implemented with a harmonic-mean
50+
coupling between `RAI` and `LAI`. This ensures that the flux goes to zero when either area index goes
51+
to zero.
52+
4953
We can also account for the distribution of roots as a function of depth. A quantity that is modeled in plant hydraulic models is the root fraction $P(z)$, satisfying $\int P(z) dz = 1$. Instead of having a single root at one discrete location, we can distribute the root system over different depths using $P(z)$. The total flux from roots between $z$ and $z+dz$ is given by
5054
```math
5155
\begin{equation}

src/standalone/Vegetation/plant_hydraulics.jl

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ transpiration. Note that the canopy height is part of the biomass model.
112112
113113
The model can be used in Canopy standalone mode by prescribing
114114
the soil matric potential at the root tips or flux in the roots. There is also the
115-
option (intendend only for debugging) to use a prescribed transpiration rate.
115+
option (intended only for debugging) to use a prescribed transpiration rate.
116116
117117
$(DocStringExtensions.FIELDS)
118118
"""
@@ -137,9 +137,9 @@ prognostic_vars(model::PlantHydraulicsModel) = (:ϑ_l,)
137137
138138
A function which returns the names of the auxiliary
139139
variables of the `PlantHydraulicsModel`,
140-
the water potential `ψ` (m) and the
141-
volume flux\\*root cross section in the roots `fa_roots` (1/s),
142-
where the cross section can be represented by an area index.
140+
the water potential `ψ` (m) and the root water flux per
141+
unit ground area `fa_roots` (m/s), which is the volume flux
142+
through the roots multiplied by the harmonic mean of LAI and RAI.
143143
"""
144144
auxiliary_vars(model::PlantHydraulicsModel) = (, :fa_roots)
145145

@@ -184,8 +184,8 @@ Computes the water flux given the absolute potential ψ (pressure/(ρg))
184184
and the conductivity K (m/s) at the center of the two layers
185185
with midpoints z1 and z2.
186186
187-
We currently assuming a harmonic
188-
mean for effective conducticity between the two layers
187+
We currently assume a harmonic
188+
mean for effective conductivity between the two layers
189189
(see CLM Technical Documentation).
190190
191191
To account for different path lengths in the two compartments Δz1 and
@@ -224,7 +224,7 @@ Base.broadcastable(x::AbstractRetentionModel) = tuple(x)
224224
Weibull{FT} <: AbstractConductivityModel{FT}
225225
226226
A concrete type specifying that a Weibull conductivity model is to be used;
227-
the struct contains the require parameters for this model.
227+
the struct contains the required parameters for this model.
228228
229229
# Fields
230230
$(DocStringExtensions.FIELDS)
@@ -235,7 +235,7 @@ struct Weibull{FT} <: AbstractConductivityModel{FT}
235235
"The absolute water potential in xylem (or xylem water potential) at which ∼63%
236236
of maximum xylem conductance is lost (Liu, 2020)."
237237
ψ63::FT
238-
"Weibull parameter c, which controls shape the shape of the conductance curve (Sperry, 2016)."
238+
"Weibull parameter c, which controls the shape of the conductance curve (Sperry, 2016)."
239239
c::FT
240240
end
241241

@@ -271,8 +271,8 @@ end
271271
"""
272272
LinearRetentionCurve{FT} <: AbstractRetentionModel{FT}
273273
274-
A concrete type specifying that a linear water retention model is to be used;
275-
the struct contains the require parameters for this model.
274+
A concrete type specifying that a linear water retention model is to be used;
275+
the struct contains the required parameters for this model.
276276
277277
When ψ = 0, the effective saturation is one, so the intercept
278278
is not a free parameter, and only the slope must be specified.
@@ -292,8 +292,8 @@ end
292292

293293
"""
294294
water_retention_curve(
295+
retention_params::LinearRetentionCurve{FT},
295296
S_l::FT,
296-
b::FT,
297297
ν::FT,
298298
S_s::FT) where {FT}
299299
@@ -319,8 +319,8 @@ end
319319

320320
"""
321321
inverse_water_retention_curve(
322+
retention_params::LinearRetentionCurve{FT},
322323
ψ::FT,
323-
b::FT,
324324
ν::FT,
325325
S_s::FT) where {FT}
326326
@@ -380,16 +380,17 @@ end
380380
A function which creates the compute_exp_tendency! function for the PlantHydraulicsModel.
381381
The compute_exp_tendency! function must comply with a rhs function of ClimaTimeSteppers.jl.
382382
383-
Below, `fa` denotes a flux multiplied by the relevant cross section
384-
(per unit area ground, or area index, AI). The tendency is
385-
∂ϑ/∂t = 1/(AI*dz)[fa_roots-transpiration)
383+
Below, `fa_roots` denotes the root water flux per unit ground area
384+
(`water_flux * harmonic_mean(LAI, RAI)`), and the transpiration is
385+
`p.canopy.turbulent_fluxes.vapor_flux`, also per unit ground area. The tendency is
386386
387-
Note that if the area_index is zero because no plant is present,
388-
AIdz is zero, and the fluxes `fa` appearing in the numerator are
389-
zero because they are scaled by AI.
387+
∂ϑ/∂t = (fa_roots - transpiration) / (LAI * dz)
390388
391-
To prevent dividing by zero, we change AI/(AI x dz)" to
392-
"AI/max(AI x dz, eps(FT))"
389+
where `dz` is the canopy height from the biomass model.
390+
391+
Note that if `LAI` is zero because no plant is present, `LAI * dz` is zero,
392+
and both fluxes in the numerator are also zero (they are scaled by area indices).
393+
To prevent dividing by zero, we use `max(LAI * dz, eps(FT))` in the denominator.
393394
"""
394395
function make_compute_exp_tendency(
395396
model::PlantHydraulicsModel{FT},
@@ -417,12 +418,12 @@ end
417418
t,
418419
) where {FT}
419420
420-
A method which computes the water flux between the soil and the above ground leaves, via the roots,
421-
and multiplied by the RAI, in the case of a model running without a prognostic
422-
soil model:
421+
A method which computes the water flux between the soil and the above ground leaves,
422+
via the roots, and multiplied by `harmonic_mean(LAI, RAI)`, in the case of a model
423+
running without a prognostic soil model:
423424
424425
Flux = -K_eff x [(ψ_leaf - ψ_soil)/(z_leaf - z_soil) + 1], where
425-
K_eff = K_soil K_leaf /(K_leaf + K_soil)
426+
K_eff = K_soil K_leaf / (K_leaf + K_soil).
426427
427428
Note that in `PrescribedSoil` mode, we compute the flux using K_soil = K_plant(ψ_soil)
428429
and K_leaf = K_plant(ψ_leaf). In `PrognosticSoil` mode, we compute the flux using
@@ -461,7 +462,7 @@ function root_water_flux_per_ground_area!(
461462

462463
# since rooting_depth is positive by convention, add the sign in here to
463464
# convert it to a coordinate: z_roots = -rooting_depth
464-
@. fa .=
465+
@. fa =
465466
water_flux(
466467
-rooting_depth,
467468
canopy.biomass.height / 2,
@@ -503,17 +504,14 @@ end
503504
"""
504505
update_hydraulics!(p, Y, hydraulics::PlantHydraulicsModel, canopy)
505506
506-
Updates the following cache variables in place:
507-
- p.canopy.hydraulics.ψ
508-
- p.canopy.hydraulics.fa[1:end-1] (within plant fluxes)
509-
510-
Other types of AbstractPlantHydraulicsModel may update different variables.
507+
Updates the water potential `p.canopy.hydraulics.ψ` in place using the retention
508+
curve. Other subtypes of `AbstractPlantHydraulicsModel` may update different cache
509+
variables.
511510
"""
512511
function update_hydraulics!(p, Y, hydraulics::PlantHydraulicsModel, canopy)
513512
ψ = p.canopy.hydraulics.ψ
514513
ϑ_l = Y.canopy.hydraulics.ϑ_l
515-
LAI = p.canopy.biomass.area_index.leaf
516-
(; retention_model, conductivity_model, S_s, ν) = hydraulics.parameters
514+
(; retention_model, S_s, ν) = hydraulics.parameters
517515

518516
@. ψ = water_retention_curve(
519517
retention_model,

test/standalone/Vegetation/plant_hydraulics_test.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ for FT in (Float32, Float64)
208208
Y[1],
209209
Canopy.hydraulic_conductivity(conductivity_model, ψ_soil0),
210210
Canopy.hydraulic_conductivity(conductivity_model, Y[1]),
211-
) .* ClimaLand.Canopy.Canopy.harmonic_mean(AI[:leaf], AI[:root])
211+
) .* ClimaLand.Canopy.harmonic_mean(AI[:leaf], AI[:root])
212212
F[1] = fa - T0A
213213
end
214214
#=======================
@@ -248,7 +248,7 @@ for FT in (Float32, Float64)
248248
canopy_exp_tendency!(dY, Y, p, 0.0)
249249

250250
@test all(abs.(parent(dY.canopy.hydraulics.ϑ_l))[:] .< ftol) # starts in equilibrium
251-
@test all(parent(p.canopy.hydraulics.fa_roots)[:] .- T0A .< ftol) # Fluxes are as we expect
251+
@test all(abs.(parent(p.canopy.hydraulics.fa_roots)[:] .- T0A) .< ftol) # Fluxes are as we expect
252252

253253
# repeat using the plant hydraulics model directly
254254
# make sure it agrees with what we get when use the canopy model ODE

0 commit comments

Comments
 (0)