{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {- | Module : Markov Description : Realization of Markov processes with known parameters. Maintainer : atloomis@math.arizona.edu Stability : Experimental Three type classes for deterministically analyzing Markov chains with known parameters. 'Markov0' is intended to list possible outcomes, 'Markov' should allow for more sophisticated analysis. A more general definition can be found in "Markov.Generic" that allows for containers other than lists. See "Markov.Example" for examples. See README for a detailed description. -} module Markov ( -- *Markov0 Markov0 (..) , chain0 -- *Markov , Markov (..) , chain -- *Combine , Combine (..) , Merge (..) , Sum (..) , Product (..) ) where import Control.Comonad (Comonad, extract) import qualified Data.List as DL import qualified Data.List.NonEmpty as NE --------------------------------------------------------------- -- Markov0 --------------------------------------------------------------- -- |A basic implementation of Markov chains. class (Eq s) => Markov0 s where transition0 :: s -> [s -> s] step0 :: s -> [s] transition0 x = const <$> step0 x step0 x = ($ x) <$> transition0 x {-# MINIMAL transition0 | step0 #-} -- |Iterated steps, with equal states combined. chain0 :: Markov0 s => [s] -> [[s]] chain0 = DL.iterate' $ DL.nub . concatMap step0 --------------------------------------------------------------------------------------- -- Markov --------------------------------------------------------------------------------------- -- |An implementation of Markov chains. class (Applicative t, Comonad t) => Markov t s where transition :: s -> [t (s -> s)] step :: t s -> [t s] sequential :: [s -> [t (s -> s)]] transition = fmap (fmap const) . step . pure step x = foldr (concatMap . step') [x] sequential where step' f y = (<*> y) <$> f (extract y) sequential = [transition] {-# MINIMAL transition | step | sequential #-} -- |Iterated steps, with equal states combined using 'summarize' operation. chain :: (Combine (t s), Ord (t s), Markov t s) => [t s] -> [[t s]] chain = DL.iterate' $ fmap summarize . NE.group . DL.sort . concatMap step --------------------------------------------------------------------------------------- -- Combine --------------------------------------------------------------------------------------- -- |Within equivalence classes, @combine@ should be associative, -- commutative, and idempotent (up to equivalence). -- I.e. if @x == y == z@, -- -- prop> (x `combine` y) `combine` z = x `combine` (y `combine` z) -- prop> x `combine` y = y `combine` x -- prop> x `combine` x == x class Combine a where combine :: a -> a -> a summarize :: NE.NonEmpty a -> a combine a b = summarize . NE.fromList $ [a,b] summarize (a NE.:| b) = foldr combine a b {-# MINIMAL combine | summarize #-} instance (Combine a, Combine b) => Combine (a,b) where combine (w,x) (y,z) = (combine w y, combine x z) instance (Combine a, Combine b, Combine c) => Combine (a,b,c) where combine (a,w,x) (b,y,z) = (combine a b, combine w y, combine x z) --------------------------------------------------------------------------------------- -- Merge --------------------------------------------------------------------------------------- -- Does not group to combine unless equal. -- |Values from a 'Monoid' which have the respective -- binary operation applied each step, -- where different values mean states should not be combined. -- E.g., strings with concatenation. newtype Merge a = Merge a deriving newtype (Eq, Semigroup, Monoid, Enum, Num, Ord, Fractional, Show) instance Combine (Merge a) where combine = const --------------------------------------------------------------------------------------- -- Sum --------------------------------------------------------------------------------------- -- |Values which are added each step, -- where different values mean states should not be combined. -- E.g., number of times a red ball is picked from an urn. newtype Sum a = Sum a deriving newtype (Eq, Enum, Num, Ord, Fractional, Show) instance Combine (Sum a) where combine = const instance Num a => Semigroup (Sum a) where x <> y = x + y instance Num a => Monoid (Sum a) where mempty = 0 --------------------------------------------------------------------------------------- -- Product --------------------------------------------------------------------------------------- -- Does not effect equality of tuple, -- @combine x y = x + y@. -- |Values which are multiplied each step, -- and combined additively for equal states. -- E.g., probabilities. newtype Product a = Product a deriving newtype (Num, Fractional, Enum, Show) instance Ord (Product a) where compare _ _ = EQ -- This causes Data.List.group to act more like Data.Discrimination.group -- |WARNING! Defined @_ == _ = True@! instance Eq (Product a) where _ == _ = True instance Num a => Combine (Product a) where combine = (+) instance Num a => Semigroup (Product a) where x <> y = x * y instance Num a => Monoid (Product a) where mempty = 1