Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SmoothedConstantInterpolation #367

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ corresponding to `(u,t)` pairs.

- `ConstantInterpolation(u,t)` - A piecewise constant interpolation.

- `SmoothedConstantInterpolation(u,t)` - An integral preserving continuously differentiable approximation of constant interpolation.
- `LinearInterpolation(u,t)` - A linear interpolation.
- `QuadraticInterpolation(u,t)` - A quadratic interpolation.
- `LagrangeInterpolation(u,t,n)` - A Lagrange interpolation of order `n`.
Expand Down
1 change: 1 addition & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ corresponding to `(u,t)` pairs.

- `ConstantInterpolation(u,t)` - A piecewise constant interpolation.

- `SmoothedConstantInterpolation(u,t)` - An integral preserving continuously differentiable approximation of constant interpolation.
- `LinearInterpolation(u,t)` - A linear interpolation.
- `QuadraticInterpolation(u,t)` - A quadratic interpolation.
- `LagrangeInterpolation(u,t,n)` - A Lagrange interpolation of order `n`.
Expand Down
1 change: 1 addition & 0 deletions docs/src/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ QuadraticInterpolation
LagrangeInterpolation
AkimaInterpolation
ConstantInterpolation
SmoothedConstantInterpolation
QuadraticSpline
CubicSpline
BSplineInterpolation
Expand Down
18 changes: 18 additions & 0 deletions docs/src/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ A = ConstantInterpolation(u, t, dir = :right)
plot(A)
```

## Smoothed Constant Interpolation

This function is much like the constant interpolation above, but the transition
between consecutive values is smoothed out so that the function is continuously
differentiable. The smoothing is done in such a way that the integral of this function
is never much off from the same integral of constant interpolation without smoothing (because of the symmetry of the smoothing sections).
The maximum smoothing distance in the `t` direction from the data points can be set
with the keyword argument `d_max`.

```@example tutorial
A = ConstantInterpolation(u, t)
plot(A)
A = SmoothedConstantInterpolation(u, t; d_max = 10)
plot!(A)
```

Note that `u[end]` is ignored.

## Quadratic Spline

This is the quadratic spline. It is a continuously differentiable interpolation
Expand Down
7 changes: 4 additions & 3 deletions src/DataInterpolations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ function Base.showerror(io::IO, e::IntegralNotInvertibleError)
end

export LinearInterpolation, QuadraticInterpolation, LagrangeInterpolation,
AkimaInterpolation, ConstantInterpolation, QuadraticSpline, CubicSpline,
BSplineInterpolation, BSplineApprox, CubicHermiteSpline, PCHIPInterpolation,
QuinticHermiteSpline, LinearInterpolationIntInv, ConstantInterpolationIntInv
AkimaInterpolation, ConstantInterpolation, SmoothedConstantInterpolation,
QuadraticSpline, CubicSpline, BSplineInterpolation, BSplineApprox,
CubicHermiteSpline, PCHIPInterpolation, QuinticHermiteSpline,
LinearInterpolationIntInv, ConstantInterpolationIntInv

# added for RegularizationSmooth, JJS 11/27/21
### Regularization data smoothing and interpolation
Expand Down
18 changes: 18 additions & 0 deletions src/derivatives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ function _derivative(A::LinearInterpolation, t::Number, iguess)
slope
end

function _derivative(A::SmoothedConstantInterpolation{<:AbstractVector}, t::Number, iguess)
idx = get_idx(A, t, iguess)
d_lower, d_upper, c_lower, c_upper = get_parameters(A, idx)

# Fix extrapolation behavior as constant for now
if t <= first(A.t) || t >= last(A.t)
return zero(c_upper / oneunit(t))
end

if (t - A.t[idx]) < d_lower
-2c_lower * ((t - A.t[idx]) / d_lower - 1) / d_lower
elseif (A.t[idx + 1] - t) < d_upper
2c_upper * (1 - (A.t[idx + 1] - t) / d_upper) / d_upper
else
zero(c_upper / oneunit(t))
end
end

function _derivative(A::QuadraticInterpolation, t::Number, iguess)
idx = get_idx(A, t, iguess)
Δt = t - A.t[idx]
Expand Down
32 changes: 32 additions & 0 deletions src/integrals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,38 @@ function _integral(
end
end

function _integral(A::SmoothedConstantInterpolation{<:AbstractVector},
idx::Number, t1::Number, t2::Number)
d_lower, d_upper, c_lower, c_upper = get_parameters(A, idx)

bound_lower = A.t[idx] + d_lower
bound_upper = A.t[idx + 1] - d_upper

out = A.u[idx] * (t2 - t1)

# Fix extrapolation behavior as constant for now
if t1 <= first(A.t)
t1 = first(A.t)
elseif t2 >= last(A.t)
t2 = last(A.t)
end

if t1 < bound_lower
t2_ = min(t2, bound_lower)
out -= c_lower * d_lower *
(((t2_ - A.t[idx]) / d_lower - 1)^3 - ((t1 - A.t[idx]) / d_lower - 1)^3) / 3
end

if t2 > bound_upper
t1_ = max(t1, bound_upper)
out += c_upper * d_upper *
((1 - (A.t[idx + 1] - t2) / d_upper)^3 -
(1 - (A.t[idx + 1] - t1_) / d_upper)^3) / 3
end

out
end

function _integral(A::QuadraticInterpolation{<:AbstractVector{<:Number}},
idx::Number, t1::Number, t2::Number)
α, β = get_parameters(A, idx)
Expand Down
55 changes: 55 additions & 0 deletions src/interpolation_caches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,61 @@ function ConstantInterpolation(
ConstantInterpolation(u, t, I, dir, extrapolate, cache_parameters, assume_linear_t)
end

"""
SmoothedConstantInterpolation(u, t; d_max = Inf, extrapolate = false,
cache_parameters = false, assume_linear_t = 1e-2)

It is a method for interpolating constantly with forward fill, with smoothing around the
value transitions to make the curve continuously differentiable while the integral never
drifts far from the integral of constant interpolation. In this interpolation type u[end] is ignored.

## Arguments

- `u`: data points.
- `t`: time points.

## Keyword Arguments

- `d_max`: the maximum distance in `t` from the data points the smoothing is allowed to reach.
- `extrapolate`: boolean value to allow extrapolation. Defaults to `false`.
- `cache_parameters`: precompute parameters at initialization for faster interpolation computations. Note: if activated, `u` and `t` should not be modified. Defaults to `false`.
- `assume_linear_t`: boolean value to specify a faster index lookup behavior for
evenly-distributed abscissae. Alternatively, a numerical threshold may be specified
for a test based on the normalized standard deviation of the difference with respect
to the straight line (see [`looks_linear`](@ref)). Defaults to 1e-2.
"""
struct SmoothedConstantInterpolation{uType, tType, IType, dType, cType, dmaxType, T, N} <:
AbstractInterpolation{T, N}
u::uType
t::tType
I::IType
p::SmoothedConstantParameterCache{dType, cType}
d_max::dmaxType
extrapolate::Bool
iguesser::Guesser{tType}
cache_parameters::Bool
linear_lookup::Bool
function SmoothedConstantInterpolation(
u, t, I, p, d_max, extrapolate, cache_parameters, assume_linear_t)
linear_lookup = seems_linear(assume_linear_t, t)
N = get_output_dim(u)
new{typeof(u), typeof(t), typeof(I), typeof(p.d),
typeof(p.c), typeof(d_max), eltype(u), N}(
u, t, I, p, d_max, extrapolate, Guesser(t), cache_parameters, linear_lookup)
end
end

function SmoothedConstantInterpolation(u, t; d_max = Inf, extrapolate = false,
cache_parameters = false, assume_linear_t = 1e-2)
u, t = munge_data(u, t)
p = SmoothedConstantParameterCache(u, t, cache_parameters, d_max)
A = SmoothedConstantInterpolation(
u, t, nothing, p, d_max, extrapolate, cache_parameters, assume_linear_t)
I = cumulative_integral(A, cache_parameters)
SmoothedConstantInterpolation(
u, t, I, p, d_max, extrapolate, cache_parameters, assume_linear_t)
end

"""
QuadraticSpline(u, t; extrapolate = false, cache_parameters = false)

Expand Down
25 changes: 24 additions & 1 deletion src/interpolation_methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function _interpolate(A::AkimaInterpolation{<:AbstractVector}, t::Number, iguess
@evalpoly wj A.u[idx] A.b[idx] A.c[idx] A.d[idx]
end

# ConstantInterpolation Interpolation
# Constant Interpolation
function _interpolate(A::ConstantInterpolation{<:AbstractVector}, t::Number, iguess)
if A.dir === :left
# :left means that value to the left is used for interpolation
Expand All @@ -139,6 +139,29 @@ function _interpolate(A::ConstantInterpolation{<:AbstractMatrix}, t::Number, igu
A.u[:, idx]
end

# Smoothed constant Interpolation
function _interpolate(A::SmoothedConstantInterpolation{<:AbstractVector}, t::Number, iguess)
idx = get_idx(A, t, iguess)
d_lower, d_upper, c_lower, c_upper = get_parameters(A, idx)

# Fix extrapolation behavior as constant for now
if t <= first(A.t)
return first(A.u)
elseif t >= last(A.t)
return A.u[end - 1]
end

out = A.u[idx]

if (t - A.t[idx]) < d_lower
out -= c_lower * ((t - A.t[idx]) / d_lower - 1)^2
elseif (A.t[idx + 1] - t) < d_upper
out += c_upper * (1 - (A.t[idx + 1] - t) / d_upper)^2
end

out
end

# QuadraticSpline Interpolation
function _interpolate(A::QuadraticSpline{<:AbstractVector}, t::Number, iguess)
idx = get_idx(A, t, iguess)
Expand Down
15 changes: 15 additions & 0 deletions src/interpolation_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,21 @@ function get_parameters(A::LinearInterpolation, idx)
end
end

function get_parameters(A::SmoothedConstantInterpolation, idx)
if A.cache_parameters
d_lower = A.p.d[idx]
d_upper = A.p.d[idx + 1]
c_lower = A.p.c[idx]
c_upper = A.p.c[idx + 1]
d_lower, d_upper, c_lower, c_upper
else
d_lower, c_lower = smoothed_linear_interpolation_parameters(A.u, A.t, A.d_max, idx)
d_upper, c_upper = smoothed_linear_interpolation_parameters(
A.u, A.t, A.d_max, idx + 1)
d_lower, d_upper, c_lower, c_upper
end
end

function get_parameters(A::QuadraticInterpolation, idx)
if A.cache_parameters
A.p.α[idx], A.p.β[idx]
Expand Down
25 changes: 25 additions & 0 deletions src/parameter_caches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@ function linear_interpolation_parameters(u::AbstractArray{T, N}, t, idx) where {
return slope
end

struct SmoothedConstantParameterCache{dType, cType}
d::dType
c::cType
end

function SmoothedConstantParameterCache(u, t, cache_parameters, d_max)
if cache_parameters
parameters = smoothed_linear_interpolation_parameters.(
Ref(u), Ref(t), d_max, eachindex(t))
d, c = collect.(eachrow(stack(collect.(parameters))))
SmoothedConstantParameterCache(d, c)
else
SmoothedConstantParameterCache(eltype(t)[], eltype(u)[])
end
end

function smoothed_linear_interpolation_parameters(u, t, d_max, idx)
# TODO: Add support for making periodic extrapolation smooth
if isone(idx) || (idx == length(t))
zero(one(eltype(t))) / 2, zero(one(eltype(u)) / 2)
else
min(t[idx] - t[idx - 1], t[idx + 1] - t[idx], 2d_max) / 2, (u[idx] - u[idx - 1]) / 2
end
end

struct QuadraticParameterCache{pType}
α::pType
β::pType
Expand Down
9 changes: 8 additions & 1 deletion test/derivative_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function test_derivatives(method; args = [], kwargs = [], name::String)
# Interpolation time points
for _t in t[2:(end - 1)]
if func isa BSplineInterpolation || func isa BSplineApprox ||
func isa CubicHermiteSpline
func isa CubicHermiteSpline || func isa SmoothedConstantInterpolation
fdiff = forward_fdm(5, 1; geom = true)(func, _t)
fdiff2 = forward_fdm(5, 1; geom = true)(t -> derivative(func, t), _t)
else
Expand Down Expand Up @@ -140,6 +140,13 @@ end
@test all(derivative.(Ref(A), t2 .+ 0.1) .== 0.0)
end

@testset "SmoothedConstantInterpolation" begin
u = [5.5, 2.7, 5.1, 3.0]
t = [2.5, 5.6, 6.3, 8.9]
test_derivatives(SmoothedConstantInterpolation; args = [u, t],
name = "Smoothed constant interpolation")
end

@testset "Quadratic Spline" begin
u = [0.0, 1.0, 3.0]
t = [-1.0, 0.0, 1.0]
Expand Down
14 changes: 14 additions & 0 deletions test/integral_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ end
name = "Linear Interpolation (Vector) with random points")
end

@testset "SmoothedConstantInterpolation" begin
u = [1.0, 4.0, 9.0, 16.0]
t = [1.0, 2.0, 3.0, 4.0]
test_integral(
SmoothedConstantInterpolation; args = [u, t], name = "Smoothed constant interpolation")

A_constant = ConstantInterpolation(u, t)
I_ref = DataInterpolations.integral(A_constant, first(t), last(t))
I_smoothed = [DataInterpolations.integral(
SmoothedConstantInterpolation(u, t; d_max), first(t), last(t))
for d_max in 0.0:0.1:1.0]
@test all(I_smoothed .≈ I_ref)
end

@testset "QuadraticInterpolation" begin
u = [1.0, 4.0, 9.0, 16.0]
t = [1.0, 2.0, 3.0, 4.0]
Expand Down
13 changes: 13 additions & 0 deletions test/interpolation_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,19 @@ end
@test A(-Inf) == first(u)
end

@testset "Smoothed constant Interpolation" begin
test_interpolation_type(SmoothedConstantInterpolation)
u = [0.0, 2.0, 1.0, 3.0]
t = [1.2, 2.5, 5.7, 8.7]
d_max = 0.5
A = SmoothedConstantInterpolation(u, t; d_max)
test_cached_index(A)

@test A(1.9) == u[1]
@test A(3.1) == u[2]
@test A(2.5) ≈ (u[1] + u[2]) / 2
end

@testset "QuadraticSpline Interpolation" begin
test_interpolation_type(QuadraticSpline)

Expand Down
8 changes: 8 additions & 0 deletions test/parameter_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ using DataInterpolations
@test A.p.slope ≈ [4.0, -2.0, 1.0, 0.0]
end

@testset "Smoothed constant Interpolation" begin
u = [1.0, 5.0, 3.0, 4.0, 4.0]
t = collect(1:5)
A = SmoothedConstantInterpolation(u, t; cache_parameters = true)
A.p.d ≈ [0.0, 0.5, 0.5, 0.5, 0.0]
A.p.c ≈ [0.0, 2.0, -1.0, 0.5, 0.0]
end

@testset "Quadratic Interpolation" begin
u = [1.0, 5.0, 3.0, 4.0, 4.0]
t = collect(1:5)
Expand Down
Loading