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

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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
8 changes: 4 additions & 4 deletions src/DataInterpolations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ function Base.showerror(io::IO, ::IntegralNotInvertibleError)
end

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

# added for RegularizationSmooth, JJS 11/27/21
### Regularization data smoothing and interpolation
Expand Down
13 changes: 13 additions & 0 deletions src/derivatives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ 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)

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 @@ -152,6 +152,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
72 changes: 72 additions & 0 deletions src/interpolation_caches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,78 @@ function ConstantInterpolation(
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.
- `extrapolation`: The extrapolation type applied left and right of the data. Possible options
are `ExtrapolationType.None` (default), `ExtrapolationType.Constant`, `ExtrapolationType.Linear`
`ExtrapolationType.Extension`, `ExtrapolationType.Periodic` (also made smooth at the boundaries) and `ExtrapolationType.Reflective`.
- `extrapolation_left`: The extrapolation type applied left of the data. See `extrapolation` for
the possible options. This keyword is ignored if `extrapolation != Extrapolation.none`.
- `extrapolation_right`: The extrapolation type applied right of the data. See `extrapolation` for
the possible options. This keyword is ignored if `extrapolation != Extrapolation.none`.
- `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
extrapolation_left::ExtrapolationType.T
extrapolation_right::ExtrapolationType.T
iguesser::Guesser{tType}
cache_parameters::Bool
linear_lookup::Bool
function SmoothedConstantInterpolation(
u, t, I, p, d_max, extrapolation_left,
extrapolation_right, 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, extrapolation_left, extrapolation_right,
Guesser(t), cache_parameters, linear_lookup)
end
end

function SmoothedConstantInterpolation(
u, t; d_max = Inf, extrapolation::ExtrapolationType.T = ExtrapolationType.None,
extrapolation_left::ExtrapolationType.T = ExtrapolationType.None,
extrapolation_right::ExtrapolationType.T = ExtrapolationType.None,
cache_parameters = false, assume_linear_t = 1e-2)
extrapolation_left, extrapolation_right = munge_extrapolation(
extrapolation, extrapolation_left, extrapolation_right)
u, t = munge_data(u, t)
p = SmoothedConstantParameterCache(
u, t, cache_parameters, d_max, extrapolation_left, extrapolation_right)
A = SmoothedConstantInterpolation(
u, t, nothing, p, d_max, extrapolation_left,
extrapolation_right, cache_parameters, assume_linear_t)
I = cumulative_integral(A, cache_parameters)
SmoothedConstantInterpolation(
u, t, I, p, d_max, extrapolation_left,
extrapolation_right, cache_parameters, assume_linear_t)
end

"""
QuadraticSpline(u, t; extrapolation::ExtrapolationType.T = ExtrapolationType.None, extrapolation_left::ExtrapolationType.T = ExtrapolationType.None,
extrapolation_right::ExtrapolationType.T = ExtrapolationType.None, cache_parameters = false)
Expand Down
18 changes: 17 additions & 1 deletion src/interpolation_methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,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 @@ -187,6 +187,22 @@ 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)

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
16 changes: 16 additions & 0 deletions src/interpolation_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ 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_constant_interpolation_parameters(
A.u, A.t, A.d_max, idx, A.extrapolation_left, A.extrapolation_right)
d_upper, c_upper = smoothed_constant_interpolation_parameters(
A.u, A.t, A.d_max, idx + 1, A.extrapolation_left, A.extrapolation_right)
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
33 changes: 33 additions & 0 deletions src/parameter_caches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,39 @@ 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, extrapolation_left, extrapolation_right)
if cache_parameters
parameters = smoothed_constant_interpolation_parameters.(
Ref(u), Ref(t), d_max, eachindex(t), extrapolation_left, extrapolation_right)
d, c = collect.(eachrow(stack(collect.(parameters))))
SmoothedConstantParameterCache(d, c)
else
SmoothedConstantParameterCache(eltype(t)[], eltype(u)[])
end
end

function smoothed_constant_interpolation_parameters(
u, t, d_max, idx, extrapolation_left, extrapolation_right)
if isone(idx) || (idx == length(t))
# If extrapolation is periodic, make the transition differentiable
if extrapolation_left == extrapolation_right == ExtrapolationType.Periodic
min(t[end] - t[end - 1], t[2] - t[1], 2d_max) / 2, (u[1] - u[end - 1]) / 2
else
d = isone(idx) ? min(t[2] - t[1], 2d_max) / 2 :
min(t[end] - t[end - 1], 2d_max) / 2
d, zero(one(eltype(u)) / 2)
end
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 @@ -31,7 +31,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 @@ -150,6 +150,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 @@ -67,6 +67,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 @@ -522,6 +522,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 @@ -15,6 +15,14 @@ end
test_cached_integration(LinearInterpolation, u, t)
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)
@test A.p.d ≈ [0.5, 0.5, 0.5, 0.5, 0.5]
@test 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