diff --git a/src/fitting.jl b/src/fitting.jl index 5397a0f..6d1d6e9 100644 --- a/src/fitting.jl +++ b/src/fitting.jl @@ -192,3 +192,88 @@ function fit_impl( γ, ϵ end + +function fit_impl( + V::Type{<:PowerVariogram}, + g::EmpiricalVariogram, + algo::WeightedLeastSquares; + scale=nothing, + exponent=nothing, + nugget=nothing, + maxscale=nothing, + maxexponent=nothing, + maxnugget=nothing +) + # coordinates of empirical variogram + x = g.abscissas + y = g.ordinates + n = g.counts + + # discard invalid bins + x = x[n .> 0] + y = y[n .> 0] + n = n[n .> 0] + + # strip units of coordinates + ux = unit(eltype(x)) + uy = unit(eltype(y)) + x′ = ustrip.(x) + y′ = ustrip.(y) + + # strip units of kwargs + scale′ = isnothing(scale) ? scale : ustrip(ux, scale) + exponent′ = isnothing(exponent) ? exponent : ustrip(uy, exponent) + nugget′ = isnothing(nugget) ? nugget : ustrip(uy, nugget) + maxscale′ = isnothing(maxscale) ? maxscale : ustrip(ux, maxscale) + maxexponent′ = isnothing(maxexponent) ? maxexponent : ustrip(uy, maxexponent) + maxnugget′ = isnothing(maxnugget) ? maxnugget : ustrip(uy, maxnugget) + + # evaluate weights + f = algo.weightfun + w = isnothing(f) ? n / sum(n) : map(xᵢ -> ustrip(f(xᵢ)), x) + + # objective function + function J(θ) + γ = V(scaling=θ[1], nugget=θ[2], exponent=θ[3]) + sum(i -> w[i] * (γ(x′[i]) - y′[i])^2, eachindex(w, x′, y′)) + end + + # linear constraints + # 1. scaling ≥ 0 + # 2. 0 ≤ exponent ≤ 2 + L(θ) = θ[1] ≥ 0.0 ? 0.0 : -θ[1] + θ[3] ≥ 0.0 ? 0.0 : -θ[3] + 2.0 ≥ θ[3] ? 0.0 : θ[3] - 2.0 + + # penalty for linear constraint (J + λL) + λ = sum(yᵢ -> yᵢ^2, y′) + + # maximum scaling, nugget and exponent + xmax = maximum(x′) + ymax = maximum(y′) + smax = isnothing(maxscale′) ? xmax : maxscale′ + emax = isnothing(maxexponent′) ? 2.0 : maxexponent′ + nmax = isnothing(maxnugget′) ? ymax : maxnugget′ + + # initial guess + sₒ = isnothing(scale′) ? smax / 3 : scale′ + eₒ = isnothing(exponent′) ? 0.95 * emax : exponent′ + nₒ = isnothing(nugget′) ? 0.01 * nmax : nugget′ + θₒ = [sₒ, eₒ, nₒ] + + # box constraints + δ = 1e-8 + sₗ, sᵤ = isnothing(scale′) ? (zero(smax), smax) : (scale′ - δ, scale′ + δ) + eₗ, eᵤ = isnothing(exponent′) ? (zero(emax), emax) : (exponent′ - δ, exponent′ + δ) + nₗ, nᵤ = isnothing(nugget′) ? (zero(nmax), nmax) : (nugget′ - δ, nugget′ + δ) + l = [sₗ, eₗ, nₗ] + u = [sᵤ, eᵤ, nᵤ] + + # solve optimization problem + sol = Optim.optimize(θ -> J(θ) + λ * L(θ), l, u, θₒ) + ϵ = Optim.minimum(sol) + θ = Optim.minimizer(sol) + + # optimal variogram (with units) + γ = V(scaling=θ[1], nugget=θ[2], exponent=θ[3]) + + γ, ϵ +end diff --git a/test/fitting.jl b/test/fitting.jl index d978935..9fec3bf 100644 --- a/test/fitting.jl +++ b/test/fitting.jl @@ -75,4 +75,32 @@ @test isapprox(sill(γ), 0.03u"K^2", atol=1e-3u"K^2") γ = GeoStatsFunctions.fit(Variogram, g, nugget=0.01u"K^2") @test isapprox(nugget(γ), 0.01u"K^2", atol=1e-3u"K^2") + + # power variograms + sₜ = 1.00 + nₜ = 0.20 + eₜ = 1.50 + γₜ = PowerVariogram(sₜ, nₜ ,eₜ) + xs = collect(0.0:0.1:10.0)u"m" + ys = γₜ.(xs) + ns = rand(1000:5000, length(xs)) + g = EmpiricalVariogram(ns, xs, ys, Euclidean(), :matheron) + γ = GeoStatsFunctions.fit(PowerVariogram, g) + @test isapprox(γ.nugget, nₜ, atol=1e-3) + @test isapprox(γ.scaling, sₜ, atol=1e-3) + @test isapprox(γ.exponent, eₜ, atol=1e-3) + + # test different settings + sₜ = 6.54 + nₜ = 1.45 + eₜ = 0.64 + γₜ = PowerVariogram(sₜ, nₜ ,eₜ) + xs = collect(0.0:10.0:200.0)u"m" + ys = γₜ.(xs) + ns = rand(100:1000, length(xs)) + g = EmpiricalVariogram(ns, xs, ys, Euclidean(), :matheron) + γ = GeoStatsFunctions.fit(PowerVariogram, g) + @test isapprox(γ.nugget, nₜ, atol=1e-3) + @test isapprox(γ.scaling, sₜ, atol=1e-3) + @test isapprox(γ.exponent, eₜ, atol=1e-3) end