packages feed

experimenter-0.1.0.4: src/Experimenter/Eval/Latex.hs

{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE DeriveAnyClass      #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE Strict              #-}
{-# LANGUAGE ViewPatterns        #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Experimenter.Eval.Latex
    ( writeLatex
    , writeAndCompileLatex
    ) where

import           Control.Arrow                ((&&&))
import           Control.DeepSeq
import           Control.Lens                 hiding ((&))
import           Control.Monad                (forM, unless, void)
import           Control.Monad.IO.Class
import           Control.Monad.Logger
import           Data.List                    as L (find, foldl')
import qualified Data.Map.Strict              as M
import qualified Data.Serialize               as S
import qualified Data.Text                    as T
import           GHC.Generics
import           System.Directory
import           System.FilePath.Posix
import           System.Posix.User
import           System.Process
import           Text.LaTeX
import           Text.LaTeX.Packages.AMSMath
import           Text.LaTeX.Packages.Hyperref
import           Text.LaTeX.Packages.Inputenc
import           Text.LaTeX.Packages.TabularX

import           Experimenter.Availability
import           Experimenter.DatabaseSetting
import           Experimenter.DB
import           Experimenter.Eval.Table
import           Experimenter.Eval.Type
import           Experimenter.Eval.Util
import           Experimenter.Models
import           Experimenter.Parameter.Type
import           Experimenter.Result.Type
import           Experimenter.Setting         (ExperimentInfoParameter (..))
import           Experimenter.Util


import           Debug.Trace

writeAndCompileLatex :: DatabaseSetting -> Evals a -> IO ()
writeAndCompileLatex dbSetup evals = writeLatex dbSetup evals >> compileLatex evals

compileLatex :: Evals a -> IO ()
compileLatex evals = do
  let exps = evals ^. evalsExperiments
      dir = expsPath exps
      n = getExpsName exps
  void $ runProcess "pdflatex" [mainFile exps] (Just dir) Nothing Nothing Nothing Nothing >>= waitForProcess
  pwd <- getCurrentDirectory
  putStrLn $ "n\nSuccessfully compiled your results! See file://" <> pwd <> "/results/" <> n <> "/" <> mainFilePdf exps


writeLatex :: DatabaseSetting -> Evals a -> IO ()
writeLatex dbSetup evals = do
  let exps = evals ^. evalsExperiments
      dir = expsPath exps
      file = dir </> mainFile exps
  liftIO $ createDirectoryIfMissing True dir
  res <- runDBSimple dbSetup $ execLaTeXT (root evals)
  renderFile file res


root :: Evals a -> LaTeXT SimpleDB ()
root evals = do
  thePreamble evals
  document $ theBody evals


-- Preamble with some basic info.
thePreamble :: (MonadIO m) => Evals a -> LaTeXT m ()
thePreamble evals = do
  let n = evals ^. evalsExperiments . experimentsName
  documentclass [] article
  user <- liftIO getLoginName
  author ("Username: " <> fromString user)
  title $ "Evaluation for ``" <> raw n <> "''"
  usepackage [utf8] inputenc
  usepackage [] "fullpage"
  usepackage [] "array"
  usepackage [] amsmath
  usepackage [pdftex] hyperref
  usepackage [] tabularxp

-- Body with a section.
theBody :: Evals a -> LaTeXT SimpleDB ()
theBody evals = do
  maketitle
  experimentsInfo (evals ^. evalsExperiments)
  experimentsEvals evals


experimentsInfo :: (MonadLogger m) => Experiments a -> LaTeXT m ()
experimentsInfo exps = do
  part "General Information"
  printTable $ Table (Row ["Parameter", "Value"])
    [ Row ["Experiment Name: ",                           CellT (exps ^. experimentsName)]
    , Row ["Start time:",                                 CellT (tshow $ exps ^. experimentsStartTime)]
    , Row ["End time:",                                   CellT (maybe "" tshow (exps ^. experimentsEndTime))]
    , Row ["Number of conducted Experiments: ",           CellT (tshow $ length (exps ^. experiments))]
    , Row ["Experiment Repetitions (complete restarts):", CellT (tshow $ exps ^. experimentsSetup . expsSetupRepetitions)]
    , Row ["Experiment Preparation Steps:",               CellT (tshow $ exps ^. experimentsSetup . expsSetupPreparationSteps)]
    , Row ["Experiment Evaluation Warm Up Steps:",        CellT (tshow $ exps ^. experimentsSetup . expsSetupEvaluationWarmUpSteps)]
    , Row ["Experiment Evaluation Steps:",                CellT (tshow $ exps ^. experimentsSetup . expsSetupEvaluationSteps)]
    , Row ["Experiment Evaluation Replications:",         CellT (tshow $ exps ^. experimentsSetup . expsSetupEvaluationReplications)]
    ]

  let infoParams = trace( "Info Params: " ++ show (length $ exps ^. experimentsInfoParameters)) $ exps ^. experimentsInfoParameters


  part "Experiment Information Parameters"
  if null infoParams
    then center $ text "No Information Parameters were defined in the experiment setup"
    else printTextwidthTable $ Table (Row ["Parameter", "Value"]) (map mkInfoParam infoParams)

  where mkInfoParam (ExperimentInfoParameter n v) = Row [CellT n, CellT (tshow v)]


experimentsEvals :: Evals a -> LaTeXT SimpleDB ()
experimentsEvals evals = do
  pagebreak (pure "4")
  part "Experiment Evaluations"
  !(force -> tablesP) <- lift $! mkResultTablesFor UnitPeriods evals
  !(force -> tablesR) <- lift $! mkResultTablesFor UnitReplications evals
  !(force -> tablesE) <- lift $! mkResultTablesFor UnitExperimentRepetition evals
  !(force -> tablesS) <- lift $! mkResultTablesFor UnitScalar evals
  let tables = tablesP <> tablesR <> tablesE <> tablesS
      mParams = M.fromList $ map (view evalExperimentNumber &&& paramSettingTable evals) (evals ^. evalsResults)
  writeTables mParams (force tables)


data EvalTables a = EvalTables
  { periodic               :: ![(Int, [(StatsDef a, Table)])]
  , replications           :: ![(Int, [(StatsDef a, Table)])]
  , experimentReplications :: ![(Int, [(StatsDef a, Table)])]
  , numbers                :: ![(Int, [(StatsDef a, Table)])]
  } deriving (Show, Generic, NFData)

instance Semigroup (EvalTables a) where
  EvalTables ap ar ae an <> EvalTables bp br be bn = EvalTables (ap <> bp) (ar <> br) (ae <> be) (an <> bn)

instance Monoid (EvalTables a) where
  mempty = EvalTables [] [] [] []


mkResultTablesFor :: Unit -> Evals a -> SimpleDB [EvalTables a]
mkResultTablesFor unit evals =
  fmap force $!
  forM (evals ^. evalsResults) $ \eval@(ExperimentEval _ avRes _) ->
    fmap (force . mconcat) $!
    forM avRes $ \av -> do
      res <- mkTransientlyAvailable av
      return $
        let ~tbl = mkExperimentTable evals (leastUnit res, eval, [res])
         in case leastUnit res of
              UnitPeriods              -> EvalTables [tbl | unit == UnitPeriods] [] [] []
              UnitReplications         -> EvalTables [] [tbl | unit == UnitReplications] [] []
              UnitExperimentRepetition -> EvalTables [] [] [tbl | unit == UnitExperimentRepetition] []
              UnitScalar               -> EvalTables [] [] [] [tbl | unit == UnitScalar]


writeTables :: M.Map Int (Maybe Table) -> [EvalTables a] -> LaTeXT SimpleDB ()
writeTables params !(force -> tables) = do
  void $ writeTableFor "Scalar Value" (concatMap numbers tables)
  void $ writeTableFor "Repetition" (concatMap experimentReplications tables)
  void $ writeTableFor "Replications" (concatMap replications tables)
  void $ writeTableFor "Periodic" (concatMap periodic tables)
  where
    writeTableFor :: LaTeXT SimpleDB () -> [(Int, [(StatsDef a, Table)])] -> LaTeXT SimpleDB [()]
    writeTableFor name tbls = do
      section (name <> " Evaluations")
      forM (M.keys params) $ \k -> do
        let tblsFiltered = filter  (( == k) . fst) tbls
        unless (null tblsFiltered) $ do
          subsection $ "Experiment No. " <> raw (tshow k)
          maybe "There are no configured parameters!" printTextwidthTable (M.findWithDefault Nothing k params)
          mapM_ (\(_, tbls') -> forM tbls' $ \statsDefTbl -> printTableWithName statsDefTbl) tblsFiltered


printTableWithName :: (MonadLogger m) => (StatsDef a, Table) -> LaTeXT m ()
printTableWithName (nm, tbl) = do
  paragraph (raw $ prettyStatsDef nm)
  printTable tbl


leastUnit :: EvalResults a -> Unit
leastUnit (EvalValue _ u _ _ _)    = u
leastUnit (EvalReducedValue _ u _) = u
leastUnit (EvalVector _ u [])      = u
leastUnit (EvalVector _ u vals)    = min u (leastUnit (head vals))


unpackUntil :: Unit -> EvalResults a -> [EvalResults a]
unpackUntil unit res | res ^. evalUnit == unit = [res]
                     | otherwise = case res of
                         EvalVector{} -> concatMap (unpackUntil unit) (res ^. evalValues)
                         _            -> error $ "cannot unpack res: " <> show res

mkNamesUntil :: Unit -> EvalResults a -> [Text]
mkNamesUntil unit res
  | res ^. evalUnit == unit = [mempty]
  | otherwise =
    case res of
      EvalVector _ u vals ->
        case u of
          UnitPeriods -> [mempty]
          UnitReplications -> map (\x -> "Rpl " <> tshow x <> ": ") [1 .. length vals]
          _ ->
            let sub = head $ map (mkNamesUntil unit) vals
            in concatMap (\x -> map (\s -> if T.isInfixOf "Rpt" s then s else "Rpt " <> tshow x <> ": " <> s) sub) [1 .. length vals]
      _ -> error $ "cannot unpack res: " <> show res


mkExperimentTable :: Evals a -> (Unit, ExperimentEval a, [EvalResults a]) -> (Int, [(StatsDef a, Table)])
mkExperimentTable _ (lowestUnit, eval, res) =
  let resUnit = map (\rs -> (mkNames rs, unpackUntil lowestUnit rs)) res
      tableRes = map (uncurry (zipWith (mkEvalResult lowestUnit))) resUnit
      tbls = map toTables tableRes
      mkNames = map (map CellT . return) . mkNamesUntil lowestUnit
      evalStatDefs = map (^. evalType) res
   in (eval ^. evalExperimentNumber, zip evalStatDefs tbls)


data TableResult = TableResult
  { header :: !Row
  , rows   :: ![Row]
  } deriving (Show)

toTables :: [TableResult] -> Table
toTables xs = Table (header $ head xs) (concatMap rows xs)

mkEvalResult :: Unit -> [Cell] -> EvalResults a -> TableResult
mkEvalResult _ _ (EvalVector _ _ []) = error "Empty evaluation. Check your eval setup."
mkEvalResult leastUnit' name (EvalVector _ _ vals) =
  TableResult (Row $ CellT (unitName leastUnit') : map (CellT . tshow) [1 .. length vals]) (map Row (foldl' mkRows [name] rowVals))
  where
    subVals = map (mkEvalResult leastUnit' []) vals
    rowVals = map rows subVals
    mkRows :: [[Cell]] -> [Row] -> [[Cell]]
    mkRows accs vs = zipWith (++) accs (map fromRow vs)
    fromRow (Row xs) = xs
    unitName UnitPeriods              = "Period:"
    unitName UnitReplications         = "Replication:"
    unitName UnitExperimentRepetition = "Experiment Repetition:"
    -- unitName (UnitBestExperimentRepetitions bestNr) = "Best " <> tshow bestNr <> " Experiment Repetitions:"
    unitName UnitScalar               = "Value:"
mkEvalResult _ [] (EvalValue _ _ _ x y) = TableResult (Row [getXValue x]) [Row [CellD y]]
  where getXValue (Left v)  = CellT $ tshow v
        getXValue (Right d) = CellD d
mkEvalResult _ (n1:_) (EvalValue _ _ _ x y) = TableResult (Row [CellEmpty, getXValue x]) [Row [n1, CellD y]]
  where getXValue (Left v)  = CellT $ tshow v
        getXValue (Right d) = CellD d
mkEvalResult _ [] (EvalReducedValue statsDef _ y) = TableResult (Row [CellT $ tshow statsDef]) [Row [CellD y]]
mkEvalResult _ (n:_) (EvalReducedValue statsDef _ y) = TableResult (Row [CellEmpty , CellT $ prettyStatsDef statsDef]) [Row [n, CellD y]]


paramSettingTable :: Evals a -> ExperimentEval a -> Maybe Table
paramSettingTable evals (ExperimentEval _ _ exp)
  | null (exp ^. parameterSetup) = Nothing
  | otherwise = Just $ Table (Row ["Parameter", "Value"]) (concatMap mkRow (exp ^. parameterSetup))
  where
    dropRow :: Row
    dropRow = Row [CellT "Skip Preparation Phase", CellT "True (No preparation phase was executed!)"]
    singleInstanceRow :: Text -> Row
    singleInstanceRow n = Row [CellT "Run a single instance", CellT $ "True (No further variations needed as specified by parameter " <> n <> "!)"]
    mkRow :: ParameterSetting a -> [Row]
    mkRow (ParameterSetting n bsV drp design) =
      case find ((== n) . parameterName) (evals ^. evalsExperiments . experimentsParameters) of
        Nothing ->
          Row
            [ CellT n
            , CellT $ "was not modified as it is not listed in the parameter setting" <>
              (if drp
                 then " [SkipPrepPhase]"
                 else "") <>
              (case design of
                 FullFactory    -> ""
                 SingleInstance -> "[SingleInstance]")
            ] :
          [dropRow | drp] ++
          [singleInstanceRow n | drp]
        Just (ParameterSetup _ setter _ _ mBounds _ _) ->
          case S.runGet S.get bsV of
            Left err -> [Row [CellT n, CellT (T.pack err)]]
            Right val ->
              let _ = setter val (evals ^. evalsExperiments . experimentsInitialState) -- only needed for type inference
               in Row
                    [ CellT n
                    , CellL $ raw (dereferLatex $ tshow val) <>
                      (case mBounds of
                         Nothing -> ""
                         Just (minVal, maxVal) -> math (text " " `in_` autoParens (text (raw (dereferLatex $ tshow minVal)) <> ", " <> text (raw (dereferLatex $ tshow maxVal))))) <>
                      (if drp
                         then " [SkipPrepPhase]"
                         else mempty) <>
                      (case design of
                         FullFactory    -> ""
                         SingleInstance -> "[SingleInstance]")
                    ] :
                  [dropRow | drp] ++
                  [singleInstanceRow n | drp]