packages feed

splines 0.3 → 0.5.0.1

raw patch · 14 files changed

+595/−236 lines, 14 filesdep +QuickCheckdep +criteriondep +splinesdep ~vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, criterion, splines, test-framework, test-framework-quickcheck2

Dependency ranges changed: vector

API changes (from Hackage documentation)

- Math.Spline.BSpline: instance (Eq (Scalar v), Eq v) => Eq (BSpline v)
- Math.Spline.BSpline: instance (Ord (Scalar v), Ord v) => Ord (BSpline v)
- Math.Spline.BSpline: instance (Show (Scalar v), Show v) => Show (BSpline v)
- Math.Spline.Class: instance (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline BSpline v
- Math.Spline.Class: instance Spline BSpline v => ControlPoints BSpline v
+ Math.Spline: cSpline :: Ord (Scalar a) => [(Scalar a, a, a)] -> CSpline a
+ Math.Spline: data CSpline a
+ Math.Spline.BSpline: deBoor :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a, Vector v a, Vector v (Scalar a)) => BSpline v a -> Scalar a -> [v a]
+ Math.Spline.BSpline: evalNaturalBSpline :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a, Vector v a) => BSpline v a -> Scalar a -> a
+ Math.Spline.BSpline.Reference: evalReferenceBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), Vector v a) => BSpline v a -> Scalar a -> a
+ Math.Spline.BSpline.Reference: fitPolyToBSplineAt :: (Fractional a, Ord a, Scalar a ~ a, Vector v a) => BSpline v a -> a -> Poly a
+ Math.Spline.Class: instance [incoherent] (Spline (BSpline v) a, Vector v a) => ControlPoints (BSpline v) a
+ Math.Spline.Class: instance [incoherent] (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), Vector v a, Vector v (Scalar a)) => Spline (BSpline v) a
+ Math.Spline.Class: instance [incoherent] (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline (BSpline Vector) v
+ Math.Spline.Class: instance [incoherent] Spline (BSpline Vector) a => ControlPoints (BSpline Vector) a
+ Math.Spline.Hermite: cSpline :: Ord (Scalar a) => [(Scalar a, a, a)] -> CSpline a
+ Math.Spline.Hermite: data CSpline a
+ Math.Spline.Hermite: evalSpline :: Spline s v => s v -> Scalar v -> v
+ Math.Spline.Hermite: instance (VectorSpace a, Fractional (Scalar a), Ord (Scalar a)) => Spline CSpline a
+ Math.Spline.Knots: distinctKnotsSet :: Eq k => Knots k -> Set k
+ Math.Spline.Knots: maxKnot :: Eq a => Knots a -> Maybe (a, Int)
+ Math.Spline.Knots: minKnot :: Eq a => Knots a -> Maybe (a, Int)
+ Math.Spline.Knots: multiplicities :: Eq t => Knots t -> [Int]
+ Math.Spline.Knots: multiplicitiesVector :: Eq a => Knots a -> Vector Int
+ Math.Spline.Knots: splitFind :: Ord k => k -> Knots k -> (Knots k, Knots k, Knots k)
- Math.NURBS: evalNURBS :: (VectorSpace v, (Scalar v) ~ w, VectorSpace w, (Scalar w) ~ w, Fractional w, Ord w) => NURBS v -> w -> v
+ Math.NURBS: evalNURBS :: (VectorSpace v, Scalar v ~ w, VectorSpace w, Scalar w ~ w, Fractional w, Ord w) => NURBS v -> w -> v
- Math.NURBS: nurbs :: (VectorSpace v, (Scalar v) ~ w, VectorSpace w, (Scalar w) ~ w) => Knots (Scalar v) -> Vector (w, v) -> NURBS v
+ Math.NURBS: nurbs :: (VectorSpace v, Scalar v ~ w, VectorSpace w, Scalar w ~ w) => Knots (Scalar v) -> Vector (w, v) -> NURBS v
- Math.NURBS: nurbsDomain :: (Scalar v) ~ (Scalar (Scalar v)) => NURBS v -> Maybe (Scalar v, Scalar v)
+ Math.NURBS: nurbsDomain :: Scalar v ~ Scalar (Scalar v) => NURBS v -> Maybe (Scalar v, Scalar v)
- Math.NURBS: nurbsKnotVector :: (Scalar v) ~ (Scalar (Scalar v)) => NURBS v -> Knots (Scalar v)
+ Math.NURBS: nurbsKnotVector :: Scalar v ~ Scalar (Scalar v) => NURBS v -> Knots (Scalar v)
- Math.NURBS: splitNURBS :: (VectorSpace v, (Scalar v) ~ w, VectorSpace w, (Scalar w) ~ w, Ord w, Fractional w) => NURBS v -> Scalar v -> Maybe (NURBS v, NURBS v)
+ Math.NURBS: splitNURBS :: (VectorSpace v, Scalar v ~ w, VectorSpace w, Scalar w ~ w, Ord w, Fractional w) => NURBS v -> Scalar v -> Maybe (NURBS v, NURBS v)
- Math.NURBS: toNURBS :: (Spline s v, (Scalar v) ~ (Scalar (Scalar v))) => s v -> NURBS v
+ Math.NURBS: toNURBS :: (Spline s v, Scalar v ~ Scalar (Scalar v)) => s v -> NURBS v
- Math.Spline: bSpline :: Knots (Scalar a) -> Vector a -> BSpline a
+ Math.Spline: bSpline :: Vector v a => Knots (Scalar a) -> v a -> BSpline v a
- Math.Spline: class (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline s v
+ Math.Spline: class (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline s v where splineDomain = knotDomain <$> knotVector <*> splineDegree evalSpline = evalSpline . toBSpline splineDegree = splineDegree . toBSpline knotVector = knotVector . toBSpline
- Math.Spline: data BSpline t
+ Math.Spline: data BSpline v t
- Math.Spline: toBSpline :: Spline s v => s v -> BSpline v
+ Math.Spline: toBSpline :: Spline s v => s v -> BSpline Vector v
- Math.Spline.BSpline: bSpline :: Knots (Scalar a) -> Vector a -> BSpline a
+ Math.Spline.BSpline: bSpline :: Vector v a => Knots (Scalar a) -> v a -> BSpline v a
- Math.Spline.BSpline: data BSpline t
+ Math.Spline.BSpline: data BSpline v t
- Math.Spline.BSpline: differentiateBSpline :: (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => BSpline v -> BSpline v
+ Math.Spline.BSpline: differentiateBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), Vector v a, Vector v (Scalar a)) => BSpline v a -> BSpline v a
- Math.Spline.BSpline: evalBSpline :: (Fractional (Scalar c), Ord (Scalar c), VectorSpace c) => BSpline c -> Scalar c -> c
+ Math.Spline.BSpline: evalBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), Vector v a, Vector v (Scalar a)) => BSpline v a -> Scalar a -> a
- Math.Spline.BSpline: insertKnot :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a)) => BSpline a -> Scalar a -> BSpline a
+ Math.Spline.BSpline: insertKnot :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a), Vector v a, Vector v (Scalar a)) => BSpline v a -> Scalar a -> BSpline v a
- Math.Spline.BSpline: integrateBSpline :: (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => BSpline v -> BSpline v
+ Math.Spline.BSpline: integrateBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), Vector v a, Vector v (Scalar a)) => BSpline v a -> BSpline v a
- Math.Spline.BSpline: splitBSpline :: (VectorSpace v, Ord (Scalar v), Fractional (Scalar v)) => BSpline v -> Scalar v -> Maybe (BSpline v, BSpline v)
+ Math.Spline.BSpline: splitBSpline :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a), Vector v a, Vector v (Scalar a)) => BSpline v a -> Scalar a -> Maybe (BSpline v a, BSpline v a)
- Math.Spline.Class: class (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline s v
+ Math.Spline.Class: class (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline s v where splineDomain = knotDomain <$> knotVector <*> splineDegree evalSpline = evalSpline . toBSpline splineDegree = splineDegree . toBSpline knotVector = knotVector . toBSpline
- Math.Spline.Class: toBSpline :: Spline s v => s v -> BSpline v
+ Math.Spline.Class: toBSpline :: Spline s v => s v -> BSpline Vector v
- Math.Spline.Knots: distinctKnots :: Knots t -> [t]
+ Math.Spline.Knots: distinctKnots :: Eq t => Knots t -> [t]
- Math.Spline.Knots: distinctKnotsVector :: Knots t -> Vector t
+ Math.Spline.Knots: distinctKnotsVector :: Eq t => Knots t -> Vector t
- Math.Spline.Knots: dropDistinctKnots :: Int -> Knots a -> Knots a
+ Math.Spline.Knots: dropDistinctKnots :: Ord a => Int -> Knots a -> Knots a
- Math.Spline.Knots: fromDistinctAscList :: [(k, Int)] -> Knots k
+ Math.Spline.Knots: fromDistinctAscList :: Eq k => [(k, Int)] -> Knots k
- Math.Spline.Knots: fromMap :: Map k Int -> Knots k
+ Math.Spline.Knots: fromMap :: Eq k => Map k Int -> Knots k
- Math.Spline.Knots: lookupDistinctKnot :: Int -> Knots a -> Maybe a
+ Math.Spline.Knots: lookupDistinctKnot :: Eq a => Int -> Knots a -> Maybe a
- Math.Spline.Knots: maxMultiplicity :: Knots t -> Int
+ Math.Spline.Knots: maxMultiplicity :: Ord t => Knots t -> Int
- Math.Spline.Knots: numDistinctKnots :: Knots t -> Int
+ Math.Spline.Knots: numDistinctKnots :: Eq t => Knots t -> Int
- Math.Spline.Knots: splitDistinctKnotsAt :: Int -> Knots a -> (Knots a, Knots a)
+ Math.Spline.Knots: splitDistinctKnotsAt :: (Ord a, Eq a) => Int -> Knots a -> (Knots a, Knots a)
- Math.Spline.Knots: splitLookup :: Int -> Knots a -> (Knots a, Maybe (a, Int), Knots a)
+ Math.Spline.Knots: splitLookup :: Int -> Knots a -> (Knots a, Maybe a, Knots a)
- Math.Spline.Knots: takeDistinctKnots :: Int -> Knots a -> Knots a
+ Math.Spline.Knots: takeDistinctKnots :: Ord a => Int -> Knots a -> Knots a
- Math.Spline.Knots: toList :: Knots k -> [(k, Int)]
+ Math.Spline.Knots: toList :: Eq k => Knots k -> [(k, Int)]
- Math.Spline.Knots: toMap :: Knots k -> Map k Int
+ Math.Spline.Knots: toMap :: Ord k => Knots k -> Map k Int
- Math.Spline.Knots: toVector :: Knots k -> Vector (k, Int)
+ Math.Spline.Knots: toVector :: Eq k => Knots k -> Vector (k, Int)
- Math.Spline.Knots: valid :: Ord k => Knots k -> Bool
+ Math.Spline.Knots: valid :: (Ord k, Num k) => Knots k -> Bool

Files

+ benchmark/DeBoor.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE FlexibleContexts #-}++import Criterion.Main++import Data.List (find)+import qualified Data.Vector.Generic.Safe as V+import qualified Data.Vector.Safe as BV+import qualified Data.Vector.Unboxed.Safe as UV+import Math.Spline.BSpline+import Math.Spline.BSpline.Reference+import Math.Spline.Knots+import Math.Polynomial++import Debug.Trace+import Control.Monad++kts = mkKnots $ [0,0,0] ++ [0..10] ++ [11,11,11]+ctPts = map sin [0..12]++unboxedSpline :: BSpline UV.Vector Double+unboxedSpline = bSpline kts (V.fromList ctPts)++boxedSpline :: BSpline BV.Vector Double+boxedSpline = bSpline kts (V.fromList ctPts)+++intervalPoly :: [(Double, Double, Poly Double)]+intervalPoly = map f $ zip3 dkts (tail dkts) (basisPolynomials kts)+  where+    dkts = distinctKnots kts+    f (begin, end, k) = (begin, end, ) . sumPolys $ zipWith scalePoly ctPts (k !! 3)++applyDeBoor :: V.Vector v Double => BSpline v Double -> Double -> Double+applyDeBoor s = evalBSpline s++{-# SPECIALIZE applyNaturalDeBoor :: BSpline BV.Vector Double -> Double -> Double #-}+{-# SPECIALIZE applyNaturalDeBoor :: BSpline UV.Vector Double -> Double -> Double #-}+applyNaturalDeBoor :: V.Vector v Double =>  BSpline v Double -> Double -> Double+applyNaturalDeBoor s = evalNaturalBSpline s++applyPoly :: Double -> Double+applyPoly x = maybe 0 (\(_,_,p) -> evalPoly p x) $ find (\(b,e,_) -> x >= b && x < e) intervalPoly++applyAndSum :: (Double -> Double) -> [Double] -> Double+applyAndSum f = sum . map f++main = defaultMain+       [ bgroup "Boxed"+         [ bench "deBoor 1000" $ whnf (applyAndSum (applyDeBoor boxedSpline)) [0,0.01..10]+         , bench "deBoor 10000" $ whnf (applyAndSum (applyDeBoor boxedSpline)) [0,0.001..10]+         , bench "natural 1000" $ whnf (applyAndSum (applyNaturalDeBoor boxedSpline)) [0,0.01..10]+         , bench "natural 10000" $ whnf (applyAndSum (applyNaturalDeBoor boxedSpline)) [0,0.001..10]+         ]+       , bgroup "Unboxed"+         [ bench "deBoor 1000" $ whnf (applyAndSum (applyDeBoor unboxedSpline)) [0,0.01..10]+         , bench "deBoor 10000" $ whnf (applyAndSum (applyDeBoor unboxedSpline)) [0,0.001..10]+         , bench "natural 1000" $ whnf (applyAndSum (applyNaturalDeBoor unboxedSpline)) [0,0.01..10]+         , bench "natural 10000" $ whnf (applyAndSum (applyNaturalDeBoor unboxedSpline)) [0,0.001..10]+         ]+       , bench "poly 1000" $ whnf (applyAndSum applyPoly) [0,0.01..10]+       , bench "poly 10000" $ whnf (applyAndSum applyPoly) [0,0.001..10]+       ]
splines.cabal view
@@ -1,8 +1,8 @@ name:                   splines-version:                0.3+version:                0.5.0.1 stability:              provisional -cabal-version:          >= 1.6+cabal-version:          >= 1.9.2 build-type:             Simple  author:                 James Cook <mokus@deepbondi.net>@@ -24,11 +24,13 @@  Library   hs-source-dirs:       src+  ghc-options:          -Wall   exposed-modules:      Math.Spline                         Math.Spline.BezierCurve                         Math.Spline.BSpline                         Math.Spline.BSpline.Reference                         Math.Spline.Class+                        Math.Spline.Hermite                         Math.Spline.ISpline                         Math.Spline.Knots                         Math.Spline.MSpline@@ -37,5 +39,31 @@   build-depends:        base >= 3 && < 5,                         containers,                         polynomial,+                        vector >= 0.8,+                        vector-space++Test-Suite splines-test+  type:                 exitcode-stdio-1.0+  hs-source-dirs:       test+  main-is:              Main.hs+  +  build-depends:        base >= 3 && <5,+                        containers,+                        polynomial,+                        splines,+                        test-framework,+                        test-framework-quickcheck2,+                        QuickCheck >= 2,                         vector,                         vector-space++Benchmark splines-bench+  type:                 exitcode-stdio-1.0+  hs-source-dirs:       benchmark+  main-is:              DeBoor.hs+  +  build-depends:        base >= 3 && < 5,+                        criterion,+                        polynomial,+                        splines,+                        vector
src/Math/NURBS.hs view
@@ -8,13 +8,13 @@     ) where  import qualified Data.Vector as V-import Data.VectorSpace+import Data.VectorSpace hiding (project) import Math.Spline.Class (Spline, toBSpline) import Math.Spline.BSpline.Internal import Math.Spline.BSpline import Math.Spline.Knots -newtype NURBS v = NURBS (BSpline (Scalar v, v))+newtype NURBS v = NURBS (BSpline V.Vector (Scalar v, v))  deriving instance (Eq   v, Eq   (Scalar v), Eq   (Scalar (Scalar v))) => Eq   (NURBS v) deriving instance (Ord  v, Ord  (Scalar v), Ord  (Scalar (Scalar v))) => Ord  (NURBS v)@@ -34,16 +34,18 @@  -- |Constructs the homogeneous-coordinates B-spline that corresponds to this -- NURBS curve+nurbsAsSpline :: VectorSpace v => NURBS v -> BSpline V.Vector (Scalar v, v) nurbsAsSpline (NURBS spline) = spline      { controlPoints = V.map homogenize (controlPoints spline) }     where-        homogenize (w,v) = (w, w *^ v)+        homogenize (w,v) = (w, v ^* w)  -- |Constructs the NURBS curve corresponding to a homogeneous-coordinates B-spline+splineAsNURBS :: (VectorSpace v, Fractional (Scalar v)) => BSpline V.Vector (Scalar v, v) -> NURBS v splineAsNURBS spline = NURBS spline      { controlPoints = V.map unHomogenize (controlPoints spline) }     where-        unHomogenize (w,v) = (w, recip w *^ v)+        unHomogenize (w,v) = (w, v ^/ w)   evalNURBS@@ -51,7 +53,7 @@       VectorSpace w, Scalar w ~ w,       Fractional w, Ord w) =>      NURBS v -> w -> v-evalNURBS nurbs = project . evalBSpline (nurbsAsSpline nurbs)+evalNURBS f = project . evalBSpline (nurbsAsSpline f)     where         project (w,v) = recip w *^ v @@ -75,6 +77,6 @@                VectorSpace w, Scalar w ~ w,                Ord w, Fractional w)     => NURBS v -> Scalar v -> Maybe (NURBS v, NURBS v)-splitNURBS nurbs t = do-    (s0, s1) <- splitBSpline (nurbsAsSpline nurbs) t+splitNURBS f t = do+    (s0, s1) <- splitBSpline (nurbsAsSpline f) t     return (splineAsNURBS s0, splineAsNURBS s1)
src/Math/Spline.hs view
@@ -7,6 +7,7 @@     , BSpline, bSpline     , MSpline, mSpline, toMSpline     , ISpline, iSpline, toISpline+    , CSpline, cSpline     ) where  import Math.Spline.Class@@ -15,3 +16,4 @@ import Math.Spline.BSpline import Math.Spline.MSpline import Math.Spline.ISpline+import Math.Spline.Hermite
src/Math/Spline/BSpline.hs view
@@ -1,11 +1,13 @@-{-# LANGUAGE MultiParamTypeClasses, StandaloneDeriving, FlexibleContexts, UndecidableInstances, TypeFamilies, ParallelListComp #-}+{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies, ParallelListComp #-} module Math.Spline.BSpline     ( BSpline     , bSpline     , evalBSpline+    , evalNaturalBSpline     , insertKnot     , splitBSpline     , differentiateBSpline, integrateBSpline+    , deBoor     ) where  import Math.Spline.Knots@@ -13,20 +15,19 @@  import Data.Maybe (fromMaybe) import Data.VectorSpace-import qualified Data.Vector as V+import qualified Data.Vector.Generic as V  -- |@bSpline kts cps@ creates a B-spline with the given knot vector and control  -- points.  The degree is automatically inferred as the difference between the  -- number of spans in the knot vector (@numKnots kts - 1@) and the number of  -- control points (@length cps@).-bSpline :: Knots (Scalar a) -> V.Vector a -> BSpline a-bSpline kts cps-    | V.null cps    = error "bSpline: no control points"-    | otherwise     = fromMaybe-        (error "bSpline: too few knots")-        (maybeSpline kts cps)+bSpline :: V.Vector v a => Knots (Scalar a) -> v a -> BSpline v a+bSpline kts cps = fromMaybe+    (error "bSpline: too few knots")+    (maybeSpline kts cps) -maybeSpline :: Knots (Scalar a) -> V.Vector a -> Maybe (BSpline a)+-- not exported; precondition: n > 0+maybeSpline :: V.Vector v a => Knots (Scalar a) -> v a -> Maybe (BSpline v a) maybeSpline kts cps      | n > m     = Nothing     | otherwise = Just (Spline (m - n) kts cps)@@ -34,23 +35,18 @@         n = V.length cps         m = numKnots kts - 1 -deriving instance (Eq   (Scalar v), Eq   v) => Eq   (BSpline v)-deriving instance (Ord  (Scalar v), Ord  v) => Ord  (BSpline v)-instance (Show (Scalar v), Show v) => Show (BSpline v) where-    showsPrec p (Spline _ kts cps) = showParen (p>10) -        ( showString "bSpline "-        . showsPrec 11 kts-        . showChar ' '-        . showsPrec 11 cps-        )- differentiateBSpline-  :: (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => BSpline v -> BSpline v+    :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), V.Vector v a+       , V.Vector v (Scalar a)) => BSpline v a -> BSpline v a differentiateBSpline spline-    | numKnots ks  < 2  = spline-    | numKnots ks == 2  = bSpline ks (V.singleton zeroV)-    | otherwise         = bSpline ks' ds'+    | V.null ds = error "differentiateBSpline: no control points"+    | m  < 1    = spline+    | p == 0    = bSpline ks (V.replicate n zeroV)+    | otherwise = bSpline ks' ds'     where+        n = V.length ds+        m = numKnots ks - 1+                 ks' = mkKnots . init . tail $ ts         ds' = V.zipWith (*^) (V.tail cs) (V.zipWith (^-^) (V.tail ds) ds)         @@ -58,10 +54,12 @@         ds = controlPoints spline                  p  = degree spline-        cs = V.fromList [fromIntegral p / (t1 - t0) | (t0,t1) <- spans p ts]+        cs = V.fromList [ if t1 /= t0 then fromIntegral p / (t1 - t0) else 0 | (t0,t1) <- spans p ts]  integrateBSpline-  :: (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => BSpline v -> BSpline v+  :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), V.Vector v a+     , V.Vector v (Scalar a)) =>+     BSpline v a -> BSpline v a integrateBSpline spline = bSpline (mkKnots ts') (V.scanl (^+^) zeroV ds')     where         ds' = V.zipWith (*^) cs (controlPoints spline)@@ -70,26 +68,31 @@         p = degree spline + 1         cs = V.fromList [(t1 - t0) / fromIntegral p | (t0,t1) <- spans p ts] +spans :: Int -> [a] -> [(a,a)] spans n xs = zip xs (drop n xs) +-- |Split a B-spline at the specified point (which must be inside the spline's domain),+-- returning two disjoint splines, the sum of which is equal to the original.  The domain+-- of the first will be below the split point and the domain of the second will be above. splitBSpline-  :: (VectorSpace v, Ord (Scalar v), Fractional (Scalar v)) =>-     BSpline v -> Scalar v -> Maybe (BSpline v, BSpline v)-splitBSpline spline@(Spline p kv ds) t -    | inDomain  = Just (Spline p (mkKnots us0) ds0, Spline p (mkKnots us1) ds1)+  :: ( VectorSpace a, Ord (Scalar a), Fractional (Scalar a), V.Vector v a+     , V.Vector v (Scalar a)) =>+     BSpline v a -> Scalar a -> Maybe (BSpline v a, BSpline v a)+splitBSpline spline@(Spline p kv _) t +    | inDomain  = Just (Spline p us0 ds0, Spline p us1 ds1)     | otherwise = Nothing     where         inDomain = case knotDomain kv p of             Nothing         -> False-            Just (t0, t1)   -> t >= t0 || t <= t1+            Just (t0, t1)   -> t >= t0 && t <= t1         -        us = knots kv+        (lt, _, gt) = splitFind t kv         dss = deBoor spline t         -        us0 = takeWhile (<t) us ++ replicate (p+1) t-        ds0 = V.fromList (trimTo (drop (p+1) us0) (map V.head dss))+        us0 = setKnotMultiplicity t (p+1) lt+        ds0 = trimTo us0 (map V.head dss)         -        us1 = replicate (p+1) t ++ dropWhile (<=t) us-        ds1 = V.reverse (V.fromList (trimTo (drop (p+1) us1) (map V.last dss)))--        trimTo list  xs = zipWith const xs list+        us1 = setKnotMultiplicity t (p+1) gt+        ds1 = V.reverse (trimTo us1 (map V.last dss))+        +        trimTo kts = V.fromList . take (numKnots kts - p - 1)
src/Math/Spline/BSpline/Internal.hs view
@@ -1,61 +1,156 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} module Math.Spline.BSpline.Internal-    (BSpline(..), mapControlPoints, evalBSpline, insertKnot, deBoor) where+    ( BSpline(..)+    , mapControlPoints+    , evalBSpline+    , evalNaturalBSpline+    , insertKnot+    , deBoor+    , slice+    ) where  import Math.Spline.Knots +import Control.Monad.ST import Data.Monoid-import Data.Vector as V+import qualified Data.Vector.Generic as V+import qualified Data.Vector.Generic.Mutable as MV+import qualified Data.Vector as BV (Vector)+import qualified Data.Vector.Unboxed as UV (Vector, Unbox) import Data.VectorSpace import Prelude as P -data BSpline t = Spline+-- | A B-spline, defined by a knot vector (see 'Knots') and a sequence of control points.+data BSpline v t = Spline     { degree        :: !Int     , knotVector    :: Knots (Scalar t)-    , controlPoints :: Vector t+    , controlPoints :: v t     } +deriving instance (Eq (Scalar a), Eq (v a)) => Eq   (BSpline v a)+deriving instance (Ord (Scalar a), Ord (v a)) => Ord  (BSpline v a)+instance (Show (Scalar a), Show a, Show (v a)) => Show (BSpline v a) where+    showsPrec p (Spline _ kts cps) = showParen (p>10) +        ( showString "bSpline "+        . showsPrec 11 kts+        . showChar ' '+        . showsPrec 11 cps+        )++mapControlPoints :: (Scalar a ~ Scalar b, V.Vector v a, V.Vector v b) => (a -> b) -> BSpline v a -> BSpline v b+{-# SPECIALIZE mapControlPoints :: (Scalar a ~ Scalar b) => (a -> b)+ -> BSpline BV.Vector a -> BSpline BV.Vector b #-} mapControlPoints f spline = spline     { controlPoints = V.map f (controlPoints spline)     , knotVector = knotVector spline     } -evalBSpline spline = V.head . P.last . deBoor spline+-- |Evaluate a B-spline at the given point.  This uses a slightly modified version of +-- de Boor's algorithm which is only strictly correct inside the domain of the spline.+-- Unlike the standard algorithm, the basis functions always sum to 1, even outside the+-- domain of the spline.  This is mainly useful for \"clamped\" splines - the values at+-- or outside the endpoints will always be the value of the nearest control point.+--+-- For a standard implementation of de Boor's algorithm, see 'evalNaturalBSpline'.+-- For a (much slower) strictly mathematically correct evaluation, see 'evalReferenceBSpline'.+evalBSpline :: ( VectorSpace a, Fractional (Scalar a), Ord (Scalar a)+               , V.Vector v a, V.Vector v (Scalar a))+     => BSpline v a -> Scalar a -> a+{-# SPECIALIZE evalBSpline :: ( VectorSpace a, Fractional (Scalar a)+  , Ord (Scalar a)) => BSpline BV.Vector a -> Scalar a -> a#-}+evalBSpline spline+     | V.null (controlPoints spline) = zeroV+     | otherwise = V.head . P.last . deBoor spline --- |Insert one knot into a 'BSpline'+-- | Evaluate a B-spline at the given point.  This uses de Boor's algorithm, which is +-- only strictly correct inside the domain of the spline.+-- +-- For a (much slower) strictly mathematically correct evaluation, see 'evalReferenceBSpline'.+{-# INLINE evalNaturalBSpline #-}+{-# SPECIALIZE evalNaturalBSpline :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a) +    => BSpline BV.Vector a -> Scalar a -> a #-}+{-# SPECIALIZE evalNaturalBSpline :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a, UV.Unbox a) +    => BSpline UV.Vector a -> Scalar a -> a #-}+evalNaturalBSpline :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a, V.Vector v a)+    => BSpline v a -> Scalar a -> a+evalNaturalBSpline spline x = runST $ do+    let Spline p kvec cps = slice spline x+        kts = V.tail (knotsVector kvec)+        s = p - 1 - V.length (V.takeWhile (x==) kts)+    +    ds <- V.thaw cps+    +    sequence_+        [ do+            -- No need to check whether u0 < x < u1:+            --  x > u0 is guaranteed by choice of 's'+            --  x < u1 is guaranteed by 'slice'+            let !u0 = kts V.! (j + i)+                !u1 = kts V.! (j + p)+                !du = u1 - u0+                !a  = if du <= 0 then 1 else (x - u0) / du+            +            d0 <- MV.read ds  j+            d1 <- MV.read ds (j + 1)+            MV.write ds j (lerp d0 d1 a)+        | i <- [0 .. s]+        , j <- [0 .. s - i]+        ]+    +    MV.read ds 0++-- |Insert one knot into a 'BSpline' without changing the spline's shape. insertKnot-  :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a)) =>-     BSpline a -> Scalar a -> BSpline a+  :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a), V.Vector v a, V.Vector v (Scalar a)) =>+     BSpline v a -> Scalar a -> BSpline v a+{-# SPECIALIZE insertKnot :: (VectorSpace a, Ord (Scalar a), Fractional (Scalar a)) =>+  BSpline BV.Vector a -> Scalar a -> BSpline BV.Vector a #-} insertKnot spline x = spline     { knotVector    = knotVector spline `mappend` knot x     , controlPoints = V.zipWith4 (interp x) us (V.drop p us) ds (V.tail ds)     }     where-        us = knotsVector (knotVector spline)+        us = V.convert $ knotsVector (knotVector spline)         p  = degree spline         ds = extend (controlPoints spline) - -- duplicate the endpoints of a list; for example, -- extend [1..5] -> [1,1,2,3,4,5,5]+{-# INLINE extend #-}+extend :: V.Vector v t => v t -> v t extend vec     | V.null vec    = V.empty-    | otherwise     = V.cons (V.head vec) (V.snoc vec (V.last vec)) +    | otherwise     = V.cons (V.head vec) (V.snoc vec (V.last vec)) -deBoor spline x = go us (controlPoints spline)+{-# SPECIALIZE deBoor :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a)+  => BSpline BV.Vector a -> Scalar a -> [BV.Vector a] #-}+{-# SPECIALIZE deBoor :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a,+                          UV.Unbox a, UV.Unbox (Scalar a))+  => BSpline UV.Vector a -> Scalar a -> [UV.Vector a] #-}++-- | The table from de Boor's algorithm, calculated for the entire spline.  If that is not necessary+-- (for example, if you are only evaluating the spline), then use 'slice' on the spline first.+-- 'splitBSpline' currently uses the whole table.  It is probably not necessary there, but it +-- greatly simplifies the definition and makes the similarity to splitting Bezier curves very obvious.+deBoor :: (Fractional (Scalar a), Ord (Scalar a), VectorSpace a, V.Vector v a, V.Vector v (Scalar a))+    => BSpline v a -> Scalar a -> [v a]+deBoor spline x = go us0 (controlPoints spline)     where-        us = knotsVector (knotVector spline)-        +        us0 = V.convert $ knotsVector (knotVector spline)         -- Upper endpoints of the intervals are the same for         -- each row in the table (they just line up differently         -- with the lower endpoints):-        uHi = V.drop (degree spline + 1) us-        -        -- On each pass, the lower endpoints of the -        -- interpolation intervals advance and the new +        uHi = V.drop (degree spline + 1) us0++        -- On each pass, the lower endpoints of the+        -- interpolation intervals advance and the new         -- coefficients are given by linear interpolation         -- on the current intervals:-        go us ds +        go us ds             | V.null ds = []             | otherwise = ds : go uLo ds'             where@@ -63,10 +158,64 @@                 ds' = V.zipWith4 (interp x) uLo uHi                                             ds (V.tail ds) +interp :: (Fractional (Scalar v), Ord (Scalar v), VectorSpace v)+    => Scalar v -> Scalar v -> Scalar v -> v -> v -> v interp x x0 x1 y0 y1     |  x <  x0  = y0     |  x >= x1  = y1     | otherwise = lerp y0 y1 a     where         a = (x - x0) / (x1 - x0)++-- "slice" a spline to contain only those knots and control points that +-- actually influence the value at 'x'.+--+-- It should be true for any valid BSpline that:+-- degree (slice f x) == degree f+-- slice (slice f x) x == slice f x+-- {x in domain of f} => {x in domain of slice f x}+-- {x in domain of f} => evalBSpline (slice f x) x == evalBSpline f x+{-# INLINE slice #-}+slice :: (Num (Scalar a), Ord (Scalar a), AdditiveGroup a, V.Vector v a)+     => BSpline v a -> Scalar a -> BSpline v a+slice spline x = spline+    { knotVector    = stakeKnots (n + n) . sdropKnots (l - n) $ knotVector spline+    , controlPoints = vtake       n      . vdrop      (l - n) $ controlPoints spline+    }+    where+        l = maybe 0 id $ V.findIndex (> x) us+        n = degree spline + 1+        +        us = knotsVector (knotVector spline)++-- Try to take n, but if there's not enough, pad the rest with 0s+vtake :: (V.Vector v t, AdditiveGroup t) => Int -> v t -> v t+{-# SPECIALIZE vtake :: AdditiveGroup t => Int -> BV.Vector t -> BV.Vector t #-}+vtake n xs+    | n <= V.length xs = V.take n xs+    | otherwise = xs V.++ V.replicate (n - V.length xs) zeroV++-- Try to drop n, but if n is negative, pad the beginning with 0s+vdrop :: (V.Vector v t, AdditiveGroup t) => Int -> v t -> v t+{-# SPECIALIZE vdrop :: AdditiveGroup t => Int -> BV.Vector t -> BV.Vector t #-}+vdrop n xs+    | n >= 0 = V.drop n xs+    | otherwise = V.replicate (-n) zeroV V.++ xs++-- Try to take n knots, but if there aren't enough, increase the multiplicity of the last knot+stakeKnots :: (Num k, Ord k) => Int -> Knots k -> Knots k+stakeKnots n kts+    | n <= nKts = takeKnots n kts+    | otherwise = case maxKnot kts of+        Nothing     -> multipleKnot 0 (n - nKts)+        Just (k, m) -> setKnotMultiplicity k (m + n - nKts) kts+    where nKts = numKnots kts++-- Try to drop n knots, but if n is negative, increase the multiplicity of the first knot by @abs n@+sdropKnots :: (Num k, Ord k) => Int -> Knots k -> Knots k+sdropKnots n kts+    | n >= 0    = dropKnots n kts+    | otherwise = case minKnot kts of+        Nothing     -> multipleKnot 0 (-n)+        Just (k, m) -> setKnotMultiplicity k (m - n) kts 
src/Math/Spline/BSpline/Reference.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE FlexibleContexts #-} -- |Reference implementation of B-Splines; very inefficient but \"obviously\" -- correct. module Math.Spline.BSpline.Reference@@ -6,15 +8,40 @@     , basisFunctions     , basisPolynomials     , basisPolynomialsAt+    , evalReferenceBSpline+    , fitPolyToBSplineAt     ) where +import qualified Data.Vector.Generic as V+import Data.VectorSpace (VectorSpace, Scalar, (^*), sumV) import Math.Spline.Knots+import Math.Spline.BSpline.Internal import Math.Polynomial (Poly) import qualified Math.Polynomial as Poly +-- | This is a fairly slow function which computes the value of a B-spline at a given point,+-- using the mathematical definition of B-splines.  It is mainly for testing purposes, as a+-- reference against which the other evaluation functions are checked.+evalReferenceBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a), V.Vector v a) +    => BSpline v a -> Scalar a -> a+evalReferenceBSpline (Spline deg kts cps) x =+    sumV (zipWith (^*) (V.toList cps) (bases kts x !! deg))++-- | This is a fairly slow function which computes one polynomial segment of a B-spline (the +-- one containing the given point), using the mathematical definition of B-splines.  It is +-- mainly for testing purposes, as a reference against which the other evaluation functions+-- are checked.+fitPolyToBSplineAt :: (Fractional a, Ord a, Scalar a ~ a, V.Vector v a)+    => BSpline v a -> a -> Poly a+fitPolyToBSplineAt (Spline deg kts cps) x = +    Poly.sumPolys (zipWith Poly.scalePoly (V.toList cps) (basisPolynomialsAt kts x !! deg))++ind :: Num a => Bool -> a ind True  = 1 ind False = 0 +-- | The values of all the B-spline basis functions for the given knot vector at the given+-- point, ordered by degree; \"b_{i,j}(x)\" is @bases kts x !! i !! j@. bases :: (Fractional a, Ord a) => Knots a -> a -> [[a]] bases kts x = coxDeBoor interp initial kts     where@@ -26,8 +53,8 @@             = (if d0 == 0 then 0 else (x       - t_j) / d0) * b_nm1_j             + (if d1 == 0 then 0 else (t_jpnp1 -   x) / d1) * b_nm1_jp1 --- Alternate version constructing table of functions rather than computing--- table of values+-- | All the B-spline basis functions for the given knot vector at the given+-- point, ordered by degree; \"b_{i,j}\" is @basisFunctions kts x !! i !! j@. basisFunctions :: (Fractional a, Ord a) => Knots a -> [[a -> a]] basisFunctions kts = coxDeBoor interp initial kts     where@@ -39,13 +66,15 @@             = (if d0 == 0 then 0 else (x       - t_j) / d0) * b_nm1_j   x             + (if d1 == 0 then 0 else (t_jpnp1 -   x) / d1) * b_nm1_jp1 x --- compute all the basis polynomials for a knot vector, ordered by knot span.+-- | All the B-spline basis polynomials for the given knot vector, ordered first +-- by knot span and then by degree. basisPolynomials :: (Fractional a, Ord a) => Knots a -> [[[Poly a]]] basisPolynomials kts     | isEmpty kts   = []     | otherwise     = [basisPolynomialsAt kts kt | kt <- init (distinctKnots kts)] --- compute all the basis polynomials for the knot span containing a given location.+-- | All the B-spline basis polynomials for the given knot vector at the given+-- point, ordered by degree; \"b_{i,j}\" is @basisPolynomialsAt kts x !! i !! j@. basisPolynomialsAt :: (Fractional a, Ord a) => Knots a -> a -> [[Poly a]] basisPolynomialsAt kts x = coxDeBoor interp initial kts     where@@ -57,22 +86,23 @@             | (t_j, t_jp1) <- knotSpans kts 1             ]         interp t_j d0 b_nm1_j t_jpnp1 d1 b_nm1_jp1-            = (if d0 == 0 then Poly.zero else (Poly.x                 - Poly.constPoly t_j) / d0) * b_nm1_j-            + (if d1 == 0 then Poly.zero else (Poly.constPoly t_jpnp1 -             Poly.x) / d1) * b_nm1_jp1+            =   (if d0 == 0 then Poly.zero else (Poly.x                 ^-^ Poly.constPoly t_j) ^/ d0) ^*^ b_nm1_j+            ^+^ (if d1 == 0 then Poly.zero else (Poly.constPoly t_jpnp1 ^-^             Poly.x) ^/ d1) ^*^ b_nm1_jp1             where-                infixl 6 +, --                p + q   = Poly.addPoly p q-                p - q   = p + (Poly.negatePoly q)+                infixl 6 ^+^, ^-^+                p ^+^ q   = Poly.addPoly p q+                p ^-^ q   = p ^+^ (Poly.negatePoly q)                 -                infixl 7 *, /-                p * q   = Poly.multPoly p q-                p / s   = Poly.scalePoly (recip s) p+                infixl 7 ^*^, ^/+                p ^*^ q   = Poly.multPoly p q+                p ^/  s   = Poly.scalePoly (recip s) p --- This is a straightforward implementation of the Cox-De Boor recursion scheme+-- | This is a straightforward implementation of the Cox-De Boor recursion scheme -- generalized in a slightly strange way; the initial vector is a parameter  -- and the actual computation of the recursion step is a function parameter. -- The purpose is to allow the same recursion to be applied when computing basis -- function values and  basis polynomials.+coxDeBoor :: Num a => (a -> a -> b -> a -> a -> b -> b) -> [b] -> Knots a -> [[b]] coxDeBoor interp initial kts = table     where         ts = knots kts@@ -90,4 +120,6 @@ spans     = spansWith (,) spanDiffs :: Num a => Int -> [a] -> [a] spanDiffs = spansWith subtract++spansWith :: (a -> a -> b) -> Int -> [a] -> [b] spansWith f n ts = zipWith f ts (drop n ts)
src/Math/Spline/BezierCurve.hs view
@@ -12,7 +12,7 @@ import qualified Data.Vector as V import Data.VectorSpace --- |A BezierCurve curve on @0 <= x <= 1@.+-- |A Bezier curve on @0 <= x <= 1@. data BezierCurve t = BezierCurve !Int !(V.Vector t) deriving (Eq, Ord)  -- |Construct a Bezier curve from a list of control points.  The degree
src/Math/Spline/Class.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, UndecidableInstances #-}+{-# LANGUAGE OverlappingInstances, IncoherentInstances #-} module Math.Spline.Class where  import Control.Applicative@@ -6,6 +7,7 @@ import qualified Math.Spline.BSpline.Internal as BSpline  import qualified Data.Vector as V+import qualified Data.Vector.Generic as G import Data.VectorSpace  -- |A spline is a piecewise polynomial vector-valued function.  The necessary@@ -30,16 +32,27 @@     knotVector :: s v -> Knots (Scalar v)     knotVector = knotVector . toBSpline     -    toBSpline :: s v -> BSpline.BSpline v+    toBSpline :: s v -> BSpline.BSpline V.Vector v +-- TODO: this class should probably go away.  all it really does is overload something that doesn't really have any implementation-independent semantics (or does it?). class Spline s v => ControlPoints s v where     controlPoints :: s v -> V.Vector v -instance (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline BSpline.BSpline v where-    evalSpline spline = V.head . last . BSpline.deBoor spline+instance (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline (BSpline.BSpline V.Vector) v where+    evalSpline = BSpline.evalBSpline     splineDegree = BSpline.degree     knotVector = BSpline.knotVector     toBSpline = id -instance Spline BSpline.BSpline v => ControlPoints BSpline.BSpline v where+instance ( VectorSpace a, Fractional (Scalar a), Ord (Scalar a), G.Vector v a+         , G.Vector v (Scalar a)) => Spline (BSpline.BSpline v) a where+    evalSpline = BSpline.evalBSpline+    splineDegree = BSpline.degree+    knotVector = BSpline.knotVector+    toBSpline (BSpline.Spline deg ks ctp) = BSpline.Spline deg ks (G.convert $ ctp)++instance Spline (BSpline.BSpline V.Vector) a => ControlPoints (BSpline.BSpline V.Vector) a where     controlPoints = BSpline.controlPoints++instance (Spline (BSpline.BSpline v) a, G.Vector v a) => ControlPoints (BSpline.BSpline v) a where+    controlPoints = V.convert . BSpline.controlPoints
+ src/Math/Spline/Hermite.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-}+module Math.Spline.Hermite+    ( CSpline, cSpline+    , evalSpline+    ) where++import Data.List+import Data.Ord+import Math.Polynomial hiding (x)+import Math.Spline.BSpline+import Math.Spline.Class+import Math.Spline.Knots+import qualified Data.Vector as V+import Data.VectorSpace++-- | Cubic Hermite splines.  These are cubic splines defined by a +-- sequence of control points and derivatives at those points.+newtype CSpline a = CSpline [(Scalar a,a,a)]++-- | Cubic splines specified by a list of control points, +-- where each control point is given by a triple of parameter value, +-- position of the spline at that parameter value,+-- and derivative of the spline at that parameter value.+cSpline :: Ord (Scalar a) => [(Scalar a,a,a)] -> CSpline a+cSpline = CSpline . sortBy (comparing fst3)+    where fst3 (a,_,_) = a++h00, h10, h01, h11 :: (Num a, Eq a) => Poly a+h00 = poly BE [ 2,-3, 0, 1]+h10 = poly BE [ 1,-2, 1, 0]+h01 = poly BE [-2, 3, 0, 0]+h11 = poly BE [ 1,-1, 0, 0]++evalHermite+  :: (Eq (Scalar v), Num (Scalar v), VectorSpace v) =>+     v -> v -> v -> v -> Scalar v -> v+evalHermite y0 m0 y1 m1 x = sumV+    [ evalPoly h x *^ p+    | p <- [ y0,  m0,  y1,  m1]+    | h <- [h00, h10, h01, h11]+    ]++evalCSpline+  :: (VectorSpace v, Ord (Scalar v), Fractional (Scalar v)) =>+     CSpline v -> Scalar v -> v+evalCSpline (CSpline cps) = loop cps+    where+        loop []         _ = zeroV+        loop [(_,y,_)]  _ = y+        loop ((x0,y0,m0):rest@((x1,y1,m1):_)) x+            | x <= x0   = y0+            | x <  x1   = let dx = x1 - x0+                           in evalHermite y0 (m0 ^* dx) y1 (m1 ^* dx) ((x - x0) / dx)+            | otherwise = loop rest x++instance (VectorSpace a, Fractional (Scalar a), Ord (Scalar a)) => Spline CSpline a where+    splineDegree _ = 3+    +    splineDomain (CSpline  []) = Nothing+    splineDomain (CSpline cps) = Just (head xs, last xs) where (xs, _, _) = unzip3 cps+    +    evalSpline = evalCSpline+    +    -- TODO: check.  Also work out a more compact translation, taking advantage of the+    -- known continuity on the interior knots.  It should be possible to work out an +    -- equivalent b-spline with only 'n+4' knots.  If that translation isn't ill-+    -- conditioned, it might be a good thing to implement.+    toBSpline (CSpline cSpl) = bSpline kts (V.fromList cps)+        where +            kts = fromList [(x,4) | x <- xs]+            +            (xs, _, _) = unzip3 cSpl+            cps = concat +                [ [ y0+                  , y0 ^+^ dy0 ^* dx3+                  , y1 ^-^ dy1 ^* dx3+                  , y1+                  ]+                | ((x0, y0, dy0), (x1, y1, dy1)) <- spans 1 cSpl+                , let dx3 = (x1 - x0) / 3+                ]++spans :: Int -> [a] -> [(a,a)]+spans n xs = zip xs (drop n xs)+
src/Math/Spline/ISpline.hs view
@@ -62,6 +62,8 @@ toISpline :: (Spline s v, Eq v) => s v -> ISpline v toISpline = fromBSpline . toBSpline +fromBSpline :: (Eq v, VectorSpace v, Fractional (Scalar v), Ord (Scalar v))+    => BSpline V.Vector v -> ISpline v fromBSpline spline     | V.head ds == zeroV      && numKnots ks >= 2 = iSpline (mkKnots (init (tail ts))) (V.tail ds')
src/Math/Spline/Knots.hs view
@@ -10,7 +10,9 @@     , toList, numDistinctKnots, lookupDistinctKnot          , knots, knotsVector-    , distinctKnots, distinctKnotsVector+    , distinctKnots, multiplicities+    , distinctKnotsVector, multiplicitiesVector+    , distinctKnotsSet          , toMap     , fromMap@@ -25,6 +27,8 @@     , maxMultiplicity     , knotMultiplicity, setKnotMultiplicity     +    , splitFind+         , fromAscList, fromDistinctAscList     , valid     @@ -34,27 +38,26 @@     , knotDomain          , uniform+    +    , minKnot+    , maxKnot     ) where  import Prelude hiding (sum, maximum)+import Control.Arrow ((***)) import Control.Monad (guard)-import Data.Foldable (Foldable(foldMap), sum, maximum)+import Data.Foldable (Foldable(foldMap), maximum)+import Data.List (sortBy, sort, unfoldr) import qualified Data.Map as M import Data.Monoid (Monoid(..))-import Data.Maybe (fromMaybe)-import qualified Data.Set as S (Set)+import Data.Ord+import qualified Data.Set as S (Set, fromAscList) import qualified Data.Vector as V-import Data.VectorSpace  -- |Knot vectors - multisets of points in a 1-dimensional space.-data Knots a = Knots !Int (M.Map a Int) deriving (Eq, Ord)+newtype Knots a = Knots (V.Vector a) deriving (Eq, Ord)  instance Show a => Show (Knots a) where-    showsPrec p ks@(Knots 0 _) = showString "empty"-    showsPrec p ks@(Knots 1 _) = showParen (p > 10)-        ( showString "knot "-        . showsPrec 11 (head $ knots ks)-        )     showsPrec p ks = showParen (p > 10)         ( showString "mkKnots "         . showsPrec 11 (knots ks)@@ -62,20 +65,19 @@  instance (Ord a) => Monoid (Knots a) where     mempty = empty-    mappend (Knots n1 v1) (Knots n2 v2) =-        Knots (n1 + n2) (M.filter (/=0) (M.unionWith (+) v1 v2))+    mappend (Knots v1) (Knots v2) =+      Knots . V.fromList . sort . V.toList $ v1 V.++ v2  instance Foldable Knots where-    foldMap f = foldMap f . knots+    foldMap f = foldMap f . knotsVector   -- |An empty knot vector empty :: Knots a-empty = Knots 0 M.empty+empty = Knots V.empty  isEmpty :: Knots a -> Bool-isEmpty (Knots 0 _) = True-isEmpty  _          = False+isEmpty (Knots v) = V.null v  -- |Create a knot vector consisting of one knot. knot :: Ord a => a -> Knots a@@ -83,79 +85,74 @@  -- |Create a knot vector consisting of one knot with the specified multiplicity. multipleKnot :: Ord a => a -> Int -> Knots a-multipleKnot k n -    | n <= 0    = Knots 0 (M.empty)-    | otherwise = Knots n (M.singleton k n)+multipleKnot k n = Knots $ V.replicate n k  -- |Create a knot vector consisting of all the knots in a list. mkKnots :: (Ord a) => [a] -> Knots a-mkKnots ks = fromList (map (\k -> (k,1)) ks)+mkKnots = Knots . V.fromList . sort  -- |Create a knot vector consisting of all the knots and corresponding  -- multiplicities in a list. fromList :: (Ord k) => [(k, Int)] -> Knots k-fromList ks = Knots (sum kMap) kMap-    where kMap = M.fromListWith (+) (filter ((>0).snd) ks)+fromList ks = Knots v+    where v = V.concat . map (\(k, mult) -> V.replicate mult k) .+              sortBy (comparing fst) $ filter ((>0).snd) ks  -- |Create a knot vector consisting of all the knots and corresponding  -- multiplicities in a list ordered by the knots' 'Ord' instance.  The -- ordering precondition is not checked. fromAscList :: Eq k => [(k, Int)] -> Knots k-fromAscList ks = Knots (sum kMap) kMap-    where kMap = M.fromAscListWith (+) (filter ((>0).snd) ks)+fromAscList ks = Knots v+    where v = V.concat . map (\(k, mult) -> V.replicate mult k)+                 $ filter ((>0).snd) ks  -- |Create a knot vector consisting of all the knots and corresponding  -- multiplicities in a list ordered by the knots' 'Ord' instance with no -- duplicates.  The preconditions are not checked.-fromDistinctAscList :: [(k, Int)] -> Knots k-fromDistinctAscList ks = Knots (sum kMap) kMap-    where kMap = M.fromDistinctAscList (filter ((>0).snd) ks)+fromDistinctAscList :: Eq k => [(k, Int)] -> Knots k+fromDistinctAscList = fromAscList -fromMap :: M.Map k Int -> Knots k-fromMap ks = Knots (sum kMap) kMap-    where-        kMap = mFilter (>0) ks-        -- filter is monotonic, I have no idea why M.filter requires Ord on the key-        mFilter p = M.fromDistinctAscList . filter (p.snd) . M.toAscList+fromMap :: Eq k => M.Map k Int -> Knots k+fromMap = fromAscList . M.toAscList  fromVector :: Ord k => V.Vector (k,Int) -> Knots k fromVector = fromList . V.toList  -- |Returns a list of all distinct knots in ascending order along with -- their multiplicities.-toList :: Knots k -> [(k, Int)]-toList = M.toList . toMap+toList :: Eq k => Knots k -> [(k, Int)]+toList = unfoldr $ \kts -> do+    kt <- minKnot kts+    return (kt, dropKnots (snd kt) kts) -toVector :: Knots k -> V.Vector (k, Int)-toVector = V.fromList . toList+toVector :: Eq k => Knots k -> V.Vector (k, Int)+toVector = V.unfoldr $ \kts -> do+    kt <- minKnot kts+    return (kt, dropKnots (snd kt) kts) -toMap :: Knots k -> M.Map k Int-toMap (Knots _ ks) = ks+toMap :: Ord k => Knots k -> M.Map k Int+toMap = M.fromAscListWith (+) . toList  -- |Returns the number of knots (not necessarily distinct) in a knot vector. numKnots :: Knots t -> Int-numKnots (Knots n _) = n+numKnots (Knots v) = V.length v  -- |Returns the number of distinct knots in a knot vector.-numDistinctKnots :: Knots t -> Int-numDistinctKnots (Knots _ ks) = M.size ks+numDistinctKnots :: Eq t => Knots t -> Int+numDistinctKnots = V.length . distinctKnotsVector -maxMultiplicity :: Knots t -> Int-maxMultiplicity (Knots 0  _) = 0-maxMultiplicity (Knots _ ks) = maximum ks+maxMultiplicity :: Ord t => Knots t -> Int+maxMultiplicity kts@(Knots v)+    | V.length v == 0 = 0+    | otherwise = maximum $ toMap kts  lookupKnot :: Int -> Knots a -> Maybe a-lookupKnot k kts-    | k < 0             = Nothing-    | k < numKnots kts  = fmap fst mbKt-    | otherwise         = Nothing-    where (_, mbKt, _) = splitLookup k kts+lookupKnot k (Knots kts) = do+    guard (0 <= k && k < V.length kts)+    V.indexM kts k -lookupDistinctKnot :: Int -> Knots a -> Maybe a-lookupDistinctKnot k (Knots _ ks)-    | k < 0         = Nothing-    | k < M.size ks = Just (fst (M.elemAt k ks))-    | otherwise     = Nothing+lookupDistinctKnot :: Eq a => Int -> Knots a -> Maybe a+lookupDistinctKnot k kts = lookupKnot k . Knots $ distinctKnotsVector kts  -- |@splitLookup n kts@: Split a knot vector @kts@ into 3 parts @(pre, mbKt, post)@ -- such that:@@ -165,133 +162,84 @@ --  * Putting the 3 parts back together yields exactly the original knot vector --  * The @n@'th knot, if one exists, will be in @mbKt@ along with its multiplicity ---splitLookup :: Int -> Knots a -> (Knots a, Maybe (a, Int), Knots a)-splitLookup k (Knots n ks) = scan 0 M.empty n ks-    where-        -- The general plan: iteratively pull the smallest knot out of "post",-        -- either moving it to "pre" or terminating by returning it along with-        -- current values of "pre" and "post"-        -        -- invariants:-        --   nPre  = sum pre-        --   nPost = sum post-        --   M.union pre post = ks-        --   every key in pre < every key in post-        scan nPre pre nPost post-            | nPost <= 0    = (Knots nPre  pre, Nothing, Knots nPost post)-            | nPre + m > k  = (Knots nPre  pre, Just kt, Knots nNewPost newPost)-            | otherwise     = scan (nPre + m) (pre `ascSnoc` kt) nNewPost newPost-            where-                Just (kt@(x,m), newPost)  = M.minViewWithKey post-                nNewPost = nPost - m-                done x = (Knots nPre  pre, x, Knots nPost post)---- Prepend or append an element to a map, without checking the precondition--- that the new pair's key is less than (greater than, resp.) all keys in --- the map.-ascCons x m = M.fromDistinctAscList (x : M.toAscList m)-ascSnoc m x = M.fromDistinctAscList (M.toAscList m ++ [x])---- Prepend or append an knot to a knot vector, without checking the--- precondition that the new knot's location is less than (greater than,--- resp.) all knots in the vector.-ascConsKnot (_,0) kts = kts-ascConsKnot kt@(k,m) (Knots n ks) = Knots (n+m) (kt `ascCons` ks)--ascSnocKnot kts (_,0) = kts-ascSnocKnot (Knots n ks) kt@(k,m) = Knots (n+m) (ks `ascSnoc` kt)--clamp lo hi = max lo . min hi+splitLookup :: Int -> Knots a -> (Knots a, Maybe a, Knots a)+splitLookup k (Knots v)+    | V.null gt = (Knots lt, Nothing, Knots V.empty)+    | otherwise = (Knots lt, Just $ V.head gt, Knots $ V.tail gt)+  where+    (lt, gt) = V.splitAt k v  dropKnots :: Int -> Knots a -> Knots a-dropKnots k kts = fromMaybe post $ do-        (x,xAvail) <- mbKt-        let xWanted = numKnots kts - (numKnots post + k)-        -        return ((x, clamp 0 xAvail xWanted) `ascConsKnot` post)-    where-        (pre, mbKt, post) = splitLookup k kts+dropKnots k (Knots v) = Knots $ V.drop k v  takeKnots :: Int -> Knots a -> Knots a-takeKnots k kts = fromMaybe pre $ do-        (x,xAvail) <- mbKt-        let xWanted = k - numKnots pre-    -        return (pre `ascSnocKnot` (x, clamp 0 xAvail xWanted))-    where-        (pre, mbKt, post) = splitLookup k kts+takeKnots k (Knots v) = Knots $ V.take k v  splitKnotsAt :: Int -> Knots a -> (Knots a, Knots a)-splitKnotsAt k kts = fromMaybe (pre, post) $ do-        (x,xAvail) <- mbKt-        let xWanted = k - numKnots pre-            xTaken = clamp 0 xAvail xWanted-    -        return ( pre `ascSnocKnot` (x,xTaken)-               , (x, xAvail - xTaken) `ascConsKnot` post-               )-    where-        (pre, mbKt, post) = splitLookup k kts+splitKnotsAt k (Knots v) = Knots *** Knots $ V.splitAt k v +-- |Count the number of knots less than the n'th distinct knot.+findDistinctKnot :: Eq a => Int -> Knots a -> Int+findDistinctKnot n = V.last . V.take (1 + max 0 n) . V.scanl (+) 0 . multiplicitiesVector -takeDistinctKnots :: Int -> Knots a -> Knots a-takeDistinctKnots k (Knots n ks) = Knots (sum kMap) kMap-    where-        kMap = M.fromDistinctAscList (take k (M.toAscList ks))+takeDistinctKnots :: (Ord a) => Int -> Knots a -> Knots a+takeDistinctKnots k kts = takeKnots (findDistinctKnot k kts) kts -dropDistinctKnots :: Int -> Knots a -> Knots a-dropDistinctKnots k (Knots n ks) = Knots (sum kMap) kMap-    where-        kMap = M.fromDistinctAscList (drop k (M.toAscList ks))+dropDistinctKnots :: (Ord a) => Int -> Knots a -> Knots a+dropDistinctKnots k kts = dropKnots (findDistinctKnot k kts) kts -splitDistinctKnotsAt :: Int -> Knots a -> (Knots a, Knots a)-splitDistinctKnotsAt k (Knots n ks) = (Knots sz1 kMap1, Knots (n - sz1) kMap2)-    where-        (ks1, ks2) = splitAt k (M.toAscList ks)-        kMap1 = M.fromDistinctAscList ks1-        kMap2 = M.fromDistinctAscList ks2-        sz1   = sum kMap1+splitDistinctKnotsAt :: (Ord a, Eq a) => Int -> Knots a -> (Knots a, Knots a)+splitDistinctKnotsAt k kts = splitKnotsAt (findDistinctKnot k kts) kts  -- |Returns a list of all knots (not necessarily distinct) of a knot vector in ascending order knots :: Knots t -> [t]-knots (Knots _ ks) = concat [replicate n k | (k,n) <- M.toAscList ks]+knots = V.toList . knotsVector  -- |Returns a vector of all knots (not necessarily distinct) of a knot vector in ascending order knotsVector :: Knots t -> V.Vector t-knotsVector (Knots _ ks) = V.concat [V.replicate n k | (k,n) <- M.toAscList ks]+knotsVector (Knots v) = v  -- |Returns a list of all distinct knots of a knot vector in ascending order-distinctKnots :: Knots t -> [t]-distinctKnots (Knots _ ks) = M.keys ks+distinctKnots :: Eq t => Knots t -> [t]+distinctKnots = map fst . toList +multiplicities :: Eq t => Knots t -> [Int]+multiplicities = map snd . toList+ -- |Returns a vector of all distinct knots of a knot vector in ascending order-distinctKnotsVector :: Knots t -> V.Vector t-distinctKnotsVector = V.fromList . distinctKnots+distinctKnotsVector :: Eq t => Knots t -> V.Vector t+distinctKnotsVector = V.map fst . toVector +multiplicitiesVector :: Eq a => Knots a -> V.Vector Int+multiplicitiesVector = V.map snd . toVector+ -- |Returns a 'S.Set' of all distinct knots of a knot vector-distinctKnotsSet :: Knots k -> S.Set k-distinctKnotsSet (Knots _ ks) = M.keysSet ks+distinctKnotsSet :: Eq k => Knots k -> S.Set k+distinctKnotsSet (Knots k) = S.fromAscList $ V.toList k  -- |Looks up the multiplicity of a knot (which is 0 if the point is not a knot) knotMultiplicity :: (Ord k) => k -> Knots k -> Int-knotMultiplicity k (Knots _ ks) = fromMaybe 0 (M.lookup k ks)+knotMultiplicity k (Knots ks) = V.length $ V.elemIndices k ks  -- |Returns a new knot vector with the given knot set to the specified  -- multiplicity and all other knots unchanged. setKnotMultiplicity :: Ord k => k -> Int -> Knots k -> Knots k-setKnotMultiplicity k n (Knots m ks)-    | n <= 0    = Knots (m     - n') (M.delete k ks)-    | otherwise = Knots (m + n - n') (M.insert k n ks)-    where-        n' = knotMultiplicity k (Knots m ks)+setKnotMultiplicity k n kts@(Knots v)+    | n <= 0    = Knots (V.filter (/= k) v)+    | otherwise = Knots $ V.concat [lt, V.replicate n k, gt]+        where (Knots lt, _, Knots gt) = splitFind k kts +splitFind :: Ord k => k -> Knots k -> (Knots k, Knots k, Knots k)+splitFind k (Knots v) = (Knots lt, Knots eq, Knots gt)+  where+    (lt, xs) = V.span (<k) v+    (eq, gt) = V.span (==k) xs+ -- |Check the internal consistency of a knot vector-valid :: Ord k => Knots k -> Bool-valid (Knots n ks) = and-    [ M.valid ks-    , n == sum ks-    , all (>0) (M.elems ks)-    ]+valid :: (Ord k, Num k) => Knots k -> Bool+valid (Knots v)+    | V.null v  = True+    | otherwise = V.and $ V.zipWith (>=) (V.tail v) v  -- |@knotSpan kts i j@ returns the knot span extending from the @i@'th knot -- to the @j@'th knot, if  @i <= j@ and both knots exist.@@ -324,7 +272,7 @@ -- the basis functions sum to 1, which is only true on this range, and so -- this is also precisely the domain on which de Boor's algorithm is valid. knotDomain :: Knots a -> Int -> Maybe (a,a)-knotDomain ks@(Knots n _) p = knotSpan ks p (n-p-1)+knotDomain ks@(Knots v) p = knotSpan ks p (V.length v-p-1)  -- |@uniform deg nPts (lo,hi)@ constructs a uniformly-spaced knot vector over -- the interval from @lo@ to @hi@ which, when used to construct a B-spline @@ -336,3 +284,14 @@         n = nPts + deg - numKnots ends         f i = (fromIntegral i * lo + fromIntegral (n - i) * hi) / fromIntegral n         internal = mkKnots [f i | i <- [0..n]]++{-# INLINE minKnot #-}+minKnot :: (Eq a) => Knots a -> Maybe (a, Int)+minKnot (Knots v)+    | V.null v  = Nothing+    | otherwise = Just (kt, V.length (V.takeWhile (kt ==) v))+    where kt = V.head v++{-# INLINE maxKnot #-}+maxKnot :: Eq a => Knots a -> Maybe (a, Int)+maxKnot (Knots v) = minKnot (Knots (V.reverse v))
src/Math/Spline/MSpline.hs view
@@ -46,6 +46,7 @@         n = V.length cps         m = numKnots kts - 1 +spans :: Int -> V.Vector a -> V.Vector (a,a) spans n xs = V.zip xs (V.drop n xs)  instance (VectorSpace v, Fractional (Scalar v), Ord (Scalar v)) => Spline MSpline v where@@ -63,6 +64,8 @@ toMSpline :: Spline s v => s v -> MSpline v toMSpline = fromBSpline . toBSpline +fromBSpline :: (VectorSpace a, Fractional (Scalar a), Ord (Scalar a))+    => BSpline (V.Vector) a -> MSpline a fromBSpline spline = mSpline ks cs     where         n = splineDegree spline + 1; n' = fromIntegral n
+ test/Main.hs view
@@ -0,0 +1,14 @@+#!/usr/bin/env runhaskell+module Main where++import Test.Framework (defaultMain, testGroup)++import Tests.BSpline.Reference (referenceBSplineTests)+import Tests.BSpline (bSplineTests)+import Tests.Knots (knotsTests)++main = defaultMain +    [ testGroup "Math.Spline.BSpline.Reference" referenceBSplineTests+    , testGroup "Math.Spline.BSpline"           bSplineTests+    , testGroup "Math.Spline.Knots"             knotsTests+    ]