mcmc-0.7.0.1: src/Mcmc/Mcmc.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : Mcmc.Mcmc
-- Description : Framework for running Markov chain Monte Carlo samplers
-- Copyright : 2021 Dominik Schrempf
-- License : GPL-3.0-or-later
--
-- Maintainer : dominik.schrempf@gmail.com
-- Stability : unstable
-- Portability : portable
--
-- Creation date: Fri May 29 10:19:45 2020.
--
-- This module provides the general framework for running MCMC samplers. By
-- design choice this module is agnostic about the details of the used
-- 'Algorithm'.
module Mcmc.Mcmc
( mcmc,
mcmcContinue,
)
where
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Mcmc.Algorithm
import Mcmc.Cycle
import Mcmc.Environment
import Mcmc.Logger
import Mcmc.Proposal (TuningType (LastTuningStep, NormalTuningStep))
import Mcmc.Settings
import System.IO
import Prelude hiding (cycle)
-- The MCMC algorithm has read access to an environment and uses an algorithm
-- transforming the state @a@.
type MCMC = ReaderT (Environment Settings) IO
mcmcExecute :: Algorithm a => a -> MCMC a
mcmcExecute a = do
logDebugB "Executing MCMC run."
s <- reader settings
a' <- case sExecutionMode s of
Fail -> mcmcNewRun a
Overwrite -> mcmcNewRun a
Continue -> mcmcContinueRun a
logDebugB "Executed MCMC run."
return a'
mcmcResetAcceptance :: Algorithm a => a -> MCMC a
mcmcResetAcceptance a = do
logDebugB "Reset acceptance rates."
return $ aResetAcceptance a
mcmcExceptionHandler :: Algorithm a => Environment Settings -> a -> AsyncException -> IO b
mcmcExceptionHandler e a err = do
putStrLn ""
putStrLn "INTERRUPT!"
putStrLn "Try to terminate gracefully and save chain for continuation."
putStrLn "Press CTRL-C (again) to terminate now."
putStrLn "Closing output files."
_ <- aCloseMonitors a
closeEnvironment e
putStrLn "Saving settings."
let s = settings e
settingsSave s
putStrLn "Saving compressed MCMC analysis."
putStrLn "For long traces, or complex objects, this may take a while."
let nm = sAnalysisName s
aSave nm a
putStrLn "Markov chain saved. Analysis can be continued."
putStrLn "Graceful termination successful."
putStrLn "Rethrowing error."
throw err
-- XXX: Exception handling. Is it enough to mask execution of monitors and catch
-- UserInterrupt during iterations?
mcmcExecuteMonitors :: Algorithm a => a -> MCMC ()
mcmcExecuteMonitors a = do
e <- ask
let s = settings e
vb = sVerbosity s
t0 = startingTime e
iTotal = burnInIterations (sBurnIn s) + fromIterations (sIterations s)
-- NOTE: Mask asynchronous exceptions when writing monitor files.
mStdLog <- liftIO $ mask_ $ aExecuteMonitors vb t0 iTotal a
forM_ mStdLog (logOutB " ")
mcmcIterate :: Algorithm a => IterationMode -> Int -> a -> MCMC a
mcmcIterate m n a
| n < 0 = error "mcmcIterate: Number of iterations is negative."
| n == 0 = return a
| otherwise = do
e <- ask
p <- sParallelizationMode . settings <$> ask
-- NOTE: User interrupt is handled during iterations.
a' <- liftIO $ catch (aIterate m p a) (mcmcExceptionHandler e a)
mcmcExecuteMonitors a'
mcmcIterate m (n - 1) a'
mcmcNewRun :: Algorithm a => a -> MCMC a
mcmcNewRun a = do
s <- reader settings
logInfoB "Start new MCMC sampler."
logInfoB "Initial state."
logInfoB $ aStdMonitorHeader a
mcmcExecuteMonitors a
when (aIsInvalidState a) (logWarnB "The initial state is invalid!")
a' <- mcmcBurnIn a
logInfoS $ "Clean chain after burn in."
let tl = sTraceLength s
a'' <- liftIO $ aCleanAfterBurnIn tl a'
let i = fromIterations $ sIterations s
logInfoS $ "Run chain for " ++ show i ++ " iterations."
logInfoB $ aStdMonitorHeader a''
mcmcIterate AllProposals i a''
mcmcContinueRun :: Algorithm a => a -> MCMC a
mcmcContinueRun a = do
s <- reader settings
let iBurnIn = burnInIterations (sBurnIn s)
iNormal = fromIterations (sIterations s)
iTotal = iBurnIn + iNormal
logInfoB "Continuation of MCMC sampler."
let iCurrent = aIteration a
logInfoS $ "Burn in iterations: " ++ show iBurnIn ++ "."
logInfoS $ "Normal iterations: " ++ show iNormal ++ "."
logInfoS $ "Total iterations: " ++ show iTotal ++ "."
logInfoS $ "Current iteration: " ++ show iCurrent ++ "."
when (iCurrent < iBurnIn) $ error "mcmcContinueRun: Can not continue burn in."
let di = iTotal - iCurrent
logInfoB $ aSummarizeCycle AllProposals a
logInfoS $ "Run chain for " ++ show di ++ " iterations."
logInfoB $ aStdMonitorHeader a
mcmcIterate AllProposals di a
mcmcBurnIn :: Algorithm a => a -> MCMC a
mcmcBurnIn a = do
s <- reader settings
case sBurnIn s of
NoBurnIn -> do
logInfoB $ aSummarizeCycle AllProposals a
logInfoS "No burn in."
return a
BurnInWithoutAutoTuning n -> do
logInfoB $ aSummarizeCycle AllProposals a
logInfoS $ "Burn in for " <> show n <> " iterations."
logInfoS "Auto tuning is disabled."
logInfoB $ aStdMonitorHeader a
a' <- mcmcIterate AllProposals n a
logInfoB $ aSummarizeCycle AllProposals a'
a'' <- mcmcResetAcceptance a'
logInfoB "Burn in finished."
return a''
BurnInWithAutoTuning n t -> do
logInfoB $ aSummarizeCycle AllProposals a
logInfoS $ "Burn in for " ++ show n ++ " iterations."
logInfoS $ "Auto tuning is enabled with a period of " ++ show t ++ "."
logInfoB $ aStdMonitorHeader a
let (m, r) = n `divMod` t
-- Don't add another auto tune period if r == 0, because then we auto
-- tune without acceptance counts and get NaNs.
xs = replicate m t <> [r | r > 0]
a' <- mcmcBurnInWithAutoTuning AllProposals xs a
logInfoB "Burn in finished."
return a'
BurnInWithCustomAutoTuning xs ys -> do
logInfoS $ "Burn in for " ++ show (sum xs + sum ys) ++ " iterations."
a' <-
if null xs
then do
logInfoB $ aSummarizeCycle AllProposals a
pure a
else do
logInfoB $ aSummarizeCycle FastProposals a
logInfoS $ "Fast custom auto tuning with periods " ++ show xs ++ "."
logInfoB $ aStdMonitorHeader a
mcmcBurnInWithAutoTuning FastProposals xs a
logInfoS $ "Full custom auto tuning with periods " ++ show ys ++ "."
logInfoB $ aStdMonitorHeader a
a'' <- mcmcBurnInWithAutoTuning AllProposals ys a'
logInfoB "Burn in finished."
return a''
-- Auto tune the proposals.
mcmcAutotune :: Algorithm a => TuningType -> Int -> a -> MCMC a
mcmcAutotune NormalTuningStep n a = do
logDebugB "Auto tune."
liftIO $ aAutoTune NormalTuningStep n a
mcmcAutotune LastTuningStep n a = do
logDebugB "Last auto tune."
liftIO $ aAutoTune LastTuningStep n a
mcmcBurnInWithAutoTuning :: Algorithm a => IterationMode -> [Int] -> a -> MCMC a
mcmcBurnInWithAutoTuning _ [] _ = error "mcmcBurnInWithAutoTuning: Empty list."
mcmcBurnInWithAutoTuning m [x] a = do
-- Last round.
a' <- mcmcIterate m x a
a'' <- mcmcAutotune LastTuningStep x a'
logInfoB $ aSummarizeCycle m a''
logInfoS $ "Acceptance rates calculated over the last " <> show x <> " iterations."
mcmcResetAcceptance a''
mcmcBurnInWithAutoTuning m (x : xs) a = do
a' <- mcmcIterate m x a
a'' <- mcmcAutotune NormalTuningStep x a'
logDebugB $ aSummarizeCycle m a''
logDebugS $ "Acceptance rates calculated over the last " <> show x <> " iterations."
logDebugB $ aStdMonitorHeader a''
a''' <- mcmcResetAcceptance a''
mcmcBurnInWithAutoTuning m xs a'''
mcmcInitialize :: Algorithm a => a -> MCMC a
mcmcInitialize a = do
logInfoS $ aName a ++ " algorithm."
s <- settings <$> ask
logDebugB "Opening monitors."
a' <- liftIO $ aOpenMonitors (sAnalysisName s) (sExecutionMode s) a
logDebugB "Monitors opened."
return a'
-- Save the MCMC run.
mcmcSave :: Algorithm a => a -> MCMC ()
mcmcSave a = do
s <- reader settings
case sSaveMode s of
NoSave -> logInfoB "Do not save the MCMC analysis."
Save -> do
logInfoB "Save settings."
liftIO $ settingsSave s
let nm = sAnalysisName s
logInfoB "Save compressed MCMC analysis."
logInfoB "For long traces, or complex objects, this may take a while."
liftIO $ aSave nm a
logInfoB "Markov chain saved."
-- Report and finish up.
mcmcClose :: Algorithm a => a -> MCMC a
mcmcClose a = do
logDebugB "Closing MCMC run."
logInfoB $ aSummarizeCycle AllProposals a
logInfoS $ aName a ++ " algorithm finished."
mcmcSave a
logInfoEndTime
a' <- liftIO $ aCloseMonitors a
e <- ask
liftIO $ closeEnvironment e
return a'
-- Initialize the run, execute the run, and close the run.
mcmcRun :: Algorithm a => a -> MCMC a
mcmcRun a = do
-- Header.
logInfoHeader
reader settings >>= logInfoB . settingsPrettyPrint
-- Initialize.
a' <- mcmcInitialize a
logInfoStartingTime
-- Execute.
a'' <- mcmcExecute a'
-- Close.
mcmcClose a''
-- | Run an MCMC algorithm with given settings.
mcmc :: Algorithm a => Settings -> a -> IO a
mcmc s a = do
settingsCheck s $ aIteration a
e <- initializeEnvironment s
runReaderT (mcmcRun a) e
-- | Continue an MCMC algorithm for the given number of iterations.
--
-- Currently, it is only possible to continue MCMC algorithms that have
-- completed successfully. This restriction is necessary, because for parallel
-- chains, it is hardly possible to ensure all chains are synchronized when the
-- process is killed or fails.
--
-- See:
--
-- - 'Mcmc.Algorithm.MHG.mhgLoad'
--
-- - 'Mcmc.Algorithm.MC3.mc3Load'
mcmcContinue :: Algorithm a => Iterations -> Settings -> a -> IO a
mcmcContinue dn s = mcmc s'
where
n' = Iterations $ fromIterations (sIterations s) + fromIterations dn
s' = s {sIterations = n', sExecutionMode = Continue}