Skip to content

Commit 4cb1823

Browse files
authored
Merge pull request #1255 from CliMA/js/constructors
add constructors for CanopyModel + integrated models
2 parents f10d8f4 + f56814a commit 4cb1823

File tree

9 files changed

+439
-30
lines changed

9 files changed

+439
-30
lines changed

NEWS.md

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

44
main
55
-------
6+
- Add convenience constructors for CanopyModel and integrated models PR[#1255](https://github.com/CliMA/ClimaLand.jl/pull/1255)
67
- Rename LandSimulationVisualization to LandSimulationVisualizationExt; add template for FluxnetSimulationsExt PR[#1259](https://github.com/CliMA/ClimaLand.jl/pull/1259)
78
- Remove root_depths from the PrescribeGroundConditions struct, and treat these ground ``drivers" consistently with how we handle atmospheric forcing PR[#1199](https://github.com/CliMA/ClimaLand.jl/pull/1240)
89
- Add constructors with default values for Canopy components PR[#1233](https://github.com/CliMA/ClimaLand.jl/pull/1233)

src/integrated/land.jl

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,58 @@ struct LandModel{
3636
canopy::VM
3737
"The snow model to be used"
3838
snow::SnM
39+
function LandModel{FT}(
40+
canopy::VM,
41+
snow::SnM,
42+
soil::SM,
43+
soilco2::MM,
44+
) where {
45+
FT,
46+
MM <: Soil.Biogeochemistry.SoilCO2Model{FT},
47+
SM <: Soil.EnergyHydrology{FT},
48+
VM <: Canopy.CanopyModel{FT},
49+
SnM <: Snow.SnowModel{FT},
50+
}
51+
prognostic_land_components = (:canopy, :snow, :soil, :soilco2)
52+
top_soil_bc = soil.boundary_conditions.top
53+
canopy_bc = canopy.boundary_conditions
54+
snow_bc = snow.boundary_conditions
55+
56+
# Integrated model checks
57+
@assert top_soil_bc.prognostic_land_components ==
58+
prognostic_land_components
59+
@assert canopy_bc.prognostic_land_components ==
60+
prognostic_land_components
61+
@assert snow_bc.prognostic_land_components == prognostic_land_components
62+
63+
@assert top_soil_bc.atmos == soilco2.drivers.atmos
64+
@assert top_soil_bc.atmos == canopy_bc.atmos
65+
@assert top_soil_bc.radiation == canopy_bc.radiation
66+
@assert top_soil_bc.atmos == snow_bc.atmos
67+
@assert top_soil_bc.radiation == snow_bc.radiation
68+
69+
@assert Domains.obtain_surface_domain(soil.domain) == canopy.domain
70+
@assert Domains.obtain_surface_domain(soilco2.domain) == canopy.domain
71+
@assert snow.domain == canopy.domain
72+
73+
@assert soil.parameters.earth_param_set ==
74+
soilco2.parameters.earth_param_set
75+
@assert soil.parameters.earth_param_set ==
76+
canopy.parameters.earth_param_set
77+
@assert soil.parameters.earth_param_set ==
78+
snow.parameters.earth_param_set
79+
80+
# LandModel-specific checks
81+
# Runoff and sublimation are also automatically included in the soil model
82+
@assert RootExtraction{FT}() in soil.sources
83+
@assert Soil.PhaseChange{FT}() in soil.sources
84+
@assert canopy.hydraulics.transpiration isa
85+
Canopy.PlantHydraulics.DiagnosticTranspiration{FT}
86+
@assert canopy_bc.ground isa PrognosticGroundConditions{FT}
87+
@assert soilco2.drivers.met isa Soil.Biogeochemistry.PrognosticMet
88+
89+
return new{FT, MM, SM, VM, SnM}(soilco2, soil, canopy, snow)
90+
end
3991
end
4092

4193

@@ -179,8 +231,7 @@ function LandModel{FT}(;
179231
drivers = soilco2_drivers,
180232
)
181233

182-
args = (soilco2, soil, canopy, snow)
183-
return LandModel{FT, typeof.(args)...}(args...)
234+
return LandModel{FT}(canopy, snow, soil, soilco2)
184235
end
185236

186237
"""

src/integrated/soil_canopy_model.jl

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,51 @@ struct SoilCanopyModel{
3030
soil::SM
3131
"The canopy model to be used"
3232
canopy::VM
33+
function SoilCanopyModel{FT}(
34+
soilco2::MM,
35+
soil::SM,
36+
canopy::VM,
37+
) where {
38+
FT,
39+
MM <: Soil.Biogeochemistry.SoilCO2Model{FT},
40+
SM <: Soil.EnergyHydrology{FT},
41+
VM <: Canopy.CanopyModel{FT},
42+
}
43+
prognostic_land_components = (:canopy, :soil, :soilco2)
44+
top_soil_bc = soil.boundary_conditions.top
45+
canopy_bc = canopy.boundary_conditions
46+
47+
# Integrated model checks
48+
@assert top_soil_bc.prognostic_land_components ==
49+
prognostic_land_components
50+
@assert canopy_bc.prognostic_land_components ==
51+
prognostic_land_components
52+
53+
@assert top_soil_bc.atmos == soilco2.drivers.atmos
54+
@assert top_soil_bc.atmos == canopy_bc.atmos
55+
@assert top_soil_bc.radiation == canopy_bc.radiation
56+
57+
@assert Domains.obtain_surface_domain(soil.domain) == canopy.domain
58+
@assert Domains.obtain_surface_domain(soilco2.domain) == canopy.domain
59+
60+
@assert soil.parameters.earth_param_set ==
61+
soilco2.parameters.earth_param_set
62+
@assert soil.parameters.earth_param_set ==
63+
canopy.parameters.earth_param_set
64+
65+
# SoilCanopyModel-specific checks
66+
# Runoff and sublimation are also automatically included in the soil model
67+
@assert RootExtraction{FT}() in soil.sources
68+
@assert Soil.PhaseChange{FT}() in soil.sources
69+
@assert canopy.hydraulics.transpiration isa
70+
Canopy.PlantHydraulics.DiagnosticTranspiration{FT}
71+
@assert canopy_bc.ground isa PrognosticSoilConditions{FT}
72+
@assert soilco2.drivers.met isa Soil.Biogeochemistry.PrognosticMet
73+
74+
return new{FT, MM, SM, VM}(soilco2, soil, canopy)
75+
end
3376
end
3477

35-
36-
3778
"""
3879
SoilCanopyModel{FT}(;
3980
soilco2_type::Type{MM},
@@ -155,11 +196,7 @@ function SoilCanopyModel{FT}(;
155196
drivers = soilco2_drivers,
156197
)
157198

158-
return SoilCanopyModel{FT, typeof(soilco2), typeof(soil), typeof(canopy)}(
159-
soilco2,
160-
soil,
161-
canopy,
162-
)
199+
return SoilCanopyModel{FT}(soilco2, soil, canopy)
163200
end
164201

165202
"""

src/standalone/Soil/Biogeochemistry/Biogeochemistry.jl

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,21 +134,29 @@ end
134134

135135
"""
136136
SoilCO2Model{FT}(;
137-
parameters::SoilCO2ModelParameters{FT},
138137
domain::ClimaLand.AbstractDomain,
139-
boundary_conditions::NamedTuple,
140-
sources::Tuple,
141138
drivers::DT,
139+
parameters = SoilCO2ModelParameters(FT),
140+
boundary_conditions::BC = (
141+
top = AtmosCO2StateBC(),
142+
bottom = SoilCO2FluxBC((p, t) -> 0.0) # no flux
143+
),
144+
sources::Tuple = (MicrobeProduction{FT}(),),
142145
) where {FT, BC, DT}
143146
144147
A constructor for `SoilCO2Model`.
148+
Defaults are provided for the parameters, boundary conditions, and sources.
149+
These can be overridden by providing the appropriate keyword arguments.
145150
"""
146151
function SoilCO2Model{FT}(;
147-
parameters::SoilCO2ModelParameters{FT},
148152
domain::ClimaLand.AbstractDomain,
149-
boundary_conditions::BC,
150-
sources::Tuple,
151153
drivers::DT,
154+
parameters::SoilCO2ModelParameters{FT} = SoilCO2ModelParameters(FT),
155+
boundary_conditions::BC = (
156+
top = AtmosCO2StateBC(),
157+
bottom = SoilCO2FluxBC((p, t) -> 0.0), # no flux
158+
),
159+
sources::Tuple = (MicrobeProduction{FT}(),),
152160
) where {FT, BC, DT}
153161
args = (parameters, domain, boundary_conditions, sources, drivers)
154162
SoilCO2Model{FT, typeof.(args)...}(args...)

src/standalone/Soil/Soil.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ function EnergyHydrology{FT}(
246246
domain.space.subsurface,
247247
FT,
248248
),
249-
S_s = ClimaCore.Fields.zeros(domain.space.subsurface) .+ 1e-3,
249+
S_s = ClimaCore.Fields.zeros(domain.space.subsurface) .+ FT(1e-3),
250250
z_0m = LP.get_default_parameter(FT, :soil_momentum_roughness_length),
251251
z_0b = LP.get_default_parameter(FT, :soil_scalar_roughness_length),
252252
emissivity = LP.get_default_parameter(FT, :emissivity_bare_soil),

src/standalone/Vegetation/Canopy.jl

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using LazyBroadcast: lazy
77
using ClimaCore
88
using ClimaCore.MatrixFields
99
import ClimaCore.MatrixFields: @name,
10+
import ClimaUtilities.TimeVaryingInputs: AbstractTimeVaryingInput
1011
import ClimaUtilities.TimeManager: ITime, date
1112
import LinearAlgebra: I, dot
1213
using ClimaLand: AbstractRadiativeDrivers, AbstractAtmosphericDrivers
@@ -125,7 +126,7 @@ end
125126
"""
126127
PlantHydraulicsModel{FT}(
127128
domain,
128-
forcing::NamedTuple;
129+
LAI::AbstractTimeVaryingInput;
129130
n_stem::Int = 0,
130131
n_leaf::Int = 1,
131132
h_stem::FT = FT(0),
@@ -147,8 +148,7 @@ end
147148
148149
Creates a PlantHydraulicsModel on the provided domain, using default parameters.
149150
150-
The required argument `forcing` should be a NamedTuple with the following field:
151-
- `LAI`: a function or ClimaUtilities TimeVaryingInput for leaf area index
151+
The required argument `LAI` should be a ClimaUtilities TimeVaryingInput for leaf area index.
152152
153153
The following default parameters are used:
154154
- n_stem = 0 (unitless) - number of stem compartments
@@ -172,15 +172,15 @@ https://doi.org/10.1029/2023WR035481
172172
"""
173173
function PlantHydraulicsModel{FT}(
174174
domain,
175-
forcing::NamedTuple;
175+
LAI::AbstractTimeVaryingInput;
176176
n_stem::Int = 0,
177177
n_leaf::Int = 1,
178178
h_stem::FT = FT(0),
179179
h_leaf::FT = FT(1),
180180
SAI::FT = FT(0),
181181
RAI::FT = FT(1),
182182
ai_parameterization = PlantHydraulics.PrescribedSiteAreaIndex{FT}(
183-
forcing.LAI,
183+
LAI,
184184
SAI,
185185
RAI,
186186
),
@@ -476,6 +476,118 @@ function CanopyModel{FT}(;
476476
return CanopyModel{FT, typeof.(args)...}(args...)
477477
end
478478

479+
"""
480+
function CanopyModel{FT}(
481+
domain::Union{
482+
ClimaLand.Domains.Point,
483+
ClimaLand.Domains.Plane,
484+
ClimaLand.Domains.SphericalSurface,
485+
},
486+
forcing::NamedTuple,
487+
LAI::AbstractTimeVaryingInput,
488+
earth_param_set;
489+
z_0m = FT(2),
490+
z_0b = FT(0.2),
491+
prognostic_land_components = (:canopy,),
492+
autotrophic_respiration = AutotrophicRespirationModel{FT}(),
493+
radiative_transfer = TwoStreamModel{FT}(domain),
494+
photosynthesis = FarquharModel{FT}(domain),
495+
conductance = MedlynConductanceModel{FT}(domain),
496+
hydraulics = PlantHydraulicsModel{FT}(domain, forcing),
497+
energy = BigLeafEnergyModel{FT}(),
498+
sif = Lee2015SIFModel{FT}(),
499+
) where {FT, PSE}
500+
501+
Creates a CanopyModel with the provided domain, forcing, and parameters.
502+
503+
Defaults are provided for each canopy component model, which can be overridden
504+
by passing in a different instance of that type of model. Default parameters are also provided
505+
for each canopy component, and can be changed with keyword arguments. Please see the documentation
506+
of each component model for details on the default parameters.
507+
508+
The required argument `forcing` should be a NamedTuple with the following field:
509+
- `atmos`: a PrescribedAtmosphere or CoupledAtmosphere object
510+
- `radiation`: a PrescribedRadiativeFluxes or CoupledRadiativeFluxes object
511+
- `ground`: a PrescribedGroundConditions, PrognosticGroundConditions, or PrognosticSoilConditions object
512+
513+
The required argument `LAI` should be a ClimaUtilities TimeVaryingInput for leaf area index.
514+
515+
When running the canopy model in standalone mode, set `prognostic_land_components = (:canopy,)`,
516+
while for running integrated land models, this should be a list of the individual models.
517+
This value of this argument must be the same across all components in the land model.
518+
"""
519+
function CanopyModel{FT}(
520+
domain::Union{
521+
ClimaLand.Domains.Point,
522+
ClimaLand.Domains.Plane,
523+
ClimaLand.Domains.SphericalSurface,
524+
},
525+
forcing::NamedTuple,
526+
LAI::AbstractTimeVaryingInput,
527+
earth_param_set;
528+
z_0m = FT(2),
529+
z_0b = FT(0.2),
530+
prognostic_land_components = (:canopy,),
531+
autotrophic_respiration = AutotrophicRespirationModel{FT}(),
532+
radiative_transfer = TwoStreamModel{FT}(domain),
533+
photosynthesis = FarquharModel{FT}(domain),
534+
conductance = MedlynConductanceModel{FT}(domain),
535+
hydraulics = PlantHydraulicsModel{FT}(domain, LAI),
536+
energy = BigLeafEnergyModel{FT}(),
537+
sif = Lee2015SIFModel{FT}(),
538+
) where {FT}
539+
(; atmos, radiation, ground) = forcing
540+
541+
if typeof(energy) <: PrescribedCanopyTempModel{FT}
542+
@info "Using the PrescribedAtmosphere air temperature as the canopy temperature"
543+
@assert typeof(atmos) <: PrescribedAtmosphere{FT}
544+
end
545+
546+
# Confirm that each spatially-varying parameter is on the correct domain
547+
for p in map(
548+
component -> propertynames(component.parameters),
549+
[
550+
autotrophic_respiration,
551+
radiative_transfer,
552+
photosynthesis,
553+
conductance,
554+
hydraulics,
555+
energy,
556+
sif,
557+
],
558+
)
559+
@assert !(p isa ClimaCore.Fields.Field) ||
560+
axes(p) == domain.space.surface
561+
end
562+
563+
boundary_conditions = AtmosDrivenCanopyBC(
564+
atmos,
565+
radiation,
566+
ground,
567+
prognostic_land_components,
568+
)
569+
570+
# TODO: move z_0m, z_0b to ClimaParams so we can call `get_default_parameter`.
571+
parameters = SharedCanopyParameters{FT, typeof(earth_param_set)}(
572+
z_0m,
573+
z_0b,
574+
earth_param_set,
575+
)
576+
args = (
577+
autotrophic_respiration,
578+
radiative_transfer,
579+
photosynthesis,
580+
conductance,
581+
hydraulics,
582+
energy,
583+
sif,
584+
boundary_conditions,
585+
parameters,
586+
domain,
587+
)
588+
return CanopyModel{FT, typeof.(args)...}(args...)
589+
end
590+
479591
ClimaLand.name(::CanopyModel) = :canopy
480592

481593
"""

0 commit comments

Comments
 (0)