packages feed

probability 0.2.2.1 → 0.2.3

raw patch · 14 files changed

+490/−44 lines, 14 filesdep +special-functorsdep +utility-ht

Dependencies added: special-functors, utility-ht

Files

ToDo view
@@ -1,6 +1,3 @@-the library could benefit from Monad transformers like WriterT and Product, see-   http://www.randomhacks.net/articles/2007/02/21/refactoring-probability-distributions- Examples:    Election and prognoses 
probability.cabal view
@@ -1,5 +1,5 @@ Name:               probability-Version:            0.2.2.1+Version:            0.2.3 License:            BSD3 License-File:       COPYRIGHT Author:             Martin Erwig <erwig@eecs.oregonstate.edu>, Steve Kollmansberger@@ -13,7 +13,8 @@    The monad is similar to the List monad for non-deterministic computations,    but extends the List monad by a measure of probability.    Small interface to R plotting.-Tested-With:    GHC==6.4.1, GHC==6.8.2+Tested-With:    GHC==6.4.1, GHC==6.8.2, GHC==6.12.3+Tested-With:    GHC==7.0.2, GHC==7.2.2 Cabal-Version:  >=1.6 Build-Type:     Simple Data-Files:@@ -27,14 +28,16 @@ Source-Repository this   type:     darcs   location: http://code.haskell.org/~thielema/probability/-  tag:      0.2.2.1+  tag:      0.2.3   Flag splitBase   description: Choose the new smaller, split-up base package.  Library-  Build-Depends: transformers >=0.0.1 && <0.3+  Build-Depends:+    utility-ht >=0.0.6 && <0.1,+    transformers >=0.0.1 && <0.3   If flag(splitBase)     Build-Depends:       containers >=0.1 && <1,@@ -42,6 +45,7 @@       base >=2 && <5   Else     Build-Depends:+      special-functors >=1.0 && <1.1,       base >=1.0 && <2    Hs-Source-Dirs:     src@@ -65,6 +69,7 @@     Numeric.Probability.Example.Diagnosis     Numeric.Probability.Example.Dice     Numeric.Probability.Example.DiceAccum+    Numeric.Probability.Example.Kruskal     Numeric.Probability.Example.MontyHall     Numeric.Probability.Example.NBoys     Numeric.Probability.Example.Predator@@ -75,3 +80,4 @@     Numeric.Probability.Monad     Numeric.Probability.PrintList     Numeric.Probability.Show+    Numeric.Probability.Either
src/Numeric/Probability/Distribution.hs view
@@ -5,10 +5,15 @@ import Numeric.Probability.Show (showR) import qualified Numeric.Probability.Shape as Shape +import Control.Applicative (Applicative(..)) import Control.Monad (liftM, liftM2, join, ) +import qualified Data.List.HT as ListHT import qualified Data.Map  as Map import qualified Data.List as List+import qualified Data.Maybe as Maybe+import Data.Ord.HT (comparing, )+import Data.Eq.HT (equating, )  import Prelude hiding (map, filter) @@ -44,10 +49,14 @@ If you need efficiency, you should remove redundant elements by 'norm'. 'norm' converts to 'Data.Map' and back internally and you can hope that the compiler fuses the lists with the intermediate Map structure.++The defined monad is equivalent to+@WriterT (Product prob) [] a@.+See <http://www.randomhacks.net/articles/2007/02/21/refactoring-probability-distributions>. -} newtype T prob a = Cons {decons :: [(a,prob)]} -certainly :: Num prob =>  a -> T prob a+certainly :: Num prob => a -> T prob a certainly x = Cons [(x,1)]  instance Num prob => Monad (T prob) where@@ -55,6 +64,10 @@   d >>= f  = Cons [(y,q*p) | (x,p) <- decons d, (y,q) <- decons (f x)]   fail _   = Cons [] +instance Num prob => Applicative (T prob) where+  pure     = certainly+  fm <*> m = Cons [(f x,q*p) | (f,p) <- decons fm, (x,q) <- decons m]+ {- Dist cannot be an instance of MonadPlus, because there is no mzero@@ -115,24 +128,24 @@ size :: T prob a -> Int size = length . decons -check :: RealFloat prob => T prob a -> T prob a+check :: (RealFloat prob, Show prob) => T prob a -> T prob a check (Cons d) =    if abs (1-sumP d) < errorMargin      then Cons d      else error ("Illegal distribution: total probability = "++show (sumP d))  -- | can fail because of rounding errors, better use 'fromFreqs'-cons :: RealFloat prob => [(a,prob)] -> T prob a+cons :: (RealFloat prob, Show prob) => [(a,prob)] -> T prob a cons = check . Cons  sumP :: Num prob => [(a,prob)] -> prob sumP = sum . List.map snd  sortP :: Ord prob => [(a,prob)] -> [(a,prob)]-sortP = List.sortBy (\x y -> compare (snd y) (snd x))+sortP = List.sortBy (comparing snd)  sortElem :: Ord a => [(a,prob)] -> [(a,prob)]-sortElem = List.sortBy (\x y -> compare (fst y) (fst x))+sortElem = List.sortBy (comparing fst)   -- ** Normalization = grouping@@ -145,8 +158,11 @@  norm'' :: (Num prob, Ord a) => [(a,prob)] -> [(a,prob)] norm'' =-   List.map (\ xs@((x,_):_) -> (x, sum (List.map snd xs))) .-   List.groupBy (\x y -> fst x == fst y) . sortElem+   List.map (\xs ->+      case xs of+         ((x,_):_) -> (x, sum (List.map snd xs))+         _ -> error "Probability.Distribution.norm'': every sub-list in groupBy must be non-empty") .+   ListHT.groupBy (equating fst) . sortElem   -- | pretty printing@@ -164,16 +180,48 @@ (//%) :: (Ord a, Show a) => T Rational a -> () -> IO () (//%) x () = putStr (pretty show x) -instance (Num prob, Ord prob, Ord a, Show a) =>+instance (Num prob, Ord prob, Show prob, Ord a, Show a) =>       Show (T prob a) where    showsPrec p (Cons xs) =       showParen (p>10)          (showString "fromFreqs " . showsPrec 11 (sortP (norm' xs))) ++{- |+We would like to have an equality test of type++> (==) :: T prob a -> T prob a -> T prob Bool++that is consistent with the 'Num' instance.+We would certainly define++> x==y = norm (liftM2 (==) x y)   .++However the 'Eq' class enforces the type++> T prob a -> T prob a -> Bool    .++We could implement this as check for equal distributions.+This would be inconsistent with the 'Num' instance+because it compares entire distributions,+not only individual outcomes.+Thus we provide this function as 'equal'.++I would prefer to omit the 'Eq' instance completely,+but unfortunately the 'Num' instance requires 'Eq' as superclass.+-} instance Eq (T prob a) where-   (==) = error "Probability.Dist.== cannot be implemented sensibly. It only exists for Num instance. Haskell98's numeric type class hierarchy sucks."+   (==) = error "Probability.Distribution.== cannot be implemented sensibly."  {-+instance (Num prob, Ord a) => Eq (T prob a) where+   (==) = equal+-}++equal :: (Num prob, Eq prob, Ord a) => T prob a -> T prob a -> Bool+equal = equating (decons . norm)++{- The Num operations consider their operands as independent distributions (like all operations on distributions do). All functions normalize their results if normalization is lost by the plain operation.@@ -271,8 +319,10 @@ cond b d d'  =  b >>= \c -> if c then d else d'  truth :: (Num prob) => T prob Bool -> prob-truth (Cons ((b,p):_:[])) = if b then p else 1-p-truth (Cons _) = error "Probability.truth: corrupt boolean random variable"+truth dist =+   case decons (norm dist) of+      (b,p):_ -> if b then p else 1-p+      [] -> error "Probability.truth: corrupt boolean random variable"   infixl 1 >>=?@@ -310,6 +360,11 @@ filter :: (Fractional prob) =>    (a -> Bool) -> T prob a -> T prob a filter p = fromFreqs . List.filter (p . fst) . decons++mapMaybe :: (Fractional prob) =>+   (a -> Maybe b) -> T prob a -> T prob b+mapMaybe f =+   fromFreqs . Maybe.mapMaybe (\(x,p) -> fmap (flip (,) p) $ f x) . decons   -- | selecting from distributions
+ src/Numeric/Probability/Either.hs view
@@ -0,0 +1,37 @@+module Numeric.Probability.Either where++import Control.Monad.Instances ()++import Control.Applicative (Applicative, pure, (<*>), liftA2, )+++newtype EitherT a m b = EitherT (m (Either a b))++instance Functor m => Functor (EitherT a m) where+   fmap f (EitherT m) = EitherT $ fmap (fmap f) m++instance Applicative m => Applicative (EitherT a m) where+   pure a = EitherT $ pure $ Right a+   EitherT af <*> EitherT am =+      EitherT $+      liftA2 (\ef em ->+         case ef of+            Left b -> Left b+            Right f ->+               case em of+                  Left b -> Left b+                  Right m -> Right $ f m) af am++instance Monad m => Monad (EitherT a m) where+   return a = EitherT $ return $ Right a+   EitherT m >>= f  =  EitherT $ do+      e <- m+      case e of+         Left b -> return $ Left b+         Right a ->+            case f a of+               EitherT n -> n+   fail s = EitherT $ fail s++throw :: Applicative m => a -> EitherT a m b+throw a = EitherT $ pure $ Left a
src/Numeric/Probability/Example/Collection.hs view
@@ -11,7 +11,7 @@ import Control.Monad.Trans.State (StateT(StateT, runStateT), evalStateT, ) import Control.Monad (liftM2, replicateM, ) -import qualified Data.List as List+import qualified Data.List.HT as ListHT import System.Random (Random)  @@ -21,16 +21,10 @@ type Probability = Rational  -{- |-see also the proposal- <http://www.haskell.org/pipermail/libraries/2008-February/009270.html>--} selectOne :: (Fractional prob) =>    StateT (Collection a) (Dist.T prob) a selectOne =-   StateT $ \c ->-      Dist.uniform $ init $-      zipWith (\xs (y:ys) -> (y, xs++ys)) (List.inits c) (List.tails c)+   StateT $ Dist.uniform . ListHT.removeEach  select1 :: (Fractional prob) => Collection a -> Dist.T prob a select1 = evalStateT selectOne
src/Numeric/Probability/Example/DiceAccum.hs view
@@ -8,7 +8,6 @@ -} module Numeric.Probability.Example.DiceAccum where -import qualified Numeric.Probability.Example.Dice as Dice import qualified Numeric.Probability.Random as Rnd import qualified Numeric.Probability.Distribution as Dist import qualified Numeric.Probability.Transition as Trans
+ src/Numeric/Probability/Example/Kruskal.hs view
@@ -0,0 +1,302 @@+{- |+Given a row of n (~50) dice and+two players starting with a random dice within the first m (~5) dice.+Every players moves along the row, according the pips on the dice.+They stop if a move would exceed the row.+What is the probability that they stop at the same die?+(It is close to one.)++Wuerfelschlange (german)+http://faculty.uml.edu/rmontenegro/research/kruskal_count/kruskal.html++Kruskal's trick+http://www.math.de/exponate/wuerfelschlange.html/+-}+module Numeric.Probability.Example.Kruskal where++import qualified Numeric.Probability.Distribution as Dist+import qualified Numeric.Probability.Transition as Trans+import qualified Numeric.Probability.Random as Random+import qualified Numeric.Probability.Object as Obj+import qualified System.Random as Rnd+import qualified Text.Printf as P+import qualified Data.List as List+import Control.Monad (replicateM, )+import Data.Function.HT (nest, compose2, )+import Data.Tuple.HT (mapSnd, )+import Data.Bool.HT (if', )+++type Die = Int++type Probability = Rational+type Dist = Dist.T Probability++die ::+   (Obj.C prob experiment, Fractional prob) =>+   Score -> experiment Die+die maxPips = Obj.uniform [1..maxPips]++++type Score = Int++{- |+We reformulate the problem to the following game:+There are two players, both of them collect a number of points.+In every round the player with the smaller score throws a die+and adds the pips to his score.+If the two players somewhen get the same score, then the game ends+and the score is the result of the game (@Just score@).+If one of the players exceeds the maximum score n,+then the game stops and players lose (@Nothing@).+-}+game ::+   (Obj.C prob experiment, Fractional prob) =>+   Score -> Score -> (Score,Score) -> experiment (Maybe Score)+game maxPips maxScore =+   let go (x,y) =+          if maxScore < max x y+            then return Nothing+            else case compare x y of+                    EQ -> return (Just x)+                    LT -> do+                       d <- die maxPips+                       go (x+d, y)+                    GT -> do+                       d <- die maxPips+                       go (x, y+d)+   in  go++gameRound ::+   Score -> Score ->+   Dist (Either (Maybe Score) (Score,Score)) ->+   Dist (Either (Maybe Score) (Score,Score))+gameRound maxPips maxScore current = Dist.norm $ do+   e <- current+   case e of+      Left end -> return $ Left end+      Right (x,y) ->+         if maxScore < max x y+           then return $ Left Nothing+           else case compare x y of+                   EQ -> return $ Left (Just x)+                   LT -> do+                      d <- die maxPips+                      return $ Right (x+d, y)+                   GT -> do+                      d <- die maxPips+                      return $ Right (x, y+d)++gameFast :: Score -> Score -> Dist (Score,Score) -> Dist (Maybe Score)+gameFast maxPips maxScore start =+   Dist.mapMaybe (either Just (error "the game must be finished after maxScore moves")) $+   nest (maxScore+1) (gameRound maxPips maxScore) (fmap Right start)++gameFastEither :: Score -> Score -> Dist (Score,Score) -> Dist (Maybe Score)+gameFastEither maxPips maxScore = Trans.untilLeft $ \(x,y) ->+   if maxScore < max x y+     then return $ Left Nothing+     else case compare x y of+             EQ -> return $ Left (Just x)+             LT -> do+                d <- die maxPips+                return $ Right (x+d, y)+             GT -> do+                d <- die maxPips+                return $ Right (x, y+d)++{- |+This version could be generalized+to both Random and Distribution monad+while remaining efficient.+-}+gameFastFix :: Score -> Score -> Dist (Score,Score) -> Dist (Maybe Score)+gameFastFix maxPips maxScore =+   Trans.fix $ \go (x,y) ->+      if maxScore < max x y+        then return Nothing+        else case compare x y of+                EQ -> return (Just x)+                LT -> do+                   d <- die maxPips+                   go (x+d, y)+                GT -> do+                   d <- die maxPips+                   go (x, y+d)++{- |+In 'gameFastFix' we group the scores by rounds.+This leads to a growing probability distribution,+but we do not need the round number.+We could process the game in a different way:+We only consider the game states+where the lower score matches the round number.+-}+gameLeastScore :: Score -> Score -> Dist (Score,Score) -> Dist (Maybe Score)+gameLeastScore maxPips maxScore =+   (Trans.fix $ \go (n,(x,y)) ->+      if n > maxScore+        then return Nothing+        else+           let next = go . (,) (succ n)+           in  case (x==n, y==n) of+                  (False, False) -> next (x,y)+                  (True, False) -> do+                     d <- die maxPips+                     next (x+d, y)+                  (False, True) -> do+                     d <- die maxPips+                     next (x, y+d)+                  (True, True) -> return (Just x))+   .+   fmap ((,) 0)++{- |+'gameLeastScore' can be written in terms of a matrix power.+For n pips we need a n² × n² matrix.+Using symmetries, we reduce it to a square matrix with size n·(n+1)/2.++/ p[n+1,(n+1,n+1)] \          / p[n,(n+0,n+0)] \+| p[n+1,(n+1,n+2)] |          | p[n,(n+0,n+1)] |+| p[n+1,(n+1,n+3)] |          | p[n,(n+0,n+2)] |+|        ...       |          |       ...      |+| p[n+1,(n+1,n+6)] |  = M/6 · | p[n,(n+0,n+5)] |+| p[n+1,(n+2,n+2)] |          | p[n,(n+1,n+1)] |+|        ...       |          |       ...      |+| p[n+1,(n+2,n+6)] |          | p[n,(n+1,n+5)] |+|        ...       |          |       ...      |+\ p[n+1,(n+6,n+6)] /          \ p[n,(n+5,n+5)] /++M[(n+1,(x,y)),(n,(x,y))] = 6++M[(n+1,(min y (n+d), max y (n+d))), (n,(n,y))] = 1++M[(n+1,(x1,y1)),(n,(x0,y0))] = 0+-}+flattenedMatrix :: Score -> [Int]+flattenedMatrix maxPips = do+   x1 <- [1..maxPips]+   y1 <- [x1..maxPips]+   x0 <- [0..maxPips-1]+   y0 <- [x0..maxPips-1]+   return $+      if' ((x0,y0) == (x1,y1)) maxPips $+      if' (x0==0 && (y0==x1 || y0==y1)) 1 0++{-+let e0 = [1,0,0,...,0]++The cumulated probability is+e0 * (I + M + M^2 + ... + M^(n-1)) * startVector+and with M = V*D*V^-1 we get+e0 * V * (I + D + D^2 + ... + D^(n-1)) * V^-1 * startVector++e0 * (I - M^n) * (I-M)^(-1) * startVector+-}+startVector :: Score -> [Int]+startVector maxPips = do+   x <- [0..maxPips-1]+   y <- [x..maxPips-1]+   return $ if' (x==y) 1 2+++compareMaybe :: (Ord a) => Maybe a -> Maybe a -> Ordering+compareMaybe Nothing _ = GT+compareMaybe _ Nothing = LT+compareMaybe (Just a) (Just b) = compare a b++cumulate :: (Ord a) => Dist (Maybe a) -> [(Maybe a, Probability)]+cumulate =+   uncurry zip . mapSnd (scanl1 (+)) . unzip .+   List.sortBy (compose2 compareMaybe fst) . Dist.decons++runExact :: Score -> IO ()+runExact maxPips =+   mapM_ (\(m,p) ->+      case m of+         Just n ->+            P.printf "%4d %7.2f   %s\n"+               n (fromRational (100*p) :: Double) (show p)+         Nothing -> putStrLn $ "total: " ++ show p) $+   cumulate $+   gameFastFix maxPips 120 $ fmap ((,) 0) $ die maxPips+++trace :: Score -> [Score] -> [Score]+trace s xs@(x:_) = s : trace (s+x) (drop x xs)+trace s [] = [s]++chop :: [Score] -> [[Score]]+chop [] = []+chop xs@(x:_) = uncurry (:) $ mapSnd chop $ splitAt x xs++meeting :: [Score] -> [Score] -> Maybe Score+meeting xt@(x:xs) yt@(y:ys) =+   case compare x y of+      LT -> meeting xs yt+      GT -> meeting xt ys+      EQ -> Just x+meeting _ _ = Nothing++{- |+This is a bruteforce implementation of the original game:+We just roll the die @maxScore@ times+and then jump from die to die according to the number of pips.+-}+bruteforce :: Score -> Score -> (Score,Score) -> Random.T (Maybe Score)+bruteforce maxPips maxScore (x,y) = do+   points <- replicateM maxScore $ die maxPips+   let run s = trace s (drop s points)+   return (meeting (run x) (run y))++runSimulation :: Score -> IO ()+runSimulation maxPips =+   mapM_ (\(m,p) ->+      case m of+         Just n ->+            P.printf "%4d %7.2f\n"+               n (fromRational (100*p) :: Double)+         Nothing -> putStrLn $ "total: " ++ show p) $+   cumulate $+   Random.runSeed (Rnd.mkStdGen 42) $ Random.dist $+   replicate 100000 $ bruteforce maxPips 120 . (,) 0 =<< die maxPips+++latexDie :: Score -> String+latexDie pips =+   "\\epsdice{" ++ show pips ++ "}"++latexMarkedDie :: Score -> String+latexMarkedDie pips =+   "\\epsdice[black]{" ++ show pips ++ "}"++latexFromChain :: [Score] -> String+latexFromChain =+   unlines . map latexDie++latexChoppedFromChain :: [Score] -> String+latexChoppedFromChain =+   unlines .+   concatMap (\(p:ps) -> latexMarkedDie p : map latexDie ps) .+   chop++makeChains :: IO ()+makeChains = do+   let chains =+          map+             (\seed ->+                Random.runSeed (Rnd.mkStdGen seed) $+                replicateM 42 $ die 6)+             [30..42]+   writeFile "KruskalDice.tex" $+      "\\noindent\n"+      +++      (List.intercalate "\\\\[4ex]\n" $+       map (("$\\rightarrow$" ++) . latexFromChain) chains)+      +++      "\\newpage\n" +++      "\\noindent\n"+      +++      (List.intercalate "\\\\[4ex]\n" $+       map (("$\\rightarrow$" ++) . latexChoppedFromChain) chains)
src/Numeric/Probability/Monad.hs view
@@ -1,16 +1,13 @@ -- | Monad helper functions module Numeric.Probability.Monad where +import Control.Monad.HT ((<=<), ) import Control.Monad (liftM, ) import Prelude hiding (iterate, ) --- | binary composition, available in GHC-6.8 Control.Monad-(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c-f >=> g = (>>= g) . f- -- | composition of a list of monadic functions compose :: Monad m => [a -> m a] -> a -> m a-compose = foldl (>=>) return+compose = foldl (flip (<=<)) return   iterate :: Monad m => Int -> (a -> m a) -> (a -> m a)@@ -18,6 +15,8 @@   -- | like 'iterate' but returns all intermediate values+-- like Control.Monad.HT.iterateLimit, but counts differently.+-- Is it actually useful this way? walk :: (Monad m) => Int -> (a -> m a) -> (a -> m [a]) walk n f =    let recourse 0 _ = return []
src/Numeric/Probability/Object.hs view
@@ -1,4 +1,7 @@-{-# OPTIONS_GHC -fglasgow-exts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-} {- | Portability: Multi-parameter type class with functional dependency @@ -15,10 +18,13 @@ import qualified Numeric.Probability.Distribution as Dist import qualified Numeric.Probability.Random as Rnd import qualified Numeric.Probability.Shape as Shape+import qualified Numeric.Probability.Either as PE  import qualified Data.List as List +import Control.Monad (liftM, ) + class Monad obj => C prob obj | obj -> prob where    fromFrequencies :: [(a,prob)] -> obj a @@ -28,6 +34,10 @@  instance Fractional prob => C prob (Dist.T prob) where    fromFrequencies = Dist.fromFreqs++instance C prob obj => C prob (PE.EitherT b obj) where+   fromFrequencies =+      PE.EitherT . liftM Right . fromFrequencies   
src/Numeric/Probability/Percentage.hs view
@@ -21,7 +21,7 @@ percent x = Cons (x/100)  -showPfix :: (RealFrac prob) => Int -> prob -> String+showPfix :: (RealFrac prob, Show prob) => Int -> prob -> String showPfix precision f =    if precision==0      then showR 3 (round (f*100) :: Integer)++"%"
src/Numeric/Probability/Random.hs view
@@ -7,7 +7,8 @@ import qualified System.Random as Random import System.Random (Random, ) -import Control.Monad.Trans.State (State, state, evalState, )+import Control.Applicative (Applicative(..))+import Control.Monad.Trans.State (State, state, evalState, runState, )  import qualified System.IO as IO import Prelude hiding (print)@@ -26,6 +27,9 @@ instance Functor T where    fmap f = Cons . fmap f . decons +instance Applicative T where+   pure x = Cons (pure x)+   fm <*> m = Cons (decons fm <*> decons m)  randomR :: Random.Random a => (a, a) -> T a randomR rng =@@ -35,7 +39,7 @@ Run random action in 'IO' monad. -} run :: T a -> IO a-run r = fmap (evalState (decons r)) Random.getStdGen+run = Random.getStdRandom . runState . decons  {- | Run random action without 'IO' using a seed.@@ -66,7 +70,9 @@  {- | 'dist' converts a list of randomly generated values into-a distribution by taking equal weights for all values+a distribution by taking equal weights for all values.+Thus @dist (replicate n rnd)@ simulates @rnd@ @n@ times+and returns an estimation of the distribution represented by @rnd@. -} dist :: (Fractional prob, Ord a) => [T a] -> Distribution prob a dist = fmap (Dist.norm . Dist.uniform) . sequence
src/Numeric/Probability/Show.hs view
@@ -1,12 +1,19 @@ module Numeric.Probability.Show where +import qualified Data.List.HT as ListHT++{- showL :: Show a => Int -> a -> String showL n x = s ++ replicate (n-length s) ' '             where s=show x +showL n x =+   ListHT.padRight1 ' ' n (show x)+-}+ showR :: Show a => Int -> a -> String-showR n x = replicate (n-length s) ' ' ++ s-            where s=show x+showR n x =+   ListHT.padLeft ' ' n (show x)  --showP :: Float -> String --showP f =  showR 3 (round (f*100))++"%"
src/Numeric/Probability/Transition.hs view
@@ -3,6 +3,11 @@  import qualified Numeric.Probability.Distribution as Dist +import qualified Numeric.Probability.Either as PE++import qualified Data.Map as Map++import qualified Data.List.HT as ListHT import qualified Data.List as List import Prelude hiding (map, maybe, id, ) @@ -49,6 +54,34 @@    [T prob a] -> T prob a compose = foldl (\acc x v -> Dist.norm (acc v >>= x)) return ++untilLeft :: (Num prob, Ord a, Ord b) =>+   (a -> Dist.T prob (Either b a)) -> Dist.T prob a -> Dist.T prob b+untilLeft f =+   let go final dist =+          if null (Dist.decons dist)+            then Dist.Cons $ Map.toList final+            else+               case ListHT.unzipEithers $+                    List.map (\(e,p) -> either (\l -> Left (l,p)) (\r -> Right (r,p)) e) $+                    Dist.decons $ Dist.norm $ dist >>= f of+                  (newFinal, stillActive) ->+                     go (Map.unionWith (+) (Map.fromListWith (+) newFinal) final) $+                     Dist.Cons stillActive+   in  go Map.empty++{- |+In @fix $ \go a -> do ...; go xy@+any action after a 'go' is ignored.+-}+fix :: (Num prob, Ord a, Ord b) =>+   ((a -> PE.EitherT a (Dist.T prob) b) ->+    (a -> PE.EitherT a (Dist.T prob) b)) ->+   Dist.T prob a -> Dist.T prob b+fix f =+   untilLeft $ \a ->+      case f PE.throw a of+         PE.EitherT m -> fmap (either Right Left) m   -- * Spreading changes into transitions
src/Numeric/Probability/Visualize.hs view
@@ -9,6 +9,7 @@  import qualified Numeric.Probability.Distribution as Dist +import Control.Monad (when, ) import Data.List (nub, sort, )  @@ -204,11 +205,11 @@                         title  fe++"\",xlab=\""++                         xLabel fe++"\",ylab=\""++                         yLabel fe++"\")")-                mapM out1' (zipWith3 drawy [1..length ys'] ps ys')-                if null (concatMap label ps)-                  then return ()-                  else out1' $ legend (incr minx) maxy ps-                out1' ("dev2bitmap(\""++(fileName fe)++".pdf\", type=\"pdfwrite\")")+                mapM_ out1' (zipWith3 drawy [1 ..] ps ys')+                when (not $ null $ concatMap label ps) $+                  out1' $ legend (incr minx) maxy ps+                out1' ("dev2bitmap(" ++ show (fileName fe ++ ".pdf") +++                       ", type=\"pdfwrite\")")   {-