combinat-0.2.9.0: Math/Combinat/Numbers/Series.hs
-- | Some basic univariate power series expansions.
-- This module is not re-exported by "Math.Combinat".
--
-- Note: the \"@convolveWithXXX@\" functions are much faster than the equivalent
-- @(XXX \`convolve\`)@!
--
-- TODO: better names for these functions.
--
{-# LANGUAGE CPP, BangPatterns, GeneralizedNewtypeDeriving #-}
module Math.Combinat.Numbers.Series where
--------------------------------------------------------------------------------
import Data.List
import Math.Combinat.Sign
import Math.Combinat.Numbers
import Math.Combinat.Partitions.Integer
import Math.Combinat.Helper
--------------------------------------------------------------------------------
-- * Trivial series
-- | The series [1,0,0,0,0,...], which is the neutral element for the convolution.
{-# SPECIALIZE unitSeries :: [Integer] #-}
unitSeries :: Num a => [a]
unitSeries = 1 : repeat 0
-- | Constant zero series
zeroSeries :: Num a => [a]
zeroSeries = repeat 0
-- | Power series representing a constant function
constSeries :: Num a => a -> [a]
constSeries x = x : repeat 0
-- | The power series representation of the identity function @x@
idSeries :: Num a => [a]
idSeries = 0 : 1 : repeat 0
-- | The power series representation of @x^n@
powerTerm :: Num a => Int -> [a]
powerTerm n = replicate n 0 ++ (1 : repeat 0)
--------------------------------------------------------------------------------
-- * Basic operations on power series
addSeries :: Num a => [a] -> [a] -> [a]
addSeries xs ys = longZipWith 0 0 (+) xs ys
sumSeries :: Num a => [[a]] -> [a]
sumSeries [] = [0]
sumSeries xs = foldl1' addSeries xs
subSeries :: Num a => [a] -> [a] -> [a]
subSeries xs ys = longZipWith 0 0 (-) xs ys
negateSeries :: Num a => [a] -> [a]
negateSeries = map negate
scaleSeries :: Num a => a -> [a] -> [a]
scaleSeries s = map (*s)
-- | A different implementation, taken from:
--
-- M. Douglas McIlroy: Power Series, Power Serious
mulSeries :: Num a => [a] -> [a] -> [a]
mulSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where
go (f:fs) ggs@(g:gs) = f*g : (scaleSeries f gs) `addSeries` go fs ggs
-- | Multiplication of power series. This implementation is a synonym for 'convolve'
mulSeriesNaive :: Num a => [a] -> [a] -> [a]
mulSeriesNaive = convolve
productOfSeries :: Num a => [[a]] -> [a]
productOfSeries = convolveMany
--------------------------------------------------------------------------------
-- * Convolution (product)
-- | Convolution of series (that is, multiplication of power series).
-- The result is always an infinite list. Warning: This is slow!
convolve :: Num a => [a] -> [a] -> [a]
convolve xs1 ys1 = res where
res = [ foldl' (+) 0 (zipWith (*) xs (reverse (take n ys)))
| n<-[1..]
]
xs = xs1 ++ repeat 0
ys = ys1 ++ repeat 0
-- | Convolution (= product) of many series. Still slow!
convolveMany :: Num a => [[a]] -> [a]
convolveMany [] = 1 : repeat 0
convolveMany xss = foldl1 convolve xss
--------------------------------------------------------------------------------
-- * Reciprocals of general power series
-- | Division of series.
--
-- Taken from: M. Douglas McIlroy: Power Series, Power Serious
divSeries :: (Eq a, Fractional a) => [a] -> [a] -> [a]
divSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where
go (0:fs) (0:gs) = go fs gs
go (f:fs) ggs@(g:gs) = let q = f/g in q : go (fs `subSeries` scaleSeries q gs) ggs
-- | Given a power series, we iteratively compute its multiplicative inverse
reciprocalSeries :: (Eq a, Fractional a) => [a] -> [a]
reciprocalSeries series = case series of
[] -> error "reciprocalSeries: empty input series (const 0 function does not have an inverse)"
(a:as) -> case a of
0 -> error "reciprocalSeries: input series has constant term 0"
_ -> map (/a) $ integralReciprocalSeries $ map (/a) series
-- | Given a power series starting with @1@, we can compute its multiplicative inverse
-- without divisions.
--
{-# SPECIALIZE integralReciprocalSeries :: [Int] -> [Int] #-}
{-# SPECIALIZE integralReciprocalSeries :: [Integer] -> [Integer] #-}
integralReciprocalSeries :: (Eq a, Num a) => [a] -> [a]
integralReciprocalSeries series = case series of
[] -> error "integralReciprocalSeries: empty input series (const 0 function does not have an inverse)"
(a:as) -> case a of
1 -> 1 : worker [1]
_ -> error "integralReciprocalSeries: input series must start with 1"
where
worker bs = let b' = - sum (zipWith (*) (tail series) bs)
in b' : worker (b':bs)
--------------------------------------------------------------------------------
-- * Composition of formal power series
-- | @g \`composeSeries\` f@ is the power series expansion of @g(f(x))@.
-- This is a synonym for @flip substitute@.
--
-- This implementation is taken from
--
-- M. Douglas McIlroy: Power Series, Power Serious
composeSeries :: (Eq a, Num a) => [a] -> [a] -> [a]
composeSeries xs ys = go (xs ++ repeat 0) (ys ++ repeat 0) where
go (f:fs) (0:gs) = f : mulSeries gs (go fs (0:gs))
go (f:fs) (_:gs) = error "PowerSeries/composeSeries: we expect the the constant term of the inner series to be zero"
-- | @substitute f g@ is the power series corresponding to @g(f(x))@.
-- Equivalently, this is the composition of univariate functions (in the \"wrong\" order).
--
-- Note: for this to be meaningful in general (not depending on convergence properties),
-- we need that the constant term of @f@ is zero.
substitute :: (Eq a, Num a) => [a] -> [a] -> [a]
substitute f g = composeSeries g f
-- | Naive implementation of 'composeSeries' (via 'substituteNaive')
composeSeriesNaive :: (Eq a, Num a) => [a] -> [a] -> [a]
composeSeriesNaive g f = substituteNaive f g
-- | Naive implementation of 'substitute'
substituteNaive :: (Eq a, Num a) => [a] -> [a] -> [a]
substituteNaive as_ bs_ =
case head as of
0 -> [ f n | n<-[0..] ]
_ -> error "PowerSeries/substituteNaive: we expect the the constant term of the inner series to be zero"
where
as = as_ ++ repeat 0
bs = bs_ ++ repeat 0
a i = as !! i
b j = bs !! j
f n = sum
[ b m * product [ (a i)^j | (i,j)<-es ] * fromInteger (multinomial (map snd es))
| p <- partitions n
, let es = toExponentialForm p
, let m = partitionWidth p
]
--------------------------------------------------------------------------------
-- * Lagrange inversions
-- | We expect the input series to match @(0:a1:_)@. with a1 nonzero The following is true for the result (at least with exact arithmetic):
--
-- > substitute f (lagrangeInversion f) == (0 : 1 : repeat 0)
-- > substitute (lagrangeInversion f) f == (0 : 1 : repeat 0)
--
-- This implementation is taken from:
--
-- M. Douglas McIlroy: Power Series, Power Serious
lagrangeInversion :: (Eq a, Fractional a) => [a] -> [a]
lagrangeInversion xs = go (xs ++ repeat 0) where
go (0:fs) = rs where rs = 0 : divSeries unitSeries (composeSeries fs rs)
go (_:fs) = error "lagrangeInversion: the series should start with (0 + a1*x + a2*x^2 + ...) where a1 is non-zero"
-- | Coefficients of the Lagrange inversion
lagrangeCoeff :: Partition -> Integer
lagrangeCoeff p = div numer denom where
numer = (-1)^m * product (map fromIntegral [n+1..n+m])
denom = fromIntegral (n+1) * product (map (factorial . snd) es)
m = partitionWidth p
n = partitionWeight p
es = toExponentialForm p
-- | We expect the input series to match @(0:1:_)@. The following is true for the result (at least with exact arithmetic):
--
-- > substitute f (integralLagrangeInversion f) == (0 : 1 : repeat 0)
-- > substitute (integralLagrangeInversion f) f == (0 : 1 : repeat 0)
--
integralLagrangeInversionNaive :: (Eq a, Num a) => [a] -> [a]
integralLagrangeInversionNaive series_ =
case series of
(0:1:rest) -> 0 : 1 : [ f n | n<-[1..] ]
_ -> error "integralLagrangeInversionNaive: the series should start with (0 + x + a2*x^2 + ...)"
where
series = series_ ++ repeat 0
as = tail series
a i = as !! i
f n = sum [ fromInteger (lagrangeCoeff p) * product [ (a i)^j | (i,j) <- toExponentialForm p ]
| p <- partitions n
]
-- | Naive implementation of 'lagrangeInversion'
lagrangeInversionNaive :: (Eq a, Fractional a) => [a] -> [a]
lagrangeInversionNaive series_ =
case series of
(0:a1:rest) -> if a1 ==0
then err
else 0 : (1/a1) : [ f n / a1^(n+1) | n<-[1..] ]
_ -> err
where
err = error "lagrangeInversionNaive: the series should start with (0 + a1*x + a2*x^2 + ...) where a1 is non-zero"
series = series_ ++ repeat 0
a1 = series !! 1
as = map (/a1) (tail series)
a i = as !! i
f n = sum [ fromInteger (lagrangeCoeff p) * product [ (a i)^j | (i,j) <- toExponentialForm p ]
| p <- partitions n
]
--------------------------------------------------------------------------------
-- * Differentiation and integration
differentiateSeries :: Num a => [a] -> [a]
differentiateSeries (y:ys) = go (1::Int) ys where
go !n (x:xs) = fromIntegral n * x : go (n+1) xs
go _ [] = []
integrateSeries :: Fractional a => [a] -> [a]
integrateSeries ys = 0 : go (1::Int) ys where
go !n (x:xs) = x / (fromIntegral n) : go (n+1) xs
go _ [] = []
--------------------------------------------------------------------------------
-- * Power series expansions of elementary functions
-- | Power series expansion of @exp(x)@
expSeries :: Fractional a => [a]
expSeries = go 0 1 where
go i e = e : go (i+1) (e / (i+1))
-- | Power series expansion of @cos(x)@
cosSeries :: Fractional a => [a]
cosSeries = go 0 1 where
go i e = e : 0 : go (i+2) (-e / ((i+1)*(i+2)))
-- | Power series expansion of @sin(x)@
sinSeries :: Fractional a => [a]
sinSeries = go 1 1 where
go i e = 0 : e : go (i+2) (-e / ((i+1)*(i+2)))
-- | Alternative implementation using differential equations.
--
-- Taken from: M. Douglas McIlroy: Power Series, Power Serious
cosSeries2, sinSeries2 :: Fractional a => [a]
cosSeries2 = unitSeries `subSeries` integrateSeries sinSeries2
sinSeries2 = integrateSeries cosSeries2
-- | Power series expansion of @cosh(x)@
coshSeries :: Fractional a => [a]
coshSeries = go 0 1 where
go i e = e : 0 : go (i+2) (e / ((i+1)*(i+2)))
-- | Power series expansion of @sinh(x)@
sinhSeries :: Fractional a => [a]
sinhSeries = go 1 1 where
go i e = 0 : e : go (i+2) (e / ((i+1)*(i+2)))
-- | Power series expansion of @log(1+x)@
log1Series :: Fractional a => [a]
log1Series = 0 : go 1 1 where
go i e = (e/i) : go (i+1) (-e)
-- | Power series expansion of @(1-Sqrt[1-4x])/(2x)@ (the coefficients are the Catalan numbers)
dyckSeries :: Num a => [a]
dyckSeries = [ fromInteger (catalan i) | i<-[(0::Int)..] ]
--------------------------------------------------------------------------------
-- * \"Coin\" series
-- | Power series expansion of
--
-- > 1 / ( (1-x^k_1) * (1-x^k_2) * ... * (1-x^k_n) )
--
-- Example:
--
-- @(coinSeries [2,3,5])!!k@ is the number of ways
-- to pay @k@ dollars with coins of two, three and five dollars.
--
-- TODO: better name?
coinSeries :: [Int] -> [Integer]
coinSeries [] = 1 : repeat 0
coinSeries (k:ks) = xs where
xs = zipWith (+) (coinSeries ks) (replicate k 0 ++ xs)
-- | Generalization of the above to include coefficients: expansion of
--
-- > 1 / ( (1-a_1*x^k_1) * (1-a_2*x^k_2) * ... * (1-a_n*x^k_n) )
--
coinSeries' :: Num a => [(a,Int)] -> [a]
coinSeries' [] = 1 : repeat 0
coinSeries' ((a,k):aks) = xs where
xs = zipWith (+) (coinSeries' aks) (replicate k 0 ++ map (*a) xs)
convolveWithCoinSeries :: [Int] -> [Integer] -> [Integer]
convolveWithCoinSeries ks series1 = worker ks where
series = series1 ++ repeat 0
worker [] = series
worker (k:ks) = xs where
xs = zipWith (+) (worker ks) (replicate k 0 ++ xs)
convolveWithCoinSeries' :: Num a => [(a,Int)] -> [a] -> [a]
convolveWithCoinSeries' ks series1 = worker ks where
series = series1 ++ repeat 0
worker [] = series
worker ((a,k):aks) = xs where
xs = zipWith (+) (worker aks) (replicate k 0 ++ map (*a) xs)
--------------------------------------------------------------------------------
-- * Reciprocals of products of polynomials
-- | Convolution of many 'pseries', that is, the expansion of the reciprocal
-- of a product of polynomials
productPSeries :: [[Int]] -> [Integer]
productPSeries = foldl (flip convolveWithPSeries) unitSeries
-- | The same, with coefficients.
productPSeries' :: Num a => [[(a,Int)]] -> [a]
productPSeries' = foldl (flip convolveWithPSeries') unitSeries
convolveWithProductPSeries :: [[Int]] -> [Integer] -> [Integer]
convolveWithProductPSeries kss ser = foldl (flip convolveWithPSeries) ser kss
-- | This is the most general function in this module; all the others
-- are special cases of this one.
convolveWithProductPSeries' :: Num a => [[(a,Int)]] -> [a] -> [a]
convolveWithProductPSeries' akss ser = foldl (flip convolveWithPSeries') ser akss
--------------------------------------------------------------------------------
-- * Reciprocals of polynomials
-- Reciprocals of polynomials, without coefficients
-- | The power series expansion of
--
-- > 1 / (1 - x^k_1 - x^k_2 - x^k_3 - ... - x^k_n)
--
pseries :: [Int] -> [Integer]
pseries ks = convolveWithPSeries ks unitSeries
-- | Convolve with (the expansion of)
--
-- > 1 / (1 - x^k_1 - x^k_2 - x^k_3 - ... - x^k_n)
--
convolveWithPSeries :: [Int] -> [Integer] -> [Integer]
convolveWithPSeries ks series1 = ys where
series = series1 ++ repeat 0
ys = worker ks ys
worker [] _ = series
worker (k:ks) ys = xs where
xs = zipWith (+) (replicate k 0 ++ ys) (worker ks ys)
--------------------------------------------------------------------------------
-- Reciprocals of polynomials, with coefficients
-- | The expansion of
--
-- > 1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3 - ... - a_n*x^k_n)
--
pseries' :: Num a => [(a,Int)] -> [a]
pseries' aks = convolveWithPSeries' aks unitSeries
-- | Convolve with (the expansion of)
--
-- > 1 / (1 - a_1*x^k_1 - a_2*x^k_2 - a_3*x^k_3 - ... - a_n*x^k_n)
--
convolveWithPSeries' :: Num a => [(a,Int)] -> [a] -> [a]
convolveWithPSeries' aks series1 = ys where
series = series1 ++ repeat 0
ys = worker aks ys
worker [] _ = series
worker ((a,k):aks) ys = xs where
xs = zipWith (+) (replicate k 0 ++ map (*a) ys) (worker aks ys)
{-
data Sign = Plus | Minus deriving (Eq,Show)
signValue :: Num a => Sign -> a
signValue Plus = 1
signValue Minus = -1
-}
signedPSeries :: [(Sign,Int)] -> [Integer]
signedPSeries aks = convolveWithSignedPSeries aks unitSeries
-- | Convolve with (the expansion of)
--
-- > 1 / (1 +- x^k_1 +- x^k_2 +- x^k_3 +- ... +- x^k_n)
--
-- Should be faster than using `convolveWithPSeries'`.
-- Note: 'Plus' corresponds to the coefficient @-1@ in `pseries'` (since
-- there is a minus sign in the definition there)!
convolveWithSignedPSeries :: [(Sign,Int)] -> [Integer] -> [Integer]
convolveWithSignedPSeries aks series1 = ys where
series = series1 ++ repeat 0
ys = worker aks ys
worker [] _ = series
worker ((a,k):aks) ys = xs where
xs = case a of
Minus -> zipWith (+) one two
Plus -> zipWith (-) one two
one = worker aks ys
two = replicate k 0 ++ ys
--------------------------------------------------------------------------------