polynomial-0.7.1: src/Math/Polynomial/VectorSpace.hs
{-# LANGUAGE ParallelListComp, ViewPatterns, FlexibleContexts #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- TODO: update all haddock comments
-- |Same general interface as Math.Polynomial, but using AdditiveGroup,
-- VectorSpace, etc., instead of Num where sensible.
module Math.Polynomial.VectorSpace
( Endianness(..)
, Poly, poly, polyDegree
, vPolyCoeffs, polyIsZero, polyIsOne
, zero, one, constPoly, x
, scalePoly, negatePoly
, composePolyWith
, addPoly, sumPolys, multPolyWith, powPolyWith
, quotRemPolyWith, quotPolyWith, remPolyWith
, evalPoly, evalPolyDeriv, evalPolyDerivs
, contractPoly
, monicPolyWith
, gcdPolyWith
, polyDeriv, polyDerivs, polyIntegral
) where
import Math.Polynomial.Type hiding (poly, polyDegree, polyIsZero)
import Math.Polynomial.Pretty ({- instance -})
import Data.List
import Data.List.ZipSum
import Data.VectorSpace
vPolyN :: (Eq a, AdditiveGroup a) => Int -> Endianness -> [a] -> Poly a
vPolyN n e = vTrim . rawListPolyN n e
poly :: (Eq a, AdditiveGroup a) => Endianness -> [a] -> Poly a
poly e = vTrim . rawListPoly e
polyDegree :: (Eq a, AdditiveGroup a) => Poly a -> Int
polyDegree p = rawPolyDegree (vTrim p)
polyIsZero :: (Eq a, AdditiveGroup a) => Poly a -> Bool
polyIsZero = null . rawPolyCoeffs . vTrim
-- |The polynomial \"1\"
one :: (Num a, Eq a) => Poly a
one = polyN 1 LE [1]
-- |The polynomial (in x) \"x\"
x :: (Num a, Eq a) => Poly a
x = polyN 2 LE [0,1]
-- |Given some constant 'k', construct the polynomial whose value is
-- constantly 'k'.
constPoly :: (Eq a, AdditiveGroup a) => a -> Poly a
constPoly x = vPolyN 1 LE [x]
-- |Given some scalar 's' and a polynomial 'f', computes the polynomial 'g'
-- such that:
--
-- > evalPoly g x = s * evalPoly f x
scalePoly :: (Eq a, VectorSpace a, AdditiveGroup (Scalar a), Eq (Scalar a))
=> Scalar a -> Poly a -> Poly a
scalePoly = (*^)
-- |Given some polynomial 'f', computes the polynomial 'g' such that:
--
-- > evalPoly g x = negate (evalPoly f x)
negatePoly :: (AdditiveGroup a, Eq a) => Poly a -> Poly a
negatePoly = vTrim . rawMapPoly negateV
-- |Given polynomials 'f' and 'g', computes the polynomial 'h' such that:
--
-- > evalPoly h x = evalPoly f x + evalPoly g x
addPoly :: (AdditiveGroup a, Eq a) => Poly a -> Poly a -> Poly a
addPoly p@(vPolyCoeffs LE -> a) q@(vPolyCoeffs LE -> b) = vPolyN n LE (zipSumV a b)
where n = max (rawPolyLength p) (rawPolyLength q)
{-# RULES
"sum Poly" forall ps. foldl addPoly zero ps = sumPolys ps
#-}
sumPolys :: (AdditiveGroup a, Eq a) => [Poly a] -> Poly a
sumPolys [] = zero
sumPolys ps = poly LE (foldl1 zipSumV (map (vPolyCoeffs LE) ps))
-- |Given polynomials 'f' and 'g', computes the polynomial 'h' such that:
--
-- > evalPoly h x = evalPoly f x * evalPoly g x
multPolyWith :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> Poly a -> Poly a -> Poly a
multPolyWith multiplyV p@(vPolyCoeffs LE -> xs) q@(vPolyCoeffs LE -> ys) = vPolyN n LE (multPolyWithLE multiplyV xs ys)
where n = 1 + rawPolyDegree p + rawPolyDegree q
-- |(Internal): multiply polynomials in LE order. O(length xs * length ys).
multPolyWithLE :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> [a] -> [a] -> [a]
multPolyWithLE _ _ [] = []
multPolyWithLE multiplyV xs (y:ys) = foldr mul [] xs
where
mul x bs
| x == zeroV = zeroV : bs
| otherwise = (multiplyV x y) : zipSumV (map (multiplyV x) ys) bs
-- |Given a polynomial 'f' and exponent 'n', computes the polynomial 'g'
-- such that:
--
-- > evalPoly g x = evalPoly f x ^ n
powPolyWith :: (AdditiveGroup a, Eq a, Integral b) => a -> (a -> a -> a) -> Poly a -> b -> Poly a
powPolyWith one multiplyV p n
| n < 0 = error "powPolyWith: negative exponent"
| otherwise = powPoly p n
where
multPoly = multPolyWith multiplyV
powPoly p 0 = constPoly one
powPoly p 1 = p
powPoly p n
| odd n = p `multPoly` powPoly p (n-1)
| otherwise = (\x -> multPoly x x) (powPoly p (n`div`2))
-- |Given polynomials @a@ and @b@, with @b@ not 'zero', computes polynomials
-- @q@ and @r@ such that:
--
-- > addPoly (multPoly q b) r == a
quotRemPolyWith :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> (a -> a -> a) -> Poly a -> Poly a -> (Poly a, Poly a)
quotRemPolyWith _ _ _ b | polyIsZero b = error "quotRemPoly: divide by zero"
quotRemPolyWith multiplyV divideV p@(vPolyCoeffs BE -> u) q@(vPolyCoeffs BE -> v)
= go [] u (polyDegree p - polyDegree q)
where
v0 | null v = zeroV
| otherwise = head v
go q u n
| null u || n < 0 = (poly LE q, poly BE u)
| otherwise = go (q0:q) u' (n-1)
where
q0 = divideV (head u) v0
u' = tail (zipSumV u (map (multiplyV (negateV q0)) v))
quotPolyWith :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> (a -> a -> a) -> Poly a -> Poly a -> Poly a
quotPolyWith multiplyV divideV u v
| polyIsZero v = error "quotPoly: divide by zero"
| otherwise = fst (quotRemPolyWith multiplyV divideV u v)
remPolyWith :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> (a -> a -> a) -> Poly a -> Poly a -> Poly a
remPolyWith _ _ _ b | polyIsZero b = error "remPoly: divide by zero"
remPolyWith multiplyV divideV (vPolyCoeffs BE -> u) (vPolyCoeffs BE -> v)
= go u (length u - length v)
where
v0 | null v = zeroV
| otherwise = head v
go u n
| null u || n < 0 = poly BE u
| otherwise = go u' (n-1)
where
q0 = divideV (head u) v0
u' = tail (zipSumV u (map (multiplyV (negateV q0)) v))
-- |@composePoly f g@ constructs the polynomial 'h' such that:
--
-- > evalPoly h = evalPoly f . evalPoly g
--
-- This is a very expensive operation and, in general, returns a polynomial
-- that is quite a bit more expensive to evaluate than @f@ and @g@ together
-- (because it is of a much higher order than either). Unless your
-- polynomials are quite small or you are quite certain you need the
-- coefficients of the composed polynomial, it is recommended that you
-- simply evaluate @f@ and @g@ and explicitly compose the resulting
-- functions. This will usually be much more efficient.
composePolyWith :: (AdditiveGroup a, Eq a) => (a -> a -> a) -> Poly a -> Poly a -> Poly a
composePolyWith multiplyV (vPolyCoeffs LE -> cs) (vPolyCoeffs LE -> ds) = poly LE (foldr mul [] cs)
where
-- Implementation note: this is a hand-inlining of the following
-- (with the 'Num' instance in "Math.Polynomial.NumInstance"):
-- > composePoly f g = evalPoly (fmap constPoly f) g
--
-- This is a very expensive operation, something like
-- O(length cs ^ 2 * length ds) I believe. There may be some more
-- tricks to improve that, but I suspect there isn't much room for
-- improvement. The number of terms in the resulting polynomial is
-- O(length cs * length ds) already, and each one is the sum of
-- quite a few terms.
mul c acc = addScalarLE c (multPolyWithLE multiplyV acc ds)
-- |(internal) add a scalar to a list of polynomial coefficients in LE order
addScalarLE :: (AdditiveGroup a, Eq a) => a -> [a] -> [a]
addScalarLE a bs | a == zeroV = bs
addScalarLE a [] = [a]
addScalarLE a (b:bs) = (a ^+^ b) : bs
-- |Evaluate a polynomial at a point or, equivalently, convert a polynomial
-- to the function it represents. For example, @evalPoly 'x' = 'id'@ and
-- @evalPoly ('constPoly' k) = 'const' k.@
evalPoly :: (VectorSpace a, Eq a, AdditiveGroup (Scalar a), Eq (Scalar a)) => Poly a -> Scalar a -> a
evalPoly (vPolyCoeffs LE -> cs) x
| x == zeroV =
if null cs
then zeroV
else head cs
| otherwise = foldr mul zeroV cs
where
mul c acc = c ^+^ acc ^* x
-- |Evaluate a polynomial and its derivative (respectively) at a point.
evalPolyDeriv :: (VectorSpace a, Eq a) => Poly a -> Scalar a -> (a,a)
evalPolyDeriv (vPolyCoeffs LE -> cs) x = foldr mul (zeroV, zeroV) cs
where
mul c (p, dp) = ((x *^ p) ^+^ c, (x *^ dp) ^+^ p)
-- |Evaluate a polynomial and all of its nonzero derivatives at a point.
-- This is roughly equivalent to:
--
-- > evalPolyDerivs p x = map (`evalPoly` x) (takeWhile (not . polyIsZero) (iterate polyDeriv p))
evalPolyDerivs :: (VectorSpace a, Eq a, Num (Scalar a)) => Poly a -> Scalar a -> [a]
evalPolyDerivs (vPolyCoeffs LE -> cs) x = trunc . zipWith (*^) factorials $ foldr mul [] cs
where
trunc list = zipWith const list cs
factorials = scanl (*) 1 (iterate (+1) 1)
mul c pds@(p:pd) = (x *^ p ^+^ c) : map (x *^) pd `zipSumV` pds
mul c [] = [c]
-- |\"Contract\" a polynomial by attempting to divide out a root.
--
-- @contractPoly p a@ returns @(q,r)@ such that @q*(x-a) + r == p@
contractPoly :: (VectorSpace a, Eq a) => Poly a -> Scalar a -> (Poly a, a)
contractPoly p@(vPolyCoeffs LE -> cs) a = (vPolyN n LE q, r)
where
n = rawPolyLength p
cut remainder swap = (swap ^+^ (a *^ remainder), remainder)
(r,q) = mapAccumR cut zeroV cs
-- |@gcdPoly a b@ computes the highest order monic polynomial that is a
-- divisor of both @a@ and @b@. If both @a@ and @b@ are 'zero', the
-- result is undefined.
gcdPolyWith :: (AdditiveGroup a, Eq a) => a -> (a -> a -> a) -> (a -> a -> a) -> Poly a -> Poly a -> Poly a
gcdPolyWith oneV multiplyV divideV a b
| polyIsZero b = if polyIsZero a
then error "gcdPolyWith: gcdPoly zero zero is undefined"
else monicPolyWith oneV divideV a
| otherwise = gcdPolyWith oneV multiplyV divideV b (a `remPoly` b)
where remPoly = remPolyWith multiplyV divideV
-- |Normalize a polynomial so that its highest-order coefficient is 1
monicPolyWith :: (AdditiveGroup a, Eq a) => a -> (a -> a -> a) -> Poly a -> Poly a
monicPolyWith oneV divideV p = case vPolyCoeffs BE p of
[] -> vPolyN n BE []
(c:cs) -> vPolyN n BE (oneV : map (`divideV` c) cs)
where n = rawPolyLength p
-- |Compute the derivative of a polynomial.
polyDeriv :: (VectorSpace a, Eq a, Num (Scalar a)) => Poly a -> Poly a
polyDeriv p@(vPolyCoeffs LE -> cs) = vPolyN (rawPolyDegree p) LE
[ n *^ c
| c <- drop 1 cs
| n <- iterate (1+) 1
]
-- |Compute all nonzero derivatives of a polynomial, starting with its
-- \"zero'th derivative\", the original polynomial itself.
polyDerivs :: (VectorSpace a, Eq a, Num (Scalar a)) => Poly a -> [Poly a]
polyDerivs p = take (1 + polyDegree p) (iterate polyDeriv p)
-- |Compute the definite integral (from 0 to x) of a polynomial.
polyIntegral :: (VectorSpace a, Eq a, Fractional (Scalar a)) => Poly a -> Poly a
polyIntegral p@(vPolyCoeffs LE -> cs) = vPolyN (1 + rawPolyLength p) LE $ zeroV :
[ c ^/ n
| c <- cs
| n <- iterate (1+) 1
]