diff --git a/Project.toml b/Project.toml index 097fce9c489f549a745032509fa1a1da5e409860..28478b6dfd55b69be12556064e1491b165630c67 100755 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CryoGrid" uuid = "a535b82e-5f3d-4d97-8b0b-d6483f5bebd5" authors = ["Brian Groenke <brian.groenke@awi.de>", "Moritz Langer <moritz.langer@awi.de>"] -version = "0.6.4" +version = "0.6.5" [deps] ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66" diff --git a/src/Numerics/math.jl b/src/Numerics/math.jl index a4684a055432198d9594e2cbfebff0c2c1ed91ea..384df6426a13957509ed33fb0f240662c332c364 100644 --- a/src/Numerics/math.jl +++ b/src/Numerics/math.jl @@ -9,7 +9,6 @@ function finitediff!(∂x::AbstractVector, x::AbstractVector, Δ::AbstractVector @. ∂x = (xâ‚‚ - xâ‚) / Δ end end - """ lineardiffusion!(∂y::AbstractVector, x::AbstractVector, Δ::AbstractVector, k::Number) @@ -24,33 +23,8 @@ function lineardiffusion!(∂y::AbstractVector, x::AbstractVector, Δ::AbstractV @. ∂y = k*((x₃ - xâ‚‚)/Δ₂ - (xâ‚‚-xâ‚)/Δâ‚)/Δ₠end end - - -@propagate_inbounds function _nonlineardiffusion!(∂y, xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) - @. ∂y = (kâ‚‚*(x₃ - xâ‚‚)/Δxâ‚‚ - kâ‚*(xâ‚‚ - xâ‚)/Δxâ‚)/Δk -end -@propagate_inbounds function _nonlineardiffusion!( - ∂y::AbstractVector{Float64}, - xâ‚::AbstractVector{Float64}, - xâ‚‚::AbstractVector{Float64}, - x₃::AbstractVector{Float64}, - kâ‚::AbstractVector{Float64}, - kâ‚‚::AbstractVector{Float64}, - Δxâ‚::AbstractVector{Float64}, - Δxâ‚‚::AbstractVector{Float64}, - Δk::AbstractVector{Float64}, -) - @turbo @. ∂y = (kâ‚‚*(x₃ - xâ‚‚)/Δxâ‚‚ - kâ‚*(xâ‚‚ - xâ‚)/Δxâ‚)/Δk -end - """ - nonlineardiffusion!( - ∂y::AbstractVector{Float64}, - x::AbstractVector{Float64}, - Δx::AbstractVector{Float64}, - k::AbstractVector{Float64}, - Δk::AbstractVector{Float64} - ) + nonlineardiffusion!(∂y, x, Δx, k, Δk) Second order Laplacian with non-linear diffusion operator, `k`. Accelerated using `LoopVectorization.@turbo` for `Float64` vectors. """ @@ -62,9 +36,31 @@ function nonlineardiffusion!(∂y::AbstractVector, x::AbstractVector, Δx::Abstr kâ‚‚ = (@view k[2:end]), Δxâ‚ = (@view Δx[1:end-1]), Δxâ‚‚ = (@view Δx[2:end]); - _nonlineardiffusion!(∂y, xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) + nonlineardiffusion!(∂y, xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) end end +""" + nonlineardiffusion(xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) + +Scalar second order Laplacian with non-linear diffusion operator, `k`. +""" +nonlineardiffusion(xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) = (kâ‚‚*(x₃ - xâ‚‚)/Δxâ‚‚ - kâ‚*(xâ‚‚ - xâ‚)/Δxâ‚)/Δk +@propagate_inbounds function nonlineardiffusion!(∂y, xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) + @. ∂y = nonlineardiffusion(xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) +end +@propagate_inbounds function nonlineardiffusion!( + ∂y::AbstractVector{Float64}, + xâ‚::AbstractVector{Float64}, + xâ‚‚::AbstractVector{Float64}, + x₃::AbstractVector{Float64}, + kâ‚::AbstractVector{Float64}, + kâ‚‚::AbstractVector{Float64}, + Δxâ‚::AbstractVector{Float64}, + Δxâ‚‚::AbstractVector{Float64}, + Δk::AbstractVector{Float64}, +) + @turbo @. ∂y = nonlineardiffusion(xâ‚, xâ‚‚, x₃, kâ‚, kâ‚‚, Δxâ‚, Δxâ‚‚, Δk) +end """ harmonicmean(xâ‚, xâ‚‚, wâ‚, wâ‚‚) diff --git a/src/Strat/params.jl b/src/Strat/params.jl index 772bea2970e7775a8f83118a9c8c8d13e070453d..b45321cb5c5b1dafe92405cf0825fbdb19cad46e 100644 --- a/src/Strat/params.jl +++ b/src/Strat/params.jl @@ -1,4 +1,5 @@ abstract type ParamTransform end +shortname(::ParamTransform) = :transform # mapping struct ParamMapping{T,name,layer} transform::T @@ -28,27 +29,22 @@ Base.show(io::IO, ::MIME"text/plain", rv::ParameterVector{T,<:Any,<:Any,Tuple{}} Base.show(io::IO, ::MIME"text/plain", rv::ParameterVector{T,<:Any,<:Any}) where {T} = println(io, "$(length(rv))-element ParameterVector{T} with $(length(mappings(rv))) mappings\n$(mappings(rv)):\n$(getfield(rv, :vals))") ComponentArrays.ComponentArray(rv::ParameterVector) = getfield(rv, :vals) -_paramval(x) = x -_paramval(p::Param) = ustrip(p.val) # extracts value from Param type and strips units function parameters(model::Tile, transforms::Pair{Symbol,<:Pair{Symbol,<:ParamTransform}}...) - type2nt(p::Param) = p - type2nt(obj) = (; filter(p -> isa(p[2], Param) || !isempty(p[2]), map(n -> Symbol(n) => type2nt(getfield(obj, n)), fieldnames(typeof(obj))))...) getparam(x) = x - function getparam(p::AbstractVector) - # currently, we assume only one variable of each name in each layer; - # this could be relaxed in the future but will need to be appropriately handled - @assert length(p) == 1 "Found duplicate parameters in a layer: $p; this is not currently supported." - return p[1] - end + getparam(x::Union{<:AbstractVector,<:Tuple}) = length(x) == 1 ? getparam(x[1]) : collect(getparam.(x)) + paramval(x) = ustrip(x) + paramval(x::Param) = ustrip(x.val) m = Model(model) nestedparams = mapflat(getparam, groupparams(m, :layer, :fieldname); maptype=NamedTuple) mappedparams = nestedparams mappings = ParamMapping[] for (layer,(var,transform)) in transforms - @set! mappedparams[layer][var] = mapflat(getparam, type2nt(transform); maptype=NamedTuple) + m_transform = Model(transform) + m_transform[:transform] = repeat([shortname(transform)], length(ModelParameters.params(m_transform))) + @set! mappedparams[layer][var] = mapflat(getparam, groupparams(m_transform, :transform, :fieldname); maptype=NamedTuple) push!(mappings, ParamMapping(transform, var, layer)) end - mappedarr = ComponentArray(mapflat(_paramval, mappedparams)) + mappedarr = ComponentArray(mapflat(paramval, mappedparams)) return ParameterVector(mappedarr, mappedparams, mappings...) end @inline @generated function updateparams!(v::AbstractVector, model::Tile, u, du, t) @@ -96,6 +92,7 @@ Applies a linear trend to a parameter `p` by reparameterizing it as: `p = pâ‚*t minval::Float64 = -Inf maxval::Float64 = Inf end +shortname(::LinearTrend) = :trend function transform(state, trend::LinearTrend) let t = min(state.t - trend.tstart, trend.tstop), β = trend.slope / trend.period, @@ -104,19 +101,29 @@ function transform(state, trend::LinearTrend) end end """ - PiecewiseLinear{N,Tb,Tv,Tl,I} <: ParamTransform +Helper type for PiecewiseLinear. +""" +struct PiecewiseKnot{Tw,Tv} + binwidth::Tw + value::Tv +end +""" + PiecewiseLinear{N,Tw,Tv} <: ParamTransform Reparameterizes parameter `p` as `p = pâ‚δâ‚t + ⋯ + pₖδₖt` where δₖ are indicators for when `tâ‚–â‚‹â‚ <= t <= tâ‚–`. To facilitate sampling and optimization, change points táµ¢ are parameterized as bin widths, which should be strictly positive. `PiecewiseLinear` will normalize them and scale by the size of the time interval. """ -@with_kw struct PiecewiseLinear{Nb,Tb,Nv,Tv} <: ParamTransform - bins::NTuple{Nb,Tb} = (1.0,); @assert Nb > 0; @assert all(bins .> 0.0) - values::NTuple{Nv,Tv} = (0.0,); @assert Nv == Nb+1 "need n+1 knots for n bins" +@with_kw struct PiecewiseLinear{N,Tw,Tv} <: ParamTransform + initialvalue::Tv + knots::NTuple{N,PiecewiseKnot{Tw,Tv}} tstart::Float64 = 0.0; @assert isfinite(tstart) tstop::Float64 = 1.0; @assert tstop > tstart; @assert isfinite(tstop) end +PiecewiseLinear(initialvalue::T; tstart=0.0, tstop=1.0) where T = PiecewiseLinear{0,Nothing,T}(initialvalue, (), tstart, tstop) +PiecewiseLinear(initialvalue, knots...; tstart=0.0, tstop=1.0) = PiecewiseLinear(initialvalue, Tuple(map(Base.splat(PiecewiseKnot), knots)), tstart, tstop) +shortname(::PiecewiseLinear) = :linear function transform(state, pc::PiecewiseLinear) function binindex(values::Tuple, st, en, x) mid = Int(floor((st + en)/2)) @@ -132,8 +139,9 @@ function transform(state, pc::PiecewiseLinear) end let tspan = pc.tstop - pc.tstart, t = min(max(state.t - pc.tstart, zero(state.t)), tspan), - ts = (0.0, cumsum((pc.bins ./ sum(pc.bins)).*tspan)...), - vals = pc.values, + bins = map(k -> k.binwidth, pc.knots), + vals = (pc.initialvalue, map(k -> k.value, pc.knots)...), + ts = (0.0, cumsum((bins ./ sum(bins)).*tspan)...), i = binindex(ts, 1, length(ts), t); vals[i] + (vals[i+1] - vals[i])*(t - ts[i]) / (ts[i+1] - ts[i]) end diff --git a/test/Strat/param_tests.jl b/test/Strat/param_tests.jl index a2d58a0a62acd1c9e85524517c45dabd469b81d3..d7bd5e37e24c582544f9c3be44678076d43d8d08 100644 --- a/test/Strat/param_tests.jl +++ b/test/Strat/param_tests.jl @@ -13,14 +13,14 @@ using CryoGrid trend = LinearTrend(slope=0.1, intercept=0.5, tstart=0.0, tstop=1.0) @test CryoGrid.Strat.transform((t=2.0,), trend) ≈ 0.6 end - @testset "PiecewiseConstant" begin - pc = PiecewiseLinear(bins=(1.0,), values=(0.0,1.0), tstart=0.0, tstop=1.0) + @testset "PiecewiseLinear" begin + pc = PiecewiseLinear(0.0, (1.0,1.0); tstart=0.0, tstop=1.0) @test CryoGrid.Strat.transform((t=-0.1,), pc) ≈ 0.0 @test CryoGrid.Strat.transform((t=0.0,), pc) ≈ 0.0 @test CryoGrid.Strat.transform((t=0.5,), pc) ≈ 0.5 @test CryoGrid.Strat.transform((t=1.0,), pc) ≈ 1.0 @test CryoGrid.Strat.transform((t=1.1,), pc) ≈ 1.0 - pc = PiecewiseLinear(bins=(0.4,0.6), values=(1.0,0.5,0.0), tstart=0.0, tstop=1.0) + pc = PiecewiseLinear(1.0, (0.4,0.5), (0.6,0.0); tstart=0.0, tstop=1.0) @test CryoGrid.Strat.transform((t=-0.1,), pc) ≈ 1.0 @test CryoGrid.Strat.transform((t=0.0,), pc) ≈ 1.0 @test CryoGrid.Strat.transform((t=0.7,), pc) ≈ 0.25