|
| 1 | +using ModelParams, Test |
| 2 | + |
| 3 | +@testset "KvExp" begin |
| 4 | + kv = KvExp(kv=10.0, f=0.01) |
| 5 | + |
| 6 | + # At the surface (z=0): Ksat = kv |
| 7 | + @test kv_at_depth(kv, 1, 0.0) ≈ 10.0 |
| 8 | + |
| 9 | + # At depth 100 cm: Ksat = kv * exp(-f*z) = 10 * exp(-1) |
| 10 | + @test kv_at_depth(kv, 3, 100.0) ≈ 10.0 * exp(-1.0) |
| 11 | + |
| 12 | + # Strictly decreasing with depth |
| 13 | + k1 = kv_at_depth(kv, 1, 10.0) |
| 14 | + k2 = kv_at_depth(kv, 2, 50.0) |
| 15 | + k3 = kv_at_depth(kv, 3, 200.0) |
| 16 | + @test k1 > k2 > k3 |
| 17 | +end |
| 18 | + |
| 19 | +@testset "KvExpConst" begin |
| 20 | + kv = KvExpConst(kv=10.0, f=0.01, z_exp=50.0) |
| 21 | + |
| 22 | + # At z < z_exp: same as exponential |
| 23 | + @test kv_at_depth(kv, 1, 20.0) ≈ 10.0 * exp(-0.01 * 20.0) |
| 24 | + |
| 25 | + # At z_exp: Ksat = kv * exp(-f * z_exp) |
| 26 | + kv_at_zexp = 10.0 * exp(-0.01 * 50.0) |
| 27 | + @test kv_at_depth(kv, 2, 50.0) ≈ kv_at_zexp |
| 28 | + |
| 29 | + # At z > z_exp: constant (same as at z_exp) |
| 30 | + @test kv_at_depth(kv, 3, 100.0) ≈ kv_at_zexp |
| 31 | + @test kv_at_depth(kv, 4, 300.0) ≈ kv_at_zexp |
| 32 | +end |
| 33 | + |
| 34 | +@testset "KvLayers" begin |
| 35 | + kv = KvLayers{Float64,3}(kv=[5.0, 10.0, 2.0]) |
| 36 | + @test kv_at_depth(kv, 1, 0.0) ≈ 5.0 |
| 37 | + @test kv_at_depth(kv, 2, 10.0) ≈ 10.0 |
| 38 | + @test kv_at_depth(kv, 3, 50.0) ≈ 2.0 |
| 39 | +end |
| 40 | + |
| 41 | +# @testset "KvExpLayers" begin |
| 42 | +# # Upper 2 layers: layered; below: exponential with f[2]=0.01 from kv[2] |
| 43 | +# kv = KvExpLayers{Float64,3}(kv=[5.0, 8.0, 3.0], f=[0.02, 0.01, 0.01]) |
| 44 | +# nlayers_kv = 2 |
| 45 | +# z_layered_cm = 40.0 # bottom of layer 2 |
| 46 | + |
| 47 | +# # Layer 1 (≤ nlayers_kv): returns kv[1] |
| 48 | +# @test kv_at_depth(kv, 1, 10.0, nlayers_kv, z_layered_cm) ≈ 5.0 |
| 49 | +# # Layer 2 (≤ nlayers_kv): returns kv[2] |
| 50 | +# @test kv_at_depth(kv, 2, 30.0, nlayers_kv, z_layered_cm) ≈ 8.0 |
| 51 | +# # Layer 3 (> nlayers_kv): exponential from kv[2] with f[2] |
| 52 | +# z3 = 60.0 |
| 53 | +# expected = 8.0 * exp(-0.01 * (z3 - z_layered_cm)) |
| 54 | +# @test kv_at_depth(kv, 3, z3, nlayers_kv, z_layered_cm) ≈ expected |
| 55 | +# end |
| 56 | + |
| 57 | +@testset "kv_layer_ksat — integral formula" begin |
| 58 | + # KvExp: layer-average = kv/f/dz * (exp(-f*z1) - exp(-f*z2)) |
| 59 | + kv = KvExp(kv=10.0, f=0.01) |
| 60 | + z1, z2 = 0.0, 20.0 |
| 61 | + expected = 10.0 / (0.01 * 20.0) * (exp(-0.01 * z1) - exp(-0.01 * z2)) |
| 62 | + @test kv_layer_ksat(kv, 1, z1, z2) ≈ expected |
| 63 | + |
| 64 | + # Thin layer: degenerates to centre-point |
| 65 | + z1, z2 = 10.0, 10.0 + 1e-10 |
| 66 | + @test kv_layer_ksat(kv, 1, z1, z2) ≈ 10.0 * exp(-0.01 * (z1 + z2) / 2) |
| 67 | + |
| 68 | + # KvExpConst: layer entirely above z_exp |
| 69 | + kvc = KvExpConst(kv=10.0, f=0.01, z_exp=50.0) |
| 70 | + @test kv_layer_ksat(kvc, 1, 0.0, 20.0) ≈ |
| 71 | + 10.0 / (0.01 * 20.0) * (1.0 - exp(-0.01 * 20.0)) |
| 72 | + |
| 73 | + # Layer entirely below z_exp: constant |
| 74 | + ksat_const = 10.0 * exp(-0.01 * 50.0) |
| 75 | + @test kv_layer_ksat(kvc, 2, 60.0, 80.0) ≈ ksat_const |
| 76 | + |
| 77 | + # Layer straddling z_exp |
| 78 | + ksat_kvc = kv_layer_ksat(kvc, 1, 40.0, 60.0) |
| 79 | + # First 10 cm: exponential from 40 to 50; last 10 cm: constant |
| 80 | + exp_part = 10.0 / (0.01 * 10.0) * (exp(-0.01 * 40) - exp(-0.01 * 50)) * 10.0 / 20.0 |
| 81 | + const_part = ksat_const * 10.0 / 20.0 |
| 82 | + @test ksat_kvc ≈ exp_part + const_part |
| 83 | + |
| 84 | + # _sync_ksat! with dz_cm correctly sets param[i].Ksat via integral |
| 85 | + par = Campbell(θ_sat=0.4, ψ_sat=-10.0, Ksat=10.0, b=4.0) |
| 86 | + kv2 = KvExp(kv=10.0, f=0.01) |
| 87 | + dz = [20.0, 20.0, 20.0] # [cm] |
| 88 | + # ps = SoilModel(par, 3; kv_profile=kv2, dz_cm=dz) |
| 89 | + # for i in 1:3 |
| 90 | + # z1_cm = (i - 1) * 20.0 |
| 91 | + # z2_cm = i * 20.0 |
| 92 | + # @test ps.param_hydraulic[i].Ksat ≈ kv_layer_ksat(kv2, i, z1_cm, z2_cm) |
| 93 | + # end |
| 94 | +end |
| 95 | + |
| 96 | +@testset "KvProfile ModelParams bounds" begin |
| 97 | + kv = KvExp{Float64}() |
| 98 | + # get_opt_info returns (x0, lb, ub, paths) in field-definition order: kv, f |
| 99 | + x0, lb, ub, paths = get_opt_info(kv) |
| 100 | + @test length(x0) == 2 |
| 101 | + @test lb[1] ≈ 0.002 # kv lower |
| 102 | + @test ub[1] ≈ 100.0 # kv upper |
| 103 | + @test lb[2] ≈ 0.0 # f lower |
| 104 | + @test ub[2] ≈ 0.1 # f upper |
| 105 | + |
| 106 | + # KvExpConst has 3 params |
| 107 | + kvc = KvExpConst{Float64}() |
| 108 | + x0c, lbc, ubc, _ = get_opt_info(kvc) |
| 109 | + @test length(x0c) == 3 |
| 110 | + @test lbc[3] ≈ 10.0 # z_exp lower |
| 111 | + @test ubc[3] ≈ 500.0 # z_exp upper |
| 112 | +end |
| 113 | + |
| 114 | +# @testset "KvProfile layer ModelParams bridge" begin |
| 115 | +# kv = KvExpLayers{Float64,3}(kv=[5.0, 8.0, 3.0], f=[0.02, 0.01, 0.01]) |
| 116 | +# @test kv isa AbstractKvLayers |
| 117 | + |
| 118 | +# params = parameters(kv) |
| 119 | +# @test params.value == [5.0, 8.0, 3.0, 0.02, 0.01, 0.01] |
| 120 | +# @test params.path == [[:kv, 1], [:kv, 2], [:kv, 3], [:f, 1], [:f, 2], [:f, 3]] |
| 121 | + |
| 122 | +# par = Campbell(θ_sat=0.4, ψ_sat=-10.0, Ksat=10.0, b=4.0) |
| 123 | +# ps = SoilModel(par, 3; kv_profile=kv, nlayers_kv=2, dz_cm=[20.0, 20.0, 20.0]) |
| 124 | +# @test ps.kv_profile isa AbstractKvLayers |
| 125 | +# @test get_params(ps, :kv_profile).value == params.value |
| 126 | +# end |
0 commit comments