Skip to content

Commit b8adcf2

Browse files
authored
move soil layers to problem (#113)
* move soil layers to problem * remove field
1 parent 93dae7b commit b8adcf2

10 files changed

Lines changed: 121 additions & 82 deletions

src/Microclimate.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export AbstractApparentHeatCapacity, BonacinaStep, TanhSmoothed, Gaussian, Weste
3838
export AbstractSoilProperties, CampbelldeVriesSoilProperties
3939

4040
# Soil hydraulics
41-
export AbstractSoilHydraulicsModel, CampbellSoilHydraulics
41+
export AbstractSoilHydraulicsModel, CampbellSoilHydraulics, CampbellHydraulicProfile
42+
export example_campbell_hydraulic_profile
4243

4344
# Soil moisture strategy
4445
export AbstractSoilMoistureStrategy, PrescribedSoilMoisture, DynamicSoilMoisture

src/simulation.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@ end
137137

138138
function CommonSolve.init(mp::MicroProblem)
139139
(; hours, depths, heights,
140-
soil_profile, soil_properties_model, soil_hydraulic_model, snow_model,
140+
soil_properties_model, soil_hydraulic_model, snow_model,
141141
vapour_pressure_equation, boundary_layer_model,
142142
radiation, evaporation_model, soil_energy_model,
143143
config) = mp.model
144144
days = mp.days
145145
longwave_model = radiation.longwave_model
146-
(; site, environment_minmax, environment_daily, environment_hourly,
146+
(; site, soil_profile, environment_minmax, environment_daily, environment_hourly,
147147
initial_soil_temperature, initial_soil_moisture,
148148
initial_snow_depth, initial_snow_temperature, initial_snow_density) = mp.inputs
149149
n_snow = n_snow_nodes(snow_model)
@@ -349,14 +349,14 @@ end
349349
function solve_soil!(cache::MicroCache)
350350
mp = cache.problem
351351
(; hours, depths, heights,
352-
soil_profile, soil_properties_model, soil_hydraulic_model, snow_model,
352+
soil_properties_model, soil_hydraulic_model, snow_model,
353353
vapour_pressure_equation, boundary_layer_model,
354354
radiation, evaporation_model, soil_energy_model,
355355
config) = mp.model
356356
days = mp.days
357357
longwave_model = radiation.longwave_model
358358
soil_freezing_model = soil_energy_model.freezing_model
359-
(; site, environment_daily, environment_hourly,
359+
(; site, soil_profile, environment_daily, environment_hourly,
360360
initial_soil_temperature, initial_soil_moisture,
361361
initial_snow_depth, initial_snow_temperature, initial_snow_density) = mp.inputs
362362
n_snow = n_snow_nodes(snow_model)
@@ -372,7 +372,7 @@ function solve_soil!(cache::MicroCache)
372372
(; convergence, rainfall_schedule, max_surface_pool) = config
373373
time_mode = mp.time_mode
374374
moisture_mode = config.soil_moisture_strategy
375-
(; campbell_b_parameter, air_entry_water_potential) = soil_hydraulic_model
375+
(; campbell_b_parameter, air_entry_water_potential) = soil_profile.hydraulics
376376
(; bulk_density, mineral_density) = soil_profile
377377
init_soil_wetness!(moisture_mode)
378378

src/soil_hydraulics/campbell.jl

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
"""
2-
CampbellSoilHydraulics(; ...)
2+
CampbellHydraulicProfile(; air_entry_water_potential,
3+
saturated_hydraulic_conductivity,
4+
campbell_b_parameter,
5+
root_density)
36
4-
Soil hydraulic parameters for Campbell's (1985) soil water balance model.
5-
Holds parameters only — the moisture-solver strategy lives on
7+
Per-depth hydraulic parameters for Campbell's (1985) soil water balance
8+
model. Lives on `SoilProfile.hydraulics` (per-location structural data),
9+
not on `CampbellSoilHydraulics` (which now carries only the plant-side
10+
scalars).
11+
12+
Each field is a vector sized to match `MicroModel.depths`.
13+
"""
14+
@kwdef struct CampbellHydraulicProfile{AEWP,SHC,CBP,RDen}
15+
air_entry_water_potential::AEWP
16+
saturated_hydraulic_conductivity::SHC
17+
campbell_b_parameter::CBP
18+
root_density::RDen
19+
end
20+
21+
"""
22+
CampbellSoilHydraulics(; root_resistance, stomatal_closure_potential,
23+
leaf_resistance, stomatal_stability_parameter,
24+
root_radius)
25+
26+
Plant-side scalar parameters for Campbell's (1985) soil water balance
27+
model. Holds parameters only — the moisture-solver strategy lives on
628
`MicroConfig.soil_moisture_strategy` (`PrescribedSoilMoisture` /
729
`DynamicSoilMoisture`), with solver tuning (`moisture_tolerance`,
830
`moisture_max_iterations`, `moisture_timestep`) carried on
931
`DynamicSoilMoisture` itself.
1032
11-
`bulk_density` and `mineral_density` are not stored here — they live on
12-
`MicroModel.soil_profile::SoilProfile` and are passed in alongside this
13-
model wherever they're needed.
33+
Per-depth soil hydraulic parameters (`air_entry_water_potential`,
34+
`saturated_hydraulic_conductivity`, `campbell_b_parameter`, `root_density`)
35+
live on `SoilProfile.hydraulics::CampbellHydraulicProfile`, alongside
36+
`bulk_density` and `mineral_density` — all per-location data on one
37+
struct, all passed in alongside this model wherever needed.
1438
1539
# References
1640
Campbell, G. S. (1985). Soil Physics with BASIC. Elsevier.
1741
"""
18-
@kwdef struct CampbellSoilHydraulics{AEWP,SHC,CBP,RDen,RRes,SCP,LRes,SSP,RRad} <: AbstractSoilHydraulicsModel
19-
air_entry_water_potential::AEWP
20-
saturated_hydraulic_conductivity::SHC
21-
campbell_b_parameter::CBP
22-
root_density::RDen
42+
@kwdef struct CampbellSoilHydraulics{RRes,SCP,LRes,SSP,RRad} <: AbstractSoilHydraulicsModel
2343
root_resistance::RRes
2444
stomatal_closure_potential::SCP
2545
leaf_resistance::LRes
@@ -28,23 +48,28 @@ Campbell, G. S. (1985). Soil Physics with BASIC. Elsevier.
2848
end
2949

3050
# TODO move real defaults to the struct keywords
31-
function example_soil_hydraulic_model(depths=DEFAULT_DEPTHS;
32-
# soil hydraulic parameters
33-
air_entry_water_potential = fill(0.7, length(depths))u"J/kg", #air entry potential
34-
saturated_hydraulic_conductivity = fill(0.0058, length(depths))u"kg*s/m^3", #saturated conductivity
35-
campbell_b_parameter = fill(1.7, length(depths)), #soil 'b' parameter
36-
# plant parameters
37-
root_density = [0, 0, 8.2, 8.0, 7.8, 7.4, 7.1, 6.4, 5.8, 4.8, 4.0, 1.8, 0.9, 0.6, 0.8, 0.4, 0.4, 0, 0] * 1e4u"m/m^3", # root density at each node (from Campell 1985 Soil Physics with Basic, p. 131)
38-
root_resistance = 2.5e+10u"m^3/kg/s", # resistance per unit length of root
51+
function example_campbell_hydraulic_profile(depths=DEFAULT_DEPTHS;
52+
air_entry_water_potential = fill(0.7, length(depths))u"J/kg", # air entry potential
53+
saturated_hydraulic_conductivity = fill(0.0058, length(depths))u"kg*s/m^3", # saturated conductivity
54+
campbell_b_parameter = fill(1.7, length(depths)), # soil 'b' parameter
55+
root_density = [0, 0, 8.2, 8.0, 7.8, 7.4, 7.1, 6.4, 5.8, 4.8, 4.0, 1.8, 0.9, 0.6, 0.8, 0.4, 0.4, 0, 0] * 1e4u"m/m^3", # root density at each node (Campbell 1985, p. 131)
56+
)
57+
CampbellHydraulicProfile(;
58+
air_entry_water_potential, saturated_hydraulic_conductivity,
59+
campbell_b_parameter, root_density,
60+
)
61+
end
62+
63+
function example_soil_hydraulic_model(;
64+
root_resistance = 2.5e+10u"m^3/kg/s", # resistance per unit length of root
3965
stomatal_closure_potential = -1500.0u"J/kg", # critical leaf water potential for stomatal closure
40-
leaf_resistance = 2.0e6u"m^4/kg/s", # resistance per unit length of leaf
41-
stomatal_stability_parameter = 10.0, # stability parameter, -
42-
root_radius = 0.001u"m", # root radius, m
66+
leaf_resistance = 2.0e6u"m^4/kg/s", # resistance per unit length of leaf
67+
stomatal_stability_parameter = 10.0, # stability parameter, -
68+
root_radius = 0.001u"m", # root radius, m
4369
)
4470
CampbellSoilHydraulics(;
45-
air_entry_water_potential, saturated_hydraulic_conductivity, campbell_b_parameter,
46-
root_density, root_resistance, stomatal_closure_potential, leaf_resistance, stomatal_stability_parameter,
47-
root_radius,
71+
root_resistance, stomatal_closure_potential, leaf_resistance,
72+
stomatal_stability_parameter, root_radius,
4873
)
4974
end
5075

@@ -105,11 +130,12 @@ function infiltration_step!(buffers, soil_hydraulic_model::CampbellSoilHydraulic
105130
relative_humidity_local = local_relative_humidity
106131

107132
dt = moisture_timestep
108-
saturated_conductivity = soil_hydraulic_model.saturated_hydraulic_conductivity
109-
campbell_b = soil_hydraulic_model.campbell_b_parameter
133+
hydraulic_profile = soil_profile.hydraulics
134+
saturated_conductivity = hydraulic_profile.saturated_hydraulic_conductivity
135+
campbell_b = hydraulic_profile.campbell_b_parameter
110136
bulk_density = soil_profile.bulk_density
111137
mineral_density = soil_profile.mineral_density
112-
root_density = soil_hydraulic_model.root_density
138+
root_density = hydraulic_profile.root_density
113139
root_resistance_param = soil_hydraulic_model.root_resistance
114140
stomatal_closure_potential = soil_hydraulic_model.stomatal_closure_potential
115141
leaf_resistance = soil_hydraulic_model.leaf_resistance
@@ -133,7 +159,7 @@ function infiltration_step!(buffers, soil_hydraulic_model::CampbellSoilHydraulic
133159
vapor_diffusivity = 2.4e-5u"m^2/s"
134160

135161
# Convert to negative absolute value; fill positions 1..num_layers, extend boundary
136-
air_entry_potential[1:num_layers] .= -abs.(soil_hydraulic_model.air_entry_water_potential)
162+
air_entry_potential[1:num_layers] .= -abs.(hydraulic_profile.air_entry_water_potential)
137163
air_entry_potential[num_layers+1] = air_entry_potential[num_layers]
138164

139165
# Saturation water content, m3/m3; extend boundary

src/types.jl

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
"""
2-
SoilProfile(; bulk_density, mineral_density)
2+
SoilProfile(; bulk_density, mineral_density, hydraulics)
33
4-
Per-depth soil column profile read by both the soil properties model
5-
(thermal) and the soil hydraulic model. Sized to match `MicroModel.depths`.
4+
Per-depth soil column profile read by the soil properties model (thermal)
5+
and the soil hydraulic model. Sized to match `MicroModel.depths`.
66
77
- `bulk_density` — dry soil bulk density (kg/m³) at each depth node
88
- `mineral_density` — soil mineral density (kg/m³) at each depth node
9+
- `hydraulics` — per-depth hydraulic parameters (e.g. `CampbellHydraulicProfile`)
10+
matched to the chosen `soil_hydraulic_model`
911
"""
10-
@kwdef struct SoilProfile{BD,MD}
12+
@kwdef struct SoilProfile{BD,MD,H}
1113
bulk_density::BD
1214
mineral_density::MD
15+
hydraulics::H
1316
end
1417

1518
function example_soil_profile(depths=DEFAULT_DEPTHS;
1619
bulk_density = 2.56u"Mg/m^3",
1720
mineral_density = 2.560u"Mg/m^3",
21+
hydraulics = example_campbell_hydraulic_profile(depths),
1822
)
1923
SoilProfile(;
2024
bulk_density = bulk_density isa AbstractVector ? bulk_density : fill(bulk_density, length(depths)),
2125
mineral_density = mineral_density isa AbstractVector ? mineral_density : fill(mineral_density, length(depths)),
26+
hydraulics,
2227
)
2328
end
2429

@@ -64,7 +69,7 @@ end
6469

6570
"""
6671
MicroModel(; hours, depths, heights,
67-
soil_profile, soil_properties_model, soil_hydraulic_model,
72+
soil_properties_model, soil_hydraulic_model,
6873
radiation=RadiationModel(),
6974
snow_model=NoSnow(),
7075
vapour_pressure_equation=GoffGratch(),
@@ -77,8 +82,6 @@ Constant-across-runs scientific description of the simulation:
7782
7883
- solver geometry (`hours`, `depths`, `heights`) # TODO generalise to sub-hourly
7984
- physical-process models:
80-
- `soil_profile::SoilProfile` — per-depth `bulk_density` and `mineral_density`
81-
profiles read by both the soil properties and soil hydraulic models
8285
- `soil_properties_model::AbstractSoilProperties` (e.g. `CampbelldeVriesSoilProperties(...)`)
8386
- `soil_hydraulic_model::AbstractSoilHydraulicsModel` (e.g. `CampbellSoilHydraulics(...)`)
8487
- `radiation::RadiationModel` — bundle of `solar_radiation_model`,
@@ -91,17 +94,12 @@ Constant-across-runs scientific description of the simulation:
9194
(carries the phase-transition `freezing_model` and ODE solver settings)
9295
- iteration/data-delivery strategy in `config::MicroConfig`
9396
94-
The days of year to simulate live on `MicroProblem`, not here — they're a
95-
per-run choice (e.g. monthly mid-month days vs daily 1:365) that can vary
96-
without changing the model.
97-
9897
Combine with a `MicroInputs` via `MicroProblem(model, inputs; days)` to run.
9998
"""
100-
@kwdef struct MicroModel{H,Dep,Ht,SPR,SPM,SHM,RAD,SNM,VPE,BLM,EVM,SEM,C}
99+
@kwdef struct MicroModel{H,Dep,Ht,SPM,SHM,RAD,SNM,VPE,BLM,EVM,SEM,C}
101100
hours::H = DEFAULT_HOURS # hour of day for solar_radiation
102101
depths::Dep = DEFAULT_DEPTHS # soil nodes - keep spacing close near the surface
103102
heights::Ht = [0.01, 2]u"m" # air nodes for temperature, wind speed and humidity profile, last height is reference height for weather data
104-
soil_profile::SPR
105103
soil_properties_model::SPM
106104
soil_hydraulic_model::SHM
107105
radiation::RAD = RadiationModel()
@@ -114,14 +112,20 @@ Combine with a `MicroInputs` via `MicroProblem(model, inputs; days)` to run.
114112
end
115113

116114
"""
117-
MicroInputs(; site, environment_minmax, environment_daily, environment_hourly=nothing,
115+
MicroInputs(; site, soil_profile, environment_minmax, environment_daily,
116+
environment_hourly=nothing,
118117
initial_soil_temperature=nothing, initial_soil_moisture,
119118
initial_snow_depth=0.0u"cm", initial_snow_temperature=u"K"(0.0u"°C"),
120119
initial_snow_density=nothing)
121120
122-
Per-run input data: site, environment forcings, and initial conditions.
121+
Per-run input data: site, soil column profile, environment forcings, and
122+
initial conditions.
123123
124124
- `site::Site` — properties of the place
125+
- `soil_profile::SoilProfile` — per-depth `bulk_density` and `mineral_density`
126+
read by both the soil properties and soil hydraulic models. Per-location
127+
structural soil-column data; lives here (not on `MicroModel`) because it
128+
varies between sites without changing the physics.
125129
- `environment_minmax`, `environment_daily`, `environment_hourly` — input forcings
126130
- `initial_*` — initial conditions (`initial_soil_temperature=nothing` falls back to
127131
the day-mean reference air temperature)
@@ -130,8 +134,9 @@ Per-run input data: site, environment forcings, and initial conditions.
130134
Pass a fresh `MicroInputs` to `reinit!(cache, inputs)` to re-solve the same
131135
model on different data without rebuilding the cache.
132136
"""
133-
@kwdef struct MicroInputs{S,EMM,EH,ED,IST,ISM,ISD,ISNT,ISND}
137+
@kwdef struct MicroInputs{S,SP<:SoilProfile,EMM,EH,ED,IST,ISM,ISD,ISNT,ISND}
134138
site::S
139+
soil_profile::SP
135140
environment_minmax::EMM
136141
environment_hourly::EH = nothing
137142
environment_daily::ED
@@ -176,7 +181,7 @@ function example_microclimate_problem(;
176181
site = example_site(),
177182
soil_profile = example_soil_profile(depths),
178183
soil_properties_model = example_soil_properties_model(),
179-
soil_hydraulic_model = example_soil_hydraulic_model(depths),
184+
soil_hydraulic_model = example_soil_hydraulic_model(),
180185
snow_model = NoSnow(),
181186
environment_minmax = example_monthly_weather(),
182187
environment_daily = example_daily_environment(days),
@@ -185,10 +190,11 @@ function example_microclimate_problem(;
185190
initial_soil_temperature = fill(u"K"(7.741667u"°C"), length(depths)),
186191
initial_soil_moisture = fill(0.42 * 0.25, length(depths)),
187192
)
188-
model = MicroModel(; hours, depths, heights,
189-
soil_profile, soil_properties_model, soil_hydraulic_model, snow_model, config)
193+
model = MicroModel(;
194+
hours, depths, heights, soil_properties_model, soil_hydraulic_model, snow_model, config
195+
)
190196
inputs = MicroInputs(;
191-
site, environment_minmax, environment_daily, environment_hourly,
197+
site, soil_profile, environment_minmax, environment_daily, environment_hourly,
192198
initial_soil_temperature, initial_soil_moisture,
193199
)
194200
MicroProblem(model, inputs; days)

test/example_constructors.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ using Test
99
@test example_hourly_environment() isa HourlyTimeseries
1010
@test example_soil_properties_model() isa CampbelldeVriesSoilProperties
1111
@test example_soil_hydraulic_model() isa CampbellSoilHydraulics
12+
@test example_campbell_hydraulic_profile() isa CampbellHydraulicProfile
1213
@test example_soil_profile() isa SoilProfile
14+
@test example_soil_profile().hydraulics isa CampbellHydraulicProfile
1315
@test example_microclimate_problem() isa MicroProblem
1416
end
1517

test/micro_testrun_daily.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ _runmoist = Bool(Int(microinput[:runmoist]))
8282
soil_profile = SoilProfile(;
8383
bulk_density = (DataFrame(CSV.File("$testdir/data/init_daily/BD.csv"))[:, 2] * 1.0u"Mg/m^3"),
8484
mineral_density = (DataFrame(CSV.File("$testdir/data/init_daily/DD.csv"))[:, 2] * 1.0u"Mg/m^3"),
85+
hydraulics = CampbellHydraulicProfile(;
86+
air_entry_water_potential = (DataFrame(CSV.File("$testdir/data/init_daily/PE.csv"))[:, 2] * 1.0u"J/kg"),
87+
saturated_hydraulic_conductivity = (DataFrame(CSV.File("$testdir/data/init_daily/KS.csv"))[:, 2] * 1.0u"kg*s/m^3"),
88+
campbell_b_parameter = (DataFrame(CSV.File("$testdir/data/init_daily/BB.csv"))[:, 2] * 1.0),
89+
root_density = DataFrame(CSV.File("$testdir/data/init_daily/L.csv"))[:, 2] * u"m/m^3",
90+
),
8591
)
8692
soil_hydraulic_model = CampbellSoilHydraulics(;
87-
# soil hydraulic parameters
88-
air_entry_water_potential = (DataFrame(CSV.File("$testdir/data/init_daily/PE.csv"))[:, 2] * 1.0u"J/kg"),
89-
saturated_hydraulic_conductivity = (DataFrame(CSV.File("$testdir/data/init_daily/KS.csv"))[:, 2] * 1.0u"kg*s/m^3"),
90-
campbell_b_parameter = (DataFrame(CSV.File("$testdir/data/init_daily/BB.csv"))[:, 2] * 1.0),
91-
# plant parameters
92-
root_density = DataFrame(CSV.File("$testdir/data/init_daily/L.csv"))[:, 2] * u"m/m^3",
9393
root_resistance = microinput[:RW] * u"m^3/kg/s",
9494
stomatal_closure_potential = -microinput[:PC] * u"J/kg",
9595
leaf_resistance = microinput[:RL] * u"m^4/kg/s",
@@ -150,14 +150,14 @@ model = MicroModel(;
150150
depths, # soil nodes - keep spacing close near the surface
151151
heights, # air nodes for temperature, wind speed and humidity profile
152152
radiation,
153-
soil_profile,
154153
soil_properties_model,
155154
soil_hydraulic_model,
156155
boundary_layer_model,
157156
config,
158157
)
159158
inputs = MicroInputs(;
160159
site,
160+
soil_profile,
161161
environment_minmax,
162162
environment_daily,
163163
environment_hourly,

test/micro_testrun_monthly.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ _runmoist = Bool(Int(microinput[:runmoist]))
102102
soil_profile = SoilProfile(;
103103
bulk_density = fill(bulk_density, length(depths)),
104104
mineral_density = fill(mineral_density, length(depths)),
105+
hydraulics = example_campbell_hydraulic_profile(depths;
106+
root_density = fill(0.0, length(depths))u"m/m^3"),
105107
)
106-
soil_hydraulic_model = example_soil_hydraulic_model(depths;
107-
root_density = fill(0.0, length(depths))u"m/m^3")
108+
soil_hydraulic_model = example_soil_hydraulic_model()
108109
radiation = RadiationModel(;
109110
solar_radiation_model = SolarProblem(;
110111
diffuse_model = Bool(Int(microinput[:IUV])) ? SolarRadiation.ChandrasekharScattering() : SolarRadiation.NoScattering(),
@@ -134,14 +135,14 @@ model = MicroModel(;
134135
depths,
135136
heights, # air nodes for temperature, wind speed and humidity profile
136137
radiation,
137-
soil_profile,
138138
soil_properties_model,
139139
soil_hydraulic_model,
140140
boundary_layer_model,
141141
config,
142142
)
143143
inputs = MicroInputs(;
144144
site,
145+
soil_profile,
145146
environment_minmax,
146147
environment_daily,
147148
environment_hourly,

0 commit comments

Comments
 (0)