module Bayes.Factor(
Factor(..)
, isomorphicFactor
, normedFactor
, Set(..)
, BayesianDiscreteVariable(..)
, Vertex(..)
, DV(..)
, DVSet(..)
, DVI
, DVISet(..)
, setDVValue
, instantiationValue
, instantiationVariable
, variableVertex
, (=:)
, forAllInstantiations
, factorFromInstantiation
, changeVariableOrder
, CPT
, testProductProject_prop
, testScale_prop
, testProjectCommut_prop
, testScalarProduct_prop
, testProjectionToScalar_prop
) where
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed((!))
import Data.Maybe(fromJust,mapMaybe)
import qualified Data.List as L
import Text.PrettyPrint.Boxes hiding((//))
import Test.QuickCheck
import Test.QuickCheck.Arbitrary
import qualified Data.IntMap as IM
import Control.Monad
import System.Random(Random)
newtype Vertex = Vertex {vertexId :: Int} deriving(Eq,Ord)
instance Show Vertex where
show (Vertex v) = "v" ++ show v
class Set s where
emptySet :: s a
union :: Eq a => s a -> s a -> s a
intersection :: Eq a => s a -> s a -> s a
difference :: Eq a => s a -> s a -> s a
isEmpty :: s a -> Bool
isElem :: Eq a => a -> s a -> Bool
addElem :: Eq a => a -> s a -> s a
nbElements :: s a -> Int
subset :: Eq a => s a -> s a -> Bool
equal :: Eq a => s a -> s a -> Bool
equal sa sb = (sa `subset` sb) && (sb `subset` sa)
instance Set [] where
emptySet = []
union = L.union
intersection = L.intersect
difference a b = a L.\\ b
isEmpty [] = True
isEmpty _ = False
isElem = L.elem
addElem a l = if a `elem` l then l else a:l
nbElements = length
subset sa sb = all (`elem` sb) sa
class BayesianDiscreteVariable v where
dimension :: v -> Int
class LabeledVertex l where
variableVertex :: l -> Vertex
data DV = DV !Vertex !Int deriving(Eq,Ord)
type DVSet = [DV]
instance Show DV where
show (DV v d) = show v ++ "(" ++ show d ++ ")"
data DVI a = DVI DV !a deriving(Eq)
instance Show a => Show (DVI a) where
show (DVI (DV v _) i) = show v ++ "=" ++ show i
factorFromInstantiation :: Factor f => DVI Int -> f
factorFromInstantiation (DVI dv a) =
let setValue i = if i == a then 1.0 else 0.0
in
fromJust . factorWithVariables [dv] . map (setValue) $ [0..dimension dv1]
type DVISet a = [DVI a]
instance BayesianDiscreteVariable DV where
dimension (DV _ d) = d
setDVValue :: DV -> a -> DVI a
setDVValue v a = DVI v a
getMinBound :: Bounded a => a -> a
getMinBound _ = minBound
(=:) :: (Bounded b, Enum b) => DV -> b -> DVI Int
(=:) a b = setDVValue a (fromEnum b fromEnum (getMinBound b))
instantiationValue (DVI _ v) = v
instantiationVariable (DVI dv _) = dv
instance LabeledVertex (DVI a) where
variableVertex (DVI v _) = variableVertex v
instance LabeledVertex DV where
variableVertex (DV v _) = v
extend :: [Bool] -> a -> [a] -> [a]
extend [] _ l = l
extend (h:t) d [] = d:extend t d []
extend (False:t) d l = d:extend t d l
extend (True:t) d (h:l') = h:extend t d l'
type InnerLoop a = [Int] -> a
type OuterLoop a b = [Int] -> [a] -> b
forSubA :: DVSet
-> DVSet
-> (DVSet -> [Int] -> [a])
-> OuterLoop a b
-> [b]
forSubA allvars outervars inner outer =
let sCode s e = if (e `isElem` s) then True else False
selection s = map (sCode s) allvars
computeOuter iouter =
let outerIdx = extend (selection outervars) 0 iouter
innerValues = inner allvars outerIdx
in
outer iouter innerValues
in
map computeOuter (forAllIndices outervars)
forSubB :: DVSet
-> InnerLoop a
-> DVSet
-> [Int]
-> [a]
forSubB innervars f allvars outerIdx =
let sCode s e = if (e `isElem` s) then True else False
selection s = map (sCode s) allvars
computeInner iinner =
let innerIdx = extend (selection innervars) 0 iinner
idx = zipWith (+) outerIdx innerIdx
in
f idx
in
map computeInner (forAllIndices innervars)
normedFactor :: Factor f => f -> f
normedFactor f = factorDivide f (factorNorm f)
class FactorPrivate f => Factor f where
isScalarFactor :: f -> Bool
emptyFactor :: f
containsVariable :: f -> DV -> Bool
factorVariables :: f -> DVSet
factorMainVariable :: f -> DV
factorMainVariable = head . factorVariables
factorWithVariables :: DVSet -> [Double] -> Maybe f
factorValue :: f -> DVISet Int -> Double
variablePosition :: f -> DV -> Maybe Int
factorDimension :: f -> Int
factorNorm :: f -> Double
factorScale :: Double -> f -> f
factorFromScalar :: Double -> f
evidenceFrom :: DVISet Int -> Maybe f
factorDivide :: f -> Double -> f
factorDivide f d = (1.0 / d) `factorScale` f
factorProduct :: [f] -> f
factorProduct [] = factorFromScalar 1.0
factorProduct l =
let allVars = L.foldl1' union . map factorVariables $ l
in
if L.null allVars
then
factorFromScalar (product . map factorNorm $ l)
else
let getFactorValueAtIndex i factor = factorValuePrivate factor (reorder i factor)
instantiationProduct instantiation = product . map (getFactorValueAtIndex instantiation) $ l
values = [instantiationProduct x | x <- forAllInstantiations allVars]
in
fromJust $ factorWithVariables allVars values
factorProjectOut :: DVSet -> f -> f
factorProjectOut s f =
let alls = factorVariables f
s' = alls `difference` s
in
if null s'
then
factorFromScalar (factorNorm f)
else
let dstValues = forSubA alls s'
(forSubB s $ factorValuePrivate f)
(\i c -> sum c)
in
fromJust $ factorWithVariables s' dstValues
factorProjectTo :: DVSet -> f -> f
factorProjectTo s f =
let alls = factorVariables f
s' = alls `difference` s
in
factorProjectOut s' f
class FactorPrivate f where
factorValuePrivate :: f -> [Int] -> Double
allValues :: DV -> [Int]
allValues (DV _ i) = [0..i1]
forAllIndices :: DVSet -> [[Int]]
forAllIndices = mapM allValues
forAllInstantiations :: DVSet -> [DVISet Int]
forAllInstantiations = mapM oneInstantiation
where
oneInstantiation v@(DV vertex _) = map (setDVValue v) . allValues $ v
changeVariableOrder :: DVSet
-> DVSet
-> [Double]
-> [Double]
changeVariableOrder oldOrder newOrder oldValues =
let oldFactor = fromJust $ factorWithVariables oldOrder oldValues :: CPT
in
[factorValue oldFactor i | i <- forAllInstantiations newOrder]
reorder :: Factor f => DVISet Int -> f -> [Int]
reorder i f =
let nbDestVars = nbElements . factorVariables $ f
v = V.replicate nbDestVars 0
asDV v = DV v 0
vectorPair bdvi = do
pos <- variablePosition f . asDV . variableVertex $ bdvi
let value = instantiationValue bdvi
return (pos, value)
allPos = mapMaybe vectorPair i
in
let testError = maybe False (const True) $ do
guard $ length allPos == nbDestVars
guard $ and . map ( (< nbDestVars) . fst) $ allPos
return ()
in
case testError of
False -> error ("reorder has not set all destination indexes ! allpos = " ++ show allPos ++ " nbDestVars = " ++ show nbDestVars ++ "\n" )
True -> V.toList $ v V.// allPos
data CPT = CPT {
dimensions :: DVSet
, mapping :: IM.IntMap Int
, values :: V.Vector Double
}
| Scalar Double
debugCPT (Scalar d) = do
putStrLn "SCALAR CPT"
print d
putStrLn ""
debugCPT (CPT d m v) = do
putStrLn "CPT"
print d
putStrLn ""
print m
putStrLn ""
print v
putStrLn ""
quickCheckVertexSize :: Int -> Int
quickCheckVertexSize 0 = 2
quickCheckVertexSize 1 = 2
quickCheckVertexSize 2 = 2
quickCheckVertexSize _ = 2
whileIn :: (Arbitrary a, Eq a) => [a] -> Gen a -> Gen a
whileIn l m = do
newVal <- m
if newVal `elem` l
then
whileIn l m
else
return newVal
generateWithoutReplacement :: (Random a, Arbitrary a, Eq a)
=> Int
-> (a,a)
-> Gen [a]
generateWithoutReplacement n b | n == 1 = generateSingle b
| n > 1 = generateMultiple n b
| otherwise = return []
where
generateSingle b = do
r <- choose b
return [r]
generateMultiple n b = do
l <- generateWithoutReplacement (n1) b
newelem <- whileIn l $ choose b
return (newelem:l)
instance Arbitrary CPT where
arbitrary = do
nbVertex <- choose (1,4) :: Gen Int
vertexNumbers <- generateWithoutReplacement nbVertex (0,50)
let dimensions = map (\i -> (DV (Vertex i) (quickCheckVertexSize i))) vertexNumbers
let valuelen = product (map dimension dimensions)
rndValues <- vectorOf valuelen (choose (0.0,1.0) :: Gen Double)
return . fromJust . factorWithVariables dimensions $ rndValues
nearlyEqual :: Double -> Double -> Bool
nearlyEqual a b =
let absA = abs a
absB = abs b
diff = abs (ab)
epsilon = 2e-5
in
case (a,b) of
(x,y) | x == y -> True
| x*y == 0 -> diff < (epsilon * epsilon)
| otherwise -> diff / (absA + absB) < epsilon
testScale_prop :: Double -> CPT -> Bool
testScale_prop s f = (factorNorm (s `factorScale` f)) `nearlyEqual` (s * (factorNorm f))
testProductProject_prop :: CPT -> CPT -> Property
testProductProject_prop fa fb = isEmpty ((factorVariables fa) `intersection` (factorVariables fb)) ==>
let r = factorProjectOut (factorVariables fb) (factorProduct [fa,fb])
fa' = r `factorDivide` (factorNorm fb)
in
fa' `isomorphicFactor` fa
testScalarProduct_prop :: Double -> CPT -> Bool
testScalarProduct_prop v f = (factorProduct [(Scalar v),f]) `isomorphicFactor` (v `factorScale` f)
testProjectionToScalar_prop :: CPT -> Bool
testProjectionToScalar_prop f =
let allVars = factorVariables f
in
(factorProjectOut allVars f) `isomorphicFactor` (factorFromScalar (factorNorm f))
testProjectCommut_prop:: CPT -> Property
testProjectCommut_prop f = length (factorVariables f) >= 3 ==>
let a = take 1 (factorVariables f)
b = take 1 . drop 1 $ factorVariables f
commuta = factorProjectOut a (factorProjectOut b f)
commutb = factorProjectOut b (factorProjectOut a f)
in
commuta `isomorphicFactor` commutb
isomorphicFactor :: Factor f => f -> f -> Bool
isomorphicFactor fa fb = maybe False (const True) $ do
let va = factorVariables fa
vb = factorVariables fb
guard (va `equal` vb)
guard (factorDimension fa == factorDimension fb)
guard $ and [factorValue fa ia `nearlyEqual` factorValue fb ia | ia <- forAllInstantiations va]
return ()
vname :: Int -> Int -> Box
vname vc i = text $ "v" ++ show vc ++ "=" ++ show i
dispFactor :: FactorPrivate f => f -> DV -> [Int] -> DVSet -> Box
dispFactor cpt h c [] =
let dstIndexes = allValues h
dependentIndexes = reverse c
factorValueAtPosition p =
let v = factorValuePrivate cpt p
in
text . show $ v
in
vsep 0 center1 . map (factorValueAtPosition . (:dependentIndexes)) $ dstIndexes
dispFactor cpt dst c (h@(DV (Vertex vc) i):l) =
hsep 1 top . map (\i -> vcat center1 [vname vc i,dispFactor cpt dst (i:c) l]) $ (allValues h)
instance Show CPT where
show (Scalar v) = "\nScalar Factor:\n" ++ show v
show c@(CPT [] _ v) = "\nEmpty CPT:\n"
show c@(CPT d _ v) =
let h@(DV (Vertex vc) _) = head d
table = dispFactor c h [] (tail d)
dstColumn = vcat center1 $ replicate (length d 1) (text "") ++ map (vname vc) (allValues h)
in
"\n" ++ show d ++ "\n" ++ render (hsep 1 top [dstColumn,table])
instance Factor CPT where
emptyFactor = emptyCPT
isScalarFactor (Scalar _) = True
isScalarFactor _ = False
factorFromScalar v = Scalar v
factorDimension f@(CPT _ _ _) = product . map dimension . factorVariables$ f
factorDimension _ = 1
containsVariable (CPT _ m _) (DV (Vertex i) _) = IM.member i m
containsVariable (Scalar _) _ = False
factorWithVariables = createCPTWithDims
factorVariables (CPT v _ _) = v
factorVariables (Scalar _) = []
factorNorm f@(CPT _ _ _) = sum [ factorValuePrivate f x | x <- forAllIndices (factorVariables f)]
factorNorm (Scalar v) = v
variablePosition (CPT _ m _) (DV (Vertex i) _) = IM.lookup i m
variablePosition (Scalar _) _ = Nothing
factorScale s (Scalar v) = Scalar (s*v)
factorScale s f =
let newValues = map (s *) [ factorValuePrivate f x | x <- forAllIndices (factorVariables f)]
in
fromJust $ factorWithVariables (factorVariables f) newValues
factorValue (Scalar v) _ = v
factorValue f i =
let multiIndex = reorder i f
in
factorValuePrivate f multiIndex
evidenceFrom [] = Nothing
evidenceFrom l =
let index = map instantiationValue l
variables = map instantiationVariable l
setValueForIndex i = if i == index then 1.0 else 0.0
in
factorWithVariables variables . map setValueForIndex $ forAllIndices variables
instance FactorPrivate CPT where
factorValuePrivate = getCPTValue
emptyCPT :: CPT
emptyCPT = CPT [] IM.empty V.empty
indexPosition :: DVSet -> [Int] -> Int
indexPosition [] _ = 0
indexPosition d pos =
let dim = map dimension d
pos' = scanr (*) (1::Int) (tail dim)
c = sum . map (\(x,y) -> x * y) $ (zip pos' pos)
in
c
getCPTValue :: CPT -> [Int] -> Double
getCPTValue (Scalar v) _ = v
getCPTValue cpt@(CPT d _ v) pos = v!(indexPosition d pos)
createCPTWithDims :: DVSet -> [Double] -> Maybe CPT
createCPTWithDims dims values =
let createDVIndex i (DV (Vertex v) _) = (v,i)
m = IM.fromList . zipWith createDVIndex ([0,1..]::[Int]) $ dims
p = product (map dimension dims)
in
if length values == p
then
Just $ CPT dims m (V.fromList values)
else
Nothing