Repository: hyrodium/BasicBSpline.jl (julia)

Issue:
Add `UniformKnotVector` <: `AbstractKnotVector`

Pull request description:
This PR resolves #125.

Patch:
diff --git a/src/BasicBSpline.jl b/src/BasicBSpline.jl
index 9cbadd058..dc6e3266e 100644
--- a/src/BasicBSpline.jl
+++ b/src/BasicBSpline.jl
@@ -5,8 +5,9 @@ using IntervalSets
 using StaticArrays
 using FastGaussQuadrature
 
-export KnotVector, AbstractKnotVector
-export BSplineSpace, dim, ⊑, ⊒, ⋢, ⋣, ≃
+export AbstractKnotVector, KnotVector, UniformKnotVector
+export BSplineSpace, UniformBSplineSpace
+export dim, ⊑, ⊒, ⋢, ⋣, ≃
 export BSplineDerivativeSpace
 export BSplineManifold, CustomBSplineManifold
 export bsplinebasis₊₀, bsplinebasis₋₀, bsplinebasis
@@ -23,6 +24,9 @@ export fittingcontrolpoints
 include("_KnotVector.jl")
 include("_BSplineSpace.jl")
 include("_BSplineBasis.jl")
+include("_UniformKnotVector.jl")
+include("_UniformBSplineSpace.jl")
+include("_UniformBSplineBasis.jl")
 include("_DerivativeSpace.jl")
 include("_DerivativeBasis.jl")
 include("_ChangeBasis.jl")
diff --git a/src/_BSplineBasis.jl b/src/_BSplineBasis.jl
index 199532e7e..7d6e30112 100644
--- a/src/_BSplineBasis.jl
+++ b/src/_BSplineBasis.jl
@@ -20,7 +20,7 @@ Right-sided limit version.
 \end{cases}
 \end{aligned}
 """
-@generated function bsplinebasis₊₀(P::BSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
+@generated function bsplinebasis₊₀(P::AbstractBSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
     U = StaticArrays.arithmetic_closure(promote_type(T,S))
     ks = [Symbol(:k,i) for i in 1:p+2]
     Ks = [Symbol(:K,i) for i in 1:p+1]
@@ -63,7 +63,7 @@ Left-sided limit version.
 \end{cases}
 \end{aligned}
 """
