packages feed

boltzmann-brain-1.6: Data/Boltzmann/Compiler/Haskell/Rational.hs

{-|
 Module      : Data.Boltzmann.Compiler.Haskell.Matrix
 Description : Rational Boltzmann system compiler for GHC.
 Copyright   : (c) Maciej Bendkowski, 2017-2019

 License     : BSD3
 Maintainer  : maciej.bendkowski@tcs.uj.edu.pl
 Stability   : experimental

 Transition matrix system compiler for rational specifications.
 The outcome sampler is a rejection-based sampler implementing the
 interruptible sampling scheme for strongly connected specifications.
 Internally, the system is represented as a adjacency-list graph with
 additional labels on edges (transition letters).
 -}
module Data.Boltzmann.Compiler.Haskell.Rational
    ( Conf(..)
    , compile
    , config
    ) where

import Prelude hiding (and)

import Language.Haskell.Exts hiding (List,Cons)
import qualified Language.Haskell.Exts as LHE

import qualified Data.Set as S
import qualified Data.Map.Strict as M

import Data.Maybe (fromMaybe)

import Data.Boltzmann.System
import Data.Boltzmann.Internal.Annotations
import Data.Boltzmann.Internal.Utils (getTime)

import Data.Boltzmann.Compiler
import Data.Boltzmann.Compiler.Haskell.Helpers

-- | Default configuration type.
data Conf = Conf { paramSys    :: PSystem Double   -- ^ Parametrised system.
                 , moduleName  :: String           -- ^ Module name.
                 , compileNote :: String           -- ^ Header comment note.
                 , withIO      :: Bool             -- ^ Generate IO actions?
                 }

instance Configuration Conf where

    config sys module' compilerNote' =
         let with = withBool (annotations $ system sys)
         in Conf { paramSys    = sys
                 , moduleName  = module'
                 , compileNote = compilerNote'
                 , withIO      = "withIO" `with` True
                 }

    compile conf = let sys        = paramSys conf
                       name'      = moduleName conf
                       note       = compileNote conf
                       withIO'    = withIO conf
                       module'    = compileModule sys name' withIO'
                   in do
                       time <- getTime
                       putStr $ moduleHeader sys note time
                       putStrLn $ prettyPrint module'

moduleHeader :: PSystem Double -> String -> String -> String
moduleHeader sys compilerNote time =
    unlines (["-- | Compiler:     " ++ compilerNote,
              "-- | Generated at: " ++ time,
              "-- | Singularity:  " ++ show (param sys)]
              ++ systemNote sys (show Rational))

