Repository: hyrodium/BasicBSpline.jl
Pull request #147 (2022-01-23 11:32:36)

Add `unsafe_mapping` function and `DomainError` handling
This PR adds `Domain` error handling like this:
```julia
julia> using BasicBSpline

julia> p1 = 2;

julia> p2 = 2;

julia> k1 = KnotVector(-10:10)+p1*KnotVector(-10,10);

julia> k2 = KnotVector(-10:10)+p2*KnotVector(-10,10);

julia> P1 = BSplineSpace{p1}(k1);

julia> P2 = BSplineSpace{p2}(k2);

julia> a = rand(dim(P1),dim(P2));

julia> M = CustomBSplineManifold(a,(P1,P2));

julia> M(2,8)
0.6890826123658653

julia> M(12,8)
ERROR: DomainError with 12:
The input 12 is out of range.
Stacktrace:
 [1] (::CustomBSplineManifold{2, (2, 2), Float64, Tuple{BSplineSpace{2, Float64}, BSplineSpace{2, Float64}}})(t1::Int64, t2::Int64)
   @ BasicBSpline ~/.julia/dev/BasicBSpline/src/_BSplineManifold.jl:94
 [2] top-level scope
   @ REPL[23]:1

julia> BasicBSpline.unsafe_mapping(M,12,8)
2.7779289672863503
```

The `unsafe_mapping(M,12,8)` doesn't check the domain, and it is a little faster than mapping `M(12,8)`.


Patch:
diff --git a/src/_BSplineManifold.jl b/src/_BSplineManifold.jl
index 319e0b28..14fe44ff 100644
--- a/src/_BSplineManifold.jl
+++ b/src/_BSplineManifold.jl
@@ -27,13 +27,14 @@ end
 bsplinespaces(M::BSplineManifold) = M.bsplinespaces
 controlpoints(M::BSplineManifold) = M.controlpoints
 
-@generated function (M::BSplineManifold{1,Deg,S,T})(t1::Real) where {Deg,S,T}
+@generated function unsafe_mapping(M::BSplineManifold{1,Deg,S,T},t1) where {Deg,S,T}
     p1, = Deg
     exs = Expr[]
     for j1 in 1:p1
         push!(exs, :(v .+= b1[$(1+j1)]*view(a,i1+$(j1),:)))
     end
-    Expr(:block,
+    Expr(
+        :block,
         :((P1,) = bsplinespaces(M)),
         :(a = controlpoints(M)),
         :(i1 = intervalindex(P1,t1)),
@@ -44,7 +45,7 @@ controlpoints(M::BSplineManifold) = M.controlpoints
     )
 end
 
-@generated function (M::BSplineManifold{2,Deg,S,T})(t1,t2) where {Deg,S,T}
+@generated function unsafe_mapping(M::BSplineManifold{2,Deg,S,T},t1,t2) where {Deg,S,T}
     p1, p2 = Deg
     exs = Expr[]
     for j2 in 1:p2+1, j1 in 1:p1+1
@@ -63,7 +64,7 @@ end
     )
 end
 
-@generated function (M::BSplineManifold{3,Deg,S,T})(t1,t2,t3) where {Deg,S,T}
+@generated function unsafe_mapping(M::BSplineManifold{3,Deg,S,T},t1,t2,t3) where {Deg,S,T}
     p1, p2, p3 = Deg
     exs = Expr[]
     for j3 in 1:p3+1, j2 in 1:p2+1, j1 in 1:p1+1
@@ -82,4 +83,25 @@ end
     )
 end
 
+@inline function (M::AbstractBSplineManifold{1})(t1)
+    Ps = bsplinespaces(M)
+    t1 in domain(Ps[1]) || throw(DomainError(t1, "The input $(t1) is out of range."))
+    unsafe_mapping(M,t1)
+end
+
+@inline function (M::AbstractBSplineManifold{2})(t1,t2)
+    Ps = bsplinespaces(M)
+    t1 in domain(Ps[1]) || throw(DomainError(t1, "The input $(t1) is out of range."))
+    t2 in domain(Ps[2]) || throw(DomainError(t2, "The input $(t2) is out of range."))
+    unsafe_mapping(M,t1,t2)
+end
+
+@inline function (M::AbstractBSplineManifold{3})(t1,t2,t3)
+    Ps = bsplinespaces(M)
+    t1 in domain(Ps[1]) || throw(DomainError(t1, "The input $(t1) is out of range."))
+    t2 in domain(Ps[2]) || throw(DomainError(t2, "The input $(t2) is out of range."))
+    t3 in domain(Ps[3]) || throw(DomainError(t3, "The input $(t3) is out of range."))
+    unsafe_mapping(M,t1,t2,t3)
+end
+
 # TODO add mappings higher dimensionnal B-spline manifold with @generated macro
diff --git a/src/_CustomBSplineManifold.jl b/src/_CustomBSplineManifold.jl
index 142c6c81..36d607eb 100644
--- a/src/_CustomBSplineManifold.jl
+++ b/src/_CustomBSplineManifold.jl
@@ -21,7 +21,7 @@ end
 bsplinespaces(M::CustomBSplineManifold) = M.bsplinespaces
 controlpoints(M::CustomBSplineManifold) = M.controlpoints
 