-@generated function bsplinebasis₋₀(P::BSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
+@generated function bsplinebasis₋₀(P::AbstractBSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
     U = StaticArrays.arithmetic_closure(promote_type(T,S))
     ks = [Symbol(:k,i) for i in 1:p+2]
     Ks = [Symbol(:K,i) for i in 1:p+1]
@@ -107,7 +107,7 @@ Modified version.
 \end{cases}
 \end{aligned}
 """
-@generated function bsplinebasis(P::BSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
+@generated function bsplinebasis(P::AbstractBSplineSpace{p,T}, i::Integer, t::S) where {p, T, S<:Real}
     U = StaticArrays.arithmetic_closure(promote_type(T,S))
     ks = [Symbol(:k,i) for i in 1:p+2]
     Ks = [Symbol(:K,i) for i in 1:p+1]
@@ -138,19 +138,19 @@ TODO: Add docstring
 """
 bsplinebasisall
 
-@inline function bsplinebasisall(P::BSplineSpace{0,T},i::Integer,t::S) where {T, S<:Real}
+@inline function bsplinebasisall(P::AbstractBSplineSpace{0,T},i::Integer,t::S) where {T, S<:Real}
     U = StaticArrays.arithmetic_closure(promote_type(T,S))
     SVector(one(U),)
 end
 
-@inline function bsplinebasisall(P::BSplineSpace{1}, i::Integer, t::Real)
+@inline function bsplinebasisall(P::AbstractBSplineSpace{1}, i::Integer, t::Real)
     k = knotvector(P)
     B1 = (k[i+2]-t)/(k[i+2]-k[i+1])
     B2 = (t-k[i+1])/(k[i+2]-k[i+1])
     return SVector(B1, B2)
 end
 
-@generated function bsplinebasisall(P::BSplineSpace{p}, i::Integer, t::Real) where p
+@generated function bsplinebasisall(P::AbstractBSplineSpace{p}, i::Integer, t::Real) where p
     bs = [Symbol(:b,i) for i in 1:p]
     Bs = [Symbol(:B,i) for i in 1:p+1]
     K1s = [:((k[i+$(p+j)]-t)/(k[i+$(p+j)]-k[i+$(j)])) for j in 1:p]
diff --git a/src/_BSplineSpace.jl b/src/_BSplineSpace.jl
index 504cad1d5..89a48719a 100644
--- a/src/_BSplineSpace.jl
+++ b/src/_BSplineSpace.jl
@@ -17,9 +17,9 @@ Construct B-spline space from given polynominal degree and knot vector.
 """
 struct BSplineSpace{p, T<:Real} <: AbstractBSplineSpace{p,T}
     knotvector::KnotVector{T}
-    global unsafe_bsplinespace(::Val{p}, k::KnotVector{T}) where {p,T} = new{p,T}(k)
+    global unsafe_bsplinespace(::Val{p}, k::AbstractKnotVector{T}) where {p,T} = new{p,T}(k)
 end
-function BSplineSpace{p}(k::KnotVector) where p
+function BSplineSpace{p}(k::AbstractKnotVector) where p
     if p < 0
         throw(DomainError(p, "degree of polynominal must be non-negative"))
     end
@@ -33,7 +33,7 @@ function BSplineSpace(P::AbstractBSplineSpace{p}) where p
     return BSplineSpace{p}(knotvector(P))
 end
 
-@inline function degree(::BSplineSpace{p}) where p
+@inline function degree(::AbstractBSplineSpace{p}) where p
     return p
 end
 
@@ -178,23 +178,22 @@ function bsplinesupport(P::AbstractBSplineSpace{p}, i::Integer) where p
 end
 
 function bsplinesupport(P::AbstractBSplineSpace{p}) where p
+    @warn "bsplinesupport(P::AbstractBSplineSpace{p}) will be removed in the next breaking release."
     k = knotvector(P)
     return [k[i]..k[i+p+1] for i in 1:dim(P)]
 end
 
 @doc raw"""
-Return a B-spline space of one degree lower.
+Internal methods for obtaining a B-spline space with one degree lower.
 ```math
-\mathcal{P}[p,k] \mapsto \mathcal{P}[p-1,k]
+\begin{aligned}
+\mathcal{P}[p,k] \mapsto \mathcal{P}[p-1,k] \\
+D^r\mathcal{P}[p,k] \mapsto D^{r-1}\mathcal{P}[p-1,k]
+\end{aligned}
 ```
 """
 _lower
 
-# TODO: Consider we really need these methods.
-# _lower(::Type{AbstractBSplineSpace{p}}) where p = AbstractBSplineSpace{p-1}
-# _lower(::Type{AbstractBSplineSpace{p,T}}) where {p,T} = AbstractBSplineSpace{p-1,T}
-# _lower(::Type{BSplineSpace{p}}) where p = BSplineSpace{p-1}
-# _lower(::Type{BSplineSpace{p,T}}) where {p,T} = BSplineSpace{p-1,T}
 _lower(P::BSplineSpace{p,T}) where {p,T} = BSplineSpace{p-1}(knotvector(P))
 
 """
@@ -203,7 +202,7 @@ TODO: add docstring
 function intervalindex(P::AbstractBSplineSpace{p},t::Real) where p
     k = knotvector(P)
     l = length(k)
-    v = view(k.vector,2+p:l-p-1)
+    v = view(_vec(k),2+p:l-p-1)
     return searchsortedlast(v,t)+1
 end
 
@@ -212,9 +211,9 @@ Expand B-spline space with given additional degree and knotvector.
 """
 function expandspace(P::BSplineSpace{p,T}; p₊::Integer=0, k₊::KnotVector{T}=KnotVector{T}()) where {p,T}
     k = knotvector(P)
-    k0 = unique(k[1+p:end-p])
+    k̂ = unique(k[1+p:end-p])
     p′ = p + p₊
-    k′ = k + p₊*k0 + k₊
+    k′ = k + p₊*k̂ + k₊
     P′ = BSplineSpace{p′}(k′)
     return P′
 end
diff --git a/src/_KnotVector.jl b/src/_KnotVector.jl
index 75dfc95fb..798bccbfd 100644
--- a/src/_KnotVector.jl
+++ b/src/_KnotVector.jl
@@ -25,10 +25,11 @@ KnotVector{T}(v::AbstractVector) where T = unsafe_knotvector(T,sort(v))
 KnotVector(v::AbstractVector{T}) where {T<:Real} = unsafe_knotvector(float(T),sort(v))
 
 KnotVector(k::KnotVector) = k
+KnotVector(k::AbstractKnotVector{T}) where T = unsafe_knotvector(T,_vec(k))
 KnotVector{T}(k::KnotVector{T}) where T = k
-KnotVector{T}(k::KnotVector{S}) where {T,S} = unsafe_knotvector(T,k.vector)
+KnotVector{T}(k::AbstractKnotVector) where T = unsafe_knotvector(T,_vec(k))
 
-Base.convert(T::Type{<:KnotVector},k::KnotVector) = T(k)
+Base.convert(T::Type{<:KnotVector},k::AbstractKnotVector) = T(k)
 function Base.promote_rule(::Type{KnotVector{T}}, ::Type{KnotVector{S}}) where {T,S}
     KnotVector{promote_type(T,S)}
 end
@@ -62,12 +63,24 @@ function Base.show(io::IO, k::KnotVector)
     end
 end
 
+function Base.show(io::IO, k::T) where T<:AbstractKnotVector
+    print(io, "$(nameof(T))($(k.vector))")
+end
+
+"""
+Convert `AbstractKnotVector` to `AbstractVector`
+"""
+_vec
+
+_vec(k::KnotVector) = k.vector
+
 Base.zero(::Type{<:KnotVector}) = KnotVector()
 Base.zero(::KnotVector{T}) where T = KnotVector{T}()
-Base.:(==)(k₁::KnotVector, k₂::KnotVector) = (k₁.vector == k₂.vector)
+Base.:(==)(k₁::AbstractKnotVector, k₂::AbstractKnotVector) = (_vec(k₁) == _vec(k₂))
+
+Base.eltype(::AbstractKnotVector{T}) where T = T
 
-Base.eltype(::KnotVector{T}) where T = T
-Base.collect(k::KnotVector) = k.vector
+Base.collect(k::KnotVector) = copy(k.vector)
 
 @doc raw"""
 Sum of knot vectors
@@ -125,9 +138,9 @@ function Base.:*(m::Integer, k::AbstractKnotVector)
 end
 Base.:*(k::AbstractKnotVector, m::Integer) = m*k
 
-Base.in(r::Real, k::KnotVector) = in(r, k.vector)
-Base.getindex(k::KnotVector, i::Integer) = k.vector[i]
-Base.getindex(k::KnotVector, v::AbstractVector{<:Integer}) = KnotVector(k.vector[v])
+Base.in(r::Real, k::AbstractKnotVector) = in(r, _vec(k))
+Base.getindex(k::AbstractKnotVector, i::Integer) = _vec(k)[i]
+Base.getindex(k::AbstractKnotVector, v::AbstractVector{<:Integer}) = KnotVector(_vec(k)[v])
 
 @doc raw"""
 Length of knot vector
@@ -176,18 +189,18 @@ KnotVector([1.0, 2.0, 3.0])
 """
 Base.unique(k::KnotVector) = KnotVector(unique(k.vector))
 Base.unique!(k::KnotVector) = KnotVector(unique!(k.vector))
-Base.iterate(k::KnotVector) = iterate(k.vector)
-Base.iterate(k::KnotVector, i::Integer) = iterate(k.vector, i)
-Base.searchsortedfirst(k::KnotVector,t) = searchsortedfirst(k.vector,t)
-Base.searchsortedlast(k::KnotVector,t) = searchsortedlast(k.vector,t)
-Base.searchsorted(k::KnotVector,t) = searchsorted(k.vector,t)
+Base.iterate(k::AbstractKnotVector) = iterate(_vec(k))
+Base.iterate(k::AbstractKnotVector, i::Integer) = iterate(_vec(k), i)
+Base.searchsortedfirst(k::AbstractKnotVector,t) = searchsortedfirst(_vec(k),t)
+Base.searchsortedlast(k::AbstractKnotVector,t) = searchsortedlast(_vec(k),t)
+Base.searchsorted(k::AbstractKnotVector,t) = searchsorted(_vec(k),t)
 
 @doc raw"""
-Check a inclusive relation ship ``k\subseteq k'``, for example:
+Check a inclusive relationship ``k\subseteq k'``, for example:
 ```math
 \begin{aligned}
 (1,2) &\subseteq (1,2,3) \\
-(1,2,2) &\subseteq (1,2,3) \\
+(1,2,2) &\not\subseteq (1,2,3) \\
 (1,2,3) &\subseteq (1,2,3) \\
 \end{aligned}
 ```
@@ -206,7 +219,6 @@ true
 """
 function Base.issubset(k::KnotVector, k′::KnotVector)
     v = k′.vector
-    l = length(v)
     i = 0
     for kᵢ in k
         i = findnext(==(kᵢ), v, i+1)
@@ -238,7 +250,7 @@ julia> 𝔫(k,2.0)
 2
 ```
 """
-function 𝔫(k::KnotVector, t::Real)
+function 𝔫(k::AbstractKnotVector, t::Real)
     # for small case, this is faster
     # return count(==(t), k.vector)
 
diff --git a/src/_UniformBSplineBasis.jl b/src/_UniformBSplineBasis.jl
new file mode 100644
index 000000000..305616ce1
--- /dev/null
+++ b/src/_UniformBSplineBasis.jl
@@ -0,0 +1,75 @@
+# Uniform BSpline Basis
+
+## Kernel funcitons of uniform B-spline basis function
+@inline function uniform_bsplinebasisall_kernel(::Val{0},t::T) where T<:Real
+    U = StaticArrays.arithmetic_closure(T)
+    SVector{1,U}(one(t))
+end
+@inline function uniform_bsplinebasisall_kernel(::Val{1},t::T) where T<:Real
+    U = StaticArrays.arithmetic_closure(T)
+    SVector{2,U}(1-t,t)
+end
+@generated function uniform_bsplinebasisall_kernel(::Val{p}, t::T) where {p, T<:Real}
+    bs = [Symbol(:b,i) for i in 1:p]
+    Bs = [Symbol(:B,i) for i in 1:p+1]
+    K1s = [:(($(j)-t)/$(p)) for j in 1:p]
+    K2s = [:((t-$(j-p))/$(p)) for j in 1:p]
+    b = Expr(:tuple, bs...)
+    B = Expr(:tuple, Bs...)
+    exs = [:($(Bs[j+1]) = ($(K1s[j+1])*$(bs[j+1]) + $(K2s[j])*$(bs[j]))) for j in 1:p-1]
+    Expr(
+        :block,
+        :($(Expr(:meta, :inline))),
+        :($b = uniform_bsplinebasisall_kernel(Val{$(p-1)}(),t)),
+        :($(Bs[1]) = $(K1s[1])*$(bs[1])),
+        exs...,
+        :($(Bs[p+1]) = $(K2s[p])*$(bs[p])),
+        :(return SVector($(B)))
+    )
+end
+
+@inline function uniform_bsplinebasis_kernel(::Val{0},t::T) where T<:Real
+    U = StaticArrays.arithmetic_closure(T)
+    return U(zero(t) ≤ t < one(t))
+end
+@inline function uniform_bsplinebasis_kernel(::Val{1},t::T) where T<:Real
+    U = StaticArrays.arithmetic_closure(T)
+    return U(max(1-abs(t-1), zero(t)))
+end
+@inline function uniform_bsplinebasis_kernel(::Val{p},t::T) where {p,T<:Real}
+    return (t*uniform_bsplinebasis_kernel(Val{p-1}(),t) + (p+1-t)*uniform_bsplinebasis_kernel(Val{p-1}(),t-1))/p
+end
+
+## bsplinebasis
+@inline function bsplinebasis(P::UniformBSplineSpace{p,T,R},i::Integer,t::S) where {p, T, R<:AbstractUnitRange, S<:Real}
+    U = StaticArrays.arithmetic_closure(promote_type(T,S))
+    k = knotvector(P)
+    @boundscheck (1 ≤ i ≤ dim(P)) || throw(DomainError(i, "index of B-spline basis function is out of range."))
+    return U(uniform_bsplinebasis_kernel(Val{p}(),t-i+1-k[1]))
+end
+@inline function bsplinebasis(P::UniformBSplineSpace{p,T},i::Integer,t::S) where {p, T, S<:Real}
+    U = StaticArrays.arithmetic_closure(promote_type(T,S))
+    k = knotvector(P)
+    @boundscheck (1 ≤ i ≤ dim(P)) || throw(DomainError(i, "index of B-spline basis function is out of range."))
+    a = @inbounds k.vector[i]
+    b = @inbounds k.vector[i+p+1]
+    return U(uniform_bsplinebasis_kernel(Val{p}(),(p+1)*(t-a)/(b-a)))
+end
+@inline bsplinebasis₋₀(P::UniformBSplineSpace,i::Integer,t::Real) = bsplinebasis(P,i,t)
+@inline bsplinebasis₊₀(P::UniformBSplineSpace,i::Integer,t::Real) = bsplinebasis(P,i,t)
+
+## bsplinebasisall
+@inline function bsplinebasisall(P::UniformBSplineSpace{p,T,R},i::Integer,t::S) where {p, T, R<:AbstractUnitRange, S<:Real}
+    U = StaticArrays.arithmetic_closure(promote_type(T,S))
+    k = knotvector(P)
+    @boundscheck (0 ≤ i ≤ length(k)-2p) || throw(DomainError(i, "index of interval is out of range."))
+    return uniform_bsplinebasisall_kernel(Val{p}(),U(t-i+1-p-k[1]))
+end
+@inline function bsplinebasisall(P::UniformBSplineSpace{p,T},i::Integer,t::S) where {p, T, S<:Real}
+    U = StaticArrays.arithmetic_closure(promote_type(T,S))
+    k = knotvector(P)
+    @boundscheck (0 ≤ i ≤ length(k)-2p) || throw(DomainError(i, "index of interval is out of range."))
+    a = @inbounds k.vector[i+p]
+    b = @inbounds k.vector[i+p+1]
+    return BasicBSpline.uniform_bsplinebasisall_kernel(Val{p}(),U((t-a)/(b-a)))
+end
diff --git a/src/_UniformBSplineSpace.jl b/src/_UniformBSplineSpace.jl
new file mode 100644
index 000000000..76440b020
--- /dev/null
+++ b/src/_UniformBSplineSpace.jl
@@ -0,0 +1,16 @@
+# Uniform BSpline Space
+
+struct UniformBSplineSpace{p, T<:Real, R} <: AbstractBSplineSpace{p,T}
+    knotvector::UniformKnotVector{T,R}
+    global unsafe_uniformbsplinespace(::Val{p}, k::UniformKnotVector{T,N}) where {p,T,N} = new{p,T,N}(k)
+end
+function UniformBSplineSpace{p}(k::UniformKnotVector) where p
+    if p < 0
+        throw(DomainError(p, "degree of polynominal must be non-negative"))
+    end
+    unsafe_uniformbsplinespace(Val{p}(), k)
+end
+
+knotvector(P::UniformBSplineSpace) = P.knotvector
+
+_lower(P::UniformBSplineSpace{p,T}) where {p,T} = UniformBSplineSpace{p-1}(knotvector(P))
diff --git a/src/_UniformKnotVector.jl b/src/_UniformKnotVector.jl
new file mode 100644
index 000000000..1e029a03b
--- /dev/null
+++ b/src/_UniformKnotVector.jl
@@ -0,0 +1,31 @@
+struct UniformKnotVector{T,R<:AbstractRange} <: AbstractKnotVector{T}
+    vector::R
+    global unsafe_uniformknotvector(::Type{T}, v::R) where R<:AbstractRange{T} where T = new{T,R}(v)
+end
+UniformKnotVector(v::AbstractRange{T}) where T = unsafe_uniformknotvector(T,sort(v))
+UniformKnotVector(k::UniformKnotVector) = k
+UniformKnotVector{T}(k::UniformKnotVector) where T = unsafe_uniformknotvector(T, k.vector)
+UniformKnotVector{T,R}(k::UniformKnotVector) where R<:AbstractRange{T} where T = unsafe_uniformknotvector(T, R(k.vector))
+
+_vec(k::UniformKnotVector) = k.vector
+
+Base.getindex(k::UniformKnotVector, v::AbstractRange{<:Integer}) = UniformKnotVector(sort(k.vector[v]))
+Base.length(k::UniformKnotVector) = length(k.vector)
+Base.firstindex(k::UniformKnotVector) = 1
+Base.lastindex(k::UniformKnotVector) = length(k)
+
+function Base.convert(::Type{UniformKnotVector{T,R}},k::UniformKnotVector) where {T,R}
+    UniformKnotVector{T,R}(k)
+end
+
+function Base.promote_rule(::Type{KnotVector{T}}, ::Type{UniformKnotVector{S,R}}) where {T,S,R}
+    KnotVector{promote_type(T,S)}
+end
+
+Base.unique(k::UniformKnotVector) = UniformKnotVector(unique(k.vector))
+
+Base.collect(k::UniformKnotVector) = collect(k.vector)
+
+Base.:+(k1::UniformKnotVector{T1},k2::UniformKnotVector{T2}) where {T1,T2} = KnotVector{promote_type(T1,T2)}([k1.vector;k2.vector])
+
+Base.zero(::UniformKnotVector{T}) where T = KnotVector{T}()


Test patch:
diff --git a/test/runtests.jl b/test/runtests.jl
index e3d35b552..ac3c4a991 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -10,6 +10,9 @@ using GeometryBasics
     include("test_KnotVector.jl")
     include("test_BSplineSpace.jl")
     include("test_BSplineBasis.jl")
+    include("test_UniformKnotVector.jl")
+    include("test_UniformBSplineSpace.jl")
+    include("test_UniformBSplineBasis.jl")
     include("test_Derivative.jl")
     include("test_ChangeBasis.jl")
     include("test_BSplineManifold.jl")
diff --git a/test/test_BSplineSpace.jl b/test/test_BSplineSpace.jl
index 07e76021c..20c9750d7 100644
--- a/test/test_BSplineSpace.jl
+++ b/test/test_BSplineSpace.jl
@@ -63,7 +63,7 @@
         @test P1 != P3
     end
 
-    @testset "subset but not sqsubset" begin
+    @testset "sqsubset and lowered B-spline space" begin
         P4 = BSplineSpace{1}(KnotVector([1, 2, 3, 4, 5]))
         P5 = BSplineSpace{2}(KnotVector([-1, 0.3, 2, 3, 3, 4, 5.2, 6]))
         _P4 = BSplineSpace{degree(P4)-1}(knotvector(P4)[2:end-1])
@@ -78,6 +78,21 @@
         @test (P5 ⊑ P4) == (_P5 ⊑ _P4)
     end
 
+    @testset "subset but not sqsubset" begin
+        P6 = BSplineSpace{2}(KnotVector(1:8))
+        P7 = BSplineSpace{2}(KnotVector(1:10))
+        @test P6 ⊆ P6
+        @test P7 ⊆ P7
+        @test P6 ⊆ P7
+        @test P7 ⊈ P6
+
+        @test P6 ⊆ P6
+        @test P7 ⊆ P7
+        @test P6 ⊆ P7
+        @test P7 ⊈ P6
+        @test domain(P6) ⊆ domain(P7)
+    end
+
     @testset "sqsubset but not subset" begin
         P6 = BSplineSpace{2}(KnotVector(1,2,3,4,5,6,7))
         P7 = BSplineSpace{2}(KnotVector(0,1,3,4,5,8,9))
@@ -86,5 +101,6 @@
         @test P6 ≃ P7
         @test P6 ⊈ P7
         @test P7 ⊈ P6
+        @test domain(P6) == domain(P7)
     end
 end
diff --git a/test/test_KnotVector.jl b/test/test_KnotVector.jl
index f9989ab33..fc5b7686a 100644
--- a/test/test_KnotVector.jl
+++ b/test/test_KnotVector.jl
@@ -19,6 +19,13 @@
         @test KnotVector(k1) isa KnotVector{Float64}
     end
 
+    @testset "eltype" begin
+        @test eltype(KnotVector([1,2,3])) == Float64
+        @test eltype(KnotVector{Int}([1,2,3])) == Int
+        @test eltype(KnotVector{BigInt}([1,2,3])) == BigInt
+        @test eltype(KnotVector{Rational{Int}}([1,2,3])) == Rational{Int}
+    end
+
     @testset "zeros" begin
         @test KnotVector() == zero(KnotVector)
         @test KnotVector() == 0*k1 == k1*0 == zero(k1)
@@ -30,6 +37,18 @@
         @test length(KnotVector([1,2,2,3])) == 4
     end
 
+    @testset "iterator" begin
+        for t in k1
+            @test t in k2
+        end
+        @test k1[1] == 1
+        @test k2[end] == 3
+        @test collect(k2) isa Vector{Float64}
+        @test [k2...] isa Vector{Float64}
+        @test collect(k2) == [k2...]
+        @test collect(k2) != k2
+    end
+
     @testset "addition, multiply" begin
         @test KnotVector([-1,2,3]) + 2 * KnotVector([2,5]) == KnotVector([-1,2,2,2,3,5,5])
         @test KnotVector([-1,2,3]) + KnotVector([2,5]) * 2 == KnotVector([-1,2,2,2,3,5,5])
@@ -37,6 +56,8 @@
         @test 2 * KnotVector([2,3]) == KnotVector([2,2,3,3])
 
         # type promotion
+        @test KnotVector{Int}(1,2) + KnotVector(3) == KnotVector(1,2,3)
+        @test KnotVector{Int}(1,2) + KnotVector(3) isa KnotVector{Float64}
         @test KnotVector{Int}(1,2) + KnotVector{Rational{Int}}(3) == KnotVector(1,2,3)
         @test KnotVector{Int}(1,2) + KnotVector{Rational{Int}}(3) isa KnotVector{Rational{Int}}
         @test KnotVector{Int}(1,2)*0 == KnotVector()
diff --git a/test/test_UniformBSplineBasis.jl b/test/test_UniformBSplineBasis.jl
new file mode 100644
index 000000000..628ffa669
--- /dev/null
+++ b/test/test_UniformBSplineBasis.jl
@@ -0,0 +1,71 @@
+@testset "UniformBSplineBasis" begin
+    @testset "bsplinebasis" begin
+        v = 2:10
+        k = KnotVector(v)
+        k1 = UniformKnotVector(v)
+        k2 = UniformKnotVector(v/2)
+        k3 = UniformKnotVector(v*2)
+        @test eltype(k1) == Int
+        @test eltype(k2) == Float64
+        @test eltype(k3) == Int
+        for p in 0:3
+            P = BSplineSpace{p}(k)
+            P1 = UniformBSplineSpace{p}(k1)
+            P2 = UniformBSplineSpace{p}(k2)
+            P3 = UniformBSplineSpace{p}(k3)
+            @test dim(P) == dim(P1)
+            @test dim(P) == dim(P2)
+            @test dim(P) == dim(P3)
+            n = dim(P)
+            @test bsplinebasis(P1,n÷2,6//1) isa Rational{Int}
+            @test bsplinebasis(P2,n÷2,6//1) isa Float64
+            @test bsplinebasis(P3,n÷2,6//1) isa Rational{Int}
+            for _ in 1:20
+                t = 10*rand()
+                for i in 1:n
+                    @test bsplinebasis(P1,i,t) ≈ bsplinebasis(P,i,t)
+                    @test bsplinebasis(P2,i,t/2) ≈ bsplinebasis(P,i,t)
+                    @test bsplinebasis(P3,i,t*2) ≈ bsplinebasis(P,i,t)
+                    @test (t ∈ bsplinesupport(P1,i)) == (bsplinebasis(P1,i,t) != 0)
+                    @test (t/2 ∈ bsplinesupport(P2,i)) == (bsplinebasis(P2,i,t/2) != 0)
+                    @test (t*2 ∈ bsplinesupport(P3,i)) == (bsplinebasis(P3,i,t*2) != 0)
+                    @test bsplinebasis(P1,i,t) isa Float64
+                    @test bsplinebasis(P2,i,t) isa Float64
+                    @test bsplinebasis(P3,i,t) isa Float64
+                    @test bsplinebasis(P1,i,t) == bsplinebasis₊₀(P1,i,t) == bsplinebasis₋₀(P1,i,t)
+                    @test bsplinebasis(P2,i,t) == bsplinebasis₊₀(P2,i,t) == bsplinebasis₋₀(P2,i,t)
+                    @test bsplinebasis(P3,i,t) == bsplinebasis₊₀(P3,i,t) == bsplinebasis₋₀(P3,i,t)
+                end
+            end
+        end
+    end
+
+    @testset "bsplinebasisall" begin
+        v = 2:10
+        k = KnotVector(v)
+        l = length(k)
+        k1 = UniformKnotVector(v)
+        k2 = UniformKnotVector(v/2)
+        k3 = UniformKnotVector(v*2)
+        for p in 0:3
+            P = BSplineSpace{p}(k)
+            P1 = UniformBSplineSpace{p}(k1)
+            P2 = UniformBSplineSpace{p}(k2)
+            P3 = UniformBSplineSpace{p}(k3)
+            @test bsplinebasisall(P1,(l-2p)÷2,6//1) isa SVector{p+1,Rational{Int}}
+            @test bsplinebasisall(P2,(l-2p)÷2,6//1) isa SVector{p+1,Float64}
+            @test bsplinebasisall(P3,(l-2p)÷2,6//1) isa SVector{p+1,Rational{Int}}
+            for _ in 1:20
+                t = 10*rand()
+                for i in 1:l-2p-1  # FIXME: This can be 0:l-2p, but don't know why the test doesn't pass.
+                    @test bsplinebasisall(P1,i,t) ≈ bsplinebasisall(P,i,t)
+                    @test bsplinebasisall(P2,i,t/2) ≈ bsplinebasisall(P,i,t)
+                    @test bsplinebasisall(P3,i,t*2) ≈ bsplinebasisall(P,i,t)
+                    @test bsplinebasisall(P1,i,t) isa SVector{p+1,Float64}
+                    @test bsplinebasisall(P2,i,t) isa SVector{p+1,Float64}
+                    @test bsplinebasisall(P3,i,t) isa SVector{p+1,Float64}
+                end
+            end
+        end
+    end
+end
diff --git a/test/test_UniformBSplineSpace.jl b/test/test_UniformBSplineSpace.jl
new file mode 100644
index 000000000..8b759b39c
--- /dev/null
+++ b/test/test_UniformBSplineSpace.jl
@@ -0,0 +1,113 @@
+@testset "UniformBSplineSpace" begin
+    Random.seed!(42)
+
+    k1 = UniformKnotVector(1:8)
+    P1 = UniformBSplineSpace{2}(k1)
+    P1′ = BSplineSpace{2}(k1)
+    @test bsplinesupport(P1, 2) == bsplinesupport(P1′, 2) == 2..5
+    @test dim(P1) == dim(P1′) == 5
+    @test exactdim(P1) == exactdim(P1′) == 5
+    @test isnondegenerate(P1) == true
+    @test isdegenerate(P1) == false
+    @test P1 == P1′
+
+    k2 = UniformKnotVector(range(1,1,length=8))
+    P2 = UniformBSplineSpace{2}(k2)
+    P2′ = BSplineSpace{2}(k2)
+    @test bsplinesupport(P2, 2) == bsplinesupport(P2′, 2) == 1..1
+    @test dim(P2) == dim(P2′) == 5
+    @test exactdim(P2) == exactdim(P2′) == 0
+    @test isnondegenerate(P2) == false
+    @test isdegenerate(P2) == true
+    @test P2 == P2′
+
+    k3 = UniformKnotVector(1.0:0.5:12.0)
+    P3 = UniformBSplineSpace{2}(k3)
+    @test P1 ⊆ P3
+    @test P2 ⋢ P3
+    @test P1 ⋢ P3
+    @test P2 ⋢ P3
+
+    k4 = UniformKnotVector(1.0:0.5:12.0)
+    P3 = UniformBSplineSpace{2}(k3)
+    @test P1 ⊆ P3
+    @test P2 ⋢ P3
+    @test P1 ⋢ P3
+    @test P2 ⋢ P3
+
+    @testset "dimension, dengenerate" begin
+        P1 = UniformBSplineSpace{2}(UniformKnotVector(1:8))
+        @test bsplinesupport(P1, 2) == 2..5
+        @test dim(P1) == 5
+        @test exactdim(P1) == 5
+        @test isnondegenerate(P1) == true
+        @test isdegenerate(P1) == false
+
+        P2 = UniformBSplineSpace{2}(UniformKnotVector(range(1,1,length=8)))
+        @test isnondegenerate(P2) == false
+        @test isdegenerate(P2) == true
+        @test isdegenerate(P2,1) == true
+        @test isdegenerate(P2,2) == true
+        @test isdegenerate(P2,3) == true
+        @test isdegenerate(P2,4) == true
+        @test isnondegenerate(P2,1) == false
+        @test isnondegenerate(P2,2) == false
+        @test isnondegenerate(P2,3) == false
+        @test isnondegenerate(P2,4) == false
+        @test dim(P2) == 5
+        @test exactdim(P2) == 0
+    end
+
+    @testset "support" begin
+        k = UniformKnotVector(0:9)
+        P = UniformBSplineSpace{2}(k)
+        @test bsplinesupport(P) == [bsplinesupport(P, i) for i in 1:dim(P)]
+        @test bsplinesupport(P,1) == 0..3
+        @test bsplinesupport(P,2) == 1..4
+    end
+
+    @testset "equality" begin
+        P1 = UniformBSplineSpace{2}(UniformKnotVector(Base.OneTo(8)))
+        P2 = UniformBSplineSpace{2}(UniformKnotVector(1:8))
+        P3 = UniformBSplineSpace{2}(UniformKnotVector(1:1:8))
+        P4 = UniformBSplineSpace{2}(UniformKnotVector(1.0:1.0:8.0))
+        P5 = UniformBSplineSpace{1}(UniformKnotVector(1.0:1.0:8.0))
+        P6 = UniformBSplineSpace{2}(UniformKnotVector(1.0:2.0:8.0))
+
+        @test P1 == P2 == P3 == P4
+        @test P1 != P5
+        @test P1 != P6
+    end
+
+    @testset "subset but not sqsubset" begin
+        P1 = UniformBSplineSpace{2}(UniformKnotVector(0:1:8))
+        P2 = UniformBSplineSpace{2}(UniformKnotVector(0.0:0.5:12.0))
+
+        @test P1 ⊆ P1
+        @test P2 ⊆ P2
+        @test P1 ⊆ P2
+        @test P2 ⊈ P1
+
+        @test P1 ⊑ P1
+        @test P2 ⊑ P2
+        @test P1 ⋢ P2
+        @test P2 ⋢ P1
+    end
+
+    @testset "sqsubset but not subset" begin
+        P1 = UniformBSplineSpace{2}(UniformKnotVector(0:1:8))
+        P2 = UniformBSplineSpace{2}(UniformKnotVector(1.0:0.5:7.0))
+
+        @test domain(P1) == domain(P2)
+        
+        @test P1 ⊆ P1
+        @test P2 ⊆ P2
+        @test P1 ⊈ P2
+        @test P2 ⊈ P1
+
+        @test P1 ⊑ P1
+        @test P2 ⊑ P2
+        @test P1 ⊑ P2
+        @test P2 ⋢ P1
+    end
+end
diff --git a/test/test_UniformKnotVector.jl b/test/test_UniformKnotVector.jl
new file mode 100644
index 000000000..01607a86d
--- /dev/null
+++ b/test/test_UniformKnotVector.jl
@@ -0,0 +1,121 @@
+@testset "UniformKnotVector" begin
+    k1 = UniformKnotVector(1:1:3)
+    k2 = UniformKnotVector(Base.OneTo(3))
+    k3 = UniformKnotVector(1:4)
+    k4 = UniformKnotVector(2:4)
+    k5 = UniformKnotVector(2.0:4.0)
+    k6 = UniformKnotVector(2.0:2.0:12.0)
+
+    @testset "equality" begin
+        @test k1 == k1
+        @test k1 == k2
+        @test k2 != k3
+        @test k4 != k3
+        @test k4 != k2
+        @test k2 == KnotVector(1:3)
+        @test k2 != 1:3
+    end
+
+    @testset "constructor, conversion" begin
+        @test k1 isa UniformKnotVector{Int,StepRange{Int,Int}}
+        @test k2 isa UniformKnotVector{Int,Base.OneTo{Int}}
+        @test k3 isa UniformKnotVector{Int,UnitRange{Int}}
+        @test UniformKnotVector(k2) isa UniformKnotVector{Int,Base.OneTo{Int}}
+        @test_throws MethodError UniformKnotVector{Float64}(k3)
+        T1 = UniformKnotVector{Float64,typeof(1.0:1.0)}
+        T2 = UniformKnotVector{Int,typeof(1:1:1)}
+        T3 = UniformKnotVector{Float64,typeof(1:1:1)}
+        @test T1 != T2
+        @test T1(k3) isa T1
+        @test T2(k3) isa T2
+        @test_throws MethodError T3(k3)
+        @test convert(T1,k3) isa T1
+        @test convert(T2,k3) isa T2
+        @test_throws MethodError convert(T3,k3)
+        @test KnotVector(k1) isa KnotVector{Int}
+        @test KnotVector(k2) isa KnotVector{Int}
+        @test KnotVector(k5) isa KnotVector{Float64}
+    end
+
+    @testset "eltype" begin
+        @test eltype(UniformKnotVector(1:3)) == Int
+        @test eltype(UniformKnotVector(1:1:3)) == Int
+        @test eltype(UniformKnotVector(1:1:BigInt(3))) == BigInt
+        @test eltype(UniformKnotVector(1.0:1.0:3.0)) == Float64
+        @test eltype(UniformKnotVector(1//1:1//5:3//1)) == Rational{Int}
+    end
+
+    @testset "length" begin
+        @test length(k2) == 3
+        @test length(k3) == 4
+        @test length(k4) == 3
+    end
+
+    @testset "iterator, getindex" begin
+        for t in k2
+            @test t in k3
+        end
+        @test k1[1] == 1
+        @test k2[end] == 3
+        @test k4[2] == 3
+        @test k4[[1,2]] == KnotVector(2,3)
+        @test k3[2:4] == k4
+        @test collect(k2) isa Vector{Int}
+        @test [k2...] isa Vector{Int}
+        @test collect(k5) isa Vector{Float64}
+        @test [k5...] isa Vector{Float64}
+        @test collect(k2) == [k2...]
+        @test collect(k2) != k2
+    end
+
+    @testset "addition, multiply" begin
+        @test k2 + k3 == KnotVector(k2) + KnotVector(k3)
+        @test k2 + k4 == KnotVector(k2) + KnotVector(k4)
+        @test 2 * k2 == k2 * 2 == k2 + k2
+    end
+
+    @testset "zeros" begin
+        @test_throws MethodError zero(UniformKnotVector)
+        @test_throws MethodError zeros(UniformKnotVector,3)
+        @test zero(k1) isa KnotVector{Int}
+        @test zero(k5) isa KnotVector{Float64}
+        @test k1 * 0 == zero(k1)
+        @test k1 + zero(k1) == k1
+    end
+
+    @testset "unique" begin
+        @test unique(k2) == k2
+        @test unique(k3) == k3
+        @test unique(k4) == k4
+    end
+
+    @testset "inclusive relation" begin
+        @test k1 == k2
+        @test k1 ⊆ k2
+        @test k2 ⊆ k1
+        @test k2 ⊆ k3
+        @test k4 ⊆ k3
+        @test k2 ⊈ k4
+        @test KnotVector(k2) ⊆ k3
+        @test KnotVector(k4) ⊆ k3
+        @test KnotVector(k2) ⊈ k4
+        @test k2 ⊆ KnotVector(k3)
+        @test k4 ⊆ KnotVector(k3)
+        @test k2 ⊈ KnotVector(k4)
+    end
+
+    @testset "string" begin
+        @test string(k1) == "UniformKnotVector(1:1:3)"
+        @test string(k2) == "UniformKnotVector(Base.OneTo(3))"
+        @test string(k3) == "UniformKnotVector(1:4)"
+    end
+
+    @testset "other operators" begin
+        @test 𝔫(k2, 0.3) == 0
+        @test 𝔫(k2, 1) == 1
+        @test 𝔫(k2, 1.0) == 1
+        @test 𝔫(k2, 2.0) == 1
+        @test 1 ∈ k2
+        @test 1.5 ∉ k2
+    end
+end