compileModule :: PSystem Double -> String -> Bool -> Module ()
compileModule sys mod' withIO' =
    Module ()
    (Just $ ModuleHead () (ModuleName () mod') Nothing
         (Just . ExportSpecList () $ declareExports withIO'))
    [LanguagePragma () [Ident () "TemplateHaskell"]]
    (declareImports withIO')
    (decls sys withIO')

declareExports :: Bool -> [ExportSpec ()]
declareExports withIO' =
    exportFunc "sampleWord"
        : exportFunc "startingState"
        : [exportFunc "sampleWordIO" | withIO']

declareImports :: Bool -> [ImportDecl ()]
declareImports withIO' =
    [importFrom "Control.Monad.Trans" [importFunc "lift"],
     importFrom "Control.Monad.Trans.Maybe" [importType "MaybeT",
                                             importFunc "runMaybeT"],

    importFrom "Data.Buffon.Machine" ([importType' "BuffonMachine",
                                        importType "DecisionTree",
                                        importFunc "decisionTree",
                                        importFunc "choice"]
                                        ++ importIO withIO'),

     importQual "Language.Haskell.TH.Syntax" "TH",

     importFrom "System.Random" [importType "RandomGen"]]

importIO :: Bool -> [ImportSpec ()]
importIO withIO' = [importFunc "runRIO" | withIO']

decls :: PSystem Double -> Bool -> [Decl ()]
decls sys withIO' = symbolDecl :
                    declWeight sys
                    ++ declSymb sys
                    ++ declDecisionTrees sys
                    ++ declTerminals sys
                    ++ declGraph sys
                    ++ declGen
                    ++ declSampler
                    ++ declStartingState sys
                    ++ concat [declSamplerIO | withIO']

-- | Type synonym for alphabet letters.
symbolDecl :: Decl ()
symbolDecl = TypeDecl () (DHead () (Ident () "Symbol")) (TyCon () $ unname "String")

-- | Converts the given system into
--   corresponding a graph representation.
toGraph :: PSystem Double -> [[(Int, Int)]]
toGraph sys = map (typeAdj $ types sys') typs
    where typs = M.toList (defs sys')
          sys' = system sys
          lts  = alphabet sys'

          typeAdj typs' (_, cons) = map (consAdj typs') cons

          -- | Assigns the given constructor a reference index,
          --   pointing to the following node (type).
          typeIdx typs' con
              | isAtomic con = -1 -- note: epsilon transition.
              | otherwise = let typ = argName (head $ args con)
                                  in typ `S.findIndex` typs'

          consAdj typs' con = (n, w)
              where a = func con -- note: that's in fact the transition letter.
                    n = typeIdx typs' con
                    w = fromMaybe (-1) (a `lookupLetter` lts)

buffonMachineType :: Type ()
buffonMachineType = typeCons "BuffonMachine"

maybeTType :: Type () -> Type ()
maybeTType = TyApp () (TyApp () maybeT' (TyApp () buffonMachineType g'))

letters :: PSystem Double -> [Letter]
letters = S.toList . alphabet . system

getWeights :: Int -> [Letter] -> [(Int, Rhs ())]
getWeights n (s : xs) = (n, UnGuardedRhs () $ toLit (weightL s)) : xs'
    where xs' = getWeights (succ n) xs

getWeights _ _ = []

getSymbols :: Int -> [Letter] -> [(Int, Rhs ())]
getSymbols n (s : xs) = (n, UnGuardedRhs () $ toString (symb s)) : xs'
    where xs' = getSymbols (succ n) xs

getSymbols _ _ = []

-- | Symbol weights.
declWeight :: PSystem Double -> [Decl ()]
declWeight sys = declTFun "weight" type' ["n"] body
    where type' = TyFun () int' int'
          body  = caseInt "n" $ getWeights 0 (letters sys)

-- | Symbol strings.
declSymb :: PSystem Double -> [Decl ()]
declSymb sys = declTFun "symbol" type' ["n"] body
    where type' = TyFun () int' (typeCons "Symbol")
          body  = caseInt "n" $ getSymbols 0 (letters sys)

getDecisionTree :: (String, [(Cons Double, Int)]) -> Exp ()
getDecisionTree (_, g) = spliceExp lift'
    where lift' = applyF (qVarExp "TH" "lift") [dt']
          dt'   = applyF (varExp "decisionTree") [prob]
          prob  = LHE.List () (init $ probList g)

getDecitionTrees :: Int -> [(String, [(Cons Double, Int)])] -> [(Int, Rhs ())]
getDecitionTrees n (s : xs) = (n, UnGuardedRhs () $ getDecisionTree s) : xs'
    where xs' = getDecitionTrees (succ n) xs

getDecitionTrees _ _ = []

declDecisionTrees :: PSystem Double -> [Decl ()]
declDecisionTrees sys = declTFun "decisionTrees" type' ["s"] body
    where type' = TyFun () int' decisionTreeType
          body = caseInt "s" $ getDecitionTrees 0 (paramTypesW sys)

getTerminals :: Int -> [(String, [(Cons Double, Int)])] -> [(Int, Rhs ())]
getTerminals n (s : xs) = (n, UnGuardedRhs () $ getTerminal s) : xs'
    where xs' = getTerminals (succ n) xs

getTerminals _ _ = []

getTerminal :: (String, [(Cons Double, Int)]) -> Exp ()
getTerminal (_, g)
    | any (isAtomic . fst) g = conExp "True"
    | otherwise = conExp "False"

declTerminals :: PSystem Double -> [Decl ()]
declTerminals sys = declTFun "isTerminal" type' ["s"] body
    where type' = TyForall () Nothing Nothing (TyFun () int' (typeCons "Bool"))
          body = caseInt "s" $ getTerminals 0 (paramTypesW sys)

declGraph :: PSystem Double -> [Decl ()]
declGraph sys = declTFun "transitionMatrix" type' ["n", "m"] body
    where type' = TyForall () Nothing Nothing (TyFun () int'
                        (TyFun () int' (TyTuple () Boxed [int', int'])))

          graph = toGraph sys
          body = caseInt "n" [(i, UnGuardedRhs () $ getNeighbourhood (graph !! i))
                                                    | i <- [0..pred (length graph)]]

getNeighbourhood :: [(Int, Int)] -> Exp ()
getNeighbourhood xs =
    caseInt "m" (getNeighbourhood' 0 xs)

getNeighbourhood' :: Int -> [(Int, Int)] -> [(Int, Rhs ())]
getNeighbourhood' n ((k,w) : xs) = (n, UnGuardedRhs () p) : xs'
    where xs' = getNeighbourhood' (succ n) xs
          p = Tuple () Boxed [toLit k, toLit w]

getNeighbourhood' _ _ = []

declGen :: [Decl ()]
declGen = declTFun "genWord" type' ["ub", "s", "acc", "ct"] mainIfStmt
    where type' = TyForall () Nothing (Just $ CxTuple () [ClassA () randomGen' [g']])
            (TyFun () int' $ TyFun () int' $ TyFun () (TyList () $ typeCons "Symbol")
                $ TyFun () int' (maybeTType $ TyTuple () Boxed
                    [TyList () $ typeCons "Symbol", int']))

          mainIfStmt = If () (varExp "ub" `lessEq` toLit 0
                            `and` applyF (varExp "isTerminal") [varExp "s"])
                         (App () return' (Tuple () Boxed [varExp "acc", varExp "ct"]))
                         mainBody

          mainBody = Do () [ choiceStmt
                        , getNext
                        , ifStmt]

          choiceStmt = choiceN "n" (applyF (varExp "decisionTrees") [varExp "s"])

          getNext = LetStmt () (BDecls () [getNext'])
          getNext' = PatBind ()
                        (PTuple () Boxed [PVar () $ Ident () "s'", PVar () $ Ident () "i"])
                        (UnGuardedRhs () $ applyF (varExp "transitionMatrix")
                                            [varExp "s", varExp "n"]) Nothing

          ifStmt = Qualifier () $ If () (less (varExp "s'") (toLit 0))
                      (App () return' (Tuple () Boxed [varExp "acc", varExp "ct"])) elseStmt

          elseStmt = Do () [ bindSymbol
                        , recursiveCall]

          bindSymbol = LetStmt () (BDecls () [bindSymbol'])
          bindSymbol' = PatBind ()
                            (PTuple () Boxed [PVar () $ Ident () "a", PVar () $ Ident () "w"])
                        (UnGuardedRhs () $
                            Tuple ()  Boxed [ applyF (varExp "symbol") [varExp "i"]
                                        , applyF (varExp "weight") [varExp "i"]])
                                             Nothing

          recursiveCall = Qualifier () $ applyF (varExp "genWord")
                            [varExp "ub" `sub` varExp "w", varExp "s'",
                              InfixApp () (varExp "a") (symbol ":") (varExp "acc"),
                              varExp "w" `add` varExp "ct"]


declSampler :: [Decl ()]
declSampler = declTFun "sampleWord" type' ["lb", "ub", "s"] constructSampler
    where type' = TyForall () Nothing (Just $ CxTuple () [ClassA () randomGen' [g']])
            (TyFun () int' $ TyFun () int' $ TyFun () int' $ TyApp () (TyApp () buffonMachineType g')
                (TyList () $ typeCons "Symbol"))

constructSampler :: Exp ()
constructSampler =
    Do () [bind "str" (applyF (varExp "runMaybeT")
            [applyF (varExp "genWord") [varExp "lb", varExp "s", LHE.List () [], toLit 0]]),
            caseSample]
    where caseSample = Qualifier () $ Case () (varExp "str")
                 [Alt () (PApp () (unname "Nothing") [])
                        (UnGuardedRhs () rec') Nothing,
                        Alt () (PApp () (unname "Just")
                 [PTuple () Boxed [PVar () $ Ident () "w",
                  PVar () $ Ident () "n"]])
                  (UnGuardedRhs () return'') Nothing]

          rec' = applyF (varExp "sampleWord") [varExp "lb", varExp "ub", varExp "s"]
          return'' = If () (lessEq (varExp "lb") (varExp "n") `and` lessEq (varExp "n") (varExp "ub"))
                        (applyF (varExp "return") [varExp "w"])
                        rec'

declSamplerIO :: [Decl ()]
declSamplerIO = declTFun "sampleWordIO" type' ["lb","ub", "s"] body
    where body  = constructSamplerIO
          type' = TyForall () Nothing Nothing
                    (TyFun () int' (TyFun () int'
                                    (TyFun () int'
                                     (TyApp () (typeVar "IO")
                                      (TyList () $ typeCons "Symbol")))))

constructSamplerIO :: Exp ()
constructSamplerIO = applyF (varExp "runRIO")
                               [applyF (varExp "sampleWord")
                                [varExp "lb"
                                ,varExp "ub"
                                ,varExp "s"]]

-- | Finds the starting state for the sampler.
startingState :: PSystem Double -> Int
startingState sys =
    gen `S.findIndex` types sys'
    where sys' = system sys
          ann' = annotations sys'
          gen = withString ann' "generate" (initType sys')

declStartingState :: PSystem Double -> [Decl ()]
declStartingState sys = declTFun "startingState" type' [] body
    where body = toLit (startingState sys)
          type' = TyForall () Nothing Nothing int'