{-# LANGUAGE NoImplicitPrelude #-}
module MathObj.PartialFraction where
import qualified Algebra.PrincipalIdealDomain as PID
import qualified Algebra.IntegralDomain as Integral
import qualified Number.Ratio as Ratio
import qualified Algebra.Ring as Ring
import qualified Algebra.Additive as Additive
import qualified Algebra.ZeroTestable as ZeroTestable
import qualified Algebra.Indexable as Indexable
import Number.Ratio((%))
import Algebra.IntegralDomain(divMod, divModZero, decomposeVarPositionalInf)
import Algebra.Units(stdAssociate, stdUnitInv)
import Algebra.Field((/))
import Algebra.Ring((*), one, product)
import Algebra.Additive((+), zero, negate)
import Algebra.ZeroTestable (isZero)
import qualified Data.List as List
import Data.Map(Map)
import qualified Data.Map as Map
import Data.Maybe(fromMaybe, )
import qualified Data.List.Match as Match
import Data.List.HT (dropWhileRev, )
import Data.List (group, sortBy, mapAccumR, )
import NumericPrelude.Base hiding (zipWith)
import NumericPrelude.Numeric(Int, fromInteger)
data T a =
Cons a (Map (Indexable.ToOrd a) [a])
deriving (Eq)
fromFractionSum :: (Indexable.C a) => a -> [(a,[a])] -> T a
fromFractionSum z m =
Cons z (indexMapFromList m)
toFractionSum :: (Indexable.C a) => T a -> (a, [(a,[a])])
toFractionSum (Cons z m) =
(z, indexMapToList m)
appPrec :: Int
appPrec = 10
instance (Show a) => Show (T a) where
showsPrec p (Cons z m) =
showParen (p >= appPrec)
(showString "PartialFraction.fromFractionSum " .
showsPrec (succ appPrec) z . showString " " .
shows (indexMapToList m))
toFraction :: PID.C a => T a -> Ratio.T a
toFraction (Cons z m) =
let fracs = map (uncurry multiToFraction) (indexMapToList m)
in foldl (+) (Ratio.fromValue z) fracs
toFactoredFraction :: (PID.C a) => T a -> ([a], a)
toFactoredFraction x@(Cons _ m) =
let r = toFraction x
denoms = concat $ Map.elems $ indexMapMapWithKey (flip Match.replicate) m
numer = foldl (flip Ratio.scale) r denoms
in (denoms, Ratio.numerator numer)
multiToFraction :: PID.C a => a -> [a] -> Ratio.T a
multiToFraction denom =
foldr (\numer acc ->
(Ratio.fromValue numer + acc) / Ratio.fromValue denom) zero
hornerRev :: Ring.C a => a -> [a] -> a
hornerRev x = foldl (\val c -> val*x+c) zero
fromFactoredFraction :: (PID.C a, Indexable.C a) => [a] -> a -> T a
fromFactoredFraction denoms0 numer0 =
let denoms = group $ sortBy Indexable.compare $ map stdAssociate denoms0
numer = foldl (*) numer0 $ map stdUnitInv denoms0
denomPowers = map product denoms
partProdLeft = scanl (*) one denomPowers
(prod:partProdRight) = scanr (*) one denomPowers
(intPart,numerRed) = divMod numer prod
facs = List.zipWith (*) partProdLeft partProdRight
numers =
fromMaybe
(error $ "PartialFraction.fromFactoredFraction: " ++
"denominators must be relatively prime")
(PID.diophantineMulti numerRed facs)
pairs = List.zipWith multiFromFraction denoms numers
in removeZeros $ reduceHeads $ Cons intPart (indexMapFromList pairs)
fromFactoredFractionAlt :: (PID.C a, Indexable.C a) => [a] -> a -> T a
fromFactoredFractionAlt denoms numer =
foldl (\p d -> scaleFrac (one%d) p) (fromValue numer) denoms
multiFromFraction :: PID.C a => [a] -> a -> (a,[a])
multiFromFraction (d:ds) n =
(d, reverse $ decomposeVarPositionalInf ds n)
multiFromFraction [] _ =
error "PartialFraction.multiFromFraction: there must be one denominator"
fromValue :: a -> T a
fromValue x = Cons x Map.empty
reduceHeads :: Integral.C a => T a -> T a
reduceHeads (Cons z m0) =
let m1 = indexMapMapWithKey (\x (y:ys) -> let (q,r) = divMod y x in (q,r:ys)) m0
in Cons
(foldl (+) z (map fst $ Map.elems m1))
(fmap snd m1)
carryRipple :: Integral.C a => a -> [a] -> (a,[a])
carryRipple b =
mapAccumR (\carry y -> divMod (y+carry) b) zero
normalizeModulo :: Integral.C a => T a -> T a
normalizeModulo (Cons z0 m0) =
let m1 = indexMapMapWithKey carryRipple m0
ints = Map.elems $ fmap fst m1
in Cons (foldl (+) z0 ints) (fmap snd m1)
removeZeros :: (Indexable.C a, ZeroTestable.C a) => T a -> T a
removeZeros (Cons z m) =
Cons z $
Map.filter (not . null) $
Map.map (dropWhileRev isZero) m
zipWith :: (Indexable.C a) => (a -> a -> a) -> ([a] -> [a] -> [a]) ->
(T a -> T a -> T a)
zipWith opS opV (Cons za ma) (Cons zb mb) =
Cons (opS za zb) (Map.unionWith opV ma mb)
instance (Indexable.C a, Integral.C a, ZeroTestable.C a) => Additive.C (T a) where
a + b = removeZeros $ normalizeModulo $ zipWith (+) (+) a b
negate (Cons z m) = Cons (negate z) (fmap negate m)
zero = fromValue zero
mulFrac :: (PID.C a) => Ratio.T a -> Ratio.T a -> (a, a)
mulFrac x y =
let dx = Ratio.denominator x
dy = Ratio.denominator y
in fromMaybe
(error "PartialFraction.mulFrac: denominators must be relatively prime")
(PID.diophantine (Ratio.numerator x * Ratio.numerator y) dy dx)
mulFrac' :: (PID.C a) => Ratio.T a -> Ratio.T a -> (Ratio.T a, Ratio.T a)
mulFrac' x y =
let (na,nb) = mulFrac x y
in (na % Ratio.denominator x, nb % Ratio.denominator y)
mulFracStupid :: (PID.C a) =>
Ratio.T a -> Ratio.T a -> ((Ratio.T a, Ratio.T a), Ratio.T a)
mulFracStupid x y =
let dx = Ratio.denominator x
dy = Ratio.denominator y
[a,b,c] =
fromMaybe
(error "PartialFraction.mulFracOverlap: (gcd 1 x) must always be a unit")
(PID.diophantineMulti
(Ratio.numerator x * Ratio.numerator y) [dy, dx, one])
in ((a % dx, b % dy), c%(dx*dy))
mulFracOverlap :: (PID.C a) =>
Ratio.T a -> Ratio.T a -> ((Ratio.T a, Ratio.T a), Ratio.T a)
mulFracOverlap x y =
let dx = Ratio.denominator x
dy = Ratio.denominator y
nx = Ratio.numerator x
ny = Ratio.numerator y
(g,(a,b)) = PID.extendedGCD dy dx
(q,r) = divModZero (nx*ny) g
in (((q*a)%dx, (q*b)%dy), r%(dx*dy))
scaleFrac :: (PID.C a, Indexable.C a) => Ratio.T a -> T a -> T a
scaleFrac s (Cons z0 m) =
let ns = Ratio.numerator s
ds = Ratio.denominator s
dsOrd = Indexable.toOrd ds
(z,zr) = divMod (z0*ns) ds
scaleFracs =
(\(scs,fracs) ->
Map.insert dsOrd [foldl (+) zr scs] $
indexMapFromList $
map (uncurry multiFromFraction) fracs) .
unzip .
map (\(dis,r) ->
let (sc,rc) = mulFrac s r
in (sc, (dis, rc))) .
Map.elems .
indexMapMapWithKey
(\d l -> (Match.replicate l d, multiToFraction d l))
in removeZeros $ reduceHeads $ Cons z
(mapApplySplit dsOrd (+)
(uncurry (:) . carryRipple ds . map (ns*))
scaleFracs m)
scaleInt :: (PID.C a, Indexable.C a) => a -> T a -> T a
scaleInt x (Cons z m) =
removeZeros $ normalizeModulo $
Cons (x*z) (Map.map (map (x*)) m)
mul :: (PID.C a, Indexable.C a) => T a -> T a -> T a
mul (Cons z m) a =
foldl
(+) (scaleInt z a)
(map (\(d,l) ->
foldr (\numer acc ->
scaleFrac (one%d) (scaleInt numer a + acc)) zero l)
(indexMapToList m))
mulFast :: (PID.C a, Indexable.C a) => T a -> T a -> T a
mulFast pa pb =
let ra = toFactoredFraction pa
rb = toFactoredFraction pb
in fromFactoredFraction (fst ra ++ fst rb) (snd ra * snd rb)
instance (PID.C a, Indexable.C a) => Ring.C (T a) where
one = fromValue one
(*) = mulFast
indexMapMapWithKey :: (a -> b -> c)
-> Map (Indexable.ToOrd a) b
-> Map (Indexable.ToOrd a) c
indexMapMapWithKey f = Map.mapWithKey (f . Indexable.fromOrd)
indexMapToList :: Map (Indexable.ToOrd a) b -> [(a, b)]
indexMapToList = map (\(k,e) -> (Indexable.fromOrd k, e)) . Map.toList
indexMapFromList :: Indexable.C a => [(a, b)] -> Map (Indexable.ToOrd a) b
indexMapFromList = Map.fromList . map (\(k,e) -> (Indexable.toOrd k, e))
mapApplySplit :: Ord a =>
a -> (c -> c -> c) ->
(b -> c) -> (Map a b -> Map a c) -> Map a b -> Map a c
mapApplySplit key addOp f g m =
maybe
(g m)
(\x -> Map.insertWith addOp key (f x) $ g (Map.delete key m))
(Map.lookup key m)