Repository: hyrodium/BasicBSpline.jl
Pull request #123 (2021-12-29 13:54:12)

Fix type promotion


Patch:
diff --git a/src/BasicBSpline.jl b/src/BasicBSpline.jl
index 965d869c..28d6cfa0 100644
--- a/src/BasicBSpline.jl
+++ b/src/BasicBSpline.jl
@@ -4,7 +4,8 @@ using LinearAlgebra
 using IntervalSets
 using FastGaussQuadrature
 
-export KnotVector, BSplineSpace, dim, ⊑, ⊒, ⋢, ⋣, ≃
+export KnotVector, AbstractKnotVector
+export BSplineSpace, dim, ⊑, ⊒, ⋢, ⋣, ≃
 export BSplineDerivativeSpace
 export BSplineManifold, CustomBSplineManifold
 export bsplinebasis₊₀, bsplinebasis₋₀, bsplinebasis
diff --git a/src/_BSplineManifold.jl b/src/_BSplineManifold.jl
index 6108e1ce..319e0b28 100644
--- a/src/_BSplineManifold.jl
+++ b/src/_BSplineManifold.jl
@@ -9,17 +9,18 @@ struct BSplineManifold{Dim,Deg,T,S<:Tuple,Dim₊₁} <: AbstractBSplineManifold{
     bsplinespaces::S
     controlpoints::Array{T,Dim₊₁}
     function BSplineManifold(a::Array{T,Dim₊₁},Ps::S) where {S<:Tuple,Dim₊₁,T<:Real}
-        if !all(isa.(Ps,AbstractBSplineSpace))
-            # TODO: update error message
-            error("invalid")
+        for P in Ps
+            if !(P isa AbstractBSplineSpace)
+                throw(TypeError(:CustomBSplineManifold,AbstractBSplineSpace,P))
+            end
         end
         if size(a)[1:Dim₊₁-1] != dim.(Ps)
-            # TODO: update error message
-            error("invalid")
+            msg = "The size of control points array $(size(a)[1:Dim₊₁-1]) and dimensions of B-spline spaces $(dim.(Ps)) must be equal."
+            throw(DimensionMismatch(msg))
         end
-        d = length(Ps)
-        p = degree.(Ps)
-        new{d,p,T,S,d+1}(Ps,a)
+        Dim = length(Ps)
+        Deg = degree.(Ps)
+        new{Dim,Deg,T,S,Dim+1}(Ps,a)
     end
 end
 
diff --git a/src/_BSplineSpace.jl b/src/_BSplineSpace.jl
index 5aa18f16..056f0b88 100644
--- a/src/_BSplineSpace.jl
+++ b/src/_BSplineSpace.jl
@@ -13,8 +13,9 @@ struct BSplineSpace{p, T<:Real} <: AbstractBSplineSpace{p,T}
     global unsafe_bsplinespace(::Val{p}, k::KnotVector{T}) where {p,T} = new{p,T}(k)
 end
 function BSplineSpace{p}(k::KnotVector) where p
-    # TOOD: add error handling like this:
-    # throw(DomainError(p, "degree of polynominal must be non-negative"))
+    if p < 0
+        throw(DomainError(p, "degree of polynominal must be non-negative"))
+    end
     unsafe_bsplinespace(Val{p}(), k)
 end
 
diff --git a/src/_CustomBSplineManifold.jl b/src/_CustomBSplineManifold.jl
index 3a5e84e2..142c6c81 100644
--- a/src/_CustomBSplineManifold.jl
+++ b/src/_CustomBSplineManifold.jl
@@ -4,16 +4,17 @@ struct CustomBSplineManifold{Dim,Deg,C,S<:Tuple} <: AbstractBSplineManifold{Dim,
     bsplinespaces::S
     controlpoints::Array{C,Dim}
     function CustomBSplineManifold(a::Array{C,Dim},Ps::S) where {S<:Tuple,C,Dim}
-        if !all(isa.(Ps,AbstractBSplineSpace))
-            # TODO: update error message
-            error("invalid")
+        for P in Ps
+            if !(P isa AbstractBSplineSpace)
+                throw(TypeError(:CustomBSplineManifold,AbstractBSplineSpace,P))
+            end
         end
         if size(a) != dim.(Ps)
-            # TODO: update error message
-            error("invalid")
+            msg = "The size of control points array $(size(a)) and dimensions of B-spline spaces $(dim.(Ps)) must be equal."
+            throw(DimensionMismatch(msg))
         end
-        p = degree.(Ps)
-        new{Dim,p,C,S}(Ps,a)
+        Deg = degree.(Ps)
+        new{Dim,Deg,C,S}(Ps,a)
     end
 end
 
diff --git a/src/_KnotVector.jl b/src/_KnotVector.jl
index 2bc96dba..2a6d2179 100644
--- a/src/_KnotVector.jl
+++ b/src/_KnotVector.jl
@@ -24,6 +24,15 @@ end
 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{T}(k::KnotVector{T}) where T = k
+KnotVector{T}(k::KnotVector{S}) where {T,S} = unsafe_knotvector(T,k.vector)
+
+Base.convert(T::Type{<:KnotVector},k::KnotVector) = T(k)
+function Base.promote_rule(::Type{KnotVector{T}}, ::Type{KnotVector{S}}) where {T,S}
+    KnotVector{promote_type(T,S)}
+end
+
 @doc raw"""
 Construct knot vector from given real numbers.
 
@@ -36,11 +45,11 @@ julia> k = KnotVector()
 KnotVector([])
 ```
 """
-function KnotVector(knotvector::T...) where T<:Real
-    return unsafe_knotvector(float(T), sort!(collect(knotvector)))
+function KnotVector(knots::Real...)
+    return KnotVector(collect(knots))
 end
-function KnotVector{T}(knotvector::Real...) where T<:Real
-    return unsafe_knotvector(T, sort!(collect(knotvector)))
+function KnotVector{T}(knots::Real...) where T<:Real
+    return unsafe_knotvector(T, sort!(collect(knots)))
 end
 KnotVector() = unsafe_knotvector(Float64, Float64[])
 
@@ -55,6 +64,9 @@ end
 Base.zero(::Type{<:KnotVector}) = KnotVector()
 Base.:(==)(k₁::KnotVector, k₂::KnotVector) = (k₁.vector == k₂.vector)
 
+Base.eltype(::KnotVector{T}) where T = T
+Base.collect(k::KnotVector) = k.vector
+
 @doc raw"""
 Sum of knot vectors
 
@@ -79,8 +91,7 @@ KnotVector([1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 8.0])
 ```
 """
 Base.:+(k1::KnotVector{T}, k2::KnotVector{T}) where T = unsafe_knotvector(T,sort!(vcat(k1.vector,k2.vector)))
-
-# TODO: add a method for KnotVector{Int} + KnotVector{Float64}
+Base.:+(k1::AbstractKnotVector, k2::AbstractKnotVector) = +(promote(k1,k2)...)
 
 @doc raw"""
 Product of integer and knot vector
@@ -101,7 +112,7 @@ julia> 2 * k
 KnotVector([1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 5.0, 5.0])
 ```
 """
-function Base.:*(p₊::Integer, k::KnotVector)
+function Base.:*(p₊::Integer, k::AbstractKnotVector)
     if p₊ == 0
         return zero(KnotVector)
     elseif p₊ > 0
@@ -110,6 +121,7 @@ function Base.:*(p₊::Integer, k::KnotVector)
         throw(DomainError(p₊, "additional degree of polynominal must be non-negative"))
     end
 end
+Base.:*(k::AbstractKnotVector, p₊::Integer) = p₊*k
 
 Base.in(r::Real, k::KnotVector) = in(r, k.vector)
 Base.getindex(k::KnotVector, i::Integer) = k.vector[i]


Test patch:
diff --git a/test/test_KnotVector.jl b/test/test_KnotVector.jl
index f887e595..e6f101e8 100644
--- a/test/test_KnotVector.jl
+++ b/test/test_KnotVector.jl
@@ -8,14 +8,20 @@
         @test k1 == KnotVector([1,3,2])::KnotVector{Float64}
         @test k1 == KnotVector(1,2,3)::KnotVector{Float64}
         @test k1 == KnotVector(1,3,2)::KnotVector{Float64}
+        @test k1 == KnotVector(1.,3,2)::KnotVector{Float64}
+        @test k1 == KnotVector{Int}(1,3,2)::KnotVector{Int}
+        @test k1 != k2
 
         @test KnotVector{Int}([1,2]) isa KnotVector{Int}
         @test KnotVector{Int}(1,2) isa KnotVector{Int}
+        @test KnotVector{Int}(1,2.) isa KnotVector{Int}
+        @test KnotVector{Int}(k1) isa KnotVector{Int}
+        @test KnotVector(k1) isa KnotVector{Float64}
     end
 
     @testset "zeros" begin
         @test KnotVector() == zero(KnotVector)
-        @test KnotVector() == 0*k1
+        @test KnotVector() == 0*k1 == k1*0
         @test KnotVector() == KnotVector(Float64[])
     end
 
@@ -26,8 +32,13 @@
 
     @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])
         @test k1 + k3 == KnotVector([1,2,2,3,4,5])
         @test 2 * KnotVector([2,3]) == KnotVector([2,2,3,3])
+
+        # type promotion
+        @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}}
     end
 
     @testset "unique" begin