Skip to content

Commit 686a047

Browse files
committed
Add module Data.Poly.Interpolation
1 parent 0665a66 commit 686a047

File tree

5 files changed

+92
-0
lines changed

5 files changed

+92
-0
lines changed

poly.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ library
3131
hs-source-dirs: src
3232
exposed-modules:
3333
Data.Poly
34+
Data.Poly.Interpolation
3435
Data.Poly.Laurent
3536
Data.Poly.Semiring
3637
Data.Poly.Orthogonal
@@ -89,6 +90,7 @@ test-suite poly-tests
8990
Dense
9091
DenseLaurent
9192
DFT
93+
Interpolation
9294
Orthogonal
9395
Quaternion
9496
TestUtils

src/Data/Poly/Internal/Dense.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module Data.Poly.Internal.Dense
2727
, scale
2828
, pattern X
2929
, eval
30+
, evalk
3031
, subst
3132
, deriv
3233
, integral
@@ -460,6 +461,14 @@ substitute' f (Poly cs) x = fst' $
460461
G.foldl' (\(acc :*: xn) cn -> acc `plus` f cn xn :*: x `times` xn) (zero :*: one) cs
461462
{-# INLINE substitute' #-}
462463

464+
-- | Evaluate the kth derivative of the polynomial at a given point.
465+
evalk :: (G.Vector v a, Num a) => Int -> Poly v a -> a -> a
466+
evalk k (Poly cs) x = fst' $
467+
G.ifoldl' (\(acc :*: xn) i cn -> acc + kth i * cn * xn :*: x * xn) (0 :*: 1) (G.drop k cs)
468+
where
469+
kth i = fromIntegral $ product [(i + 1)..(i + k)]
470+
{-# INLINE evalk #-}
471+
463472
-- | Take the derivative of the polynomial.
464473
--
465474
-- >>> deriv (X^3 + 3 * X) :: UPoly Int

src/Data/Poly/Interpolation.hs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module Data.Poly.Interpolation
4+
( lagrange
5+
, hermite
6+
) where
7+
8+
#if __GLASGOW_HASKELL__ < 910
9+
import Data.Foldable (foldl')
10+
#endif
11+
12+
import Data.Poly.Internal.Dense
13+
import qualified Data.Vector.Generic as G
14+
15+
-- | Compute the [Lagrange interpolating polynomial](https://en.wikipedia.org/wiki/Lagrange_polynomial).
16+
--
17+
-- This is the (unique) polynomial of minimal degree interpolating the given points.
18+
-- The values are given as @(x, y)@ pairs where @y@ is the value at @x@. The @x@ values must be distinct.
19+
lagrange :: (G.Vector v a, Eq a, Fractional a) => [(a, a)] -> Poly v a
20+
lagrange = fst . foldl' f (0, 1)
21+
where
22+
f (p, w) (x, y) =
23+
let a = (y - eval p x) / eval w x
24+
in (p + scale 0 a w, scale 1 1 w - scale 0 x w) -- (p + a * w, w * (X - x))
25+
{-# INLINABLE lagrange #-}
26+
27+
-- | Compute the [Hermite interpolating polynomial](https://en.wikipedia.org/wiki/Hermite_interpolation).
28+
--
29+
-- This is the (unique) polynomial of minimal degree interpolating the given points and derivatives.
30+
-- The values are given as @(x, ys)@ pairs where @ys !! k@ is the k-th derivative at @x@. The @x@ values must be distinct.
31+
hermite :: (G.Vector v a, Eq a, Fractional a) => [(a, [a])] -> Poly v a
32+
hermite = fst . foldl' f (0, 1)
33+
where
34+
f (p, w) (x, ys) = let (_, p', w') = foldl' g (0, p, w) ys in (p', w')
35+
where
36+
g (k, p', w') y =
37+
let a = (y - evalk k p' x) / evalk k w' x
38+
in (k + 1, p' + scale 0 a w', scale 1 1 w' - scale 0 x w')
39+
{-# INLINABLE hermite #-}

test/Interpolation.hs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
3+
module Interpolation (testSuite) where
4+
5+
import Data.Function (on)
6+
import Data.List (nubBy)
7+
import Data.Poly hiding (scale)
8+
import Data.Poly.Interpolation
9+
import Test.Tasty
10+
import Test.Tasty.QuickCheck
11+
12+
import TestUtils ()
13+
14+
testSuite :: TestTree
15+
testSuite = localOption (QuickCheckMaxSize 10) $ testGroup "Interpolation"
16+
[ testProperty "lagrange interpolates" $ \xys -> prop_lagrange (nubBy ((==) `on` fst) xys)
17+
, testProperty "hermite interpolates" $ \xys -> prop_hermite (nubBy ((==) `on` (\(x, _, _, _, _) -> x)) xys)
18+
, testProperty "lagrange == hermite" $ \xys -> prop_lagrange_hermite (nubBy ((==) `on` fst) xys)
19+
]
20+
21+
prop_lagrange :: [(Rational, Rational)] -> Property
22+
prop_lagrange xys =
23+
let p = lagrange xys :: VPoly Rational
24+
in conjoin $ map (\(x, y) -> eval p x === y) xys
25+
26+
prop_hermite :: [(Rational, Rational, Rational, Rational, Rational)] -> Property
27+
prop_hermite xys =
28+
let
29+
p = hermite (map (\(x, y, y', y'', y''') -> (x, [y, y', y'', y'''])) xys) :: VPoly Rational
30+
p' = deriv p
31+
p'' = deriv p'
32+
p''' = deriv p''
33+
in conjoin $ map (\(x, y, y', y'', y''') -> eval p x === y .&&. eval p' x === y' .&&. eval p'' x === y'' .&&. eval p''' x === y''') xys
34+
35+
prop_lagrange_hermite :: [(Rational, Rational)] -> Property
36+
prop_lagrange_hermite xys =
37+
let
38+
p = lagrange xys :: VPoly Rational
39+
q = hermite (map (\(x, y) -> (x, [y])) xys) :: VPoly Rational
40+
in p === q

test/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import qualified Dense
88
import qualified DenseLaurent
99
import qualified DFT
1010
import qualified Orthogonal
11+
import qualified Interpolation
1112
#ifdef SupportSparse
1213
import qualified Multi
1314
import qualified MultiLaurent
@@ -20,6 +21,7 @@ main = defaultMain $ testGroup "All"
2021
[ Dense.testSuite
2122
, DenseLaurent.testSuite
2223
, DFT.testSuite
24+
, Interpolation.testSuite
2325
, Orthogonal.testSuite
2426
#ifdef SupportSparse
2527
, Sparse.testSuite

0 commit comments

Comments
 (0)