···11-Haskell implementation of Hidden Markov Models & related algorithms from AIMA book
11+Haskell implementations of Hidden Markov Models & related algorithms from AIMA book and using them for approaches mentioned in the paper [Assessing the resilience of stochastic dynamic systems under partial observability](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0202337)
+93-1
src/cHMM.hs
···1010-- Maintainer : Kaushik Chakraborty <git@kaushikc.org>
1111-- Stability : experimental
1212--
1313+-- Cost based HMM
1414+-- https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0202337
1315---------------------------------
14161517module CHMM where
···1820import GHC.TypeNats
1921import Data.Proxy (Proxy(..))
2022import Linear.V
2323+import Linear
2124import Combinatorics
2225import qualified Data.Vector as Vec
23262427import HMM
2528-----------------------
26292727-trajectory :: forall n t a . (KnownNat n, KnownNat t) => Proxy t -> V n a -> V (n ^ t) (V n a)
3030+-- * Documentation
3131+3232+-- | For a specific resiliency property that need evaluation, following algorithm infer the probability of the same property in linear time taking into account corresponding observations and state trajectories
3333+cHMMInfer :: forall s u t b i. (KnownNat s, KnownNat u, KnownNat t, Eq b, Ord b, Integral i, Show i, Show b)
3434+ => HMM s u R b -- ^ HMM model
3535+ -> V t b -- ^ evidence/observation trajectory for time @1 .. t@
3636+ -> V t i -- ^ state trajectory for time @1 .. t@; represented as @0@-based index of state
3737+ -> R -- ^ property probability
3838+cHMMInfer hmm evs xs = (\x y z -> (x * y) / z) <$> y0 <*> y1 <*> y2 $ zs
3939+ where
4040+ y0 :: Vec.Vector (i, b) -> R
4141+ y0 = foldl' (\m (s,o') ->
4242+ m *
4343+ sensorDiagonal hmm o' ^?! _Just . ix (fromIntegral s) . ix (fromIntegral s))
4444+ 1
4545+4646+ y1 :: Vec.Vector (i, b) -> R
4747+ y1 vs = ifoldl (\idx m (s,_) ->
4848+ m *
4949+ bool
5050+ (hmm ^?! tModel . ix (vs ^?! ix (idx - 1) . _1 . to fromIntegral) . ix (fromIntegral s))
5151+ ((Linear.transpose (hmm ^. prior) !*! hmm ^. tModel) ^?! ix 0 . ix (fromIntegral s))
5252+ (idx == 0))
5353+ 1 vs
5454+5555+ y2 :: KnownNat s => Vec.Vector (i, b) -> R
5656+ y2 vs = sum . extract1 $
5757+ ifoldl (\idx m (_,o') ->
5858+ bool
5959+ ((sensorDiagonal hmm o' ^?! _Just) !*! Linear.transpose (hmm ^. tModel) !*! m)
6060+ ((sensorDiagonal hmm o' ^?! _Just) !*! Linear.transpose (hmm ^. tModel) !*! hmm ^. prior)
6161+ (idx == 0)
6262+ )
6363+ (unitColumn :: Message s R)
6464+ vs
6565+6666+ zs :: Vec.Vector (i, b)
6767+ zs = Vec.zip (toVector xs) (toVector evs)
6868+6969+-- | a trajectory is a sequence of variables which can be random variables (r.v) as part of either sensor or transition model of a HMM or values from the support of some r.v.
7070+trajectory :: forall n t a . (KnownNat n, KnownNat t)
7171+ => Proxy t -- ^ choice parameter
7272+ -> V n a -- ^ superset of choice variables which is divided into sets each of length provided by the choice parameter
7373+ -> V (n ^ t) (V t a) -- ^ final set with @n^t@ ordered subsets each of length @t@
2874trajectory p = V . Vec.fromList . fmap (V . Vec.fromList) . variateRep t' . toList
2975 where
3076 t' = fromIntegral $ natVal p
31777878+-- | HMM model representing the swarm robotics use case of the paper
7979+--
8080+-- Transition Model
8181+--
8282+-- @
8383+-- [0.7393859 , 0.2233470 , 0.0330670 , 0.0042001]
8484+-- [0.0928202 , 0.7172515 , 0.1719556 , 0.0179727]
8585+-- [0.0139341 , 0.1755173 , 0.6936519 , 0.1168966]
8686+-- [0.0047083 , 0.0403520 , 0.2561893 , 0.6987504]
8787+-- @
8888+--
8989+-- Sensor Model
9090+--
9191+-- @
9292+-- [1.00000, 0.00000, 0.00000, 0.00000]
9393+-- [0.10000, 0.90000, 0.00000, 0.00000]
9494+-- [0.01000, 0.18000, 0.81000, 0.00000]
9595+-- [0.00100, 0.02700, 0.24300, 0.72900]
9696+-- @
9797+--
9898+swarmRobotsHMM :: HMM 4 4 R Int
9999+swarmRobotsHMM = mkHMM_
100100+ (Just $ V $ Vec.replicate 4 (toV $ V1 0.25))
101101+ (V $ Vec.fromList
102102+ [
103103+ V $ Vec.fromList [0.7393859 , 0.2233470 , 0.0330670 , 0.0042001],
104104+ V $ Vec.fromList [0.0928202 , 0.7172515 , 0.1719556 , 0.0179727],
105105+ V $ Vec.fromList [0.0139341 , 0.1755173 , 0.6936519 , 0.1168966],
106106+ V $ Vec.fromList [0.0047083 , 0.0403520 , 0.2561893 , 0.6987504]
107107+ ])
108108+ (V $ Vec.fromList [1..4])
109109+ (Linear.transpose (V $ Vec.fromList
110110+ [
111111+ V $ Vec.fromList [1.00000, 0.00000, 0.00000, 0.00000],
112112+ V $ Vec.fromList [0.10000, 0.90000, 0.00000, 0.00000],
113113+ V $ Vec.fromList [0.01000, 0.18000, 0.81000, 0.00000],
114114+ V $ Vec.fromList [0.00100, 0.02700, 0.24300, 0.72900]
115115+ ]))
116116+117117+-- | inferring the /l-resistance/ property of the swarm robots as represented in the 'swarmRobotsHMM' model given a series of observation and a paticular /l/ value to check against
118118+--
119119+-- E.g.
120120+--
121121+-- >>> runCHMMInfer [3,2,3] 2
122122+-- 0.7756162284376005
123123+--
32124runCHMMInfer :: [Int] -> Int -> R
33125runCHMMInfer evs l = let v = Vec.fromList [0 .. dim (swarmRobotsHMM ^. tModel) - 1]
34126 n = length evs
+27-82
src/hmm.hs
···1313--
1414---------------------------------
15151616-module HMM where
1616+module HMM
1717+ (
1818+ HMM(..),
1919+ -- ** Smart Constructors
2020+ mkHMM_, mkHMM, mkHMM1, sensorDiagonal,
2121+ -- * Utility Functions
2222+ normalise, inverse, reverseV, extract1, (!**!), unitColumn,
2323+ -- ** Converting to/from HMatrix matrices
2424+ toHM, fromHM,
2525+ -- * Inference Algorithms
2626+ -- ** Forward-Backward
2727+ forward, backward, forwardBackward,
2828+ -- ** Fixed Lag Smoothing
2929+ fixedLagSmoothing,
3030+ -- * Sample HMM Models
3131+ umbrellaHMM,
3232+ -- * Miscellaneous
3333+ -- ** Type Synonyms
3434+ R, TransitionModel, SensorDiagonal, SensorModel, Message, Distribution,
3535+ -- ** Data Types
3636+ Persistent(..),
3737+ -- ** Test Functions
3838+ runFLSAlgo, runFBAlgo,
3939+ -- ** Lenses
4040+ prior, tModel, sDist, sModel
4141+) where
17421843import Text.Printf (printf)
1944···3358import qualified Numeric.LinearAlgebra as HMat
3459-----------------------------------------------------------------------------------
35603636--- * Type synonyms
3761type R = Double
3862type TransitionModel (s :: Nat) a = V s (V s a)
3963type SensorDiagonal (s :: Nat) a = V s (V s a)
···4165type Message (s :: Nat) a = V s (V 1 a)
4266type Distribution (s :: Nat) a = V s a
43674444--- * HMM
4545-4668-- | Hidden Markov Models
4769data HMM (s :: Nat) (t :: Nat) a b = HMM {
4870 -- | prior distribution which is used as initial forward message
···5577 _sModel :: SensorModel t s a
5678 } deriving (Show)
57795858--- |
5959--- === __HMM lenses__
6060---
6180makeLenses ''HMM
62816363--- ** Smart constructors
6482mkHMM_ :: (KnownNat s, KnownNat t) => Maybe (Message s R) -- ^ Prior distribution on the initial state, @P(X0)@. If nothing then considered @(0.5,0.5)@
6583 -> TransitionModel s R -- ^ Transition Model as @sxs@ matrix, @s@ being the number of states
6684 -> V t b -- ^ evidence values vector map
···88106sensorDiagonal hmm e = findIndexOf (sDist.folded) (== e) hmm >>= (\i -> hmm ^? sModel . ix i)
89107901089191--- * Utility Functions
9210993110-- | Multiply each number by a constant such that the sum is 1.0
94111normalise :: (Fractional a, Foldable f, Functor f) => f a -> f a
···119136unitColumn :: forall s. KnownNat s => Message s R
120137unitColumn = V $ Vec.replicate (fromIntegral $ natVal (Proxy :: Proxy s)) (toV $ V1 1.0)
121138122122--- ** Converting to/from __HMatrix__ matrices
123123-124139-- | take a @sxt@ 'Linear.V.V' matrix and give corresponding 'Numeric.LinearAlgebra.Static.L' version
125140toHM :: (KnownNat s, KnownNat t) => V s (V t R) -> HM.L s t
126141toHM = HM.matrix . foldMap (Vec.toList . toVector)
···128143-- | take a @sxt@ 'Numeric.LinearAlgebra.Static.L' matrix and give corresponding 'Linear.V.V' version
129144fromHM :: (KnownNat s, KnownNat t) => HM.L s t -> V s (V t R)
130145fromHM m = V $ V <$> Vec.fromList (fmap Vec.fromList $ HMat.toLists $ HM.extract m)
131131-132132-133133--- * Inference Algorithms
134134-135135--- ** Forward-Backward
136146137147-- | Filtering message propagated forward
138148forward :: (KnownNat s) => HMM s t R b -> SensorDiagonal s R -> Message s R -> Message s R
···171181 liftA2 (\f b' -> extract1 $ normalise $ f !**! b') fv_0 revBs
172182173183174174--- ** Fixed Lag Smoothing
175175-176176--- *** Data Types
177184-- | Persistent State
178185data Persistent (s :: Nat) a b d= Persistent {
179186 _t :: d, -- ^ current time
···182189 _e_td_t :: Vec.Vector b -- ^ double-ended list of evidence from @t − d@ to @t@
183190 } deriving (Show)
184191185185--- |
186186--- === __Persistent lenses__
187187---
188192makeLenses ''Persistent
189193190190--- *** Smart constructor
191194-- | Initial persistent state where
192195--
193196-- * t = 1
···200203persistentInit :: forall s b d. (KnownNat s, Integral d) => Message s R -> Persistent s R b d
201204persistentInit p = Persistent { _t = 1, _f_msg = p, _b = identity, _e_td_t = Vec.empty}
202205203203--- *** Algo
204206-- | Online Algorithm for smoothing with a fixed time lag of @d@ steps
205207fixedLagSmoothing :: forall s u b d. (KnownNat s, KnownNat u, Eq b, Integral d)
206208 => HMM s u R b -- ^ HMM model
···230232 t += 1
231233 mzero
232234233233--- ** Cost based HMM(cHMM)
234234--- Inference algo from cost-HMM paper
235235-cHMMInfer :: forall s u t b i. (KnownNat s, KnownNat u, KnownNat t, Eq b, Ord b, Integral i, Show i, Show b)
236236- => HMM s u R b -- ^ HMM model
237237- -> V t b -- ^ evidence/observation trajectory for time @1 .. t@
238238- -> V t i -- ^ state trajectory for time @1 .. t@; represented as @0@-based index of state
239239- -> R
240240-cHMMInfer hmm evs xs = (\x y z -> (x * y) / z) <$> y0 <*> y1 <*> y2 $ zs
241241- where
242242- y0 :: Vec.Vector (i, b) -> R
243243- y0 = foldl' (\m (s,o') ->
244244- m *
245245- sensorDiagonal hmm o' ^?! _Just . ix (fromIntegral s) . ix (fromIntegral s))
246246- 1
247235248248- y1 :: Vec.Vector (i, b) -> R
249249- y1 vs = ifoldl (\idx m (s,_) ->
250250- m *
251251- bool
252252- (hmm ^?! tModel . ix (vs ^?! ix (idx - 1) . _1 . to fromIntegral) . ix (fromIntegral s))
253253- ((Linear.transpose (hmm ^. prior) !*! hmm ^. tModel) ^?! ix 0 . ix (fromIntegral s))
254254- (idx == 0))
255255- 1 vs
256256-257257- y2 :: KnownNat s => Vec.Vector (i, b) -> R
258258- y2 vs = sum . extract1 $
259259- ifoldl (\idx m (_,o') ->
260260- bool
261261- ((sensorDiagonal hmm o' ^?! _Just) !*! Linear.transpose (hmm ^. tModel) !*! m)
262262- ((sensorDiagonal hmm o' ^?! _Just) !*! Linear.transpose (hmm ^. tModel) !*! hmm ^. prior)
263263- (idx == 0)
264264- )
265265- (unitColumn :: Message s R)
266266- vs
267267-268268- zs :: Vec.Vector (i, b)
269269- zs = Vec.zip (toVector xs) (toVector evs)
270270-271271--- * Sample HMM Models
272236umbrellaHMM :: HMM 2 2 R Bool
273237umbrellaHMM = mkHMM1 (V2 (V2 0.7 0.3) (V2 0.3 0.7)) (V2 (V2 0.9 0.2) (V2 0.1 0.8))
274238275275-swarmRobotsHMM :: HMM 4 4 R Int
276276-swarmRobotsHMM = mkHMM_
277277- (Just $ V $ Vec.replicate 4 (toV $ V1 0.25))
278278- (V $ Vec.fromList
279279- [
280280- V $ Vec.fromList [0.7393859 , 0.2233470 , 0.0330670 , 0.0042001],
281281- V $ Vec.fromList [0.0928202 , 0.7172515 , 0.1719556 , 0.0179727],
282282- V $ Vec.fromList [0.0139341 , 0.1755173 , 0.6936519 , 0.1168966],
283283- V $ Vec.fromList [0.0047083 , 0.0403520 , 0.2561893 , 0.6987504]
284284- ])
285285- (V $ Vec.fromList [1..4])
286286- (Linear.transpose (V $ Vec.fromList
287287- [
288288- V $ Vec.fromList [1.00000, 0.00000, 0.00000, 0.00000],
289289- V $ Vec.fromList [0.10000, 0.90000, 0.00000, 0.00000],
290290- V $ Vec.fromList [0.01000, 0.18000, 0.81000, 0.00000],
291291- V $ Vec.fromList [0.00100, 0.02700, 0.24300, 0.72900]
292292- ]))
293239294294--- * Main
295240runFLSAlgo :: [Bool] -> Integer -> [Maybe (Distribution 2 String)]
296241runFLSAlgo bs d = let hmm = mkHMM1 (V2 (V2 0.7 0.3) (V2 0.3 0.7)) (V2 (V2 0.9 0.2) (V2 0.1 0.8))
297242 initState = persistentInit (V $ Vec.fromList [V $ Vec.singleton 0.5, V $ Vec.singleton 0.5]) :: Persistent 2 R Bool Integer
···303248304249305250runFBAlgo :: [Bool] -> [Distribution 2 R]
306306-runFBAlgo bs = reifyVectorNat (Vec.fromList bs) (toList . forwardBackward umbrellaHMM)251251+runFBAlgo bs = reifyVectorNat (Vec.fromList bs) (toList . forwardBackward umbrellaHMM)