From b4dcb2867a3884f4b3c695f00bc0f1c55d2374a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20O=C5=A1lej=C5=A1ek?= Date: Tue, 22 Oct 2024 17:05:38 +0200 Subject: [PATCH 1/4] Added power variogram fit function and tests --- src/fitting.jl | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ test/fitting.jl | 29 +++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/fitting.jl b/src/fitting.jl index 5397a0f..175678c 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) + λ = fill(sum(yᵢ -> yᵢ^2, y′), 3) + + # 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..20b68a5 100644 --- a/test/fitting.jl +++ b/test/fitting.jl @@ -75,4 +75,33 @@ @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 variogram + # generating 'own' empirical variogram since there are no available data for power variogram + sₜ = 1.00 + nₜ = 0.20 + eₜ = 1.50 + γₜ = PowerVariogram(sₜ, nₜ ,eₜ) + abscissas = collect(0.0:0.1:10.0)u"m" + ordinates = γₜ.(abscissas) + counts = rand(1000:5000, length(abscissas)) + g = EmpiricalVariogram(counts, abscissas, ordinates, 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ₜ) + abscissas = collect(0.0:10.0:200.0)u"m" + ordinates = γₜ.(abscissas) + counts = rand(100:1000, length(abscissas)) + g = EmpiricalVariogram(counts, abscissas, ordinates, 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 From 0fb61c1c7f1b9d9442cbd0f1a4f3228ece345e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Tue, 22 Oct 2024 17:46:04 -0300 Subject: [PATCH 2/4] Minor adjustments to tests --- test/fitting.jl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/fitting.jl b/test/fitting.jl index 20b68a5..9fec3bf 100644 --- a/test/fitting.jl +++ b/test/fitting.jl @@ -76,16 +76,15 @@ γ = GeoStatsFunctions.fit(Variogram, g, nugget=0.01u"K^2") @test isapprox(nugget(γ), 0.01u"K^2", atol=1e-3u"K^2") - # power variogram - # generating 'own' empirical variogram since there are no available data for power variogram + # power variograms sₜ = 1.00 nₜ = 0.20 eₜ = 1.50 γₜ = PowerVariogram(sₜ, nₜ ,eₜ) - abscissas = collect(0.0:0.1:10.0)u"m" - ordinates = γₜ.(abscissas) - counts = rand(1000:5000, length(abscissas)) - g = EmpiricalVariogram(counts, abscissas, ordinates, Euclidean(), :matheron) + 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) @@ -96,10 +95,10 @@ nₜ = 1.45 eₜ = 0.64 γₜ = PowerVariogram(sₜ, nₜ ,eₜ) - abscissas = collect(0.0:10.0:200.0)u"m" - ordinates = γₜ.(abscissas) - counts = rand(100:1000, length(abscissas)) - g = EmpiricalVariogram(counts, abscissas, ordinates, Euclidean(), :matheron) + 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) From 7843636d51c78644fd5e04a59ae3cfe881b34dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Tue, 22 Oct 2024 17:52:15 -0300 Subject: [PATCH 3/4] Update src/fitting.jl --- src/fitting.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fitting.jl b/src/fitting.jl index 175678c..253cef0 100644 --- a/src/fitting.jl +++ b/src/fitting.jl @@ -238,7 +238,7 @@ function fit_impl( sum(i -> w[i] * (γ(x′[i]) - y′[i])^2, eachindex(w, x′, y′)) end - # Linear constraints + # 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] From 3998b88d3ff9445235befbbaed97018cdc2db468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20O=C5=A1lej=C5=A1ek?= Date: Tue, 22 Oct 2024 23:22:54 +0200 Subject: [PATCH 4/4] Modified linear constraints to avoid unnecessary vector allocation. --- src/fitting.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fitting.jl b/src/fitting.jl index 253cef0..6d1d6e9 100644 --- a/src/fitting.jl +++ b/src/fitting.jl @@ -241,10 +241,10 @@ function fit_impl( # 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] + 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) - λ = fill(sum(yᵢ -> yᵢ^2, y′), 3) + λ = sum(yᵢ -> yᵢ^2, y′) # maximum scaling, nugget and exponent xmax = maximum(x′) @@ -268,7 +268,7 @@ function fit_impl( u = [sᵤ, eᵤ, nᵤ] # solve optimization problem - sol = Optim.optimize(θ -> J(θ) + λ' * L(θ), l, u, θₒ) + sol = Optim.optimize(θ -> J(θ) + λ * L(θ), l, u, θₒ) ϵ = Optim.minimum(sol) θ = Optim.minimizer(sol)