Skip to content

Commit f2ec5d1

Browse files
authored
Fix linear spline interpolation of closed parametric curves (#96)
* Fix linear spline interpolation of closed curves * Add tests
1 parent e54a3df commit f2ec5d1

File tree

3 files changed

+39
-18
lines changed

3 files changed

+39
-18
lines changed

src/Collocation/Collocation.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ using ..DifferentialOps
1212
using ..Recombinations: num_constraints
1313

1414
using BandedMatrices
15+
using LinearAlgebra: LinearAlgebra
1516
using SparseArrays
1617
using StaticArrays: Size, SVector, MVector, SMatrix, MMatrix
1718

1819
# For Documenter only:
1920
using ..Recombinations: RecombinedBSplineBasis
2021
using ..BoundaryConditions: Natural
2122

23+
const IdentityMatrix = typeof(LinearAlgebra.I)
24+
2225
include("matrix.jl")
2326
include("points.jl")
2427
include("cyclic.jl")
@@ -140,6 +143,19 @@ _default_matrix_type(::BSplineOrder, ::Type{<:PeriodicBSplineBasis}, ::Type{T})
140143
_default_matrix_type(::BSplineOrder{4}, ::Type{<:PeriodicBSplineBasis}, ::Type{T}) where {T} =
141144
CyclicTridiagonalMatrix{T}
142145

146+
# In this case the collocation matrix is simply the identity matrix.
147+
# We assume knots are located at collocation points (otherwise this is not true).
148+
_default_matrix_type(::BSplineOrder{2}, ::Type{<:PeriodicBSplineBasis}, ::Type{T}) where {T} =
149+
IdentityMatrix
150+
151+
allocate_collocation_matrix(::Type{IdentityMatrix}, x, B) = LinearAlgebra.I
152+
153+
function collocation_matrix!(C::IdentityMatrix, B::PeriodicBSplineBasis, x, ::Derivative{0}; kwargs...)
154+
ts = @view parent(knots(B))[1:end-1] # knot locations in [0, L)
155+
ts == x || throw(ArgumentError("expected collocation points to match B-spline knots"))
156+
C
157+
end
158+
143159
allocate_collocation_matrix(::Type{M}, x, B) where {M <: AbstractMatrix} =
144160
M(undef, length(x), length(B))
145161

src/SplineInterpolations/SplineInterpolations.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module SplineInterpolations
22

33
using ..BSplines
44
using ..Collocation
5-
using ..Collocation: CyclicTridiagonalMatrix
5+
using ..Collocation: CyclicTridiagonalMatrix, IdentityMatrix
66
using ..Splines
77

88
using BandedMatrices
@@ -50,7 +50,7 @@ locations.
5050
"""
5151
struct SplineInterpolation{
5252
S <: Spline,
53-
F <: Factorization,
53+
F <: Union{Factorization, IdentityMatrix},
5454
Points <: AbstractVector,
5555
ColMatCopy <: Union{Nothing, AbstractMatrix},
5656
} <: SplineWrapper{S}
@@ -66,12 +66,12 @@ struct SplineInterpolation{
6666
C_copy :: ColMatCopy
6767

6868
function SplineInterpolation(
69-
B::AbstractBSplineBasis, C::Factorization, x::AbstractVector,
69+
B::AbstractBSplineBasis, C::Union{Factorization, IdentityMatrix}, x::AbstractVector,
7070
::Type{T};
7171
C_copy = nothing,
7272
) where {T}
7373
N = length(B)
74-
size(C) == (N, N) ||
74+
C isa IdentityMatrix || size(C) == (N, N) ||
7575
throw(DimensionMismatch("collocation matrix has wrong dimensions"))
7676
length(x) == N ||
7777
throw(DimensionMismatch("wrong number of collocation points"))
@@ -100,10 +100,12 @@ end
100100

101101
# Factorise in-place when possible.
102102
_factorise!(C::AbstractMatrix) = lu!(C)
103+
_factorise!(I::IdentityMatrix) = I # identity matrix (for periodic linear splines)
103104
_factorise!(C::SparseMatrixCSC) = lu(C) # SparseMatrixCSC doesn't support `lu!`
104105

105106
# Create copy if required by the matrix type.
106107
_maybe_copy_matrix(::AbstractMatrix) = nothing
108+
_maybe_copy_matrix(::IdentityMatrix) = nothing # identity matrix (for periodic linear splines)
107109
_maybe_copy_matrix(C::CyclicTridiagonalMatrix) = copy(C) # periodic cubic splines
108110

109111
interpolation_points(S::SplineInterpolation) = S.x

test/periodic.jl

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,21 @@ using LinearAlgebra
44
using Random
55
using Test
66

7-
# For now this only works for cubic splines.
8-
function test_parametric_curve(ord::BSplineOrder{4})
7+
# For now this only works for linear (k = 2) and cubic (k = 4) splines.
8+
function test_parametric_curve(ord::BSplineOrder{k}) where {k}
99
L = 2π
1010
θs = range(0, L; length = 6)[1:5] # input angles in [0, 2π)
1111
points = [SVector(cos(θ), sin(θ)) for θ θs]
12-
S = interpolate(θs, copy(points), ord, Periodic(L))
12+
S = @inferred interpolate(θs, copy(points), ord, Periodic(L))
1313
a, b = boundaries(basis(S))
1414
@test a == 0
1515
@test b == L
1616
# Verify continuity of the curve
17-
S′ = Derivative(1) * S
18-
S″ = Derivative(2) * S
19-
S‴ = Derivative(3) * S
2017
@test S(a) == S(b)
21-
@test S′(a) == S′(b)
22-
@test S″(a) == S″(b)
23-
@test S‴(a) == S‴(b)
18+
for n 1:(k - 1)
19+
dS = Derivative(n) * S
20+
@test dS(a) == dS(b)
21+
end
2422
nothing
2523
end
2624

@@ -117,7 +115,8 @@ function test_periodic_splines(ord::BSplineOrder)
117115
end
118116

119117
@testset "Approximations" begin
120-
@test isapprox(app_in(x), ftest(x); rtol=0.01)
118+
rtol = k 2 ? 0.02 : 0.01
119+
@test isapprox(app_in(x), ftest(x); rtol) # requires slightly larger tolerance for k = 2
121120
@test isapprox(app_L2(x), ftest(x); rtol=0.01)
122121
end
123122
end
@@ -148,7 +147,9 @@ function test_periodic_splines(ord::BSplineOrder)
148147
coefficients(Sp) .= @view coefs[(δ+1):+length(Sp))]
149148
@test Sn(x) == Sp(x)
150149
@test (Derivative(1) * Sn)(x) == (Derivative(1) * Sp)(x)
151-
@test (Derivative(2) * Sn)(x) == (Derivative(2) * Sp)(x)
150+
if k > 2
151+
@test (Derivative(2) * Sn)(x) == (Derivative(2) * Sp)(x)
152+
end
152153
∫n = integral(Sn)
153154
∫p = integral(Sp)
154155
@test ∫n(x′) - ∫n(x) ∫p(x′) - ∫p(x)
@@ -159,8 +160,10 @@ function test_periodic_splines(ord::BSplineOrder)
159160
xs = @inferred collocation_points(B)
160161
@test iseven(k) == (xs == breaks[begin:end-1]) # this is the default for even k
161162
C = @inferred collocation_matrix(B, xs)
162-
@test cond(Array(C)) < 1e3
163-
@test all((1), sum(C; dims=2)) # partition of unity
163+
if C !== LinearAlgebra.I # case of linear splines (k = 2)
164+
@test cond(Array(C)) < 1e3
165+
@test all((1), sum(C; dims=2)) # partition of unity
166+
end
164167

165168
# Interpolate manually
166169
ys = ftest.(xs)
@@ -192,7 +195,7 @@ function test_periodic_splines(ord::BSplineOrder)
192195
end
193196
end
194197

195-
if k == 4
198+
if k (2, 4)
196199
@testset "Parametric closed curve" test_parametric_curve(ord)
197200
end
198201

0 commit comments

Comments
 (0)