hirt-0.0.1.1: Engine.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Engine where
import Irt
import Statistics
import Types
import qualified Listable as L
import Data.List
import qualified Data.Map as M
import qualified Data.Vector.Generic as V
import Data.VectorSpace
import Text.Printf
data State = State
{ responses :: Responses
, params :: TaskParams
, thetas :: Thetas
, logLikelihood :: Double
, dparam :: TaskParam
, dtheta :: Double
, updatedTheta :: Bool
, updatedParams :: Bool
} deriving (Eq, Ord, Show)
updateOne :: State -> State
updateOne s@State {..} = s { logLikelihood = lh }
where
lh = totalLogLikelihood responses params thetas
updateTwo :: State -> State -> State
updateTwo old new = updateOne $ new { dparam = L.fromList dp, dtheta = dt}
where
[dt] = diff (thetas old) (thetas new)
dp = diff (params old) (params new)
diff xs ys = (max' . trans) (sub xs ys)
where
max' :: [[Double]] -> [Double]
max' = map (maximum . map abs)
trans = transpose . map (L.toList) . V.toList
sub = V.zipWith (^-^)
getTaskParamsList :: State -> [(Task, TaskParam)]
getTaskParamsList State {..} = zip (V.toList $ tasks responses) (V.toList params)
getThetasList :: State -> [(Contestant, Theta)]
getThetasList State {..} = zip (V.toList $ contestants responses) (V.toList thetas)
infinity :: Double
infinity = 1.0/0.0
init :: Responses -> State
init (responses@Responses {..}) = State
{ responses = responses
, params = V.replicate (V.length tasks) defaultTask
, thetas = V.replicate (V.length contestants) 0
, logLikelihood = -infinity
, dparam = L.fromList $ repeat infinity
, dtheta = infinity
, updatedTheta = False
, updatedParams = False
}
data EngineParams = EngineParams
{ algorithm :: Algorithm
, maxRounds :: Int
, precision :: Double
, intPrec :: Double
}
updateThetas :: State -> [(Contestant, Theta)] -> State
updateThetas s@State {..} xs = s
{ thetas = V.fromList . map (ts M.!) . V.toList $ contestants responses
, updatedTheta = True
}
where
ts = M.fromList xs
updateParams :: State -> [(Task, TaskParam)] -> State
updateParams s@State {..} xs = s
{ params = V.fromList . map (ps M.!) . V.toList $ tasks responses
, updatedParams = True
}
where
ps = M.fromList xs
enhanceTaskParams :: EngineParams -> State -> State
enhanceTaskParams EngineParams {..} s@State {..} = s
{ params = estimateAB $ groupByTask responses thetas params
, updatedTheta = False
, updatedParams = True
}
enhanceThetas :: EngineParams -> State -> State
enhanceThetas EngineParams {..} s@State {..} = s
{ thetas = estimateTheta $ groupByContestant responses thetas params
, updatedTheta = True
, updatedParams = False
}
oneJML :: EngineParams -> State -> State
oneJML engine s@State { updatedTheta }
| updatedTheta = enhanceTaskParams engine s
| otherwise = enhanceThetas engine s
oneBFGS :: EngineParams -> State -> State
oneBFGS EngineParams {..} s@State {..} = s
{ thetas = ts
, params = ps
, updatedTheta = True
, updatedParams = True
}
where
(ts,ps) = estimateBfgs precision responses thetas params
oneRound :: EngineParams -> State -> State
oneRound e@EngineParams { algorithm = JML } = oneJML e
oneRound e@EngineParams { algorithm = LBFGSB } = oneBFGS e
dparamC :: TaskParam -> Double
dparamC a = paramC (a ^+^ a) - paramC a
runEngine :: EngineParams -> State -> IO State
runEngine engine@EngineParams {..} s = loop 1 [] s
where
loop n states state@State {..}
| n > maxRounds = return state
| magnitude dparam + dtheta < precision = return state
| otherwise = do
printf "L:%+14.8f " logLikelihood :: IO ()
printf "dA:%11.8f dB:%11.8f dC:%11.8f " (paramA dparam) (paramB dparam) (dparamC dparam) :: IO ()
printf "dT:%11.8f\n" dtheta :: IO ()
loop (n+1) (state:states) $ updateTwo state . oneRound engine $ state
thetaStatistic :: StatisticType -> State -> IO Statistic
thetaStatistic t State {..} = statTheta t $ groupByContestant responses thetas params
taskStatistic :: StatisticType -> State -> IO Statistic
taskStatistic t State {..} = statTask t $ groupByTask responses thetas params