-@generated function (M::CustomBSplineManifold{1,Deg,S,T})(t1::Real) where {Deg,S,T}
+@generated function unsafe_mapping(M::CustomBSplineManifold{1,Deg,S,T},t1::Real) where {Deg,S,T}
     p1, = Deg
     exs = Expr[]
     for j1 in 1:p1
@@ -38,7 +38,7 @@ controlpoints(M::CustomBSplineManifold) = M.controlpoints
     )
 end
 
-@generated function (M::CustomBSplineManifold{2,Deg,S,T})(t1,t2) where {Deg,S,T}
+@generated function unsafe_mapping(M::CustomBSplineManifold{2,Deg,S,T},t1,t2) where {Deg,S,T}
     p1, p2 = Deg
     exs = Expr[]
     for j2 in 1:p2+1, j1 in 1:p1+1
@@ -57,7 +57,7 @@ end
     )
 end
 
-@generated function (M::CustomBSplineManifold{3,Deg,S,T})(t1,t2,t3) where {Deg,S,T}
+@generated function unsafe_mapping(M::CustomBSplineManifold{3,Deg,S,T},t1,t2,t3) where {Deg,S,T}
     p1, p2, p3 = Deg
     exs = Expr[]
     for j3 in 1:p3+1, j2 in 1:p2+1, j1 in 1:p1+1


Test patch:
diff --git a/test/test_BSplineManifold.jl b/test/test_BSplineManifold.jl
index 1523154e..2fb23f34 100644
--- a/test/test_BSplineManifold.jl
+++ b/test/test_BSplineManifold.jl
@@ -43,6 +43,7 @@
                 @test M(t) ≈ M′(t)
                 @test M(t) ≈ M′′(t)
             end
+            @test_throws DomainError M(-5)
         end
 
         @testset "CustomBSplineManifold-1dim" begin
@@ -67,6 +68,7 @@
                 @test M(t...) ≈ M′(t...)
                 @test M(t...) ≈ M′′(t...)
             end
+            @test_throws DomainError M(-5)
         end
     end
 
@@ -98,6 +100,7 @@
                 @test M(t...) ≈ M′(t...)
                 @test M(t...) ≈ M′′(t...)
             end
+            @test_throws DomainError M(-5,-8)
         end
 
         @testset "CustomBSplineManifold-2dim" begin
@@ -126,6 +129,57 @@
                 @test M(t...) ≈ M′(t...)
                 @test M(t...) ≈ M′′(t...)
             end
+            @test_throws DomainError M(-5,-8)
+        end
+    end
+
+    @testset "3dim" begin
+        @testset "BSplineManifold-3dim" begin
+            Random.seed!(42)
+
+            P1 = BSplineSpace{1}(KnotVector([0, 0, 1, 1]))
+            P2 = BSplineSpace{1}(KnotVector([1, 1, 2, 3, 3]))
+            P3 = BSplineSpace{2}(KnotVector(rand(16)))
+            n1 = dim(P1) # 2
+            n2 = dim(P2) # 3
+            n3 = dim(P3)
+            a = [rand(2) for i1 in 1:n1, i2 in 1:n2, i3 in 1:n3]
+            M = BSplineManifold(arrayofvector2array(a), (P1, P2, P3))
+            @test dim(M) == 3
+
+            p₊ = (1, 0, 1)
+            k₊ = (KnotVector(), KnotVector(1.45), KnotVector())
+
+            M′′ = refinement(M, p₊=p₊, k₊=k₊)
+            ts = [[rand(), 1 + 2 * rand(), 0.5] for _ in 1:10]
+            for t in ts
+                @test M(t...) ≈ M′′(t...)
+            end
+            @test_throws DomainError M(-5,-8,-120)
+        end
+
+        @testset "CustomBSplineManifold-3dim" begin
+            Random.seed!(42)
+
+            P1 = BSplineSpace{1}(KnotVector([0, 0, 1, 1]))
+            P2 = BSplineSpace{1}(KnotVector([1, 1, 2, 3, 3]))
+            P3 = BSplineSpace{2}(KnotVector(rand(16)))
+            n1 = dim(P1) # 2
+            n2 = dim(P2) # 3
+            n3 = dim(P3)
+            a = [Point(i1, i2, 3) for i1 in 1:n1, i2 in 1:n2, i3 in 1:n3]
+            M = CustomBSplineManifold(a, (P1, P2, P3))
+            @test dim(M) == 3
+
+            p₊ = (1, 0, 1)
+            k₊ = (KnotVector(), KnotVector(1.45), KnotVector())
+
+            M′′ = refinement(M, p₊=p₊, k₊=k₊)
+            ts = [[rand(), 1 + 2 * rand(), 0.5] for _ in 1:10]
+            for t in ts
+                @test M(t...) ≈ M′′(t...)
+            end
+            @test_throws DomainError M(-5,-8,-120)
         end
     end
 end