boltzmann-brain-1.3.1.3: Data/Boltzmann/System/Tuner.hs
{-|
Module : Data.Boltzmann.System.Tuner
Description : Interface utilities with the Paganini tuner.
Copyright : (c) Maciej Bendkowski, 2017
License : BSD3
Maintainer : maciej.bendkowski@tcs.uj.edu.pl
Stability : experimental
General utilities managing the IO interface between Boltzmann Brain
and the Paganini tuner script.
-}
module Data.Boltzmann.System.Tuner
( PSolver(..)
, PArg(..)
, defaultArgs
, writeSpecification
, readPaganini
, runPaganini
) where
import Control.Monad
import Control.Exception
import System.IO
import System.Exit
import System.Process hiding (system)
import Text.Megaparsec
import Text.Megaparsec.String
import Numeric.LinearAlgebra hiding (size,double)
import Data.Map (Map)
import qualified Data.Map.Strict as M
import Data.MultiSet (MultiSet)
import qualified Data.MultiSet as B
import qualified Data.Set as S
import Data.Maybe
import Data.Boltzmann.System
import Data.Boltzmann.Internal.Parser
-- | Catch IO exceptions.
try' :: IO a -> IO (Either IOException a)
try' = Control.Exception.try
-- | Paganini convex program solvers.
data PSolver = SCS
| ECOS
| CVXOPT
instance Show PSolver where
show SCS = "SCS"
show ECOS = "ECOS"
show CVXOPT = "CVXOPT"
-- | Paganini arguments.
data PArg = PArg { solver :: PSolver
, precision :: Double
, maxiters :: Int
, sysType :: SystemType
} deriving (Show)
toArgs :: PArg -> [String]
toArgs arg = ["-s", show (solver arg)
,"-p", show (precision arg)
,"-m", show (maxiters arg)
,"-t", show (sysType arg)]
rationalArgs :: PArg
rationalArgs = PArg { solver = SCS
, precision = 1.0e-20
, maxiters = 2500
, sysType = Rational
}
algebraicArgs :: PArg
algebraicArgs = PArg { solver = ECOS
, precision = 1.0e-20
, maxiters = 20
, sysType = Algebraic
}
-- | Determines default Paganini arguments.
-- Note: It is assumed that the given system is either
-- rational or algebraic. Otherwise, and error is raised.
defaultArgs :: System a -> PArg
defaultArgs sys =
case systemType sys of
Rational -> rationalArgs
Algebraic -> algebraicArgs
_ -> error "Unsupported"
logger :: String -> IO ()
logger s = hPutStrLn stderr ("[LOG] " ++ s)
writeListLn :: Show a => Handle -> [a] -> IO ()
writeListLn h xs = hPutStrLn h (showsList xs)
printer :: Show a => (a -> String -> String) -> [a] -> String
printer _ [] = ""
printer f xs = foldl1 (\a b -> (a . (" " ++) . b))
(map f xs) ""
showsList :: Show a => [a]
-> String
showsList = printer shows
-- | Writes the system specification into the given
-- file handle. In paricular, to Paganini's standard
-- input handle.
writeSpecification :: System Int -> Handle -> IO ()
writeSpecification sys hout = do
logger "Writing specification"
let freqs = frequencies sys
let seqs = seqTypes sys
let spec = toPSpec sys
-- # of equations and frequencies
writeListLn hout [numTypes spec + numSeqTypes spec
,length freqs]
-- vector of frequencies
writeListLn hout freqs
-- type specifications
let find' x = x `S.findIndex` M.keysSet (defs sys)
foldM_ (typeSpecification hout find' spec) 0 (M.elems $ defs sys)
-- sequence specifications
mapM_ (seqSpecification hout find' spec) seqs
logger "Finished writing specification"
getArgs :: System Int -> Maybe PArg -> PArg
getArgs sys = fromMaybe (defaultArgs sys)
handleIOEx :: String -> IO a
handleIOEx ex = do
hPutStrLn stderr $ "[ERROR] " ++ ex
exitWith (ExitFailure 1)
-- | Communicates with Paganini and collects the respective
-- tuning vector for the given system. If communication is not possible,
-- for instance due to the missing Paganini script, the current process
-- is terminated with an error message on the standard error output.
runPaganini :: System Int -> Maybe PArg
-> IO (Either (ParseError Char Dec)
(PSystem Double))
runPaganini sys arg = do
logger "Running paganini"
let arg' = getArgs sys arg
logger (printer (++) $ "Arguments: " : toArgs arg')
-- Execute the paganini tuning script.
pp <- try' $ createProcess (proc "paganini" (toArgs arg')){ std_out = CreatePipe
, std_in = CreatePipe }
case pp of
Left _ -> handleIOEx "Could not locate the paganini tuner. Is is available in the PATH?"
Right (Just hin, Just hout, _, _) -> do
-- write to paganini's stdout
writeSpecification sys hin
-- read output parameters
s <- hGetContents hout
let spec = toPSpec sys
let pag = parse (paganiniStmt spec) "" s
case pag of
Left err -> return $ Left err
Right (rho, us, ts) -> do
logger "Parsed paganini output"
let ts' = fromList ts
let sys' = parametrise sys rho ts' us
logger "Finished"
return $ Right sys'
_ -> handleIOEx "Could not establish inter-process communication with paganini."
-- | Parses the given input string as a Paganini tuning vector.
readPaganini :: System Int -> String
-> IO (Either (ParseError Char Dec)
(PSystem Double))
readPaganini sys f = do
let spec = toPSpec sys
pag <- parsePaganini spec f
case pag of
Left err -> return $ Left err
Right (rho, us, ts) -> do
let ts' = fromList ts
return (Right $ parametrise sys rho ts' us)
frequencies :: System Int -> [Double]
frequencies sys = concatMap (mapMaybe frequency)
((M.elems . defs) sys)
-- | Paganini helper specification.
data PSpec = PSpec { numFreqs :: Int
, numTypes :: Int
, numSeqTypes :: Int
}
toPSpec :: System Int -> PSpec
toPSpec sys = PSpec { numFreqs = d
, numTypes = ts
, numSeqTypes = ss
}
where ts = size sys
d = length $ frequencies sys
ss = length $ seqTypes sys
typeSpecification :: Handle -> (String -> Int) -> PSpec
-> Int -> [Cons Int] -> IO Int
typeSpecification hout find' spec idx cons = do
let n = length cons
hPrint hout n -- # of constructors
foldM (consSpecification hout find' spec) idx cons
consSpecification :: Handle -> (String -> Int) -> PSpec
-> Int -> Cons Int -> IO Int
consSpecification hout find' spec idx cons = do
let (vec, idx') = consVec find' spec idx cons
writeListLn hout vec -- constructor specification
return idx'
indicator :: Int -> Int -> [Int]
indicator n k = indicator' n k 1
indicator' :: Int -> Int -> Int -> [Int]
indicator' 0 _ _ = []
indicator' n 0 x = x : replicate (n-1) 0
indicator' n k x = 0 : indicator' (n-1) (k-1) x
occurrences :: Cons a -> (MultiSet String, MultiSet String)
occurrences cons = occurrences' (B.empty,B.empty) $ args cons
occurrences' :: (MultiSet String, MultiSet String)
-> [Arg] -> (MultiSet String, MultiSet String)
occurrences' (ts,sts) [] = (ts,sts)
occurrences' (ts,sts) (Type s : xs) = occurrences' (s `B.insert` ts,sts) xs
occurrences' (ts,sts) (List s : xs) = occurrences' (ts, s `B.insert` sts) xs
consVec :: (String -> Int) -> PSpec -> Int -> Cons Int -> ([Int], Int)
consVec find' spec idx cons =
let (tocc, socc) = occurrences cons
w = fromIntegral $ weight cons
dv = indicator' (numFreqs spec) idx (weight cons)
tv = typeVec find' (numTypes spec) tocc
sv = typeVec find' (numSeqTypes spec) socc
in case frequency cons of
Just _ -> (w : dv ++ tv ++ sv, idx + 1)
Nothing -> (w : replicate (numFreqs spec) 0 ++ tv ++ sv, idx)
typeVec :: (String -> Int) -> Int -> MultiSet String -> [Int]
typeVec find' size' m = typeVec' find' vec ls
where vec = M.fromList [(n,0) | n <- [0..size'-1]]
ls = B.toOccurList m
typeVec' :: (String -> Int) -> Map Int Int -> [(String,Int)] -> [Int]
typeVec' _ vec [] = M.elems vec
typeVec' find' vec ((t,n) : xs) = typeVec' find' vec' xs
where vec' = M.insert (find' t) n vec
seqSpecification :: Handle -> (String -> Int) -> PSpec
-> String -> IO ()
seqSpecification hout find' spec st = do
let n = 1 + numTypes spec + numFreqs spec + numSeqTypes spec
let f = replicate (numFreqs spec) 0
let t = indicator (numTypes spec) (find' st)
let s = indicator (numSeqTypes spec) (find' st)
hPrint hout (2 :: Int) -- # of constructors
writeListLn hout $ replicate n (0 :: Int)
writeListLn hout $ 0 : f ++ t ++ s
paganiniStmt :: PSpec -> Parser (Double, [Double], [Double])
paganiniStmt spec = do
rho <- double
us <- parseN double $ numFreqs spec
ts <- parseN double $ numTypes spec
return (rho, us, ts)
-- | Parses the given Paganini specification.
parsePaganini :: PSpec -> String
-> IO (Either (ParseError Char Dec)
(Double, [Double], [Double]))
parsePaganini spec = parseFromFile (paganiniStmt spec)
-- | Compute the numerical Boltzmann probabilities for the given system.
parametrise :: System Int -> Double -> Vector Double -> [Double] -> PSystem Double
parametrise sys rho ts us = PSystem { system = computeProb sys rho ts us
, values = ts
, param = rho
, weights = sys
}
evalExp :: System Int -> Double -> Vector Double
-> [Double] -> Cons Int -> (Double, [Double])
evalExp sys rho ts us exp' =
let w = weight exp'
xs = args exp'
exp'' = (rho ^^ w) * product (map (evalA sys ts) xs)
in case frequency exp' of
Nothing -> (exp'', us)
Just _ -> (head us ^^ w * exp'', tail us)
computeExp :: System Int -> Double -> Vector Double
-> [Double] -> Double -> Double -> [Cons Int]
-> ([Cons Double], [Double])
computeExp _ _ _ us _ _ [] = ([], us)
computeExp sys rho ts us tw w (e:es) = (e { weight = x / tw } : es', us'')
where (es', us'') = computeExp sys rho ts us' tw x es
(w', us') = evalExp sys rho ts us e
x = w + w'
computeProb' :: System Int -> Double -> Vector Double
-> [Double] -> [(String, [Cons Int])]
-> [(String, [Cons Double])]
computeProb' _ _ _ _ [] = []
computeProb' sys rho ts us ((t,cons):tys) = (t,cons') : tys'
where (cons', us') = computeExp sys rho ts us (value t sys ts) 0.0 cons
tys' = computeProb' sys rho ts us' tys
computeProb :: System Int -> Double -> Vector Double -> [Double] -> System Double
computeProb sys rho ts us = sys { defs = M.fromList tys }
where tys = computeProb' sys rho ts us (M.toList $ defs sys)