Skip to content

Commit 8edde64

Browse files
committed
SoilColumn基本完成
1 parent 460cfe6 commit 8edde64

9 files changed

Lines changed: 139 additions & 147 deletions

File tree

src/Kv.jl

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export kv_at_depth, effective_ksat, kv_layer_ksat
1+
export kv_at_depth, kv_layer_ksat
22

33
# ──────────────────────────────────────────────
44
# Unified dispatch: kv_at_depth(profile, layer, z_cm)
@@ -110,14 +110,3 @@ end
110110
# f_base = T(kv.f[nlayers_kv])
111111
# _kv_integral(kv_base, f_base, z1_cm - z_layered_cm, z2_cm - z_layered_cm)
112112
# end
113-
114-
"""
115-
effective_ksat(ps, i, z_cm) → Ksat [cm h⁻¹]
116-
117-
Return point-estimate Ksat at centre depth z_cm for layer i.
118-
Primarily for inspection — the solver uses `ps.param_hydraulic[i].Ksat` (pre-integrated).
119-
The default `kv_profile` is `KvLayers`, initialized from hydraulic Ksat.
120-
"""
121-
function effective_ksat(ps::SoilModel, i::Int, z_cm::Real)
122-
kv_at_depth(ps.hydraulic.kv, i, z_cm)
123-
end

src/ModelParams.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ include("GOF.jl")
3838
include("Optim/Optim.jl")
3939

4040
include("Kv_Profile.jl")
41-
include("Model_SoilDiffEqs.jl")
4241
include("Kv.jl")
43-
44-
include("Optim.jl")
42+
include("Model_SoilDiffEqs.jl")
43+
include("SoilColumn.jl")
4544

4645
export bounds, units, @bounds, @units
4746

src/Model_SoilDiffEqs.jl

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export _sync_ksat!
77
export AbstractThermal, AbstractThermalLayers
88
export ThermalMain, ThermalMainLayers, ThermalBase, ThermalBaseLayers, ThermalProfile
99

10-
export SoilModel
10+
export SoilColumn
1111

1212
abstract type AbstractRetention{T<:Real} end
1313
abstract type AbstractThermal{T<:Real} end
@@ -65,7 +65,7 @@ _sync_ksat!(kv, layers, dz_cm) = nothing
6565

6666
## Hydraulic Profile
6767
# T 自动从 profile 的 typeof 推断,吸收掉 MultiLayer 的 NT 类型参数
68-
mutable struct HydraulicProfile{FT<:AbstractFloat,N,
68+
@with_kw mutable struct HydraulicProfile{FT<:AbstractFloat,N,
6969
P<:AbstractRetention{FT},T<:MultiLayer{FT,N,P},K}
7070

7171
profile::T
@@ -104,7 +104,7 @@ const ThermalBaseLayers{FT,N} = MultiLayer{FT,N,ThermalBase{FT}}
104104
const AbstractThermalLayers{FT,N} = MultiLayer{FT,N,S} where {S<:AbstractThermal{FT}}
105105

106106
# ThermalProfile可以暂时不使用
107-
mutable struct ThermalProfile{FT<:AbstractFloat,N,
107+
@with_kw mutable struct ThermalProfile{FT<:AbstractFloat,N,
108108
P<:AbstractThermal{FT},
109109
T<:MultiLayer{FT,N,P}}
110110
profile::T
@@ -117,19 +117,3 @@ function ThermalProfile{FT,N}(
117117
layers = Vector(profile)
118118
ThermalProfile{FT,N,eltype(layers),typeof(profile)}(profile, layers)
119119
end
120-
121-
122-
##
123-
mutable struct SoilModel{FT<:AbstractFloat,N,
124-
H<:HydraulicProfile{FT,N},T<:ThermalProfile{FT,N}}
125-
126-
hydraulic::H # 水力剖面
127-
thermal::T # 热力剖面
128-
end
129-
130-
# 外构造器:N 作为类型参数显式传入,避免 runtime N → 类型不稳定
131-
function SoilModel{FT,N}(
132-
hydraulic=HydraulicProfile{FT,N}(),
133-
thermal=ThermalProfile{FT,N}()) where {FT<:AbstractFloat,N}
134-
SoilModel{FT,N,typeof(hydraulic),typeof(thermal)}(hydraulic, thermal)
135-
end

src/MultiLayer.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ has_definedbounds(x::AbstractLayers) = true
8585
:($(Tuple(keep)))
8686
end
8787

88+
# 如果一个结构体,已经提前定义了get_params,就不递归展开了
8889
function get_params(x::MultiLayer{FT,N,S}; path=[], with_unit=true) where {FT,N,S}
8990
fields = _ml_float_fields(S)
9091
res = map(fields) do field

src/Optim.jl

Lines changed: 0 additions & 101 deletions
This file was deleted.

src/SoilColumn.jl

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
export update_params!, optim_params
2+
3+
##
4+
@with_kw mutable struct SoilColumn{FT<:AbstractFloat,N,
5+
H<:HydraulicProfile{FT,N},T<:ThermalProfile{FT,N}}
6+
hydraulic::H # 水力剖面
7+
thermal::T # 热力剖面
8+
end
9+
10+
# 外构造器:N 作为类型参数显式传入,避免 runtime N → 类型不稳定
11+
function SoilColumn{FT,N}(
12+
hydraulic=HydraulicProfile{FT,N}(),
13+
thermal=ThermalProfile{FT,N}()) where {FT<:AbstractFloat,N}
14+
15+
SoilColumn{FT,N,typeof(hydraulic),typeof(thermal)}(hydraulic, thermal)
16+
end
17+
18+
# effective_ksat(ps::SoilColumn, i::Int, z_cm::Real) = kv_at_depth(ps.hydraulic.kv, i, z_cm)
19+
20+
# Sync Ksat from kv profile into the SoA hydraulic profile (writes to profile.Ksat[i]).
21+
function _sync_ksat!(kv::Union{AbstractKv,AbstractKvLayers},
22+
profile::AbstractRetentionLayers{FT,N},
23+
dz_cm::AbstractVector) where {FT,N}
24+
z_cm = FT(0)
25+
@inbounds for i in 1:N
26+
dz_i = isempty(dz_cm) ? FT(0) : FT(dz_cm[i])
27+
profile.Ksat[i] = kv_layer_ksat(kv, i, z_cm, z_cm + dz_i)
28+
z_cm += dz_i
29+
end
30+
end
31+
32+
33+
# mod: :hydraulic | :thermal | [:hydraulic, :thermal] | :all
34+
# list_fix: field names excluded from calibration, matched via params.name
35+
# list_sameLayer: field names shared across all hydraulic layers (e.g. Campbell ψ_sat);
36+
# returns one deduped row per name; update_params! broadcasts it back.
37+
function optim_params(ps::SoilColumn, mod::Union{Symbol,AbstractVector{Symbol}};
38+
inds=nothing,
39+
list_sameLayer::Vector{Symbol}=Symbol[],
40+
list_fix::Vector{Symbol}=Symbol[])
41+
42+
params = parameters(ps) # base struct-traversal in Params.jl
43+
n = nrow(params)
44+
mask = trues(n)
45+
46+
# module filter
47+
if mod !== :all
48+
mods = mod isa Symbol ? (mod,) : Tuple(mod)
49+
for (i, p) in enumerate(params.path)
50+
mask[i] && p[1] mods && (mask[i] = false)
51+
end
52+
end
53+
54+
# layer index filter
55+
if !isnothing(inds)
56+
inds_set = Set(inds)
57+
for (i, p) in enumerate(params.path)
58+
mask[i] && isa(p[end], Integer) && p[end] inds_set && (mask[i] = false)
59+
end
60+
end
61+
62+
# list_fix: z_exp is a design parameter for KvExpPiecewise, always excluded
63+
fix_set = Set(list_fix)
64+
ps.hydraulic.kv isa KvExpPiecewise && push!(fix_set, :z_exp)
65+
for (i, name) in enumerate(params.name)
66+
mask[i] && name fix_set && (mask[i] = false)
67+
end
68+
69+
# list_sameLayer: keep only first occurrence per name
70+
same_set = Set(list_sameLayer)
71+
seen = Set{Symbol}()
72+
for (i, name) in enumerate(params.name)
73+
mask[i] || continue
74+
name in same_set || continue
75+
name in seen ? (mask[i] = false) : push!(seen, name)
76+
end
77+
params[mask, :]
78+
end
79+
80+
81+
# list_sameLayer: broadcast the source-layer value to all hydraulic profile layers
82+
# after update!, and before update_hydraulic! (VG: m depends on n)
83+
# list_fix: excluded from params by get_params, so update! never touches them
84+
function update_params!(ps::SoilColumn{FT,N}, paths, theta;
85+
params=nothing,
86+
list_sameLayer::Vector{Symbol}=Symbol[],
87+
list_fix::Vector{Symbol}=Symbol[]) where {FT<:Real,N}
88+
89+
isnothing(params) && (params = optim_params(ps, :hydraulic; list_sameLayer, list_fix))
90+
length(paths) == length(theta) ||
91+
throw(ArgumentError("paths and theta must have the same length, got $(length(paths)) and $(length(theta))."))
92+
93+
update!(ps, paths, theta; params)
94+
95+
# broadcast list_sameLayer from source layer to all layers before update_hydraulic!
96+
# 要求在
97+
for (i, name) in enumerate(params.name)
98+
name in list_sameLayer || continue
99+
I = params.path[i][end] # layer index
100+
I isa Integer || continue
101+
h = getproperty(ps.hydraulic.profile, name)
102+
fill!(h, h[I])
103+
end
104+
105+
# update derived fields (e.g. VanGenuchten m = 1 − 1/n); must precede _sync_ksat!
106+
update_hydraulic!(ps.hydraulic.profile)
107+
_sync_ksat!(ps.hydraulic.kv, ps.hydraulic.profile, ps.hydraulic.dz_cm)
108+
109+
# rebuild AoS caches from updated SoA profiles
110+
@inbounds for i in 1:N
111+
ps.hydraulic.layers[i] = ps.hydraulic.profile[i]
112+
ps.thermal.layers[i] = ps.thermal.profile[i]
113+
end
114+
return nothing
115+
end

test/Model_SoilDiffEqs.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ _struct_is_stable(x) = isconcretetype(typeof(x)) && _fieldtypes_are_concrete(x)
1212
kv = @inferred KvLayers(retention)
1313
hydraulic = HydraulicProfile{FT,N}(retention, kv)
1414
thermal = ThermalProfile{FT,N}()
15-
model = SoilModel{FT,N}(hydraulic, thermal)
15+
model = SoilColumn{FT,N}(hydraulic, thermal)
1616

1717
@testset "concrete instance layouts" begin
1818
@test _struct_is_stable(p)
@@ -29,7 +29,7 @@ _struct_is_stable(x) = isconcretetype(typeof(x)) && _fieldtypes_are_concrete(x)
2929
@test hydraulic isa HydraulicProfile{FT,N,Campbell{FT},typeof(retention),typeof(kv)}
3030
@test thermal.profile isa ThermalMainLayers{FT,N}
3131
@test thermal isa ThermalProfile{FT,N,ThermalMain{FT},typeof(thermal.profile)}
32-
@test model isa SoilModel{FT,N,typeof(hydraulic),typeof(thermal)}
32+
@test model isa SoilColumn{FT,N,typeof(hydraulic),typeof(thermal)}
3333
end
3434

3535
@testset "nested fields remain concrete" begin
@@ -45,7 +45,7 @@ _struct_is_stable(x) = isconcretetype(typeof(x)) && _fieldtypes_are_concrete(x)
4545
@test (@inferred KvLayers(retention)) isa KvLayers{FT,N}
4646
@test (@inferred HydraulicProfile{FT,N}()) isa HydraulicProfile{FT,N}
4747
@test (@inferred ThermalProfile{FT,N}()) isa ThermalProfile{FT,N}
48-
@test (@inferred SoilModel{FT,N}()) isa SoilModel{FT,N}
48+
@test (@inferred SoilColumn{FT,N}()) isa SoilColumn{FT,N}
4949
end
5050

5151
params = parameters(model)

test/debug.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ p = Campbell(; b=2.0)
77
profile = Layers(p, N)
88

99
kv = KvLayers(profile)
10+
kv = KvExp()
1011
hydraulic = HydraulicProfile{FT,N}(profile, kv)
12+
@time parameters(hydraulic)
1113

12-
# model = SoilModel{FT,N}(; hydraulic)
13-
# parameters(model)
1414

15-
@code_warntype kv = KvLayers(profile)
16-
@code_warntype hydraulic = HydraulicProfile{FT,N}(profile, kv)
17-
@code_warntype model = SoilModel{FT,N}(hydraulic)
15+
##
16+
model = SoilColumn{FT,N}(hydraulic)
17+
@time parameters(model)
18+
19+
##
20+
# @code_warntype kv = KvLayers(profile)
21+
# @code_warntype hydraulic = HydraulicProfile{FT,N}(profile, kv)
22+
# @code_warntype model = SoilColumn{FT,N}(hydraulic)
1823
# @code_warntype parameters(model)

test/test-type_stability.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ using ModelParams, Test, Parameters
3131
ThermalProfile{FT,N,ThermalMain{FT},typeof(layers)}
3232
end
3333

34-
@testset "SoilModel" begin
34+
@testset "SoilColumn" begin
3535
# 无参路径:constprop 全走默认值,可 @inferred
36-
@test (@inferred SoilModel{FT,N}()) isa SoilModel{FT,N}
36+
@test (@inferred SoilColumn{FT,N}()) isa SoilColumn{FT,N}
3737
# 显式 hydraulic:N 作为类型参数传入,thermal 从 hydraulic 的类型参数 N 派生
3838
h = HydraulicProfile{FT,N}()
39-
model = @inferred SoilModel{FT,N}(h)
40-
@test model isa SoilModel{FT,N}
39+
model = @inferred SoilColumn{FT,N}(h)
40+
@test model isa SoilColumn{FT,N}
4141
@test model.hydraulic === h
4242
@test model.thermal isa ThermalProfile{FT,N}
4343
end

0 commit comments

Comments
 (0)