mcmc 0.3.0 → 0.4.0.0
raw patch · 39 files changed
+2653/−1630 lines, 39 filesdep +circulardep +deepseqdep +monad-paralleldep −QuickCheckdep −hspec-discoverdep −math-functionsdep ~aesondep ~bytestringdep ~containersPVP ok
version bump matches the API change (PVP)
Dependencies added: circular, deepseq, monad-parallel, pretty-show
Dependencies removed: QuickCheck, hspec-discover, math-functions
Dependency ranges changed: aeson, bytestring, containers, criterion, data-default, directory, dirichlet, double-conversion, hspec, log-domain, microlens, mwc-random, primitive, statistics, time, transformers, vector, zlib
API changes (from Hackage documentation)
- Mcmc: Cleaner :: Int -> (a -> a) -> Cleaner a
- Mcmc: [clEvery] :: Cleaner a -> Int
- Mcmc: [clFunction] :: Cleaner a -> a -> a
- Mcmc: cleanWith :: Cleaner a -> Status a -> Status a
- Mcmc: data Cleaner a
- Mcmc: debug :: Status a -> Status a
- Mcmc: force :: Status a -> Status a
- Mcmc: fromList :: [Proposal a] -> Cycle a
- Mcmc: loadStatus :: FromJSON a => (a -> Log Double) -> (a -> Log Double) -> Cycle a -> Monitor a -> Maybe (Cleaner a) -> FilePath -> IO (Status a)
- Mcmc: mh :: ToJSON a => Status a -> IO (Status a)
- Mcmc: mhContinue :: ToJSON a => Int -> Status a -> IO (Status a)
- Mcmc: noData :: Status a -> Status a
- Mcmc: quiet :: Status a -> Status a
- Mcmc: saveWith :: Int -> Status a -> Status a
- Mcmc: status :: String -> (a -> Log Double) -> (a -> Log Double) -> Cycle a -> Monitor a -> a -> Maybe Int -> Maybe Int -> Int -> GenIO -> Status a
- Mcmc.Item: Item :: a -> Log Double -> Log Double -> Item a
- Mcmc.Item: [likelihood] :: Item a -> Log Double
- Mcmc.Item: [prior] :: Item a -> Log Double
- Mcmc.Item: [state] :: Item a -> a
- Mcmc.Item: data Item a
- Mcmc.Item: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Item.Item a)
- Mcmc.Item: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Item.Item a)
- Mcmc.Item: instance GHC.Classes.Eq a => GHC.Classes.Eq (Mcmc.Item.Item a)
- Mcmc.Item: instance GHC.Classes.Ord a => GHC.Classes.Ord (Mcmc.Item.Item a)
- Mcmc.Item: instance GHC.Read.Read a => GHC.Read.Read (Mcmc.Item.Item a)
- Mcmc.Item: instance GHC.Show.Show a => GHC.Show.Show (Mcmc.Item.Item a)
- Mcmc.Mcmc: mcmcAutotune :: Mcmc a ()
- Mcmc.Mcmc: mcmcClean :: Mcmc a ()
- Mcmc.Mcmc: mcmcDebugB :: ByteString -> Mcmc a ()
- Mcmc.Mcmc: mcmcDebugS :: String -> Mcmc a ()
- Mcmc.Mcmc: mcmcInfoB :: ByteString -> Mcmc a ()
- Mcmc.Mcmc: mcmcInfoS :: String -> Mcmc a ()
- Mcmc.Mcmc: mcmcMonitorExec :: ToJSON a => Mcmc a ()
- Mcmc.Mcmc: mcmcOutB :: ByteString -> Mcmc a ()
- Mcmc.Mcmc: mcmcOutS :: String -> Mcmc a ()
- Mcmc.Mcmc: mcmcReport :: ToJSON a => Mcmc a ()
- Mcmc.Mcmc: mcmcResetA :: Mcmc a ()
- Mcmc.Mcmc: mcmcRun :: ToJSON a => Mcmc a () -> Status a -> IO (Status a)
- Mcmc.Mcmc: mcmcSummarizeCycle :: Mcmc a ByteString
- Mcmc.Mcmc: mcmcWarnB :: ByteString -> Mcmc a ()
- Mcmc.Mcmc: mcmcWarnS :: String -> Mcmc a ()
- Mcmc.Mcmc: type Mcmc a = StateT (Status a) IO
- Mcmc.Metropolis: mh :: ToJSON a => Status a -> IO (Status a)
- Mcmc.Metropolis: mhContinue :: ToJSON a => Int -> Status a -> IO (Status a)
- Mcmc.Monitor: mAppend :: String -> Monitor a -> IO (Monitor a)
- Mcmc.Monitor.Parameter: (@.) :: (b -> a) -> MonitorParameter a -> MonitorParameter b
- Mcmc.Monitor.ParameterBatch: (@#) :: (b -> a) -> MonitorParameterBatch a -> MonitorParameterBatch b
- Mcmc.Monitor.ParameterBatch: monitorBatchCustom :: String -> ([a] -> a) -> (a -> Builder) -> MonitorParameterBatch a
- Mcmc.Proposal: acceptanceRatios :: Acceptance k -> Map k Double
- Mcmc.Proposal: autotuneCycle :: Acceptance (Proposal a) -> Cycle a -> Cycle a
- Mcmc.Proposal: fromList :: [Proposal a] -> Cycle a
- Mcmc.Proposal: getNIterations :: Cycle a -> Int -> GenIO -> IO [[Proposal a]]
- Mcmc.Proposal: instance GHC.Show.Show (Mcmc.Proposal.Proposal a)
- Mcmc.Save: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Save.Save a)
- Mcmc.Save: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Save.Save a)
- Mcmc.Save: loadStatus :: FromJSON a => (a -> Log Double) -> (a -> Log Double) -> Cycle a -> Monitor a -> Maybe (Cleaner a) -> FilePath -> IO (Status a)
- Mcmc.Save: saveStatus :: ToJSON a => FilePath -> Status a -> IO ()
- Mcmc.Status: Cleaner :: Int -> (a -> a) -> Cleaner a
- Mcmc.Status: Status :: String -> Item a -> Int -> Trace a -> Acceptance (Proposal a) -> Maybe Int -> Maybe Int -> Int -> Bool -> Maybe Int -> Verbosity -> GenIO -> Maybe (Int, UTCTime) -> Maybe Handle -> (a -> Log Double) -> (a -> Log Double) -> Maybe (Cleaner a) -> Cycle a -> Monitor a -> Status a
- Mcmc.Status: [acceptance] :: Status a -> Acceptance (Proposal a)
- Mcmc.Status: [autoTuningPeriod] :: Status a -> Maybe Int
- Mcmc.Status: [burnInIterations] :: Status a -> Maybe Int
- Mcmc.Status: [clEvery] :: Cleaner a -> Int
- Mcmc.Status: [clFunction] :: Cleaner a -> a -> a
- Mcmc.Status: [cleaner] :: Status a -> Maybe (Cleaner a)
- Mcmc.Status: [cycle] :: Status a -> Cycle a
- Mcmc.Status: [forceOverwrite] :: Status a -> Bool
- Mcmc.Status: [generator] :: Status a -> GenIO
- Mcmc.Status: [item] :: Status a -> Item a
- Mcmc.Status: [iteration] :: Status a -> Int
- Mcmc.Status: [iterations] :: Status a -> Int
- Mcmc.Status: [likelihoodF] :: Status a -> a -> Log Double
- Mcmc.Status: [logHandle] :: Status a -> Maybe Handle
- Mcmc.Status: [monitor] :: Status a -> Monitor a
- Mcmc.Status: [name] :: Status a -> String
- Mcmc.Status: [priorF] :: Status a -> a -> Log Double
- Mcmc.Status: [save] :: Status a -> Maybe Int
- Mcmc.Status: [start] :: Status a -> Maybe (Int, UTCTime)
- Mcmc.Status: [trace] :: Status a -> Trace a
- Mcmc.Status: [verbosity] :: Status a -> Verbosity
- Mcmc.Status: cleanWith :: Cleaner a -> Status a -> Status a
- Mcmc.Status: data Cleaner a
- Mcmc.Status: data Status a
- Mcmc.Status: debug :: Status a -> Status a
- Mcmc.Status: force :: Status a -> Status a
- Mcmc.Status: noData :: Status a -> Status a
- Mcmc.Status: quiet :: Status a -> Status a
- Mcmc.Status: saveWith :: Int -> Status a -> Status a
- Mcmc.Status: status :: String -> (a -> Log Double) -> (a -> Log Double) -> Cycle a -> Monitor a -> a -> Maybe Int -> Maybe Int -> Int -> GenIO -> Status a
- Mcmc.Trace: data Trace a
- Mcmc.Trace: headT :: Trace a -> Item a
- Mcmc.Trace: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance GHC.Base.Monoid (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance GHC.Base.Semigroup (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance GHC.Classes.Eq a => GHC.Classes.Eq (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance GHC.Read.Read a => GHC.Read.Read (Mcmc.Trace.Trace a)
- Mcmc.Trace: instance GHC.Show.Show a => GHC.Show.Show (Mcmc.Trace.Trace a)
- Mcmc.Trace: pushT :: Item a -> Trace a -> Trace a
- Mcmc.Trace: singletonT :: Item a -> Trace a
- Mcmc.Trace: takeItems :: Int -> Trace a -> [Item a]
- Mcmc.Trace: takeT :: Int -> Trace a -> Trace a
- Mcmc.Verbosity: Debug :: Verbosity
- Mcmc.Verbosity: Info :: Verbosity
- Mcmc.Verbosity: Quiet :: Verbosity
- Mcmc.Verbosity: Warn :: Verbosity
- Mcmc.Verbosity: data Verbosity
- Mcmc.Verbosity: debug :: Applicative m => Verbosity -> m () -> m ()
- Mcmc.Verbosity: info :: Applicative m => Verbosity -> m () -> m ()
- Mcmc.Verbosity: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Verbosity.Verbosity
- Mcmc.Verbosity: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Verbosity.Verbosity
- Mcmc.Verbosity: instance GHC.Classes.Eq Mcmc.Verbosity.Verbosity
- Mcmc.Verbosity: instance GHC.Classes.Ord Mcmc.Verbosity.Verbosity
- Mcmc.Verbosity: instance GHC.Show.Show Mcmc.Verbosity.Verbosity
- Mcmc.Verbosity: warn :: Applicative m => Verbosity -> m () -> m ()
+ Mcmc: cycleFromList :: [Proposal a] -> Cycle a
+ Mcmc: mcmc :: Algorithm a => Settings -> a -> IO a
+ Mcmc: mcmcContinue :: Algorithm a => Int -> Settings -> a -> IO a
+ Mcmc: noLikelihood :: LikelihoodFunction a
+ Mcmc: noPrior :: PriorFunction a
+ Mcmc: type LikelihoodFunction a = a -> Log Double
+ Mcmc: type PriorFunction a = a -> Log Double
+ Mcmc.Algorithm: aAutoTune :: Algorithm a => a -> a
+ Mcmc.Algorithm: aCloseMonitors :: Algorithm a => a -> IO a
+ Mcmc.Algorithm: aExecuteMonitors :: Algorithm a => Verbosity -> UTCTime -> Int -> a -> IO (Maybe ByteString)
+ Mcmc.Algorithm: aIterate :: Algorithm a => ParallelizationMode -> a -> IO a
+ Mcmc.Algorithm: aIteration :: Algorithm a => a -> Int
+ Mcmc.Algorithm: aName :: Algorithm a => a -> String
+ Mcmc.Algorithm: aOpenMonitors :: Algorithm a => AnalysisName -> ExecutionMode -> a -> IO a
+ Mcmc.Algorithm: aResetAcceptance :: Algorithm a => a -> a
+ Mcmc.Algorithm: aSave :: Algorithm a => AnalysisName -> a -> IO ()
+ Mcmc.Algorithm: aStdMonitorHeader :: Algorithm a => a -> ByteString
+ Mcmc.Algorithm: aSummarizeCycle :: Algorithm a => a -> ByteString
+ Mcmc.Algorithm: class Algorithm a
+ Mcmc.Algorithm.MC3: MC3 :: MC3Settings -> MHGChains a -> ReciprocalTemperatures -> Int -> Acceptance Int -> GenIO -> MC3 a
+ Mcmc.Algorithm.MC3: MC3Settings :: NChains -> SwapPeriod -> NSwaps -> MC3Settings
+ Mcmc.Algorithm.MC3: NChains :: Int -> NChains
+ Mcmc.Algorithm.MC3: NSwaps :: Int -> NSwaps
+ Mcmc.Algorithm.MC3: SwapPeriod :: Int -> SwapPeriod
+ Mcmc.Algorithm.MC3: [fromNChains] :: NChains -> Int
+ Mcmc.Algorithm.MC3: [fromNSwaps] :: NSwaps -> Int
+ Mcmc.Algorithm.MC3: [fromSwapPeriod] :: SwapPeriod -> Int
+ Mcmc.Algorithm.MC3: [mc3Generator] :: MC3 a -> GenIO
+ Mcmc.Algorithm.MC3: [mc3Iteration] :: MC3 a -> Int
+ Mcmc.Algorithm.MC3: [mc3MHGChains] :: MC3 a -> MHGChains a
+ Mcmc.Algorithm.MC3: [mc3NChains] :: MC3Settings -> NChains
+ Mcmc.Algorithm.MC3: [mc3NSwaps] :: MC3Settings -> NSwaps
+ Mcmc.Algorithm.MC3: [mc3ReciprocalTemperatures] :: MC3 a -> ReciprocalTemperatures
+ Mcmc.Algorithm.MC3: [mc3Settings] :: MC3 a -> MC3Settings
+ Mcmc.Algorithm.MC3: [mc3SwapAcceptance] :: MC3 a -> Acceptance Int
+ Mcmc.Algorithm.MC3: [mc3SwapPeriod] :: MC3Settings -> SwapPeriod
+ Mcmc.Algorithm.MC3: data MC3 a
+ Mcmc.Algorithm.MC3: data MC3Settings
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Algorithm.MC3.MC3Settings
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Algorithm.MC3.NChains
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Algorithm.MC3.NSwaps
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Algorithm.MC3.SwapPeriod
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Algorithm.MC3.SavedMC3 a)
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Algorithm.MC3.MC3Settings
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Algorithm.MC3.NChains
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Algorithm.MC3.NSwaps
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Algorithm.MC3.SwapPeriod
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Algorithm.MC3.SavedMC3 a)
+ Mcmc.Algorithm.MC3: instance Data.Aeson.Types.ToJSON.ToJSON a => Mcmc.Algorithm.Algorithm (Mcmc.Algorithm.MC3.MC3 a)
+ Mcmc.Algorithm.MC3: instance GHC.Classes.Eq Mcmc.Algorithm.MC3.MC3Settings
+ Mcmc.Algorithm.MC3: instance GHC.Classes.Eq Mcmc.Algorithm.MC3.NChains
+ Mcmc.Algorithm.MC3: instance GHC.Classes.Eq Mcmc.Algorithm.MC3.NSwaps
+ Mcmc.Algorithm.MC3: instance GHC.Classes.Eq Mcmc.Algorithm.MC3.SwapPeriod
+ Mcmc.Algorithm.MC3: instance GHC.Classes.Eq a => GHC.Classes.Eq (Mcmc.Algorithm.MC3.SavedMC3 a)
+ Mcmc.Algorithm.MC3: instance GHC.Read.Read Mcmc.Algorithm.MC3.MC3Settings
+ Mcmc.Algorithm.MC3: instance GHC.Read.Read Mcmc.Algorithm.MC3.NChains
+ Mcmc.Algorithm.MC3: instance GHC.Read.Read Mcmc.Algorithm.MC3.NSwaps
+ Mcmc.Algorithm.MC3: instance GHC.Read.Read Mcmc.Algorithm.MC3.SwapPeriod
+ Mcmc.Algorithm.MC3: instance GHC.Read.Read a => GHC.Read.Read (Mcmc.Algorithm.MC3.SavedMC3 a)
+ Mcmc.Algorithm.MC3: instance GHC.Show.Show Mcmc.Algorithm.MC3.MC3Settings
+ Mcmc.Algorithm.MC3: instance GHC.Show.Show Mcmc.Algorithm.MC3.NChains
+ Mcmc.Algorithm.MC3: instance GHC.Show.Show Mcmc.Algorithm.MC3.NSwaps
+ Mcmc.Algorithm.MC3: instance GHC.Show.Show Mcmc.Algorithm.MC3.SwapPeriod
+ Mcmc.Algorithm.MC3: instance GHC.Show.Show a => GHC.Show.Show (Mcmc.Algorithm.MC3.SavedMC3 a)
+ Mcmc.Algorithm.MC3: mc3 :: MC3Settings -> PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> a -> GenIO -> IO (MC3 a)
+ Mcmc.Algorithm.MC3: mc3Load :: FromJSON a => PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> AnalysisName -> IO (MC3 a)
+ Mcmc.Algorithm.MC3: mc3Save :: ToJSON a => AnalysisName -> MC3 a -> IO ()
+ Mcmc.Algorithm.MC3: newtype NChains
+ Mcmc.Algorithm.MC3: newtype NSwaps
+ Mcmc.Algorithm.MC3: newtype SwapPeriod
+ Mcmc.Algorithm.MC3: type MHGChains a = Vector (MHG a)
+ Mcmc.Algorithm.MC3: type ReciprocalTemperatures = Vector Double
+ Mcmc.Algorithm.Metropolis: MHG :: Chain a -> MHG a
+ Mcmc.Algorithm.Metropolis: [fromMHG] :: MHG a -> Chain a
+ Mcmc.Algorithm.Metropolis: instance Data.Aeson.Types.ToJSON.ToJSON a => Mcmc.Algorithm.Algorithm (Mcmc.Algorithm.Metropolis.MHG a)
+ Mcmc.Algorithm.Metropolis: mhg :: PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> a -> GenIO -> IO (MHG a)
+ Mcmc.Algorithm.Metropolis: mhgAccept :: Log Double -> GenIO -> IO Bool
+ Mcmc.Algorithm.Metropolis: mhgLoad :: FromJSON a => PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> AnalysisName -> IO (MHG a)
+ Mcmc.Algorithm.Metropolis: mhgSave :: ToJSON a => AnalysisName -> MHG a -> IO ()
+ Mcmc.Algorithm.Metropolis: newtype MHG a
+ Mcmc.Chain.Chain: Chain :: Int -> Link a -> Int -> Trace a -> Acceptance (Proposal a) -> GenIO -> Int -> PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> Chain a
+ Mcmc.Chain.Chain: [acceptance] :: Chain a -> Acceptance (Proposal a)
+ Mcmc.Chain.Chain: [chainId] :: Chain a -> Int
+ Mcmc.Chain.Chain: [cycle] :: Chain a -> Cycle a
+ Mcmc.Chain.Chain: [generator] :: Chain a -> GenIO
+ Mcmc.Chain.Chain: [iteration] :: Chain a -> Int
+ Mcmc.Chain.Chain: [likelihoodFunction] :: Chain a -> LikelihoodFunction a
+ Mcmc.Chain.Chain: [link] :: Chain a -> Link a
+ Mcmc.Chain.Chain: [monitor] :: Chain a -> Monitor a
+ Mcmc.Chain.Chain: [priorFunction] :: Chain a -> PriorFunction a
+ Mcmc.Chain.Chain: [start] :: Chain a -> Int
+ Mcmc.Chain.Chain: [trace] :: Chain a -> Trace a
+ Mcmc.Chain.Chain: data Chain a
+ Mcmc.Chain.Chain: noLikelihood :: LikelihoodFunction a
+ Mcmc.Chain.Chain: noPrior :: PriorFunction a
+ Mcmc.Chain.Chain: type LikelihoodFunction a = a -> Log Double
+ Mcmc.Chain.Chain: type PriorFunction a = a -> Log Double
+ Mcmc.Chain.Link: Link :: a -> Log Double -> Log Double -> Link a
+ Mcmc.Chain.Link: [likelihood] :: Link a -> Log Double
+ Mcmc.Chain.Link: [prior] :: Link a -> Log Double
+ Mcmc.Chain.Link: [state] :: Link a -> a
+ Mcmc.Chain.Link: data Link a
+ Mcmc.Chain.Link: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Link: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Link: instance GHC.Classes.Eq a => GHC.Classes.Eq (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Link: instance GHC.Classes.Ord a => GHC.Classes.Ord (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Link: instance GHC.Read.Read a => GHC.Read.Read (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Link: instance GHC.Show.Show a => GHC.Show.Show (Mcmc.Chain.Link.Link a)
+ Mcmc.Chain.Save: SavedChain :: Int -> Link a -> Int -> Stack Vector (Link a) -> Acceptance Int -> Vector Word32 -> [Maybe Double] -> SavedChain a
+ Mcmc.Chain.Save: [savedAcceptance] :: SavedChain a -> Acceptance Int
+ Mcmc.Chain.Save: [savedId] :: SavedChain a -> Int
+ Mcmc.Chain.Save: [savedIteration] :: SavedChain a -> Int
+ Mcmc.Chain.Save: [savedLink] :: SavedChain a -> Link a
+ Mcmc.Chain.Save: [savedSeed] :: SavedChain a -> Vector Word32
+ Mcmc.Chain.Save: [savedTrace] :: SavedChain a -> Stack Vector (Link a)
+ Mcmc.Chain.Save: [savedTuningParameters] :: SavedChain a -> [Maybe Double]
+ Mcmc.Chain.Save: data SavedChain a
+ Mcmc.Chain.Save: fromSavedChain :: PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> SavedChain a -> IO (Chain a)
+ Mcmc.Chain.Save: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Chain.Save.SavedChain a)
+ Mcmc.Chain.Save: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Chain.Save.SavedChain a)
+ Mcmc.Chain.Save: instance GHC.Classes.Eq a => GHC.Classes.Eq (Mcmc.Chain.Save.SavedChain a)
+ Mcmc.Chain.Save: instance GHC.Read.Read a => GHC.Read.Read (Mcmc.Chain.Save.SavedChain a)
+ Mcmc.Chain.Save: instance GHC.Show.Show a => GHC.Show.Show (Mcmc.Chain.Save.SavedChain a)
+ Mcmc.Chain.Save: toSavedChain :: Chain a -> IO (SavedChain a)
+ Mcmc.Chain.Trace: data Trace a
+ Mcmc.Chain.Trace: freezeT :: Trace a -> IO (Stack Vector (Link a))
+ Mcmc.Chain.Trace: headT :: Trace a -> IO (Link a)
+ Mcmc.Chain.Trace: lengthT :: Trace a -> Int
+ Mcmc.Chain.Trace: pushT :: Link a -> Trace a -> IO (Trace a)
+ Mcmc.Chain.Trace: replicateT :: Int -> Link a -> IO (Trace a)
+ Mcmc.Chain.Trace: takeT :: Int -> Trace a -> IO (Vector (Link a))
+ Mcmc.Chain.Trace: thawT :: Stack Vector (Link a) -> IO (Trace a)
+ Mcmc.Environment: Environment :: Settings -> Maybe Handle -> UTCTime -> Environment
+ Mcmc.Environment: [logHandle] :: Environment -> Maybe Handle
+ Mcmc.Environment: [settings] :: Environment -> Settings
+ Mcmc.Environment: [startingTime] :: Environment -> UTCTime
+ Mcmc.Environment: data Environment
+ Mcmc.Environment: initializeEnvironment :: Settings -> IO Environment
+ Mcmc.Environment: instance GHC.Classes.Eq Mcmc.Environment.Environment
+ Mcmc.Environment: instance GHC.Show.Show Mcmc.Environment.Environment
+ Mcmc.Mcmc: mcmc :: Algorithm a => Settings -> a -> IO a
+ Mcmc.Mcmc: mcmcContinue :: Algorithm a => Int -> Settings -> a -> IO a
+ Mcmc.Monitor: getMonitorBatchSize :: MonitorBatch a -> Int
+ Mcmc.Monitor: msHeader :: MonitorStdOut a -> ByteString
+ Mcmc.Monitor.Time: renderTime :: FormatTime t => t -> String
+ Mcmc.Prior: poisson :: Double -> Int -> Log Double
+ Mcmc.Proposal: PDimension :: Int -> PDimension
+ Mcmc.Proposal: PDimensionUnknown :: PDimension
+ Mcmc.Proposal: [pDimension] :: Proposal a -> PDimension
+ Mcmc.Proposal: acceptanceRate :: Ord k => k -> Acceptance k -> Maybe (Int, Int, Double)
+ Mcmc.Proposal: acceptanceRates :: Acceptance k -> Map k Double
+ Mcmc.Proposal: autoTuneCycle :: Acceptance (Proposal a) -> Cycle a -> Cycle a
+ Mcmc.Proposal: cycleFromList :: [Proposal a] -> Cycle a
+ Mcmc.Proposal: data PDimension
+ Mcmc.Proposal: getOptimalRate :: PDimension -> Double
+ Mcmc.Proposal: instance (GHC.Classes.Ord k, GHC.Read.Read k) => GHC.Read.Read (Mcmc.Proposal.Acceptance k)
+ Mcmc.Proposal: instance GHC.Base.Monoid Mcmc.Proposal.PName
+ Mcmc.Proposal: instance GHC.Base.Semigroup Mcmc.Proposal.PName
+ Mcmc.Proposal: instance GHC.Classes.Eq k => GHC.Classes.Eq (Mcmc.Proposal.Acceptance k)
+ Mcmc.Proposal: instance GHC.Show.Show k => GHC.Show.Show (Mcmc.Proposal.Acceptance k)
+ Mcmc.Proposal: orderProposals :: Cycle a -> GenIO -> IO [Proposal a]
+ Mcmc.Proposal: proposalHLine :: ByteString
+ Mcmc.Proposal: proposalHeader :: ByteString
+ Mcmc.Proposal: summarizeProposal :: PName -> PDescription -> PWeight -> Maybe Double -> PDimension -> Maybe (Int, Int, Double) -> ByteString
+ Mcmc.Settings: AnalysisName :: String -> AnalysisName
+ Mcmc.Settings: BurnInWithAutoTuning :: Int -> Int -> BurnInSpecification
+ Mcmc.Settings: BurnInWithoutAutoTuning :: Int -> BurnInSpecification
+ Mcmc.Settings: Continue :: ExecutionMode
+ Mcmc.Settings: Debug :: Verbosity
+ Mcmc.Settings: Fail :: ExecutionMode
+ Mcmc.Settings: Info :: Verbosity
+ Mcmc.Settings: Iterations :: Int -> Iterations
+ Mcmc.Settings: NoBurnIn :: BurnInSpecification
+ Mcmc.Settings: NoSave :: SaveMode
+ Mcmc.Settings: Overwrite :: ExecutionMode
+ Mcmc.Settings: Parallel :: ParallelizationMode
+ Mcmc.Settings: Quiet :: Verbosity
+ Mcmc.Settings: Save :: SaveMode
+ Mcmc.Settings: Sequential :: ParallelizationMode
+ Mcmc.Settings: Settings :: AnalysisName -> BurnInSpecification -> Iterations -> ExecutionMode -> ParallelizationMode -> SaveMode -> Verbosity -> Settings
+ Mcmc.Settings: Warn :: Verbosity
+ Mcmc.Settings: [fromAnalysisName] :: AnalysisName -> String
+ Mcmc.Settings: [fromIterations] :: Iterations -> Int
+ Mcmc.Settings: [sAnalysisName] :: Settings -> AnalysisName
+ Mcmc.Settings: [sBurnIn] :: Settings -> BurnInSpecification
+ Mcmc.Settings: [sExecutionMode] :: Settings -> ExecutionMode
+ Mcmc.Settings: [sIterations] :: Settings -> Iterations
+ Mcmc.Settings: [sParallelizationMode] :: Settings -> ParallelizationMode
+ Mcmc.Settings: [sSaveMode] :: Settings -> SaveMode
+ Mcmc.Settings: [sVerbosity] :: Settings -> Verbosity
+ Mcmc.Settings: burnInIterations :: BurnInSpecification -> Int
+ Mcmc.Settings: data BurnInSpecification
+ Mcmc.Settings: data ExecutionMode
+ Mcmc.Settings: data ParallelizationMode
+ Mcmc.Settings: data SaveMode
+ Mcmc.Settings: data Settings
+ Mcmc.Settings: data Verbosity
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.AnalysisName
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.BurnInSpecification
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.ExecutionMode
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.Iterations
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.ParallelizationMode
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.SaveMode
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.Settings
+ Mcmc.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Settings.Verbosity
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.AnalysisName
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.BurnInSpecification
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.ExecutionMode
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.Iterations
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.ParallelizationMode
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.SaveMode
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.Settings
+ Mcmc.Settings: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Settings.Verbosity
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.AnalysisName
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.BurnInSpecification
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.ExecutionMode
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.Iterations
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.ParallelizationMode
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.SaveMode
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.Settings
+ Mcmc.Settings: instance GHC.Classes.Eq Mcmc.Settings.Verbosity
+ Mcmc.Settings: instance GHC.Classes.Ord Mcmc.Settings.Verbosity
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.AnalysisName
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.BurnInSpecification
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.ExecutionMode
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.Iterations
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.ParallelizationMode
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.SaveMode
+ Mcmc.Settings: instance GHC.Read.Read Mcmc.Settings.Verbosity
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.AnalysisName
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.BurnInSpecification
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.ExecutionMode
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.Iterations
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.ParallelizationMode
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.SaveMode
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.Settings
+ Mcmc.Settings: instance GHC.Show.Show Mcmc.Settings.Verbosity
+ Mcmc.Settings: newtype AnalysisName
+ Mcmc.Settings: newtype Iterations
+ Mcmc.Settings: openWithExecutionMode :: ExecutionMode -> FilePath -> IO Handle
+ Mcmc.Settings: settingsCheck :: Settings -> Int -> IO ()
+ Mcmc.Settings: settingsLoad :: AnalysisName -> IO Settings
+ Mcmc.Settings: settingsSave :: Settings -> IO ()
- Mcmc.Monitor: mOpen :: String -> Bool -> Monitor a -> IO (Monitor a)
+ Mcmc.Monitor: mOpen :: String -> String -> ExecutionMode -> Monitor a -> IO (Monitor a)
- Mcmc.Monitor.ParameterBatch: MonitorParameterBatch :: String -> ([a] -> Builder) -> MonitorParameterBatch a
+ Mcmc.Monitor.ParameterBatch: MonitorParameterBatch :: String -> (Vector a -> Builder) -> MonitorParameterBatch a
- Mcmc.Monitor.ParameterBatch: [mbpFunc] :: MonitorParameterBatch a -> [a] -> Builder
+ Mcmc.Monitor.ParameterBatch: [mbpFunc] :: MonitorParameterBatch a -> Vector a -> Builder
- Mcmc.Proposal: Proposal :: PName -> PDescription -> PWeight -> ProposalSimple a -> Maybe (Tuner a) -> Proposal a
+ Mcmc.Proposal: Proposal :: PName -> PDescription -> PDimension -> PWeight -> ProposalSimple a -> Maybe (Tuner a) -> Proposal a
- Mcmc.Proposal: createProposal :: PDescription -> (Double -> ProposalSimple a) -> PName -> PWeight -> Tune -> Proposal a
+ Mcmc.Proposal: createProposal :: PDescription -> (Double -> ProposalSimple a) -> PDimension -> PName -> PWeight -> Tune -> Proposal a
- Mcmc.Proposal: pushA :: (Ord k, Show k) => k -> Bool -> Acceptance k -> Acceptance k
+ Mcmc.Proposal: pushA :: Ord k => k -> Bool -> Acceptance k -> Acceptance k
- Mcmc.Proposal: tune :: Double -> Proposal a -> Maybe (Proposal a)
+ Mcmc.Proposal: tune :: (Double -> Double) -> Proposal a -> Maybe (Proposal a)
- Mcmc.Proposal: tuneCycle :: Map (Proposal a) Double -> Cycle a -> Cycle a
+ Mcmc.Proposal: tuneCycle :: Map (Proposal a) (Double -> Double) -> Cycle a -> Cycle a
- Mcmc.Proposal.Simplex: dirichlet :: PName -> PWeight -> Tune -> Proposal Simplex
+ Mcmc.Proposal.Simplex: dirichlet :: PDimension -> PName -> PWeight -> Tune -> Proposal Simplex
Files
- ChangeLog.md +17/−0
- README.md +22/−6
- bench/Bench.hs +72/−3
- bench/Normal.hs +69/−33
- bench/Poisson.hs +15/−21
- mcmc.cabal +107/−105
- src/Mcmc.hs +100/−73
- src/Mcmc/Algorithm.hs +62/−0
- src/Mcmc/Algorithm/MC3.hs +546/−0
- src/Mcmc/Algorithm/Metropolis.hs +254/−0
- src/Mcmc/Chain/Chain.hs +95/−0
- src/Mcmc/Chain/Link.hs +47/−0
- src/Mcmc/Chain/Save.hs +108/−0
- src/Mcmc/Chain/Trace.hs +75/−0
- src/Mcmc/Environment.hs +49/−0
- src/Mcmc/Internal/ByteString.hs +9/−3
- src/Mcmc/Internal/Random.hs +48/−0
- src/Mcmc/Internal/Shuffle.hs +14/−28
- src/Mcmc/Item.hs +0/−48
- src/Mcmc/Mcmc.hs +234/−222
- src/Mcmc/Metropolis.hs +0/−196
- src/Mcmc/Monitor.hs +73/−92
- src/Mcmc/Monitor/Parameter.hs +0/−14
- src/Mcmc/Monitor/ParameterBatch.hs +28/−42
- src/Mcmc/Monitor/Time.hs +7/−1
- src/Mcmc/Prior.hs +42/−28
- src/Mcmc/Proposal.hs +229/−115
- src/Mcmc/Proposal/Bactrian.hs +9/−9
- src/Mcmc/Proposal/Generic.hs +1/−1
- src/Mcmc/Proposal/Scale.hs +9/−22
- src/Mcmc/Proposal/Simplex.hs +13/−7
- src/Mcmc/Proposal/Slide.hs +13/−13
- src/Mcmc/Save.hs +0/−180
- src/Mcmc/Settings.hs +236/−0
- src/Mcmc/Status.hs +0/−218
- src/Mcmc/Trace.hs +0/−63
- src/Mcmc/Verbosity.hs +0/−40
- test/Mcmc/ProposalSpec.hs +6/−6
- test/Mcmc/SaveSpec.hs +44/−41
ChangeLog.md view
@@ -5,6 +5,23 @@ ## Unreleased changes +## 0.4.0.0++- Greatly improve documentation.+- Major design change: Introduction of the `Algorithm` type class; algorithms+ are data types. See `MHG`.+- Metropolic-coupled Markov chain Monte Carlo algorithm (parallel chains).+- Optimal acceptance rate depends on dimension of proposal.+- Use a circular trace with constant memory usage (big change).+- Therefore, batch monitors use vectors now.+- Always save chain with complete trace (but with sensible length).+- Determine necessary trace length at initialization.+- Rename `Item` to `Link`.+- Rename `Status` to `Chain` and separate `Settings` and `Environment` from the+ `Chain`.+- Many bug fixes.++ ## 0.3.0 - New shorter example/test for dating trees.
README.md view
@@ -1,14 +1,17 @@ -# Markov chain Monte Carlo+# Markov chain Monte Carlo sampler <p align="center"><img src="https://travis-ci.org/dschrempf/mcmc.svg?branch=master"/></p> -Sample from a posterior using Markov chain Monte Carlo methods.+Sample from a posterior using Markov chain Monte Carlo (MCMC) algorithms. -At the moment, the library is tailored to the Metropolis-Hastings algorithm-since it covers most use cases. More algorithms will be implemented soon.+At the moment, the following algorithms are available: +- Metropolis-Hastings-Green <sup><a id="fnr.1" class="footref" href="#fn.1">1</a></sup>;+- Metropolis-coupled Markov chain Monte Carlo (also known as parallel+ tempering) <sup><a id="fnr.2" class="footref" href="#fn.2">2</a></sup> <sup>, </sup><sup><a id="fnr.3" class="footref" href="#fn.3">3</a></sup>. + ## Documentation The [source code](https://hackage.haskell.org/package/mcmc) contains detailed documentation about general concepts as well@@ -17,8 +20,8 @@ ## Examples -Have a look at the [example MCMC analyses](https://github.com/dschrempf/mcmc/tree/master/mcmc-examples). They can be built with [Stack](https://docs.haskellstack.org/en/stable/README/) and are-attached to this repository.+[Example MCMC analyses](https://github.com/dschrempf/mcmc/tree/master/mcmc-examples) can be built with [Stack](https://docs.haskellstack.org/en/stable/README/) and are attached to this+repository. git clone https://github.com/dschrempf/mcmc.git cd mcmc@@ -28,3 +31,16 @@ stack exec archery ++# Footnotes++<sup><a id="fn.1" href="#fnr.1">1</a></sup> Geyer, C. J., Introduction to Markov chain Monte Carlo, In Handbook of+Markov Chain Monte Carlo (pp. 45) (2011). CRC press.++<sup><a id="fn.2" href="#fnr.2">2</a></sup> Geyer, C. J., Markov chain monte carlo maximum likelihood, Computing+Science and Statistics, Proceedings of the 23rd Symposium on the Interface,+(), (1991).++<sup><a id="fn.3" href="#fnr.3">3</a></sup> Altekar, G., Dwarkadas, S., Huelsenbeck, J. P., & Ronquist, F., Parallel+metropolis coupled markov chain monte carlo for bayesian phylogenetic inference,+Bioinformatics, 20(3), 407–415 (2004).
bench/Bench.hs view
@@ -22,7 +22,76 @@ main = do g <- create defaultMain- [ bench "Normal" $ nfIO (normalBench g),- bench "NormalBactrian" $ nfIO (normalBactrianBench g),- bench "Poisson" $ nfIO (poissonBench g)+ [ bgroup+ "Normal"+ [ bench "Slide" $ nfIO (normalSlideBench g),+ bench "Bactrian" $ nfIO (normalBactrianBench g),+ bench "LargeCycle" $ nfIO (normalLargeCycleBench g)+ ],+ bench "Poisson" $ nfIO (poissonBench g),+ bgroup+ "MC3"+ [ bench "MC3 2" $ nfIO (normalMC3 g 2),+ bench "MC3 3" $ nfIO (normalMC3 g 3),+ bench "MC3 4" $ nfIO (normalMC3 g 4),+ bench "MC3 5" $ nfIO (normalMC3 g 5),+ bench "MC3 10" $ nfIO (normalMC3 g 10)+ ] ]++-- benchmarking Normal/Slide+-- time 42.31 ms (41.88 ms .. 42.60 ms)+-- 1.000 R² (0.999 R² .. 1.000 R²)+-- mean 42.75 ms (42.52 ms .. 43.29 ms)+-- std dev 661.4 μs (347.4 μs .. 1.074 ms)++-- benchmarking Normal/Bactrian+-- time 45.51 ms (45.30 ms .. 45.92 ms)+-- 1.000 R² (0.999 R² .. 1.000 R²)+-- mean 45.41 ms (45.31 ms .. 45.61 ms)+-- std dev 276.0 μs (141.5 μs .. 460.8 μs)++-- benchmarking Normal/LargeCycle+-- time 68.82 ms (67.18 ms .. 70.81 ms)+-- 0.999 R² (0.997 R² .. 1.000 R²)+-- mean 67.68 ms (67.26 ms .. 68.59 ms)+-- std dev 1.074 ms (618.2 μs .. 1.602 ms)++-- benchmarking Poisson+-- time 72.94 ms (63.07 ms .. 87.73 ms)+-- 0.953 R² (0.920 R² .. 1.000 R²)+-- mean 64.76 ms (62.84 ms .. 71.62 ms)+-- std dev 5.785 ms (783.9 μs .. 10.08 ms)+-- variance introduced by outliers: 26% (moderately inflated)++-- benchmarking MC3/MC3 2+-- time 13.08 ms (12.73 ms .. 13.44 ms)+-- 0.993 R² (0.986 R² .. 0.997 R²)+-- mean 13.41 ms (13.16 ms .. 13.72 ms)+-- std dev 682.7 μs (520.3 μs .. 874.0 μs)+-- variance introduced by outliers: 22% (moderately inflated)++-- benchmarking MC3/MC3 3+-- time 19.19 ms (18.86 ms .. 19.59 ms)+-- 0.998 R² (0.996 R² .. 1.000 R²)+-- mean 19.28 ms (19.11 ms .. 19.51 ms)+-- std dev 454.1 μs (339.1 μs .. 608.4 μs)++-- benchmarking MC3/MC3 4+-- time 25.01 ms (24.21 ms .. 25.66 ms)+-- 0.997 R² (0.996 R² .. 0.999 R²)+-- mean 24.21 ms (23.99 ms .. 24.55 ms)+-- std dev 606.1 μs (414.7 μs .. 738.5 μs)++-- benchmarking MC3/MC3 5+-- time 28.39 ms (26.99 ms .. 29.34 ms)+-- 0.995 R² (0.990 R² .. 0.999 R²)+-- mean 31.13 ms (30.09 ms .. 33.49 ms)+-- std dev 3.009 ms (984.0 μs .. 4.319 ms)+-- variance introduced by outliers: 40% (moderately inflated)++-- benchmarking MC3/MC3 10+-- time 57.25 ms (56.98 ms .. 57.61 ms)+-- 1.000 R² (1.000 R² .. 1.000 R²)+-- mean 57.46 ms (57.34 ms .. 57.56 ms)+-- std dev 192.4 μs (140.1 μs .. 284.0 μs)
bench/Normal.hs view
@@ -1,6 +1,6 @@ -- | -- Module : Normal--- Description : Benchmark Metropolis-Hastings algorithm+-- Description : Benchmark Metropolis-Hastings-Green algorithm -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --@@ -10,34 +10,28 @@ -- -- Creation date: Wed May 6 00:10:11 2020. module Normal- ( normalBench,+ ( normalSlideBench, normalBactrianBench,+ normalLargeCycleBench,+ normalMC3, ) where import Control.Monad import Mcmc-import Numeric.Log as L-import Statistics.Distribution hiding- ( mean,- stdDev,- )-import Statistics.Distribution.Normal import System.Random.MWC trueMean :: Double trueMean = 5 -trueStdDev :: Double-trueStdDev = 4+stdDev :: Double+stdDev = 4 -lh :: Double -> Log Double-lh = Exp . logDensity (normalDistr trueMean trueStdDev)+lh :: LikelihoodFunction Double+lh = normal trueMean stdDev -proposals :: Cycle Double-proposals =- fromList- [slideSymmetric 1.0 (PName "Medium") (PWeight 1) Tune]+cc :: Cycle Double+cc = cycleFromList [slideSymmetric 1.0 (PName "Medium") (PWeight 1) Tune] mons :: [MonitorParameter Double] mons = [monitorDouble "mu"]@@ -48,26 +42,68 @@ mon :: Monitor Double mon = Monitor monStd [] [] -nBurn :: Maybe Int-nBurn = Just 2000--nAutoTune :: Maybe Int-nAutoTune = Just 200+normalSlideBench :: GenIO -> IO ()+normalSlideBench g = do+ let s =+ Settings+ (AnalysisName "Normal")+ (BurnInWithAutoTuning 2000 500)+ (Iterations 20000)+ Overwrite+ Sequential+ NoSave+ Quiet+ a <- mhg noPrior lh cc mon 0 g+ void $ mcmc s a -nIter :: Int-nIter = 20000+ccLarge :: Cycle Double+ccLarge =+ cycleFromList+ [slideSymmetric 1.0 (PName $ "Medium " ++ show i) (PWeight 1) Tune | i <- [0 .. 100 :: Int]] -normalBench :: GenIO -> IO ()-normalBench g = do- let s = quiet $ status "Normal" (const 1) lh proposals mon 0 nBurn nAutoTune nIter g- void $ mh s+-- Should have the same run time as 'normalSlide'.+normalLargeCycleBench :: GenIO -> IO ()+normalLargeCycleBench g = do+ let s =+ Settings+ (AnalysisName "Normal")+ (BurnInWithAutoTuning 20 5)+ (Iterations 200)+ Overwrite+ Sequential+ NoSave+ Quiet+ a <- mhg noPrior lh ccLarge mon 0 g+ void $ mcmc s a -proposalsBactrian :: Cycle Double-proposalsBactrian =- fromList- [slideBactrian 0.5 1.0 (PName "Bactrian") (PWeight 1) Tune]+ccBactrian :: Cycle Double+ccBactrian = cycleFromList [slideBactrian 0.5 1.0 (PName "Bactrian") (PWeight 1) Tune] normalBactrianBench :: GenIO -> IO () normalBactrianBench g = do- let s = quiet $ status "NormalBactrian" (const 1) lh proposalsBactrian mon 0 nBurn nAutoTune nIter g- void $ mh s+ let s =+ Settings+ (AnalysisName "NormalBactrian")+ (BurnInWithAutoTuning 2000 200)+ (Iterations 20000)+ Overwrite+ Sequential+ NoSave+ Quiet+ a <- mhg noPrior lh ccBactrian mon 0 g+ void $ mcmc s a++normalMC3 :: GenIO -> Int -> IO ()+normalMC3 g n = do+ let mcmcS =+ Settings+ (AnalysisName "MC3")+ (BurnInWithAutoTuning 200 20)+ (Iterations 2000)+ Overwrite+ Sequential+ NoSave+ Quiet+ mc3S = MC3Settings (NChains n) (SwapPeriod 2) (NSwaps 1)+ a <- mc3 mc3S noPrior lh cc mon 0 g+ void $ mcmc mcmcS a
bench/Poisson.hs view
@@ -20,11 +20,6 @@ import Lens.Micro import Mcmc import Numeric.Log hiding (sum)-import Statistics.Distribution hiding- ( mean,- stdDev,- )-import Statistics.Distribution.Poisson import System.Random.MWC type I = (Double, Double)@@ -39,13 +34,12 @@ m = sum ys / fromIntegral (length ys) f :: Int -> Double -> I -> Log Double-f ft yr (a, b) = Exp $ logProbability (poisson l) (fromIntegral ft)+f ft yr (a, b) = poisson l (fromIntegral ft) where l = exp $ a + b * yr -lh :: I -> Log Double-lh x =- product [f ft yr x | (ft, yr) <- zip fatalities normalizedYears]+lh :: LikelihoodFunction I+lh x = product [f ft yr x | (ft, yr) <- zip fatalities normalizedYears] proposalAlpha :: Proposal I proposalAlpha = _1 @~ slideSymmetric 0.2 (PName "Alpha") (PWeight 1) NoTune@@ -54,7 +48,7 @@ proposalBeta = _2 @~ slideSymmetric 0.2 (PName "Beta") (PWeight 1) NoTune proposals :: Cycle I-proposals = fromList [proposalAlpha, proposalBeta]+proposals = cycleFromList [proposalAlpha, proposalBeta] initial :: I initial = (0, 0)@@ -71,16 +65,16 @@ mon :: Monitor I mon = Monitor monStd [] [] -nBurn :: Maybe Int-nBurn = Just 2000--nAutoTune :: Maybe Int-nAutoTune = Just 200--nIter :: Int-nIter = 10000- poissonBench :: GenIO -> IO () poissonBench g = do- let s = quiet $ status "Poisson" (const 1) lh proposals mon initial nBurn nAutoTune nIter g- void $ mh s+ let s =+ Settings+ (AnalysisName "Poisson")+ (BurnInWithAutoTuning 2000 200)+ (Iterations 10000)+ Overwrite+ Sequential+ NoSave+ Quiet+ a <- mhg noPrior lh proposals mon initial g+ void $ mcmc s a
mcmc.cabal view
@@ -1,117 +1,119 @@-cabal-version: 2.2-name: mcmc-version: 0.3.0-synopsis: Sample from a posterior using Markov chain Monte Carlo-description: Please see the README on GitHub at <https://github.com/dschrempf/mcmc#readme>-category: Math, Statistics-homepage: https://github.com/dschrempf/mcmc#readme-bug-reports: https://github.com/dschrempf/mcmc/issues-author: Dominik Schrempf-maintainer: dominik.schrempf@gmail.com-copyright: Dominik Schrempf (2020)-license: GPL-3.0-or-later-build-type: Simple+cabal-version: 2.2+name: mcmc+version: 0.4.0.0+license: GPL-3.0-or-later+copyright: Dominik Schrempf (2020)+maintainer: dominik.schrempf@gmail.com+author: Dominik Schrempf+homepage: https://github.com/dschrempf/mcmc#readme+bug-reports: https://github.com/dschrempf/mcmc/issues+synopsis: Sample from a posterior using Markov chain Monte Carlo+description:+ Please see the README on GitHub at <https://github.com/dschrempf/mcmc#readme> +category: Math, Statistics+build-type: Simple extra-source-files: README.md ChangeLog.md source-repository head- type: git- location: https://github.com/dschrempf/mcmc+ type: git+ location: https://github.com/dschrempf/mcmc library- exposed-modules:- Mcmc- Mcmc.Item- Mcmc.Mcmc- Mcmc.Metropolis- Mcmc.Monitor- Mcmc.Monitor.Log- Mcmc.Monitor.Parameter- Mcmc.Monitor.ParameterBatch- Mcmc.Monitor.Time- Mcmc.Prior- Mcmc.Proposal- Mcmc.Proposal.Bactrian- Mcmc.Proposal.Generic- Mcmc.Proposal.Scale- Mcmc.Proposal.Slide- Mcmc.Proposal.Simplex- Mcmc.Save- Mcmc.Status- Mcmc.Trace- Mcmc.Verbosity- other-modules:- Mcmc.Internal.ByteString- Mcmc.Internal.Shuffle- Paths_mcmc- autogen-modules:- Paths_mcmc- hs-source-dirs:- src- ghc-options: -Wall- build-depends:- aeson- , base >=4.7 && <5- , bytestring- , containers- , data-default- , directory- , dirichlet- , double-conversion- , log-domain- , math-functions- , microlens- , mwc-random- , primitive- , statistics- , time- , transformers- , vector- , zlib- default-language: Haskell2010+ exposed-modules:+ Mcmc+ Mcmc.Algorithm+ Mcmc.Algorithm.Metropolis+ Mcmc.Algorithm.MC3+ Mcmc.Chain.Chain+ Mcmc.Chain.Link+ Mcmc.Chain.Save+ Mcmc.Chain.Trace+ Mcmc.Environment+ Mcmc.Mcmc+ Mcmc.Monitor+ Mcmc.Monitor.Log+ Mcmc.Monitor.Parameter+ Mcmc.Monitor.ParameterBatch+ Mcmc.Monitor.Time+ Mcmc.Prior+ Mcmc.Proposal+ Mcmc.Proposal.Bactrian+ Mcmc.Proposal.Generic+ Mcmc.Proposal.Scale+ Mcmc.Proposal.Slide+ Mcmc.Proposal.Simplex+ Mcmc.Settings + hs-source-dirs: src+ other-modules:+ Mcmc.Internal.ByteString+ Mcmc.Internal.Random+ Mcmc.Internal.Shuffle+ Paths_mcmc++ autogen-modules: Paths_mcmc+ default-language: Haskell2010+ ghc-options: -Wall -Wunused-packages+ build-depends:+ aeson >=1.5.4.1 && <1.6,+ base >=4.7 && <5,+ bytestring >=0.10.10.0 && <0.11,+ circular >=0.3.1.1 && <0.4,+ containers >=0.6.2.1 && <0.7,+ data-default >=0.7.1.1 && <0.8,+ deepseq >=1.4.4.0 && <1.5,+ directory >=1.3.6.0 && <1.4,+ dirichlet >=0.1.0.0 && <0.2,+ double-conversion >=2.0.2.0 && <2.1,+ log-domain ==0.13.*,+ microlens >=0.4.11.2 && <0.5,+ mwc-random >=0.14.0.0 && <0.15,+ monad-parallel >=0.7.2.3 && <0.8,+ pretty-show ==1.10.*,+ primitive >=0.7.1.0 && <0.8,+ statistics >=0.15.2.0 && <0.16,+ time >=1.9.3 && <1.10,+ transformers >=0.5.6.2 && <0.6,+ vector >=0.12.1.2 && <0.13,+ zlib >=0.6.2.2 && <0.7+ test-suite mcmc-test- type: exitcode-stdio-1.0- main-is: Spec.hs- other-modules:- Mcmc.ProposalSpec- Mcmc.SaveSpec- Paths_mcmc- hs-source-dirs:- test- ghc-options: -Wall- build-depends:- QuickCheck- , base >=4.7 && <5- , directory- , hspec- , hspec-discover- , log-domain- , mcmc- , mwc-random- , statistics- , vector- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs: test+ other-modules:+ Mcmc.ProposalSpec+ Mcmc.SaveSpec+ Paths_mcmc + default-language: Haskell2010+ ghc-options: -Wall -Wunused-packages+ build-depends:+ base >=4.7 && <5,+ hspec >=2.7.4 && <2.8,+ log-domain ==0.13.*,+ mcmc -any,+ mwc-random >=0.14.0.0 && <0.15,+ statistics >=0.15.2.0 && <0.16+ benchmark mcmc-bench- type: exitcode-stdio-1.0- main-is: Bench.hs- other-modules:- Normal- Poisson- Paths_mcmc- hs-source-dirs:- bench- ghc-options: -Wall- build-depends:- base >=4.7 && <5- , criterion- , log-domain- , mcmc- , microlens- , mwc-random- , statistics- , vector- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ hs-source-dirs: bench+ other-modules:+ Normal+ Poisson+ Paths_mcmc++ default-language: Haskell2010+ ghc-options: -Wall -Wunused-packages+ build-depends:+ base >=4.7 && <5,+ criterion >=1.5.7.0 && <1.6,+ log-domain ==0.13.*,+ mcmc -any,+ microlens >=0.4.11.2 && <0.5,+ mwc-random >=0.14.0.0 && <0.15
src/Mcmc.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Mcmc--- Description : Markov chain Monte Carlo algorithms, batteries included+-- Description : Markov chain Monte Carlo samplers, batteries included -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --@@ -12,37 +12,64 @@ -- -- Creation date: Tue May 5 18:01:15 2020. ----- A short introduction to update mechanisms using the Metropolis-Hastings--- algorithm (see Geyer, C. J., 2011; Introduction to Markov Chain Monte Carlo. In--- Handbook of Markov Chain Monte Carlo (pp. 45), Chapman \& Hall/CRC).+-- For an introduction to Markov chain Monte Carlo (MCMC) samplers and update+-- mechanisms using the Metropolis-Hastings-Green algorithm, please see Geyer,+-- C. J., (2011), Introduction to Markov Chain Monte Carlo, In Handbook of+-- Markov Chain Monte Carlo (pp. 45), CRC press. ----- For examples, please see--- [mcmc-examples](https://github.com/dschrempf/mcmc/tree/master/mcmc-examples).+-- This library focusses on classical Markov chain Monte Carlo algorithms such+-- as the Metropolis-Hastings-Green [1] algorithm, or population methods+-- involving parallel chains such as the Metropolic-coupled Markov chain Monte+-- Carlo [2] algorithm. In particular, sequential Monte Carlo [3] algorithms+-- following a moving posterior distribution are not provided. --+-- An MCMC sampler can be run with 'mcmc', for example using the+-- Metropolis-Hastings-Green algorithm 'mhg'.+--+-- Usually, it is best to start with an example:+--+-- - Basic inference of the [accuracy of an+-- archer](https://github.com/dschrempf/mcmc/tree/master/mcmc-examples/Archery/Archery.hs)+--+-- - [More involved+-- examples](https://github.com/dschrempf/mcmc/tree/master/mcmc-examples/Archery/Archery.hs)+-- -- __The import of this module alone should cover most use cases.__+--+-- @[1]@ Geyer, C. J. (2011), Introduction to markov chain monte carlo, In+-- Handbook of Markov Chain Monte Carlo (pp. 45), CRC press.+--+-- @[2]@ Geyer, C. J. (1991), Markov chain monte carlo maximum likelihood,+-- Computing Science and Statistics, Proceedings of the 23rd Symposium on the+-- Interface.+--+-- @[3]@ Sequential monte carlo methods in practice (2001), Editors: Arnaud+-- Doucet, Nando de Freitas, and Neil Gordon, Springer New York. module Mcmc ( -- * Proposals - -- | A 'Proposal' is an instruction about how to advance a given Markov chain so- -- that it possibly reaches a new state. That is, 'Proposal's specify how the- -- chain traverses the state space. As far as this MCMC library is- -- concerned, 'Proposal's are /elementary updates/ in that they cannot be- -- decomposed into smaller updates.+ -- | A 'Proposal' is an instruction about how to advance a given Markov+ -- chain so that it possibly reaches a new state. That is, 'Proposal's+ -- specify how the chain traverses the state space. As far as this MCMC+ -- library is concerned, 'Proposal's are considered to be /elementary+ -- updates/ in that they cannot be decomposed into smaller updates. -- -- 'Proposal's can be combined to form composite updates, a technique often -- referred to as /composition/. On the other hand, /mixing/ (used in the -- sense of mixture models) is the random choice of a 'Proposal' (or a -- composition of 'Proposal's) from a given set. --- -- The __composition__ and __mixture__ of 'Proposal's allows specification of- -- nearly all MCMC algorithms involving a single chain (i.e., population+ -- The __composition__ and __mixture__ of 'Proposal's allows specification+ -- of nearly all MCMC algorithms involving a single chain (i.e., population -- methods such as particle filters are excluded). In particular, Gibbs- -- samplers of all sorts can be specified using this procedure.+ -- samplers of all sorts can be specified using this procedure. For+ -- reference, please see the short [encyclopedia of MCMC+ -- methods](https://dschrempf.github.io/coding/2020-11-12-encyclopedia-of-markov-chain-monte-carlo-methods/). --- -- This library enables composition and mixture of 'Proposal's via the 'Cycle'- -- data type. Essentially, a 'Cycle' is a set of 'Proposal's. The chain advances- -- after the completion of each 'Cycle', which is called an __iteration__,- -- and the iteration counter is increased by one.+ -- This library enables composition and mixture of 'Proposal's via the+ -- 'Cycle' data type. Essentially, a 'Cycle' is a set of 'Proposal's. The+ -- chain advances after the completion of each 'Cycle', which is called an+ -- __iteration__, and the iteration counter is increased by one. -- -- The 'Proposal's in a 'Cycle' can be executed in the given order or in a -- random sequence which allows, for example, specification of a fixed scan@@ -56,32 +83,35 @@ -- Proposals are named according to what they do, i.e., how they change the -- state of a Markov chain, and not according to the intrinsically used -- probability distributions. For example, 'slideSymmetric' is a sliding- -- proposal. Under the hood, it uses the normal distribution with mean zero and- -- given variance. The sampled variate is added to the current value of the- -- variable (hence, the name slide). The same nomenclature is used by- -- RevBayes [1]. The probability distributions and intrinsic properties of a+ -- proposal. Under the hood, it uses the normal distribution with mean zero+ -- and given variance. The sampled variate is added to the current value of+ -- the variable (hence, the name slide). The same nomenclature is used by+ -- RevBayes [4]. The probability distributions and intrinsic properties of a -- specific proposal are specified in detail in the documentation. -- -- The other method, which is used intrinsically, is more systematic, but -- also a little bit more complicated: we separate between the proposal- -- distribution and how the state is affected. And here, I am not only- -- referring to the accessor (i.e., the lens), but also to the operator- -- (addition, multiplication, any other binary operator). For example, the- -- sliding proposal (without tuning information) is implemented as+ -- distribution and how the state is affected. And here, I am referring to+ -- the operator (addition, multiplication, any other binary operator). For+ -- example, the sliding proposal with mean @m@, standard deviation @s@, and+ -- tuning parameter @t@ is implemented as -- -- @- -- slideSimple :: Lens' a Double -> Double -> Double -> Double -> ProposalSimple a- -- slideSimple l m s t = genericContinuous l (normalDistr m (s * t)) (+) (-)+ -- slideSimple :: Double -> Double -> Double -> ProposalSimple Double+ -- slideSimple m s t =+ -- genericContinuous (normalDistr m (s * t)) (+) (Just negate) Nothing -- @ -- -- This specification is more involved. Especially since we need to know the- -- probability of jumping back, and so we need to know the inverse operator.- -- However, it also allows specification of new proposals with great ease.+ -- probability of jumping back, and so we need to know the inverse operator+ -- 'negate'. However, it also allows specification of new proposals with+ -- great ease. --- -- [1] Höhna, S., Landis, M. J., Heath, T. A., Boussau, B., Lartillot, N., Moore,- -- B. R., Huelsenbeck, J. P., …, Revbayes: bayesian phylogenetic inference using- -- graphical models and an interactive model-specification language, Systematic- -- Biology, 65(4), 726–736 (2016). http://dx.doi.org/10.1093/sysbio/syw021+ -- @[4]@ Höhna, S., Landis, M. J., Heath, T. A., Boussau, B., Lartillot, N.,+ -- Moore, B. R., Huelsenbeck, J. P., …, Revbayes: bayesian phylogenetic+ -- inference using graphical models and an interactive model-specification+ -- language, Systematic Biology, 65(4), 726–736 (2016).+ -- http://dx.doi.org/10.1093/sysbio/syw021 PName (..), PWeight (..), Proposal,@@ -98,41 +128,28 @@ slideBactrian, module Mcmc.Proposal.Simplex, Cycle,- fromList,+ cycleFromList, Order (..), setOrder, - -- * Initialization-- -- | The 'Status' contains all information to run an MCMC chain. It is- -- constructed using the function 'status'.- --- -- The 'Status' of a Markov chain includes information about current state- -- ('Mcmc.Item.Item') and iteration, the history of the chain- -- ('Mcmc.Trace.Trace'), the 'Acceptance' ratios, and the random number- -- generator.- --- -- Further, the 'Status' includes auxiliary variables and functions such as- -- the prior and likelihood functions, instructions to move around the state- -- space (see above) and to monitor the MCMC run, as well as some auxiliary- -- information.- status,- Cleaner (..),- cleanWith,- saveWith,- force,- quiet,- debug,- noData,+ -- * Settings+ module Mcmc.Settings, - -- * Monitor+ -- * Monitors -- | A 'Monitor' describes which part of the Markov chain should be logged- -- and where. There are three different types:- -- - 'MonitorStdOut': Log to standard output.- -- - 'MonitorFile': Log to a file.- -- - 'MonitorBatch': Log summary statistics such as the mean of the last- -- - states to a file.+ -- and where. Monitor files can directly be loaded into established MCMC+ -- analysis tools working with tab separated tables (for example,+ -- [Tracer](http://tree.bio.ed.ac.uk/software/tracer/)).+ --+ -- There are three different 'Monitor' types:+ --+ -- ['MonitorStdOut'] Log to standard output.+ --+ -- ['MonitorFile'] Log to a file.+ --+ -- ['MonitorBatch'] Log summary statistics such as the mean of the last+ -- states to a file. Monitor (Monitor), MonitorStdOut, monitorStdOut,@@ -144,22 +161,33 @@ module Mcmc.Monitor.ParameterBatch, -- * Prior distributions++ -- | Convenience functions for computing priors. module Mcmc.Prior, + -- * MCMC samplers+ mcmc,+ mcmcContinue,++ -- | See also 'settingsLoad', 'mhgLoad', and 'mc3Load'.+ -- * Algorithms+ module Mcmc.Algorithm.Metropolis,+ module Mcmc.Algorithm.MC3, - -- | At the moment, the library is tailored to the Metropolis-Hastings- -- algorithm ('mh') since it covers most use cases. However, implementation- -- of more algorithms is planned in the future.- mh,- mhContinue, - -- * Misc- loadStatus,+ -- * Useful type synonyms+ PriorFunction,+ noPrior,+ LikelihoodFunction,+ noLikelihood, ) where -import Mcmc.Metropolis+import Mcmc.Algorithm.MC3+import Mcmc.Algorithm.Metropolis+import Mcmc.Chain.Chain+import Mcmc.Mcmc import Mcmc.Monitor import Mcmc.Monitor.Parameter import Mcmc.Monitor.ParameterBatch@@ -169,5 +197,4 @@ import Mcmc.Proposal.Scale import Mcmc.Proposal.Simplex import Mcmc.Proposal.Slide-import Mcmc.Save-import Mcmc.Status+import Mcmc.Settings
+ src/Mcmc/Algorithm.hs view
@@ -0,0 +1,62 @@+-- |+-- Module : Mcmc.Algorithm+-- Description : Algortihms for Markov chain Monte Carlo samplers+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Mon Nov 16 14:37:11 2020.+module Mcmc.Algorithm+ ( Algorithm (..),+ )+where++import qualified Data.ByteString.Lazy.Char8 as BL+import Data.Time+import Mcmc.Settings++-- | Class for algorithms used by MCMC samplers.+class Algorithm a where+ -- | Name.+ aName :: a -> String++ -- | Current iteration.+ aIteration :: a -> Int++ -- | Sample the next state.+ aIterate :: ParallelizationMode -> a -> IO a++ -- | Auto tune all proposals.+ aAutoTune :: a -> a++ -- | Reset acceptance counts.+ aResetAcceptance :: a -> a++ -- | Summarize the cycle.+ aSummarizeCycle :: a -> BL.ByteString++ -- | Open required monitor files and setup corresponding file handles.+ aOpenMonitors :: AnalysisName -> ExecutionMode -> a -> IO a++ -- | Execute file monitors and possibly return a monitor string to be written+ -- to the standard output and the log file.+ aExecuteMonitors ::+ Verbosity ->+ -- | Starting time.+ UTCTime ->+ -- | Total number of iterations including burn in.+ Int ->+ a ->+ IO (Maybe BL.ByteString)++ -- | Header of monitor to standard output.+ aStdMonitorHeader :: a -> BL.ByteString++ -- | Close monitor files and remove the file handles.+ aCloseMonitors :: a -> IO a++ -- | Save analysis.+ aSave :: AnalysisName -> a -> IO ()
+ src/Mcmc/Algorithm/MC3.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Mcmc.Algorithm.MC3+-- Description : Metropolis-coupled Markov chain Monte Carlo algorithm+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Mon Nov 23 15:20:33 2020.+--+-- The Metropolis-coupled Markov chain Monte Carlo ('MC3') algorithm.+--+-- Also known as parallel tempering.+--+-- Like any other parallel MCMC algorithm, the 'MC3' algorithm is essentially an+-- 'Mcmc.Algorithm.Metropolis.MHG' algorithm on the product space of all+-- parallel chains.+--+-- For example, see+--+-- - Geyer, C. J., Markov chain monte carlo maximum likelihood, Computing+-- Science and Statistics, Proceedings of the 23rd Symposium on the Interface,+-- (1991).+--+-- - Altekar, G., Dwarkadas, S., Huelsenbeck, J. P., & Ronquist, F., Parallel+-- metropolis coupled markov chain monte carlo for bayesian phylogenetic+-- inference, Bioinformatics, 20(3), 407–415 (2004).+module Mcmc.Algorithm.MC3+ ( -- * Definitions+ NChains (..),+ SwapPeriod (..),+ NSwaps (..),+ MC3Settings (..),+ MHGChains,+ ReciprocalTemperatures,++ -- * Metropolis-coupled Markov chain Monte Carlo algorithm+ MC3 (..),+ mc3,+ mc3Save,+ mc3Load,+ )+where++import Codec.Compression.GZip+import Control.Monad+import qualified Control.Monad.Parallel as P+import Data.Aeson+import Data.Aeson.TH+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy.Char8 as BL+import qualified Data.Double.Conversion.ByteString as BC+import Data.List+import qualified Data.Map.Strict as M+import Data.Time+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as U+import Data.Word+-- import Debug.Trace hiding (trace)+import Mcmc.Algorithm+import Mcmc.Algorithm.Metropolis+import Mcmc.Chain.Chain+import Mcmc.Chain.Link+import Mcmc.Chain.Save+import Mcmc.Chain.Trace+import Mcmc.Internal.Random+import Mcmc.Internal.Shuffle+import Mcmc.Monitor+import Mcmc.Proposal+import Mcmc.Settings+import Numeric.Log hiding (sum)+import System.Random.MWC++-- | Total number of parallel chains.+--+-- Must be two or larger.+newtype NChains = NChains {fromNChains :: Int}+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''NChains)++-- | The period of proposing state swaps between chains.+--+-- Must be one or larger.+newtype SwapPeriod = SwapPeriod {fromSwapPeriod :: Int}+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''SwapPeriod)++-- | The number of proposed swaps at each swapping event.+--+-- Must be in @[1, NChains - 1]@.+newtype NSwaps = NSwaps {fromNSwaps :: Int}+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''NSwaps)++-- | MC3 settings.+data MC3Settings = MC3Settings+ { -- | The number of chains has to be larger equal two.+ mc3NChains :: NChains,+ mc3SwapPeriod :: SwapPeriod,+ mc3NSwaps :: NSwaps+ }+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''MC3Settings)++-- | Vector of MHG chains.+type MHGChains a = V.Vector (MHG a)++-- | Vector of reciprocal temperatures.+type ReciprocalTemperatures = U.Vector Double++data SavedMC3 a = SavedMC3+ { savedMC3Settings :: MC3Settings,+ savedMC3Chains :: V.Vector (SavedChain a),+ savedMC3ReciprocalTemperatures :: ReciprocalTemperatures,+ savedMC3Iteration :: Int,+ savedMC3SwapAcceptance :: Acceptance Int,+ savedMC3Generator :: U.Vector Word32+ }+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''SavedMC3)++toSavedMC3 ::+ MC3 a ->+ IO (SavedMC3 a)+toSavedMC3 (MC3 s mhgs bs i ac g) = do+ scs <- V.mapM (toSavedChain . fromMHG) mhgs+ g' <- saveGen g+ return $ SavedMC3 s scs bs i ac g'++fromSavedMC3 ::+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ SavedMC3 a ->+ IO (MC3 a)+fromSavedMC3 pr lh cc mn (SavedMC3 s scs bs i ac g') = do+ mhgs <- V.mapM (fmap MHG . fromSavedChain pr lh cc mn) scs+ g <- loadGen g'+ return $ MC3 s mhgs bs i ac g++-- | The MC3 algorithm.+data MC3 a = MC3+ { mc3Settings :: MC3Settings,+ -- | The first chain is the cold chain with temperature 1.0.+ mc3MHGChains :: MHGChains a,+ -- | Vector of reciprocal temperatures.+ mc3ReciprocalTemperatures :: ReciprocalTemperatures,+ -- | Current iteration.+ mc3Iteration :: Int,+ -- | Number of accepted and rejected swaps.+ mc3SwapAcceptance :: Acceptance Int,+ mc3Generator :: GenIO+ }++instance ToJSON a => Algorithm (MC3 a) where+ aName = const "Metropolis-coupled Markov chain Monte Carlo (MC3)"+ aIteration = mc3Iteration+ aIterate = mc3Iterate+ aAutoTune = mc3AutoTune+ aResetAcceptance = mc3ResetAcceptance+ aSummarizeCycle = mc3SummarizeCycle+ aOpenMonitors = mc3OpenMonitors+ aExecuteMonitors = mc3ExecuteMonitors+ aStdMonitorHeader = mc3StdMonitorHeader+ aCloseMonitors = mc3CloseMonitors+ aSave = mc3Save++-- The prior and likelihood values of the current link are updated.+--+-- NOTE: The trace is not changed! In particular, the prior and likelihood+-- values are not updated for any link of the trace, and no new link is added to+-- the trace.+setReciprocalTemperature ::+ -- Cold prior function.+ PriorFunction a ->+ -- Cold likelihood function.+ LikelihoodFunction a ->+ -- New reciprocal temperature.+ Double ->+ MHG a ->+ MHG a+setReciprocalTemperature prf lhf beta a =+ MHG $+ c+ { priorFunction = prf',+ likelihoodFunction = lhf',+ link = Link x (prf' x) (lhf' x)+ }+ where+ c = fromMHG a+ b' = Exp $ log beta+ -- We need twice the amount of computations compared to taking the power+ -- after calculating the posterior (pr x * lh x) ** b'. However, I don't+ -- think this is a serious problem.+ --+ -- To minimize computations, it is key to avoid modification of the+ -- reciprocal temperature for the cold chain.+ prf' = (** b') . prf+ lhf' = (** b') . lhf+ x = state $ link c++initMHG ::+ -- Cold prior function.+ PriorFunction a ->+ -- Cold likelihood function.+ LikelihoodFunction a ->+ -- Index of MHG chain.+ Int ->+ -- Reciprocal temperature.+ Double ->+ MHG a ->+ IO (MHG a)+initMHG prf lhf i beta a+ | i < 0 = error "initMHG: Chain index negative."+ -- Do not temper with the cold chain.+ | i == 0 = return a+ | otherwise = do+ -- We have to push the current link in the trace, since it is not set by+ -- 'setReciprocalTemperature'. The other links in the trace are still+ -- pointing to the link of the cold chain, but this has no effect.+ t' <- pushT l t+ return $ MHG $ c {chainId = i, trace = t'}+ where+ a' = setReciprocalTemperature prf lhf beta a+ c = fromMHG a'+ l = link c+ t = trace c++-- TODO: Splitmix. Initialization of the MC3 algorithm is an IO action because+-- the generators have to be split. And also because of the mutable trace.++-- | Initialize an MC3 algorithm with a given number of chains.+--+-- Call 'error' if:+--+-- - The number of chains is one or lower.+--+-- - The swap period is zero or negative.+mc3 ::+ MC3Settings ->+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ a ->+ GenIO ->+ IO (MC3 a)+mc3 s pr lh cc mn i0 g+ | n < 2 = error "mc3: The number of chains must be two or larger."+ | sp < 1 = error "mc3: The swap period must be strictly positive."+ | sn < 1 || sn > n - 1 = error "mc3: The number of swaps must be in [1, NChains - 1]."+ | otherwise = do+ -- Split random number generators.+ gs <- V.fromList <$> splitGen n g+ cs <- V.mapM (mhg pr lh cc mn i0) gs+ hcs <- V.izipWithM (initMHG pr lh) (V.convert bs) cs+ return $ MC3 s hcs bs 0 (emptyA [0 .. n - 2]) g+ where+ n = fromNChains $ mc3NChains s+ sp = fromSwapPeriod $ mc3SwapPeriod s+ sn = fromNSwaps $ mc3NSwaps s+ -- NOTE: The initial choice of reciprocal temperatures is based on a few+ -- tests but otherwise pretty arbitrary.+ --+ -- NOTE: Have to 'take n' elements, because vectors are not as lazy as+ -- lists.+ bs = U.fromList $ take n $ iterate (* 0.92) 1.0++mc3Fn :: AnalysisName -> FilePath+mc3Fn (AnalysisName nm) = nm ++ ".mc3"++-- | Save an MC3 algorithm.+mc3Save ::+ ToJSON a =>+ AnalysisName ->+ MC3 a ->+ IO ()+mc3Save nm a = do+ savedMC3 <- toSavedMC3 a+ BL.writeFile (mc3Fn nm) $ compress $ encode savedMC3++-- | Load an MC3 algorithm.+--+-- See 'Mcmc.Mcmc.mcmcContinue'.+mc3Load ::+ FromJSON a =>+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ AnalysisName ->+ IO (MC3 a)+mc3Load pr lh cc mn nm = do+ savedMC3 <- eitherDecode . decompress <$> BL.readFile (mc3Fn nm)+ either error (fromSavedMC3 pr lh cc mn) savedMC3++-- I call the chains left and right, because it is easy to think about them as+-- being left and right. Of course, the left chain may also have a larger index+-- than the right chain.+swapWith ::+ -- Index i>=0 of left chain.+ Int ->+ -- Index j>=0, j/=i of right chain.+ Int ->+ MHGChains a ->+ (MHGChains a, Log Double)+swapWith i j xs+ | i < 0 = error "swapWith: Left index is negative."+ | j < 0 = error "swapWith: Right index is negative."+ | i == j = error "swapWith: Indices are equal."+ | otherwise = (xs', q)+ where+ -- Gather information from current chains.+ cl = fromMHG $ xs V.! i+ cr = fromMHG $ xs V.! j+ ll = link cl+ lr = link cr+ prl = prior ll+ prr = prior lr+ lhl = likelihood ll+ lhr = likelihood lr+ -- Swap the states.+ xl' = state lr+ xr' = state ll+ -- Compute new priors and likelihoods.+ prl' = priorFunction cl xl'+ prr' = priorFunction cr xr'+ lhl' = likelihoodFunction cl xl'+ lhr' = likelihoodFunction cr xr'+ -- Set the new links and the proposed state.+ ll' = Link xl' prl' lhl'+ lr' = Link xr' prr' lhr'+ cl' = cl {link = ll'}+ cr' = cr {link = lr'}+ xs' = xs V.// [(i, MHG cl'), (j, MHG cr')]+ -- Compute the Metropolis ratio.+ nominator = prl' * prr' * lhl' * lhr'+ denominator = prl * prr * lhl * lhr+ q = nominator / denominator++mc3ProposeSwap ::+ MC3 a ->+ -- Index of left chain.+ Int ->+ IO (MC3 a)+mc3ProposeSwap a i = do+ -- 1. Sample new state and get the Metropolis ratio.+ let (!y, !r) = swapWith i (i + 1) $ mc3MHGChains a+ -- 2. Accept or reject.+ accept <- mhgAccept r g+ if accept+ then do+ let !ac' = pushA i True (mc3SwapAcceptance a)+ return $ a {mc3MHGChains = y, mc3SwapAcceptance = ac'}+ else do+ let !ac' = pushA i False (mc3SwapAcceptance a)+ return $ a {mc3SwapAcceptance = ac'}+ where+ g = mc3Generator a++-- TODO: Splimix. 'mc3Iterate' is actually not parallel, but concurrent because+-- of the IO constraint. Use pure parallel code when we have a pure generator.+-- However, we have honor the mutable traces.+mc3Iterate ::+ ToJSON a =>+ ParallelizationMode ->+ MC3 a ->+ IO (MC3 a)+mc3Iterate pm a = do+ -- 1. Maybe propose swaps.+ --+ -- NOTE: Swaps have to be proposed first, because the traces are automatically+ -- updated at step 2.+ let s = mc3Settings a+ a' <-+ if mc3Iteration a `mod` fromSwapPeriod (mc3SwapPeriod s) == 0+ then do+ let n = V.length $ mc3MHGChains a+ is = [0 .. n - 2]+ ns = fromNSwaps $ mc3NSwaps s+ is' <- shuffle is $ mc3Generator a+ foldM mc3ProposeSwap a (take ns is')+ else return a+ -- 2. Iterate all chains and increment iteration.+ mhgs <- case pm of+ Sequential -> V.mapM (aIterate pm) (mc3MHGChains a')+ Parallel ->+ -- See 'Control.Monad.Parallel' of package 'monad-parallel'. Go via a+ -- list, and use 'forkIO'.+ V.fromList <$> P.mapM (aIterate pm) (V.toList (mc3MHGChains a'))+ let i = mc3Iteration a'+ return $ a' {mc3MHGChains = mhgs, mc3Iteration = succ i}++tuneBeta ::+ -- The old reciprocal temperatures are needed to retrieve the old ratios.+ ReciprocalTemperatures ->+ -- Index i of left chain. Change the reciprocal temperature of chain (i+1).+ Int ->+ -- Exponent xi of the reciprocal temperature ratio.+ Double ->+ -- The new reciprocal temperatures are updated incrementally using the+ -- reciprocal temperature ratios during the fold (see 'mc3AutoTune' below).+ ReciprocalTemperatures ->+ ReciprocalTemperatures+tuneBeta bsOld i xi bsNew = bsNew U.// [(j, brNew)]+ where+ j = i + 1+ blOld = bsOld U.! i+ brOld = bsOld U.! j+ blNew = bsNew U.! i+ -- The new ratio is in (0,1).+ rNew = (brOld / blOld) ** xi+ brNew = blNew * rNew++mc3AutoTune :: ToJSON a => MC3 a -> MC3 a+mc3AutoTune a = a {mc3MHGChains = mhgs'', mc3ReciprocalTemperatures = bs'}+ where+ mhgs = mc3MHGChains a+ -- 1. Auto tune all chains.+ mhgs' = V.map aAutoTune mhgs+ -- 2. Auto tune temperatures.+ optimalRate = getOptimalRate PDimensionUnknown+ currentRates = acceptanceRates $ mc3SwapAcceptance a+ -- We assume that the acceptance rate of state swaps between two chains is+ -- roughly proportional to the ratio of the temperatures of the chains.+ -- Hence, we focus on temperature ratios, actually reciprocal temperature+ -- ratios, which is the same. Also, by working with ratios in (0,1) of+ -- neighboring chains, we ensure the monotonicity of the reciprocal+ -- temperatures.+ --+ -- The factor (1/2) was determined by a few tests and is otherwise+ -- absolutely arbitrary.+ xi i = exp $ (/ 2) $ (currentRates M.! i) - optimalRate+ bs = mc3ReciprocalTemperatures a+ n = fromNChains $ mc3NChains $ mc3Settings a+ -- Do not change the temperature, and the prior and likelihood functions of+ -- the cold chain.+ bs' = foldl' (\xs j -> tuneBeta bs j (xi j) xs) bs [0 .. n - 2]+ coldChain = fromMHG $ V.head mhgs'+ coldPrF = priorFunction coldChain+ coldLhF = likelihoodFunction coldChain+ mhgs'' =+ V.head mhgs'+ `V.cons` V.zipWith+ (setReciprocalTemperature coldPrF coldLhF)+ (V.convert $ U.tail bs')+ (V.tail mhgs')++mc3ResetAcceptance :: ToJSON a => MC3 a -> MC3 a+mc3ResetAcceptance a = a'+ where+ -- 1. Reset acceptance of all chains.+ mhgs' = V.map aResetAcceptance (mc3MHGChains a)+ -- 2. Reset acceptance of swaps.+ ac' = resetA $ mc3SwapAcceptance a+ --+ a' = a {mc3MHGChains = mhgs', mc3SwapAcceptance = ac'}++-- Information in cycle summary:+--+-- - The complete summary of the cycle of the cold chain.+--+-- - The combined acceptance rate of proposals within the hot chains.+--+-- - The temperatures of the chains and the acceptance rates of the state swaps.+mc3SummarizeCycle :: ToJSON a => MC3 a -> BL.ByteString+mc3SummarizeCycle a =+ BL.intercalate "\n" $+ [ "MC3: Cycle of cold chain.",+ coldMHGCycleSummary+ ]+ ++ [ "MC3: Average acceptance rate across all chains: " <> BL.fromStrict (BC.toFixed 2 ar)+ | not $ isNaN ar+ ]+ ++ [ "MC3: Reciprocal temperatures of the chains: " <> BL.intercalate ", " bsB <> ".",+ "MC3: Summary of state swaps.",+ "MC3: The swap period is " <> swapPeriodB <> ".",+ "MC3: The state swaps are executed in random order.",+ proposalHeader,+ proposalHLine+ ]+ ++ [ summarizeProposal+ (PName $ show i ++ " <-> " ++ show (i + 1))+ (PDescription "Swap states between chains")+ (PWeight 1)+ (Just $ bs U.! (i + 1))+ PDimensionUnknown+ (acceptanceRate i swapAcceptance)+ | i <- [0 .. n - 2]+ ]+ ++ [proposalHLine]+ where+ mhgs = mc3MHGChains a+ coldMHGCycleSummary = aSummarizeCycle $ V.head mhgs+ cs = V.map fromMHG mhgs+ as = V.map (acceptanceRates . acceptance) cs+ vAr = V.map (\m -> sum m / fromIntegral (length m)) as+ ar = V.sum vAr / fromIntegral (V.length vAr)+ bs = mc3ReciprocalTemperatures a+ bsB = map (BL.fromStrict . BC.toFixed 2) $ U.toList bs+ swapPeriod = fromSwapPeriod $ mc3SwapPeriod $ mc3Settings a+ swapPeriodB = BB.toLazyByteString $ BB.intDec swapPeriod+ swapAcceptance = mc3SwapAcceptance a+ n = fromNChains $ mc3NChains $ mc3Settings a++-- No extra monitors are opened.+mc3OpenMonitors :: ToJSON a => AnalysisName -> ExecutionMode -> MC3 a -> IO (MC3 a)+mc3OpenMonitors nm em a = do+ mhgs' <- V.mapM (aOpenMonitors nm em) (mc3MHGChains a)+ return $ a {mc3MHGChains = mhgs'}++mc3ExecuteMonitors ::+ ToJSON a =>+ Verbosity ->+ -- Starting time.+ UTCTime ->+ -- Total number of iterations.+ Int ->+ MC3 a ->+ IO (Maybe BL.ByteString)+mc3ExecuteMonitors vb t0 iTotal a = V.head <$> V.imapM f (mc3MHGChains a)+ where+ -- The first chain honors verbosity.+ f 0 = aExecuteMonitors vb t0 iTotal+ -- All other chains are to be quiet.+ f _ = aExecuteMonitors Quiet t0 iTotal++mc3StdMonitorHeader :: ToJSON a => MC3 a -> BL.ByteString+mc3StdMonitorHeader = aStdMonitorHeader . V.head . mc3MHGChains++mc3CloseMonitors :: ToJSON a => MC3 a -> IO (MC3 a)+mc3CloseMonitors a = do+ mhgs' <- V.mapM aCloseMonitors $ mc3MHGChains a+ return $ a {mc3MHGChains = mhgs'}
+ src/Mcmc/Algorithm/Metropolis.hs view
@@ -0,0 +1,254 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module : Mcmc.Algorithm.Metropolis+-- Description : Metropolis-Hastings-Green algorithm+-- Copyright : (c) Dominik Schrempf 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Tue May 5 20:11:30 2020.+--+-- The Metropolis-Hastings-Green ('MHG') algorithm.+--+-- For example, see Geyer, C. J., Introduction to Markov chain Monte Carlo, In+-- Handbook of Markov Chain Monte Carlo (pp. 45) (2011). CRC press.+module Mcmc.Algorithm.Metropolis+ ( MHG (..),+ mhg,+ mhgSave,+ mhgLoad,+ mhgAccept,+ )+where++import Codec.Compression.GZip+import Control.Monad+import Control.Monad.IO.Class+import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as BL+import Data.Time+import Mcmc.Algorithm+import Mcmc.Chain.Chain+import Mcmc.Chain.Link+import Mcmc.Chain.Save+import Mcmc.Chain.Trace+import Mcmc.Monitor+import Mcmc.Proposal+import Mcmc.Settings+import Numeric.Log+import System.Random.MWC+import Text.Printf+import Prelude hiding (cycle)++-- | The MHG algorithm.+newtype MHG a = MHG {fromMHG :: Chain a}++instance ToJSON a => Algorithm (MHG a) where+ aName = const "Metropolis-Hastings-Green (MHG)"+ aIteration = iteration . fromMHG+ aIterate = mhgIterate+ aAutoTune = mhgAutoTune+ aResetAcceptance = mhgResetAcceptance+ aSummarizeCycle = mhgSummarizeCycle+ aOpenMonitors = mhgOpenMonitors+ aExecuteMonitors = mhgExecuteMonitors+ aStdMonitorHeader = mhgStdMonitorHeader+ aCloseMonitors = mhgCloseMonitors+ aSave = mhgSave++-- NOTE: IO is required because the trace is mutable.++-- | Initialize an MHG algorithm.+mhg ::+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ -- | The initial state in the state space @a@.+ a ->+ -- | A source of randomness. For reproducible runs, make sure to use+ -- generators with the same seed.+ GenIO ->+ IO (MHG a)+mhg pr lh cc mn i0 g = do+ -- The trace is a mutable vector and the mutable state needs to be handled by+ -- a monad.+ tr <- replicateT traceLength l0+ return $ MHG $ Chain 0 l0 0 tr ac g 0 pr lh cc mn+ where+ l0 = Link i0 (pr i0) (lh i0)+ ac = emptyA $ ccProposals cc+ batchMonitorSizes = map getMonitorBatchSize $ mBatches mn+ traceLength = maximum $ 1 : batchMonitorSizes++mhgFn :: AnalysisName -> FilePath+mhgFn (AnalysisName nm) = nm ++ ".mhg"++-- | Save an MHG algorithm.+mhgSave ::+ ToJSON a =>+ AnalysisName ->+ MHG a ->+ IO ()+mhgSave nm (MHG c) = do+ savedChain <- toSavedChain c+ BL.writeFile (mhgFn nm) $ compress $ encode savedChain++-- | Load an MHG algorithm.+--+-- See 'Mcmc.Mcmc.mcmcContinue'.+mhgLoad ::+ FromJSON a =>+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ AnalysisName ->+ IO (MHG a)+mhgLoad pr lh cc mn nm = do+ savedChain <- eitherDecode . decompress <$> BL.readFile (mhgFn nm)+ chain <- either error (fromSavedChain pr lh cc mn) savedChain+ return $ MHG chain++-- The MHG ratio.+--+-- 'Infinity' if fX is zero. In this case, the proposal is always accepted.+--+-- 'NaN' if (fY or q) and fX are zero. In this case, the proposal is always+-- rejected.++-- There is a discrepancy between authors saying that one should (a) always+-- accept the new state when the current posterior is zero (Chapter 4 of the+-- Handbook of Markov Chain Monte Carlo), or (b) almost surely reject the+-- proposal when either fY or q are zero (Chapter 1). Since I trust the author+-- of Chapter 1 (Charles Geyer) I choose to follow option (b).+mhgRatio :: Log Double -> Log Double -> Log Double -> Log Double -> Log Double+-- q = qYX / qXY * jXY; see 'ProposalSimple'.+-- j = Jacobian.+mhgRatio fX fY q j = fY / fX * q * j+{-# INLINE mhgRatio #-}++-- | Accept or reject a proposal with given MHG ratio?+mhgAccept :: Log Double -> GenIO -> IO Bool+mhgAccept r g+ | ln r >= 0.0 = return True+ | otherwise = do+ b <- uniform g+ return $ b < exp (ln r)++mhgPropose :: MHG a -> Proposal a -> IO (MHG a)+mhgPropose (MHG c) p = do+ -- 1. Sample new state.+ (!y, !q, !j) <- liftIO $ s x g+ -- 2. Calculate Metropolis-Hastings-Green ratio.+ let !pY = pF y+ !lY = lF y+ !r = mhgRatio (pX * lX) (pY * lY) q j+ -- 3. Accept or reject.+ -- if ln r >= 0.0+ -- then do+ -- let !ac' = pushA p True ac+ -- return $ MHG $ c {link = Link y pY lY, acceptance = ac'}+ -- else do+ -- b <- uniform g+ -- if b < exp (ln r)+ -- then do+ -- let !ac' = pushA p True ac+ -- return $ MHG $ c {link = Link y pY lY, acceptance = ac'}+ -- else do+ -- let !ac' = pushA p False ac+ -- return $ MHG $ c {acceptance = pushA p False ac'}+ accept <- mhgAccept r g+ if accept+ then do+ let !ac' = pushA p True ac+ return $ MHG $ c {link = Link y pY lY, acceptance = ac'}+ else do+ let !ac' = pushA p False ac+ return $ MHG $ c {acceptance = pushA p False ac'}+ where+ s = pSimple p+ (Link x pX lX) = link c+ pF = priorFunction c+ lF = likelihoodFunction c+ ac = acceptance c+ g = generator c++mhgPush :: MHG a -> IO (MHG a)+mhgPush (MHG c) = do+ t' <- pushT i t+ return $ MHG c {trace = t', iteration = succ n}+ where+ i = link c+ t = trace c+ n = iteration c++-- Ignore the number of capabilities. I have tried a lot of stuff, but the MHG+-- algorithm is just inherently sequential. Parallelization can be achieved by+-- having parallel prior and/or likelihood functions, or by using algorithms+-- running parallel chains such as 'MC3'.+mhgIterate :: ParallelizationMode -> MHG a -> IO (MHG a)+mhgIterate _ a = do+ ps <- orderProposals cc g+ a' <- foldM mhgPropose a ps+ mhgPush a'+ where+ c = fromMHG a+ cc = cycle c+ g = generator c++mhgAutoTune :: MHG a -> MHG a+mhgAutoTune (MHG c) = MHG $ c {cycle = autoTuneCycle ac cc}+ where+ ac = acceptance c+ cc = cycle c++mhgResetAcceptance :: MHG a -> MHG a+mhgResetAcceptance (MHG c) = MHG $ c {acceptance = resetA ac}+ where+ ac = acceptance c++mhgSummarizeCycle :: MHG a -> BL.ByteString+mhgSummarizeCycle (MHG c) = summarizeCycle ac cc+ where+ cc = cycle c+ ac = acceptance c++mhgOpenMonitors :: AnalysisName -> ExecutionMode -> MHG a -> IO (MHG a)+mhgOpenMonitors nm em (MHG c) = do+ m' <- mOpen pre suf em m+ return $ MHG c {monitor = m'}+ where+ m = monitor c+ pre = fromAnalysisName nm+ suf = printf "%02d" $ chainId c++mhgExecuteMonitors ::+ Verbosity ->+ -- Starting time.+ UTCTime ->+ -- Total number of iterations.+ Int ->+ MHG a ->+ IO (Maybe BL.ByteString)+mhgExecuteMonitors vb t0 iTotal (MHG c) = mExec vb i i0 t0 tr iTotal m+ where+ i = iteration c+ i0 = start c+ tr = trace c+ m = monitor c++mhgStdMonitorHeader :: MHG a -> BL.ByteString+mhgStdMonitorHeader (MHG c) = msHeader (mStdOut $ monitor c)++mhgCloseMonitors :: MHG a -> IO (MHG a)+mhgCloseMonitors (MHG c) = do+ m' <- mClose m+ return $ MHG $ c {monitor = m'}+ where+ m = monitor c
+ src/Mcmc/Chain/Chain.hs view
@@ -0,0 +1,95 @@+-- |+-- Module : Mcmc.Chain.Chain+-- Description : Simple representation of a Markov chain+-- Copyright : (c) Dominik Schrempf 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Tue May 5 18:01:15 2020.+module Mcmc.Chain.Chain+ ( PriorFunction,+ noPrior,+ LikelihoodFunction,+ noLikelihood,+ Chain (..),+ )+where++-- Note: It is not necessary to add another type @b@ to store supplementary+-- information about the chain. The information can just be stored in @a@+-- equally well.++import Mcmc.Chain.Link+import Mcmc.Chain.Trace+import Mcmc.Monitor+import Mcmc.Proposal+import Numeric.Log+import System.Random.MWC hiding (save)+import Prelude hiding (cycle)++-- | Prior function.+type PriorFunction a = a -> Log Double++-- | Flat prior function. Useful for testing and debugging.+noPrior :: PriorFunction a+noPrior = const 1.0++-- | Likelihood function.+type LikelihoodFunction a = a -> Log Double++-- | Flat likelihood function. Useful for testing and debugging.+noLikelihood :: LikelihoodFunction a+noLikelihood = const 1.0++-- | The chain contains all information to run an MCMC sampler.+--+-- The state of a chain has type @a@. If necessary, the type @a@ can also be+-- used to store auxiliary information.+--+-- For example, the chain stores information about the current 'Link' and+-- 'iteration', the 'Trace', the 'Acceptance' rates, and the random number+-- generator.+--+-- Further, the chain includes auxiliary variables and functions such as the+-- prior and likelihood functions, or 'Proposal's to move around the state space+-- and to 'Monitor' an MCMC run.+--+-- The 'Mcmc.Environment.Environment' of the chain is not stored externally.+data Chain a = Chain+ { -- Variables; saved.+ -- | Chain index; useful if more chains are run.+ chainId :: Int,+ -- | The current 'Link' of the chain combines the current state and the+ -- current likelihood. The link is updated after a proposal has been+ -- executed.+ link :: Link a,+ -- | The current iteration or completed number of cycles.+ iteration :: Int,+ -- | The 'Trace' of the Markov chain. In contrast to the link, the trace is+ -- updated only after all proposals in the cycle have been executed.+ trace :: Trace a,+ -- | For each 'Proposal', store the list of accepted (True) and rejected (False)+ -- proposals; for reasons of efficiency, the list is also stored in reverse+ -- order.+ acceptance :: Acceptance (Proposal a),+ -- | The random number generator.+ generator :: GenIO,+ --+ -- Variables and functions; not saved.++ -- | Starting iteration of the chain; used to calculate run time and ETA.+ start :: Int,+ -- | The prior function. The un-normalized posterior is the product of the+ -- prior and the likelihood.+ priorFunction :: PriorFunction a,+ -- | The likelihood function. The un-normalized posterior is the product of+ -- the prior and the likelihood.+ likelihoodFunction :: LikelihoodFunction a,+ -- | A set of 'Proposal's form a 'Cycle'.+ cycle :: Cycle a,+ -- | A 'Monitor' observing the chain.+ monitor :: Monitor a+ }
+ src/Mcmc/Chain/Link.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module : Mcmc.Chain.Link+-- Description : The state combined with auxiliary variables+-- Copyright : (c) Dominik Schrempf 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Wed May 20 09:10:27 2020.+module Mcmc.Chain.Link+ ( Link (..),+ )+where++import Data.Aeson+import Data.Aeson.Types+import Numeric.Log++-- | Link of a Markov chain. For reasons of computational efficiency, each state+-- is associated with the corresponding prior and likelihood.+data Link a = Link+ { -- | The current state in the state space @a@.+ state :: a,+ -- | The current prior.+ prior :: Log Double,+ -- | The current likelihood.+ likelihood :: Log Double+ }+ deriving (Eq, Ord, Show, Read)++instance ToJSON a => ToJSON (Link a) where+ toJSON (Link x (Exp p) (Exp l)) = object ["s" .= x, "p" .= p, "l" .= l]+ toEncoding (Link x (Exp p) (Exp l)) = pairs ("s" .= x <> "p" .= p <> "l" .= l)++link :: FromJSON a => Object -> Parser (Link a)+link v = do+ s <- v .: "s"+ p <- v .: "p"+ l <- v .: "l"+ return $ Link s (Exp p) (Exp l)++instance FromJSON a => FromJSON (Link a) where+ parseJSON = withObject "Link" link
+ src/Mcmc/Chain/Save.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Mcmc.Chain.Save+-- Description : Save and load a Markov chain+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--++-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Tue Jun 16 10:18:54 2020.+--+-- Save and load chains. It is easy to save and restore the current state and+-- likelihood (or the trace), but it is not feasible to store all the proposals+-- and so on, so they have to be provided again when continuing a run.+module Mcmc.Chain.Save+ ( SavedChain (..),+ toSavedChain,+ fromSavedChain,+ )+where++import Control.Monad+import Data.Aeson+import Data.Aeson.TH+import Data.List hiding (cycle)+import qualified Data.Map as M+import Data.Maybe+import qualified Data.Vector as VB+import qualified Data.Vector.Unboxed as VU+import Data.Word+import Mcmc.Chain.Chain+import Mcmc.Chain.Link+import Mcmc.Chain.Trace+import Mcmc.Internal.Random+import Mcmc.Monitor+import Mcmc.Proposal+import Prelude hiding (cycle)+import qualified Data.Stack.Circular as C++-- | Storable values of a Markov chain.+--+-- See 'toSavedChain'.+data SavedChain a = SavedChain+ {+ savedId :: Int,+ savedLink :: Link a,+ savedIteration :: Int,+ savedTrace :: C.Stack VB.Vector (Link a),+ savedAcceptance :: Acceptance Int,+ savedSeed :: VU.Vector Word32,+ savedTuningParameters :: [Maybe Double]+ }+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''SavedChain)++-- | Save a chain.+toSavedChain ::+ Chain a ->+ IO (SavedChain a)+toSavedChain (Chain ci it i tr ac g _ _ _ cc _) = do+ g' <- saveGen g+ tr' <- freezeT tr+ return $ SavedChain ci it i tr' ac' g' ts+ where+ ps = ccProposals cc+ ac' = transformKeysA ps [0 ..] ac+ ts = [fmap tParam mt | mt <- map pTuner ps]++-- | Load a saved chain.+--+-- Recompute and check the prior and likelihood for the last state because the+-- functions may have changed. Of course, we cannot test for the same function,+-- but having the same prior and likelihood at the last state is already a good+-- indicator.+fromSavedChain ::+ PriorFunction a ->+ LikelihoodFunction a ->+ Cycle a ->+ Monitor a ->+ SavedChain a ->+ IO (Chain a)+fromSavedChain pr lh cc mn (SavedChain ci it i tr ac' g' ts)+ | pr (state it) /= prior it =+ error "fromSave: Provided prior function does not match the saved prior."+ | lh (state it) /= likelihood it =+ error "fromSave: Provided likelihood function does not match the saved likelihood."+ | otherwise = do+ g <- loadGen g'+ tr' <- thawT tr+ return $ Chain ci it i tr' ac g i pr lh cc' mn+ where+ ac = transformKeysA [0 ..] (ccProposals cc) ac'+ getTuningF mt = case mt of+ Nothing -> const 1.0+ Just t -> const t+ cc' =+ tuneCycle+ ( M.map getTuningF $+ M.fromList $+ zip (ccProposals cc) ts+ )+ cc
+ src/Mcmc/Chain/Trace.hs view
@@ -0,0 +1,75 @@+-- |+-- Module : Mcmc.Chain.Trace+-- Description : History of a Markov chain+-- Copyright : (c) Dominik Schrempf 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Wed May 20 09:11:25 2020.+module Mcmc.Chain.Trace+ ( Trace,+ replicateT,+ lengthT,+ pushT,+ headT,+ takeT,+ freezeT,+ thawT,+ )+where++import Control.Monad.Primitive+import qualified Data.Stack.Circular as C+import qualified Data.Vector as VB+import Mcmc.Chain.Link++-- | A 'Trace' is a mutable circular stack that passes through a list of states+-- with associated priors and likelihoods called 'Link's.+newtype Trace a = Trace {fromTrace :: C.MStack VB.Vector RealWorld (Link a)}++-- | Initialize a trace of given length by replicating the same value.+--+-- Be careful not to compute summary statistics before pushing enough values.+--+-- Call 'error' if the maximum size is zero or negative.+replicateT :: Int -> Link a -> IO (Trace a)+replicateT n l = Trace <$> C.replicate n l++-- | Get the length of the trace.+lengthT :: Trace a -> Int+lengthT = C.size . fromTrace++-- | Push a 'Link' on the 'Trace'.+pushT :: Link a -> Trace a -> IO (Trace a)+pushT x t = do+ s' <- C.push x (fromTrace t)+ return $ Trace s'+{-# INLINEABLE pushT #-}++-- | Get the most recent link of the trace.+--+-- See 'C.get'.+headT :: Trace a -> IO (Link a)+headT = C.get . fromTrace+{-# INLINEABLE headT #-}++-- | Get the k most recent links of the trace.+--+-- See 'C.take'.+takeT :: Int -> Trace a -> IO (VB.Vector (Link a))+takeT k = C.take k . fromTrace++-- | Freeze the mutable trace for storage.+--+-- See 'C.freeze'.+freezeT :: Trace a -> IO (C.Stack VB.Vector (Link a))+freezeT = C.freeze . fromTrace++-- | Thaw a circular stack.+--+-- See 'See.thaw'.+thawT :: C.Stack VB.Vector (Link a) -> IO (Trace a)+thawT t = Trace <$> C.thaw t
+ src/Mcmc/Environment.hs view
@@ -0,0 +1,49 @@+-- |+-- Module : Mcmc.Environment+-- Description : Runtime environment+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Tue Nov 17 11:00:09 2020.+module Mcmc.Environment+ ( Environment (..),+ initializeEnvironment,+ )+where++import Data.Time.Clock+import Mcmc.Settings+import System.IO++-- | The environment of an MCMC run.+data Environment = Environment+ { settings :: Settings,+ -- | We have to use 'Maybe' here, because we do not want to open any log+ -- file when being 'Quiet'.+ logHandle :: Maybe Handle,+ -- | Used to calculate the ETA.+ startingTime :: UTCTime+ }+ deriving (Eq, Show)++-- | Initialize the environment.+--+-- Open log file, get current time.+initializeEnvironment ::+ Settings ->+ IO Environment+initializeEnvironment s = do+ t <- getCurrentTime+ mh <- case sVerbosity s of+ Quiet -> return Nothing+ _ -> do+ h <- openWithExecutionMode em fn+ return $ Just h+ return $ Environment s mh t+ where+ fn = fromAnalysisName (sAnalysisName s) ++ ".log"+ em = sExecutionMode s
src/Mcmc/Internal/ByteString.hs view
@@ -10,7 +10,8 @@ -- -- Creation date: Mon Aug 3 10:46:27 2020. module Mcmc.Internal.ByteString- ( alignRightWith,+ ( alignRightWithNoTrim,+ alignRightWith, alignRight, alignLeftWith, alignLeft,@@ -19,11 +20,16 @@ import qualified Data.ByteString.Lazy.Char8 as BL +-- | For a given width, align string to the right; use given fill character.+alignRightWithNoTrim :: Char -> Int -> BL.ByteString -> BL.ByteString+alignRightWithNoTrim c n s = BL.replicate (fromIntegral n - l) c <> s+ where+ l = BL.length s+ -- | For a given width, align string to the right; use given fill character; -- trim on the left if string is longer. alignRightWith :: Char -> Int -> BL.ByteString -> BL.ByteString-alignRightWith c n s =- BL.replicate (fromIntegral n - l) c <> BL.take (fromIntegral n) s+alignRightWith c n s = BL.replicate (fromIntegral n - l) c <> BL.take (fromIntegral n) s where l = BL.length s
+ src/Mcmc/Internal/Random.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- |+-- Module : Mcmc.Internal.Random+-- Description : Tools for random calculations+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Wed Nov 25 07:14:52 2020.+module Mcmc.Internal.Random+ ( splitGen,+ saveGen,+ loadGen,+ )+where++import Control.Monad+import Control.Monad.Primitive+import qualified Data.Vector.Unboxed as V+import Data.Word+import System.Random.MWC++-- | Split a generator.+--+-- Splitting an MWC pseudo number generator is not good practice. However, I+-- have to go with this solution for now, and wait for proper support of+-- spittable pseudo random number generators such as @splitmix@.+splitGen :: PrimMonad m => Int -> Gen (PrimState m) -> m [Gen (PrimState m)]+splitGen n gen+ | n <= 0 = return []+ | otherwise = do+ seeds :: [V.Vector Word32] <- replicateM n $ uniformVector gen 256+ mapM initialize seeds++-- TODO: Splitmix. Remove or amend these functions as soon as split mix is used+-- and is available with the statistics package.++-- | Save a generator to a seed.+saveGen :: GenIO -> IO (V.Vector Word32)+saveGen = fmap fromSeed . save++-- | Load a generator from a seed.+loadGen :: V.Vector Word32 -> IO GenIO+loadGen = restore . toSeed
src/Mcmc/Internal/Shuffle.hs view
@@ -13,51 +13,37 @@ -- From https://wiki.haskell.org/Random_shuffle. module Mcmc.Internal.Shuffle ( shuffle,- shuffleN, grabble, ) where import Control.Monad import Control.Monad.ST-import Data.Vector (Vector) import qualified Data.Vector as V import qualified Data.Vector.Mutable as M import System.Random.MWC- ( GenIO,- uniformR,- ) --- | Shuffle a list.-shuffle :: [a] -> GenIO -> IO [a]-shuffle xs g = head <$> grabble xs 1 (length xs) g---- | Shuffle a list @n@ times.-shuffleN :: [a] -> Int -> GenIO -> IO [[a]]-shuffleN xs n = grabble xs n (length xs)+-- Fisher-Yates shuffle. See also+-- 'System.Random.MWC.Distributions.uniformPermutation' which is a little+-- cleaner, in my opinion. However, I would like to move away from MWC so I+-- leave the custom implementation for now. --- -- Using System.Random.Shuffle. Speed is the same, so stay without additional dependency.--- -- | Shuffle a list @n@ times.--- shuffleN :: [a] -> Int -> GenIO -> IO [[a]]--- shuffleN xs n g = replicateM n $ fmap (shuffle xs) (rseqM (length xs - 1) g)--- where--- rseqM :: Int -> GenIO -> IO [Int]--- rseqM 0 _ = return []--- rseqM i gen = liftM2 (:) (uniformR (0, i) gen) (rseqM (i - 1) gen)+-- | Shuffle a vector.+shuffle :: [a] -> GenIO -> IO [a]+shuffle xs = grabble xs (length xs) -- | @grabble xs m n@ is /O(m*n')/, where @n' = min n (length xs)@. Choose @n'@ -- elements from @xs@, without replacement, and that @m@ times.-grabble :: [a] -> Int -> Int -> GenIO -> IO [[a]]-grabble xs m n gen = do- swapss <- replicateM m $- forM [0 .. min (l - 1) n] $ \i -> do- j <- uniformR (i, l) gen- return (i, j)- return $ map (V.toList . V.take n . swapElems (V.fromList xs)) swapss+grabble :: [a] -> Int -> GenIO -> IO [a]+grabble xs m gen = do+ swaps <- forM [0 .. min (l - 1) m] $ \i -> do+ j <- uniformR (i, l) gen+ return (i, j)+ return $ (V.toList . V.take m . swapElems (V.fromList xs)) swaps where l = length xs - 1 -swapElems :: Vector a -> [(Int, Int)] -> Vector a+swapElems :: V.Vector a -> [(Int, Int)] -> V.Vector a swapElems xs swaps = runST $ do mxs <- V.unsafeThaw xs mapM_ (uncurry $ M.unsafeSwap mxs) swaps
− src/Mcmc/Item.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}---- |--- Module : Mcmc.Item--- Description : Links of Markov chains--- Copyright : (c) Dominik Schrempf 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Wed May 20 09:10:27 2020.-module Mcmc.Item- ( Item (..),- )-where--import Data.Aeson-import Data.Aeson.Types-import Numeric.Log---- | An 'Item', or link of the Markov chain. For reasons of computational--- efficiency, each state is associated with the corresponding prior and--- likelihood.-data Item a = Item- { -- | The current state in the state space @a@.- state :: a,- -- | The current prior.- prior :: Log Double,- -- | The current likelihood.- likelihood :: Log Double- }- deriving (Eq, Ord, Show, Read)--instance ToJSON a => ToJSON (Item a) where- toJSON (Item x (Exp p) (Exp l)) = object ["s" .= x, "p" .= p, "l" .= l]- toEncoding (Item x (Exp p) (Exp l)) = pairs ("s" .= x <> "p" .= p <> "l" .= l)--item :: FromJSON a => Object -> Parser (Item a)-item v = do- s <- v .: "s"- p <- v .: "p"- l <- v .: "l"- return $ Item s (Exp p) (Exp l)--instance FromJSON a => FromJSON (Item a) where- parseJSON = withObject "Item" item
src/Mcmc/Mcmc.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Mcmc.Mcmc--- Description : Mcmc helpers+-- Description : Framework for running Markov chain Monte Carlo samplers -- Copyright : (c) Dominik Schrempf, 2020 -- License : GPL-3.0-or-later --@@ -12,265 +12,277 @@ -- -- Creation date: Fri May 29 10:19:45 2020. ----- Functions to work with the 'Mcmc' state transformer.+-- 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,- mcmcOutB,- mcmcOutS,- mcmcWarnB,- mcmcWarnS,- mcmcInfoB,- mcmcInfoS,- mcmcDebugB,- mcmcDebugS,- mcmcAutotune,- mcmcClean,- mcmcResetA,- mcmcSummarizeCycle,- mcmcReport,- mcmcMonitorExec,- mcmcRun,+ ( mcmc,+ mcmcContinue, ) where import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Trans.State-import Data.Aeson+-- import Control.Monad.Trans.RWS.CPS+import Control.Monad.Trans.Reader import qualified Data.ByteString.Lazy.Char8 as BL import Data.Maybe import Data.Time.Clock-import Data.Time.Format-import Mcmc.Item-import Mcmc.Monitor+import Mcmc.Algorithm+import Mcmc.Environment import Mcmc.Monitor.Time-import Mcmc.Proposal-import Mcmc.Save-import Mcmc.Status hiding (debug)-import Mcmc.Verbosity-import Numeric.Log-import System.Directory+import Mcmc.Settings import System.IO+import Text.Show.Pretty import Prelude hiding (cycle) --- | An Mcmc state transformer; usually fiddling around with this type is not--- required, but it is used by the different inference algorithms.-type Mcmc a = StateT (Status a) IO--msgPrepare :: Char -> BL.ByteString -> BL.ByteString-msgPrepare c t = BL.cons c $ ": " <> t+-- The MCMC algorithm has read access to an environment and uses an algorithm+-- transforming the state @a@.+type MCMC a = ReaderT Environment IO a --- | Write to standard output and log file.-mcmcOutB :: BL.ByteString -> Mcmc a ()-mcmcOutB msg = do- h <- fromMaybe (error "mcmcOut: Log handle is missing.") <$> gets logHandle- liftIO $ BL.putStrLn msg >> BL.hPutStrLn h msg+msgPrepare :: BL.ByteString -> BL.ByteString -> BL.ByteString+msgPrepare pref msg = BL.intercalate "\n" $ map (BL.append pref) $ BL.lines msg --- | Write to standard output and log file.-mcmcOutS :: String -> Mcmc a ()-mcmcOutS = mcmcOutB . BL.pack+-- Write to standard output and log file.+mcmcOutB :: BL.ByteString -> BL.ByteString -> MCMC ()+mcmcOutB pref msg = do+ h <- fromMaybe (error "mcmcOut: Log handle is missing.") <$> reader logHandle+ liftIO $ BL.putStrLn msg' >> BL.hPutStrLn h msg'+ where+ msg' = msgPrepare pref msg --- Perform warning action.-mcmcWarnA :: Mcmc a () -> Mcmc a ()-mcmcWarnA a = gets verbosity >>= \v -> info v a+-- -- Perform warning action.+-- mcmcWarnA :: MCMC a () -> MCMC a ()+-- mcmcWarnA a = reader (verbosity . settings) >>= \v -> when (v >= Warn) a --- | Print warning message.-mcmcWarnB :: BL.ByteString -> Mcmc a ()-mcmcWarnB = mcmcWarnA . mcmcOutB . msgPrepare 'W'+-- -- Print warning message.+-- mcmcWarnB :: BL.ByteString -> MCMC a ()+-- mcmcWarnB = mcmcWarnA . mcmcOutB . msgPrepare 'W' --- | Print warning message.-mcmcWarnS :: String -> Mcmc a ()-mcmcWarnS = mcmcWarnB . BL.pack+-- -- Print warning message.+-- mcmcWarnS :: String -> MCMC a ()+-- mcmcWarnS = mcmcWarnB . BL.pack -- Perform info action.-mcmcInfoA :: Mcmc a () -> Mcmc a ()-mcmcInfoA a = gets verbosity >>= \v -> info v a+mcmcInfoA :: MCMC () -> MCMC ()+mcmcInfoA a = reader (sVerbosity . settings) >>= \v -> when (v >= Info) a --- | Print info message.-mcmcInfoB :: BL.ByteString -> Mcmc a ()-mcmcInfoB = mcmcInfoA . mcmcOutB . msgPrepare 'I'+-- Print info message.+mcmcInfoB :: BL.ByteString -> MCMC ()+mcmcInfoB = mcmcInfoA . mcmcOutB "I: " --- | Print info message.-mcmcInfoS :: String -> Mcmc a ()+-- Print info message.+mcmcInfoS :: String -> MCMC () mcmcInfoS = mcmcInfoB . BL.pack -- Perform debug action.-mcmcDebugA :: Mcmc a () -> Mcmc a ()-mcmcDebugA a = gets verbosity >>= \v -> debug v a+mcmcDebugA :: MCMC () -> MCMC ()+mcmcDebugA a = reader (sVerbosity . settings) >>= \v -> when (v == Debug) a --- | Print debug message.-mcmcDebugB :: BL.ByteString -> Mcmc a ()-mcmcDebugB = mcmcDebugA . mcmcOutB . msgPrepare 'D'+-- Print debug message.+mcmcDebugB :: BL.ByteString -> MCMC ()+mcmcDebugB = mcmcDebugA . mcmcOutB "D: " --- | Print debug message.-mcmcDebugS :: String -> Mcmc a ()+-- Print debug message.+mcmcDebugS :: String -> MCMC () mcmcDebugS = mcmcDebugB . BL.pack --- | Auto tune the 'Proposal's in the 'Cycle' of the chain. Reset acceptance counts.--- See 'autotuneCycle'.-mcmcAutotune :: Mcmc a ()-mcmcAutotune = do- mcmcDebugB "Auto tune."- s <- get- let a = acceptance s- c = cycle s- c' = autotuneCycle a c- put $ s {cycle = c'}+mcmcReportTime :: MCMC ()+mcmcReportTime = do+ mcmcDebugB "Report time."+ ti <- reader startingTime+ mcmcInfoS $ "Starting time of MCMC sampler: " <> renderTime ti --- | Clean the state.-mcmcClean :: Mcmc a ()-mcmcClean = do- s <- get- let cl = cleaner s- i = iteration s- case cl of- Just (Cleaner n f) | i `mod` n == 0 -> do- mcmcDebugB "Clean state."- let (Item st pr lh) = item s- mcmcDebugS $- "Old log prior and log likelihood: " ++ show (ln pr) ++ ", " ++ show (ln lh) ++ "."- let prF = priorF s- lhF = likelihoodF s- st' = f st- pr' = prF st'- lh' = lhF st'- mcmcDebugS $- "New log prior and log likelihood: " ++ show (ln pr') ++ ", " ++ show (ln lh') ++ "."- let dLogPr = abs $ ln pr - ln pr'- dLogLh = abs $ ln lh - ln lh'- when- (dLogPr > 0.01)- (mcmcWarnS $ "Log of old and new prior differ by " ++ show dLogPr ++ ".")- when- (dLogPr > 0.01)- (mcmcWarnS $ "Log of old and new likelihood differ by " ++ show dLogLh ++ ".")- put $ s {item = Item st' pr' lh'}- _ -> return ()+mcmcExecute :: Algorithm a => a -> MCMC a+mcmcExecute a = do+ mcmcDebugB "Executing MCMC run."+ s <- reader settings+ a' <- case sExecutionMode s of+ Fail -> mcmcNewRun a+ Overwrite -> mcmcNewRun a+ Continue -> mcmcContinueRun a+ mcmcDebugB "Executed MCMC run."+ return a' --- | Reset acceptance counts.-mcmcResetA :: Mcmc a ()-mcmcResetA = do- mcmcDebugB "Reset acceptance ratios."- s <- get- let a = acceptance s- put $ s {acceptance = resetA a}+-- Reset acceptance counts.+mcmcResetAcceptance :: Algorithm a => a -> MCMC a+mcmcResetAcceptance a = do+ mcmcDebugB "Reset acceptance rates."+ return $ aResetAcceptance a --- | Print short summary of 'Proposal's in 'Cycle'. See 'summarizeCycle'.-mcmcSummarizeCycle :: Mcmc a BL.ByteString-mcmcSummarizeCycle = do- a <- gets acceptance- c <- gets cycle- return $ summarizeCycle a c+-- Execute the monitors of the chain.+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)+ mStdLog <- liftIO (aExecuteMonitors vb t0 iTotal a)+ forM_ mStdLog (mcmcOutB " ") -fTime :: FormatTime t => t -> String-fTime = formatTime defaultTimeLocale "%B %-e, %Y, at %H:%M %P, %Z."+mcmcIterate :: Algorithm a => Int -> a -> MCMC a+mcmcIterate n a+ | n < 0 = error "mcmcIterate: Number of iterations is negative."+ | n == 0 = return a+ | otherwise = do+ p <- sParallelizationMode . settings <$> ask+ a' <- liftIO $ aIterate p a+ mcmcExecuteMonitors a'+ mcmcIterate (n -1) a' --- Open log file.-mcmcOpenLog :: Mcmc a ()-mcmcOpenLog = do- s <- get- let lfn = name s ++ ".log"- n = iteration s- frc = forceOverwrite s- fe <- liftIO $ doesFileExist lfn- mh <- liftIO $ case verbosity s of- Quiet -> return Nothing- _ ->- Just <$> case (fe, n, frc) of- (False, _, _) -> openFile lfn WriteMode- (True, 0, True) -> openFile lfn WriteMode- (True, 0, False) -> error "mcmcInit: Log file exists; use 'force' to overwrite output files."- (True, _, _) -> openFile lfn AppendMode- put s {logHandle = mh}- mcmcDebugS $ "Log file name: " ++ lfn ++ "."- mcmcDebugB "Log file opened."+mcmcNewRun :: Algorithm a => a -> MCMC a+mcmcNewRun a = do+ s <- reader settings+ mcmcInfoB "Start new MCMC sampler."+ mcmcInfoB "Initial state."+ mcmcInfoB $ aStdMonitorHeader a+ mcmcExecuteMonitors a+ mcmcInfoB $ aSummarizeCycle a+ a' <- mcmcBurnIn a+ a'' <- mcmcResetAcceptance a'+ let i = fromIterations $ sIterations s+ mcmcInfoS $ "Run chain for " ++ show i ++ " iterations."+ mcmcInfoB $ aStdMonitorHeader a''+ mcmcIterate i a'' --- Set the total number of iterations, the current time and open the 'Monitor's--- of the chain. See 'mOpen'.-mcmcInit :: Mcmc a ()-mcmcInit = do- mcmcOpenLog- s <- get- -- Start time.- t <- liftIO getCurrentTime- mcmcInfoS $ "Start time: " <> fTime t- -- Monitor.- let m = monitor s- n = iteration s- nm = name s- frc = forceOverwrite s- m' <- if n == 0 then liftIO $ mOpen nm frc m else liftIO $ mAppend nm m- put $ s {monitor = m', start = Just (n, t)}+mcmcContinueRun :: Algorithm a => a -> MCMC a+mcmcContinueRun a = do+ s <- reader settings+ let iTotal = fromIterations (sIterations s) + burnInIterations (sBurnIn s)+ mcmcInfoB "Continuation of MCMC sampler."+ let iCurrent = aIteration a+ mcmcInfoS $ "Current iteration: " ++ show iCurrent ++ "."+ mcmcInfoS $ "Total iterations: " ++ show iTotal ++ "."+ let di = iTotal - iCurrent+ mcmcInfoB $ aSummarizeCycle a+ mcmcInfoS $ "Run chain for " ++ show di ++ " iterations."+ mcmcInfoB $ aStdMonitorHeader a+ mcmcIterate di a --- | Report what is going to be done.-mcmcReport :: ToJSON a => Mcmc a ()-mcmcReport = do- s <- get- let b = burnInIterations s- t = autoTuningPeriod s- n = iterations s- c = cleaner s- case b of- Just b' -> mcmcInfoS $ "Burn in for " <> show b' <> " iterations."- Nothing -> return ()- case t of- Just t' -> mcmcInfoS $ "Auto tune every " <> show t' <> " iterations (during burn in only)."- Nothing -> return ()- case c of- Just (Cleaner c' _) -> mcmcInfoS $ "Clean state every " <> show c' <> " iterations."- Nothing -> return ()- mcmcInfoS $ "Run chain for " <> show n <> " iterations."- mcmcInfoB "Initial state."- mcmcMonitorExec+mcmcBurnIn :: Algorithm a => a -> MCMC a+mcmcBurnIn a = do+ s <- reader settings+ case sBurnIn s of+ NoBurnIn -> do+ mcmcInfoS "No burn in."+ return a+ BurnInWithoutAutoTuning n -> do+ mcmcInfoS $ "Burn in for " <> show n <> " iterations."+ mcmcInfoS "Auto tuning is disabled."+ mcmcInfoB $ aStdMonitorHeader a+ a' <- mcmcIterate n a+ mcmcInfoB $ aSummarizeCycle a'+ mcmcInfoB "Burn in finished."+ return a'+ BurnInWithAutoTuning n t -> do+ mcmcInfoS $ "Burn in for " ++ show n ++ " iterations."+ mcmcInfoS $ "Auto tuning is enabled with a period of " ++ show t ++ "."+ mcmcInfoB $ aStdMonitorHeader a+ a' <- mcmcBurnInWithAutoTuning n t a+ mcmcInfoB "Burn in finished."+ return a' --- Save the status of an MCMC run. See 'saveStatus'.-mcmcSave :: ToJSON a => Mcmc a ()-mcmcSave = do- s <- get- case save s of- Just n -> do- mcmcInfoB $ "Save Markov chain with trace of length " <> BL.pack (show n) <> "."+-- Auto tune the proposals.+mcmcAutotune :: Algorithm a => a -> MCMC a+mcmcAutotune a = do+ mcmcDebugB "Auto tune."+ return $ aAutoTune a++mcmcBurnInWithAutoTuning :: Algorithm a => Int -> Int -> a -> MCMC a+mcmcBurnInWithAutoTuning b t a+ | b > t = do+ a' <- mcmcResetAcceptance a+ a'' <- mcmcIterate t a'+ mcmcDebugB $ aSummarizeCycle a''+ a''' <- mcmcAutotune a''+ mcmcDebugB $ aStdMonitorHeader a''+ mcmcBurnInWithAutoTuning (b - t) t a'''+ | otherwise = do+ a' <- mcmcResetAcceptance a+ a'' <- mcmcIterate b a'+ mcmcInfoB $ aSummarizeCycle a''+ mcmcInfoS $ "Acceptance rates calculated over the last " <> show b <> " iterations."+ return a''++mcmcInitialize :: Algorithm a => a -> MCMC a+mcmcInitialize a = do+ mcmcInfoS $ aName a ++ " algorithm."+ s <- settings <$> ask+ mcmcDebugB "Opening monitors."+ a' <- liftIO $ aOpenMonitors (sAnalysisName s) (sExecutionMode s) a+ mcmcDebugB "Monitors opened."+ return a'++-- Save the MCMC run.+mcmcSave :: Algorithm a => a -> MCMC ()+mcmcSave a = do+ s <- reader settings+ case sSaveMode s of+ NoSave -> mcmcInfoB "Do not save the MCMC analysis."+ Save -> do+ mcmcInfoB "Save settings."+ liftIO $ settingsSave s+ let nm = sAnalysisName s+ mcmcInfoB "Save compressed MCMC analysis." mcmcInfoB "For long traces, or complex objects, this may take a while."- liftIO $ saveStatus (name s <> ".mcmc") s- mcmcInfoB "Done saving Markov chain."- Nothing -> mcmcInfoB "Do not save the Markov chain."+ liftIO $ aSave nm a+ mcmcInfoB "Markov chain saved." --- | Execute the 'Monitor's of the chain. See 'mExec'.-mcmcMonitorExec :: ToJSON a => Mcmc a ()-mcmcMonitorExec = do- s <- get- let i = iteration s- j = iterations s + fromMaybe 0 (burnInIterations s)- m = monitor s- (ss, st) = fromMaybe (error "mcmcMonitorExec: Starting state and time not set.") (start s)- tr = trace s- vb = verbosity s- mt <- liftIO $ mExec vb i ss st tr j m- forM_ mt mcmcOutB+-- Report and finish up.+mcmcClose :: Algorithm a => a -> MCMC a+mcmcClose a = do+ mcmcDebugB "Closing MCMC run."+ mcmcInfoB $ aSummarizeCycle a+ mcmcInfoS $ aName a ++ " algorithm finished."+ mcmcSave a+ ti <- reader startingTime+ te <- liftIO getCurrentTime+ let dt = te `diffUTCTime` ti+ mcmcInfoB $ "Wall clock run time: " <> renderDuration dt <> "."+ mcmcInfoS $ "End time: " <> renderTime te+ a' <- liftIO $ aCloseMonitors a+ h <- reader logHandle+ liftIO $ forM_ h hClose+ return a' --- Close the 'Monitor's of the chain. See 'mClose'.-mcmcClose :: ToJSON a => Mcmc a ()-mcmcClose = do- s <- get- mcmcSummarizeCycle >>= mcmcInfoB- mcmcInfoB "Metropolis-Hastings sampler finished."- let m = monitor s- m' <- liftIO $ mClose m- put $ s {monitor = m'}- mcmcSave- t <- liftIO getCurrentTime- let rt = case start s of- Nothing -> error "mcmcClose: Start time not set."- Just (_, st) -> t `diffUTCTime` st- mcmcInfoB $ "Wall clock run time: " <> renderDuration rt <> "."- mcmcInfoS $ "End time: " <> fTime t- case logHandle s of- Just h -> liftIO $ hClose h- Nothing -> return ()+-- Initialize the run, execute the run, and close the run.+mcmcRun :: Algorithm a => a -> MCMC a+mcmcRun a = do+ mcmcDebugB "The settings are:"+ reader settings >>= mcmcDebugS . ppShow --- | Run an MCMC algorithm.-mcmcRun :: ToJSON a => Mcmc a () -> Status a -> IO (Status a)-mcmcRun algorithm = execStateT $ do- mcmcInit- algorithm- mcmcClose+ -- Initialize.+ a' <- mcmcInitialize a+ mcmcReportTime++ -- 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.+--+-- See:+--+-- - 'Mcmc.Algorithm.Metropolis.mhgLoad'+--+-- - 'Mcmc.Algorithm.MC3.mc3Load'+mcmcContinue :: Algorithm a => Int -> Settings -> a -> IO a+mcmcContinue dn s = mcmc s'+ where+ n' = Iterations $ fromIterations (sIterations s) + dn+ s' = s {sIterations = n', sExecutionMode = Continue}
− src/Mcmc/Metropolis.hs
@@ -1,196 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}---- |--- Module : Mcmc.Metropolis--- Description : Metropolis-Hastings at its best--- Copyright : (c) Dominik Schrempf 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Tue May 5 20:11:30 2020.------ Metropolis-Hastings algorithm.-module Mcmc.Metropolis- ( mh,- mhContinue,- )-where--import Control.Monad-import Control.Monad.IO.Class-import Control.Monad.Trans.State-import Data.Aeson-import Data.Maybe-import Mcmc.Item-import Mcmc.Mcmc-import Mcmc.Proposal-import Mcmc.Status-import Mcmc.Trace-import Numeric.Log-import System.Random.MWC-import Prelude hiding (cycle)---- The Metropolis-Hastings ratio.------ 'Infinity' if fX is zero. In this case, the proposal is always accepted.------ 'NaN' if (fY or q) and fX are zero. In this case, the proposal is always--- rejected.---- There is a discrepancy between authors saying that one should (a) always--- accept the new state when the current posterior is zero (Chapter 4 of the--- Handbook of Markov Chain Monte Carlo), or (b) almost surely reject the--- proposal when either fY or q are zero (Chapter 1). Since I trust the author--- of Chapter 1 (Charles Geyer) I choose to follow option (b).-mhRatio :: Log Double -> Log Double -> Log Double -> Log Double -> Log Double--- q = qYX / qXY * jXY; see 'ProposalSimple'.--- j = Jacobian.-mhRatio fX fY q j = fY / fX * q * j-{-# INLINE mhRatio #-}--mhPropose :: Proposal a -> Mcmc a ()-mhPropose m = do- let p = pSimple m- s <- get- let (Item x pX lX) = item s- pF = priorF s- lF = likelihoodF s- a = acceptance s- g = generator s- -- 1. Sample new state.- (!y, !q, !j) <- liftIO $ p x g- -- 2. Calculate Metropolis-Hastings ratio.- let !pY = pF y- !lY = lF y- !r = mhRatio (pX * lX) (pY * lY) q j- -- 3. Accept or reject.- if ln r >= 0.0- then put $ s {item = Item y pY lY, acceptance = pushA m True a}- else do- b <- uniform g- if b < exp (ln r)- then put $ s {item = Item y pY lY, acceptance = pushA m True a}- else put $ s {acceptance = pushA m False a}---- TODO: Splitmix. Split the generator here. See SaveSpec -> mhContinue.---- Run one iterations; perform all proposals in a Cycle.-mhIter :: ToJSON a => [Proposal a] -> Mcmc a ()-mhIter ps = do- mapM_ mhPropose ps- s <- get- let i = item s- t = trace s- n = iteration s- put $ s {trace = pushT i t, iteration = succ n}- mcmcClean- mcmcMonitorExec---- Run N iterations.-mhNIter :: ToJSON a => Int -> Mcmc a ()-mhNIter n = do- mcmcDebugS $ "Run " <> show n <> " iterations."- c <- gets cycle- g <- gets generator- cycles <- liftIO $ getNIterations c n g- forM_ cycles mhIter---- Burn in and auto tune.-mhBurnInN :: ToJSON a => Int -> Maybe Int -> Mcmc a ()-mhBurnInN b (Just t)- | t <= 0 = error "mhBurnInN: Auto tuning period smaller equal 0."- | b > t = do- mcmcResetA- mhNIter t- mcmcSummarizeCycle >>= mcmcDebugB- mcmcAutotune- mhBurnInN (b - t) (Just t)- | otherwise = do- mcmcResetA- mhNIter b- mcmcSummarizeCycle >>= mcmcInfoB- mcmcInfoS $ "Acceptance ratios calculated over the last " <> show b <> " iterations."-mhBurnInN b Nothing = mhNIter b---- Initialize burn in for given number of iterations.-mhBurnIn :: ToJSON a => Int -> Maybe Int -> Mcmc a ()-mhBurnIn b t- | b < 0 = error "mhBurnIn: Negative number of burn in iterations."- | b == 0 = return ()- | otherwise = do- mcmcInfoS $ "Burn in for " <> show b <> " cycles."- mcmcDebugS $ "Auto tuning period is " <> show t <> "."- mhBurnInN b t- mcmcInfoB "Burn in finished."---- Run for given number of iterations.-mhRun :: ToJSON a => Int -> Mcmc a ()-mhRun n = do- mcmcResetA- mcmcInfoS $ "Run chain for " <> show n <> " iterations."- -- let (m, r) = n `quotRem` 100- -- -- Print header to standard output every 100 iterations.- -- replicateM_ m $ do- -- mcmcMonitorStdOutHeader- -- mhNIter 100- -- when (r > 0) $ do- -- mcmcMonitorStdOutHeader- -- mhNIter r- mhNIter n--mhT :: ToJSON a => Mcmc a ()-mhT = do- mcmcInfoB "Metropolis-Hastings sampler."- mcmcSummarizeCycle >>= mcmcInfoB- mcmcReport- s <- get- let b = fromMaybe 0 (burnInIterations s)- mhBurnIn b (autoTuningPeriod s)- mhRun $ iterations s--mhContinueT :: ToJSON a => Int -> Mcmc a ()-mhContinueT dn = do- mcmcInfoB "Continuation of Metropolis-Hastings sampler."- mcmcInfoS $ "Run chain for " <> show dn <> " additional iterations."- mcmcSummarizeCycle >>= mcmcInfoB- mhRun dn---- | Continue a Markov chain for a given number of Metropolis-Hastings steps.------ At the moment, when an MCMC run is continued, the old @.mcmc@ file is--- deleted. This behavior may change in the future.------ This means that an interrupted continuation also breaks previous runs. This--- step is necessary because, otherwise, incomplete monitor files are left on--- disk, if a continuation is canceled. Subsequent continuations would append to--- the incomplete monitor files and produce garbage.-mhContinue ::- ToJSON a =>- -- | Additional number of Metropolis-Hastings steps.- Int ->- -- | Loaded status of the Markov chain.- Status a ->- IO (Status a)-mhContinue dn s- | dn <= 0 = error "mhContinue: The number of iterations is zero or negative."- | otherwise = mcmcRun (mhContinueT dn) s'- where- n' = iterations s + dn- s' = s {iterations = n'}---- | Run a Markov chain for a given number of Metropolis-Hastings steps.-mh ::- ToJSON a =>- -- | Initial (or last) status of the Markov chain.- Status a ->- IO (Status a)-mh s =- if iteration s == 0- then mcmcRun mhT s- else do- putStrLn "To continue a Markov chain run, please use 'mhContinue'."- error $ "mh: Current iteration " ++ show (iteration s) ++ " is non-zero."
src/Mcmc/Monitor.hs view
@@ -16,14 +16,15 @@ Monitor (..), MonitorStdOut, monitorStdOut,+ msHeader, MonitorFile, monitorFile, MonitorBatch, monitorBatch,+ getMonitorBatchSize, -- * Use monitors mOpen,- mAppend, mExec, mClose, )@@ -33,21 +34,24 @@ import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy.Char8 as BL import Data.Int+import Data.List hiding (sum) import Data.Time.Clock+import qualified Data.Vector as VB+import Mcmc.Chain.Link+import Mcmc.Chain.Trace import Mcmc.Internal.ByteString-import Mcmc.Item import Mcmc.Monitor.Log import Mcmc.Monitor.Parameter import Mcmc.Monitor.ParameterBatch import Mcmc.Monitor.Time-import Mcmc.Trace-import Mcmc.Verbosity+import Mcmc.Settings import Numeric.Log-import System.Directory import System.IO import Prelude hiding (sum) --- | A 'Monitor' describes which part of the Markov chain should be logged and+-- | A 'Monitor' observing the chain.+--+-- A 'Monitor' describes which part of the Markov chain should be logged and -- where. Further, they allow output of summary statistics per iteration in a -- flexible way. data Monitor a = Monitor@@ -78,7 +82,7 @@ | otherwise = MonitorStdOut ps p msIWidth :: Int-msIWidth = 12+msIWidth = 9 msWidth :: Int msWidth = 22@@ -88,6 +92,7 @@ where vals = map (alignRight msWidth) (tail xs) +-- | Header of monitor to standard output. msHeader :: MonitorStdOut a -> BL.ByteString msHeader m = BL.intercalate "\n" [row, sep] where@@ -96,21 +101,21 @@ ["Iteration", "Log-Prior", "Log-Likelihood", "Log-Posterior"] ++ nms ++ ["Runtime", "ETA"]- sep = " " <> BL.replicate (BL.length row - 3) '-'+ sep = BL.replicate (BL.length row) '-' nms = [BL.pack $ mpName p | p <- msParams m] msDataLine :: Int ->- Item a ->+ Link a -> Int -> UTCTime -> Int -> MonitorStdOut a -> IO BL.ByteString-msDataLine i (Item x p l) ss st j m = do+msDataLine i (Link x p l) ss st j m = do ct <- getCurrentTime let dt = ct `diffUTCTime` st- -- Careful, don't evaluate this when i == ss.+ -- NOTE: Don't evaluate this when i == ss. timePerIter = dt / fromIntegral (i - ss) -- -- Always 0; doesn't make much sense. -- tpi = if (i - ss) < 10@@ -128,7 +133,7 @@ msExec :: Int ->- Item a ->+ Link a -> Int -> UTCTime -> Int ->@@ -136,9 +141,9 @@ IO (Maybe BL.ByteString) msExec i it ss st j m | i `mod` msPeriod m /= 0 = return Nothing- | i `mod` (msPeriod m * 100) == 0 = do- l <- msDataLine i it ss st j m- return $ Just $ msHeader m <> "\n" <> l+ -- -- | i `mod` (msPeriod m * 100) == 0 = do+ -- -- l <- msDataLine i it ss st j m+ -- -- return $ Just $ msHeader m <> "\n" <> l | otherwise = Just <$> msDataLine i it ss st j m -- | Monitor to a file; constructed with 'monitorFile'.@@ -165,32 +170,12 @@ mfRenderRow :: [BL.ByteString] -> BL.ByteString mfRenderRow = BL.intercalate "\t" -open' :: String -> Bool -> IO Handle-open' n frc = do- fe <- doesFileExist n- case (fe, frc) of- (False, _) -> openFile n WriteMode- (True, True) -> openFile n WriteMode- (True, False) -> error $ "open': File \"" <> n <> "\" exists; probably use 'force'?"--mfOpen :: String -> Bool -> MonitorFile a -> IO (MonitorFile a)-mfOpen n frc m = do- let mfn = n <> mfName m <> ".monitor"- h <- open' mfn frc- hSetBuffering h LineBuffering+mfOpen :: String -> String -> ExecutionMode -> MonitorFile a -> IO (MonitorFile a)+mfOpen pre suf em m = do+ let fn = intercalate "." $ filter (not . null) [pre, mfName m, suf, "monitor"]+ h <- openWithExecutionMode em fn return $ m {mfHandle = Just h} -mfAppend :: String -> MonitorFile a -> IO (MonitorFile a)-mfAppend n m = do- let fn = n <> mfName m <> ".monitor"- fe <- doesFileExist fn- if fe- then do- h <- openFile fn AppendMode- hSetBuffering h LineBuffering- return $ m {mfHandle = Just h}- else error $ "mfAppend: Monitor file does not exist: " ++ fn ++ "."- mfHeader :: MonitorFile a -> IO () mfHeader m = case mfHandle m of Nothing ->@@ -206,10 +191,10 @@ mfExec :: Int ->- Item a ->+ Link a -> MonitorFile a -> IO ()-mfExec i (Item x p l) m+mfExec i (Link x p l) m | i `mod` mfPeriod m /= 0 = return () | otherwise = case mfHandle m of Nothing ->@@ -231,11 +216,10 @@ Just h -> hClose h Nothing -> error $ "mfClose: File was not opened for monitor " <> mfName m <> "." --- | Monitor to a file, but calculate batch means for the given batch size;--- constructed with 'monitorBatch'.+-- | Batch monitor to a file. ----- Batch monitors are slow at the moment because the monitored parameter has to--- be extracted from the state for each iteration.+-- Calculate summary statistics over the last given number of iterations (batch+-- size). Construct with 'monitorBatch'. data MonitorBatch a = MonitorBatch { mbName :: String, mbHandle :: Maybe Handle,@@ -243,12 +227,11 @@ mbSize :: Int } --- | Monitor parameters to a file, see 'MonitorBatch'.+-- | Batch monitor parameters to a file, see 'MonitorBatch'. monitorBatch :: -- | Name; used as part of the file name. String ->- -- | Instructions about which parameters to log- -- and how to calculate the batch means.+ -- | Instructions about how to calculate the summary statistics. [MonitorParameterBatch a] -> -- | Batch size. Int ->@@ -257,23 +240,17 @@ | p < 2 = error "monitorBatch: Batch size has to be 2 or larger." | otherwise = MonitorBatch n Nothing ps p -mbOpen :: String -> Bool -> MonitorBatch a -> IO (MonitorBatch a)-mbOpen n frc m = do- let mfn = n <> mbName m <> ".batch"- h <- open' mfn frc- hSetBuffering h LineBuffering- return $ m {mbHandle = Just h}+-- | Batch monitor size.+--+-- Useful to determine the trace length.+getMonitorBatchSize :: MonitorBatch a -> Int+getMonitorBatchSize = mbSize -mbAppend :: String -> MonitorBatch a -> IO (MonitorBatch a)-mbAppend n m = do- let fn = n <> mbName m <> ".batch"- fe <- doesFileExist fn- if fe- then do- h <- openFile fn AppendMode- hSetBuffering h LineBuffering- return $ m {mbHandle = Just h}- else error $ "mbAppend: Monitor file does not exist: " ++ fn ++ "."+mbOpen :: String -> String -> ExecutionMode -> MonitorBatch a -> IO (MonitorBatch a)+mbOpen pre suf em m = do+ let fn = intercalate "." $ filter (not . null) [pre, mbName m, suf, "batch"]+ h <- openWithExecutionMode em fn+ return $ m {mbHandle = Just h} mbHeader :: MonitorBatch a -> IO () mbHeader m = case mbHandle m of@@ -288,15 +265,15 @@ ["Iteration", "Mean log-Prior", "Mean log-Likelihood", "Mean log-Posterior"] ++ [BL.pack $ mbpName mbp | mbp <- mbParams m] -mean :: [Log Double] -> Log Double-mean xs = sum xs / fromIntegral (length xs)+mean :: VB.Vector (Log Double) -> Log Double+mean xs = VB.sum xs / fromIntegral (VB.length xs) mbExec :: Int -> Trace a -> MonitorBatch a -> IO ()-mbExec i t' m+mbExec i t m | (i `mod` mbSize m /= 0) || (i == 0) = return () | otherwise = case mbHandle m of Nothing ->@@ -304,22 +281,21 @@ "mbExec: No handle available for batch monitor with name " <> mbName m <> "."- Just h ->+ Just h -> do+ xs <- takeT (mbSize m) t+ let lps = VB.map prior xs+ lls = VB.map likelihood xs+ los = VB.zipWith (*) lps lls+ mlps = mean lps+ mlls = mean lls+ mlos = mean los BL.hPutStrLn h $ mfRenderRow $ BL.pack (show i) : renderLog mlps : renderLog mlls : renderLog mlos :- [BB.toLazyByteString $ mbpFunc mbp (map state t) | mbp <- mbParams m]- where- t = takeItems (mbSize m) t'- lps = map prior t- lls = map likelihood t- los = zipWith (*) lps lls- mlps = mean lps- mlls = mean lls- mlos = mean los+ [BB.toLazyByteString $ mbpFunc mbp (VB.map state xs) | mbp <- mbParams m] mbClose :: MonitorBatch a -> IO () mbClose m = case mbHandle m of@@ -327,22 +303,22 @@ Nothing -> error $ "mfClose: File was not opened for batch monitor: " <> mbName m <> "." -- | Open the files associated with the 'Monitor'.-mOpen :: String -> Bool -> Monitor a -> IO (Monitor a)-mOpen n frc (Monitor s fs bs) = do- fs' <- mapM (mfOpen n frc) fs- mapM_ mfHeader fs'- bs' <- mapM (mbOpen n frc) bs- mapM_ mbHeader bs'+mOpen ::+ -- Base name prefix.+ String ->+ -- Base name suffix.+ String ->+ ExecutionMode ->+ Monitor a ->+ IO (Monitor a)+mOpen pre suf em (Monitor s fs bs) = do+ fs' <- mapM (mfOpen pre suf em) fs+ unless (em == Continue) $ mapM_ mfHeader fs'+ bs' <- mapM (mbOpen pre suf em) bs+ unless (em == Continue) $ mapM_ mbHeader bs' hSetBuffering stdout LineBuffering return $ Monitor s fs' bs' --- | Open the files associated with the 'Monitor' in append mode.-mAppend :: String -> Monitor a -> IO (Monitor a)-mAppend n (Monitor s fs bs) = do- fs' <- mapM (mfAppend n) fs- bs' <- mapM (mbAppend n) bs- return $ Monitor s fs' bs'- -- | Execute monitors; print status information to files and return text to be -- printed to standard output and log file. mExec ::@@ -362,11 +338,16 @@ Monitor a -> IO (Maybe BL.ByteString) mExec v i ss st xs j (Monitor s fs bs) = do- mapM_ (mfExec i $ headT xs) fs+ x <- headT xs+ mapM_ (mfExec i x) fs+ -- NOTE: Batch monitors are slow because separate batch monitors will extract+ -- separate immutable stacks from the trace. However, using folds on the+ -- mutable stack only could be an option! But then, we require two polymorphic+ -- types (for the fold). mapM_ (mbExec i xs) bs if v == Quiet then return Nothing- else msExec i (headT xs) ss st j s+ else msExec i x ss st j s -- | Close the files associated with the 'Monitor'. mClose :: Monitor a -> IO (Monitor a)
src/Mcmc/Monitor/Parameter.hs view
@@ -15,7 +15,6 @@ ( -- * Parameter monitors MonitorParameter (..), (>$<),- (@.), monitorInt, monitorDouble, monitorDoubleF,@@ -45,19 +44,6 @@ instance Contravariant MonitorParameter where contramap f (MonitorParameter n m) = MonitorParameter n (m . f)---- | Convert a parameter monitor from one data type to another.------ DEPRECATED.------ For example, to monitor a 'Double' value being the first entry of a tuple:------ @--- mon = fst @. monitorDouble--- @-(@.) :: (b -> a) -> MonitorParameter a -> MonitorParameter b-(@.) = contramap-{-# DEPRECATED (@.) "Superseded by the contravariant instance, use '(>$<)'." #-} -- | Monitor 'Int'. monitorInt ::
src/Mcmc/Monitor/ParameterBatch.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Mcmc.Monitor.ParameterBatch--- Description : Monitor parameters+-- Description : Batch monitor parameters -- Copyright : (c) Dominik Schrempf, 2020 -- License : GPL-3.0-or-later --@@ -12,25 +12,27 @@ -- -- Creation date: Fri May 29 11:11:49 2020. ----- Batch mean monitors.+-- A batch monitor prints summary statistics of a parameter collected over a+-- specific number of last iterations. The functions provided in this module+-- calculate the mean of the monitored parameter. However, custom batch monitors+-- can use more complex functions. module Mcmc.Monitor.ParameterBatch ( -- * Batch parameter monitors MonitorParameterBatch (..), (>$<),- (@#), monitorBatchMean, monitorBatchMeanF, monitorBatchMeanE,- monitorBatchCustom, ) where import qualified Data.ByteString.Builder as BB import qualified Data.Double.Conversion.ByteString as BC import Data.Functor.Contravariant+import qualified Data.Vector as VB -- | Instruction about a parameter to monitor via batch means. Usually, the--- monitored parameter is average over the batch size. However, arbitrary+-- monitored parameter is averaged over the batch size. However, arbitrary -- functions performing more complicated analyses on the states in the batch can -- be provided. --@@ -42,35 +44,26 @@ -- mon = fst >$< monitorBatchMean -- @ ----- XXX: Batch monitors are slow at the moment because the monitored parameter--- has to be extracted from the state for each iteration.+-- Batch monitors may be slow because the monitored parameter has to be+-- extracted from the state for each iteration. data MonitorParameterBatch a = MonitorParameterBatch { -- | Name of batch monitored parameter. mbpName :: String,- -- | Instruction about how to extract the batch mean from the trace.- mbpFunc :: [a] -> BB.Builder+ -- | For a given batch, extract the summary statistics.+ mbpFunc :: VB.Vector a -> BB.Builder } -instance Contravariant (MonitorParameterBatch) where- contramap f (MonitorParameterBatch n m) = MonitorParameterBatch n (m . map f)---- | Convert a batch parameter monitor from one data type to another.------ For example, to batch monitor the mean of the first entry of a tuple:------ @--- mon = fst @# monitorBatchMean--- @-(@#) :: (b -> a) -> MonitorParameterBatch a -> MonitorParameterBatch b-(@#) f (MonitorParameterBatch n m) = MonitorParameterBatch n (m . map f)-{-# DEPRECATED (@#) "Superseded by the contravariant instance, use '(>$<)'." #-}+instance Contravariant MonitorParameterBatch where+ contramap f (MonitorParameterBatch n m) = MonitorParameterBatch n (m . VB.map f) -mean :: Real a => [a] -> Double-mean xs = realToFrac (sum xs) / fromIntegral (length xs)-{-# SPECIALIZE mean :: [Double] -> Double #-}-{-# SPECIALIZE mean :: [Int] -> Double #-}+mean :: Real a => VB.Vector a -> Double+mean xs = realToFrac (VB.sum xs) / fromIntegral (VB.length xs)+{-# SPECIALIZE mean :: VB.Vector Double -> Double #-}+{-# SPECIALIZE mean :: VB.Vector Int -> Double #-} --- | Batch monitor. Print the mean with eight decimal places (half precision).+-- | Batch mean monitor.+--+-- Print the mean with eight decimal places (half precision). monitorBatchMean :: Real a => -- | Name.@@ -80,8 +73,10 @@ {-# SPECIALIZE monitorBatchMean :: String -> MonitorParameterBatch Int #-} {-# SPECIALIZE monitorBatchMean :: String -> MonitorParameterBatch Double #-} --- | Batch monitor. Print the mean with full precision computing the shortest--- string of digits that correctly represent the number.+-- | Batch mean monitor.+--+-- Print the mean with full precision computing the shortest string of digits+-- that correctly represent the number. monitorBatchMeanF :: Real a => -- | Name.@@ -91,8 +86,10 @@ {-# SPECIALIZE monitorBatchMeanF :: String -> MonitorParameterBatch Int #-} {-# SPECIALIZE monitorBatchMeanF :: String -> MonitorParameterBatch Double #-} --- | Batch monitor real float parameters such as 'Double' with scientific--- notation and eight decimal places.+-- | Batch mean monitor.+--+-- Print the real float parameters such as 'Double' with scientific notation and+-- eight decimal places. monitorBatchMeanE :: Real a => -- | Name.@@ -101,14 +98,3 @@ monitorBatchMeanE n = MonitorParameterBatch n (BB.byteString . BC.toExponential 8 . mean) {-# SPECIALIZE monitorBatchMeanE :: String -> MonitorParameterBatch Int #-} {-# SPECIALIZE monitorBatchMeanE :: String -> MonitorParameterBatch Double #-}---- | Batch monitor parameters with custom lens and builder.-monitorBatchCustom ::- -- | Name.- String ->- -- | Function to calculate the batch mean.- ([a] -> a) ->- -- | Custom builder.- (a -> BB.Builder) ->- MonitorParameterBatch a-monitorBatchCustom n f b = MonitorParameterBatch n (b . f)
src/Mcmc/Monitor/Time.hs view
@@ -14,12 +14,14 @@ module Mcmc.Monitor.Time ( renderDuration, renderDurationS,+ renderTime, ) where import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy.Char8 as BL import Data.Time.Clock+import Data.Time.Format import Mcmc.Internal.ByteString -- | Adapted from System.ProgressBar.renderDuration of package@@ -35,7 +37,7 @@ -- Total amount of seconds ts :: Int ts = round dt- renderDecimal n = alignRightWith '0' 2 $ BB.toLazyByteString $ BB.intDec n+ renderDecimal n = alignRightWithNoTrim '0' 2 $ BB.toLazyByteString $ BB.intDec n -- | Render duration in seconds. renderDurationS :: NominalDiffTime -> BL.ByteString@@ -43,3 +45,7 @@ where ts :: Int ts = round dt++-- | Render a time stamp.+renderTime :: FormatTime t => t -> String+renderTime = formatTime defaultTimeLocale "%B %-e, %Y, at %H:%M %P, %Z."
src/Mcmc/Prior.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Prior--- Description : Convenience functions to compute priors+-- Description : Convenience functions for computing priors -- Copyright : (c) Dominik Schrempf, 2020 -- License : GPL-3.0-or-later --@@ -12,18 +12,21 @@ -- -- Creation date: Thu Jul 23 13:26:14 2020. module Mcmc.Prior- ( -- * Continuous priors+ ( -- * Improper priors largerThan, positive, lowerThan, negative,- uniform,- normal,++ -- * Continuous priors exponential, gamma,- -- -- * Discrete priors- -- No discrete priors are available yet.+ normal,+ uniform, + -- * Discrete priors+ poisson,+ -- * Auxiliary functions product', )@@ -36,6 +39,7 @@ import qualified Statistics.Distribution.Exponential as S import qualified Statistics.Distribution.Gamma as S import qualified Statistics.Distribution.Normal as S+import qualified Statistics.Distribution.Poisson as S -- | Improper uniform prior; strictly larger than a given value. largerThan :: Double -> Double -> Log Double@@ -57,18 +61,27 @@ negative :: Double -> Log Double negative = lowerThan 0 --- | Uniform prior on [a, b].-uniform ::- -- | Lower bound a.+-- | Exponential distributed prior.+exponential ::+ -- | Rate. Double ->- -- | Upper bound b. Double ->+ Log Double+exponential l x = Exp $ S.logDensity d x+ where+ d = S.exponential l++-- | Gamma distributed prior.+gamma ::+ -- | Shape. Double ->+ -- | Scale.+ Double ->+ Double -> Log Double-uniform a b x- | x <= a = 0- | x >= b = 0- | otherwise = Exp 0+gamma k t x = Exp $ S.logDensity d x+ where+ d = S.gammaDistr k t -- | Normal distributed prior. normal ::@@ -82,27 +95,28 @@ where d = S.normalDistr m s --- | Exponential distributed prior.-exponential ::- -- | Rate.+-- | Uniform prior on [a, b].+uniform ::+ -- | Lower bound a. Double ->+ -- | Upper bound b. Double ->+ Double -> Log Double-exponential l x = Exp $ S.logDensity d x- where- d = S.exponential l+uniform a b x+ | x <= a = 0+ | x >= b = 0+ | otherwise = Exp 0 --- | Gamma distributed prior.-gamma ::- -- | Shape.- Double ->- -- | Scale.- Double ->+-- | Poisson distributed prior.+poisson ::+ -- | Rate. Double ->+ Int -> Log Double-gamma k t x = Exp $ S.logDensity d x+poisson l x = Exp $ S.logProbability d x where- d = S.gammaDistr k t+ d = S.poisson l -- | Intelligent product that stops when encountering a zero. --
src/Mcmc/Proposal.hs view
@@ -1,10 +1,11 @@ {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} -- | -- Module : Mcmc.Proposal--- Description : Proposals and cycles+-- Description : Proposals are instruction to move around the state space -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --@@ -18,6 +19,7 @@ PName (..), PDescription (..), PWeight (..),+ PDimension (..), Proposal (..), (@~), ProposalSimple,@@ -25,15 +27,19 @@ Tune (..), createProposal, tune,+ getOptimalRate,+ proposalHeader,+ proposalHLine,+ summarizeProposal, -- * Cycle Order (..), Cycle (ccProposals),- fromList,+ cycleFromList, setOrder,- getNIterations,+ orderProposals, tuneCycle,- autotuneCycle,+ autoTuneCycle, summarizeCycle, -- * Acceptance@@ -42,10 +48,12 @@ pushA, resetA, transformKeysA,- acceptanceRatios,+ acceptanceRate,+ acceptanceRates, ) where +import Control.DeepSeq import Data.Aeson import Data.Bifunctor import qualified Data.ByteString.Builder as BB@@ -54,7 +62,6 @@ import qualified Data.Double.Conversion.ByteString as BC import Data.Function import Data.List-import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe import Lens.Micro@@ -66,6 +73,7 @@ -- | Proposal name. newtype PName = PName {fromPName :: String} deriving (Show, Eq, Ord)+ deriving (Monoid, Semigroup) via String -- | Proposal description. newtype PDescription = PDescription {fromPDescription :: String}@@ -76,19 +84,57 @@ newtype PWeight = PWeight {fromPWeight :: Int} deriving (Show, Eq, Ord) +-- | Proposal dimension.+--+-- The number of affected, independent parameters.+--+-- The optimal acceptance rate of low dimensional proposals is higher than for+-- high dimensional ones.+--+-- Optimal acceptance rates are still subject to controversies. As far as I+-- know, research has focused on random walk proposal with a multivariate normal+-- distribution of dimension @d@. In this case, the following acceptance rates+-- are desired:+--+-- - one dimension: 0.44 (numerical results);+--+-- - five and more dimensions: 0.234 (numerical results);+--+-- - infinite dimensions: 0.234 (theorem for specific target distributions).+--+-- See Handbook of Markov chain Monte Carlo, chapter 4.+--+-- Of course, many proposals may not be classical random walk proposals. For+-- example, the beta proposal on a simplex ('Mcmc.Proposal.Simplex.beta')+-- samples one new variable of the simplex from a beta distribution while+-- rescaling all other variables. What is the dimension of this proposal? I+-- don't know, but I set the dimension to 2. The reason is that if the dimension+-- of the simplex is 2, two variables are changed. If the dimension of the+-- simplex is high, one variable is changed substantially, while all others are+-- changed marginally.+--+-- Further, if a proposal changes a number of variables in the same way (and not+-- independently like in a random walk proposal), I still set the dimension of+-- the proposal to the number of variables changed.+--+-- Finally, I assume that proposals of unknown dimension have high dimension,+-- and use the optimal acceptance rate 0.234.+data PDimension = PDimension Int | PDimensionUnknown+ -- | A 'Proposal' is an instruction about how the Markov chain will traverse the -- state space @a@. Essentially, it is a probability mass or probability density--- conditioned on the current state (i.e., a kernel).+-- conditioned on the current state (i.e., a Markov kernel). -- -- A 'Proposal' may be tuneable in that it contains information about how to enlarge--- or shrink the step size to tune the acceptance ratio.------ No proposals with the same name and description are allowed in a 'Cycle'.+-- or shrink the step size to tune the acceptance rate. data Proposal a = Proposal { -- | Name of the affected variable. pName :: PName, -- | Description of the proposal type and parameters. pDescription :: PDescription,+ -- | Dimension of the proposal. The dimension is used to calculate the+ -- optimal acceptance rate, and does not have to be exact.+ pDimension :: PDimension, -- | The weight determines how often a 'Proposal' is executed per iteration of -- the Markov chain. pWeight :: PWeight,@@ -98,10 +144,6 @@ pTuner :: Maybe (Tuner a) } --- XXX: This should be removed.-instance Show (Proposal a) where- show m = fromPName (pName m) <> " " <> fromPDescription (pDescription m) <> ", weight " <> show (fromPWeight $ pWeight m)- instance Eq (Proposal a) where m == n = pName m == pName n && pDescription m == pDescription n @@ -116,15 +158,15 @@ -- scaleFirstEntryOfTuple = _1 @~ scale -- @ (@~) :: Lens' b a -> Proposal a -> Proposal b-(@~) l (Proposal n d w s t) = Proposal n d w (convertS l s) (convertT l <$> t)+(@~) l (Proposal n r d w s t) = Proposal n r d w (convertProposalSimple l s) (convertTuner l <$> t) -- | Simple proposal without tuning information. -- -- Instruction about randomly moving from the current state to a new state, -- given some source of randomness. ----- In order to calculate the Metropolis-Hastings ratio, we need to know the--- ratio of the backward to forward kernels (i.e., the probability masses or+-- In order to calculate the Metropolis-Hastings-Green ratio, we need to know+-- the ratio of the backward to forward kernels (i.e., the probability masses or -- probability densities) and the absolute value of the determinant of the -- Jacobian matrix. --@@ -139,81 +181,82 @@ -- determinant of the Jacobian matrix differs from 1.0. type ProposalSimple a = a -> GenIO -> IO (a, Log Double, Log Double) -convertS :: Lens' b a -> ProposalSimple a -> ProposalSimple b-convertS l s = s'+convertProposalSimple :: Lens' b a -> ProposalSimple a -> ProposalSimple b+convertProposalSimple l s = s' where s' v g = do (x', r, j) <- s (v ^. l) g return (set l x' v, r, j) --- | Tune the acceptance ratio of a 'Proposal'; see 'tune', or 'autotuneCycle'.+-- | Tune the acceptance rate of a 'Proposal'; see 'tune', or 'autoTuneCycle'. data Tuner a = Tuner { tParam :: Double, tFunc :: Double -> ProposalSimple a } -convertT :: Lens' b a -> Tuner a -> Tuner b-convertT l (Tuner p f) = Tuner p f'+convertTuner :: Lens' b a -> Tuner a -> Tuner b+convertTuner l (Tuner p f) = Tuner p f' where- f' x = convertS l $ f x+ f' x = convertProposalSimple l $ f x -- | Tune the proposal? data Tune = Tune | NoTune deriving (Show, Eq) --- | Create a possibly tuneable proposal.+-- | Create a tuneable proposal. createProposal :: -- | Description of the proposal type and parameters. PDescription -> -- | Function creating a simple proposal for a given tuning parameter. The -- larger the tuning parameter, the larger the proposal (and the lower the- -- expected acceptance ratio), and vice versa.+ -- expected acceptance rate), and vice versa. (Double -> ProposalSimple a) ->+ -- | Dimension.+ PDimension -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Activate tuning? Tune -> Proposal a-createProposal d f n w Tune = Proposal n d w (f 1.0) (Just $ Tuner 1.0 f)-createProposal d f n w NoTune = Proposal n d w (f 1.0) Nothing+createProposal r f d n w Tune = Proposal n r d w (f 1.0) (Just $ Tuner 1.0 f)+createProposal r f d n w NoTune = Proposal n r d w (f 1.0) Nothing -- Minimal tuning parameter; subject to change. tuningParamMin :: Double tuningParamMin = 1e-12 --- | Tune a 'Proposal'. Return 'Nothing' if 'Proposal' is not tuneable. If the parameter--- @dt@ is larger than 1.0, the 'Proposal' is enlarged, if @0<dt<1.0@, it is--- shrunk. Negative tuning parameters are not allowed.-tune :: Double -> Proposal a -> Maybe (Proposal a)-tune dt m- | dt <= 0 = error $ "tune: Tuning parameter not positive: " <> show dt <> "."- | otherwise = do- (Tuner t f) <- pTuner m- -- Ensure that the tuning parameter is not too small.- let t' = max tuningParamMin (t * dt)- return $ m {pSimple = f t', pTuner = Just $ Tuner t' f}+-- | Tune a 'Proposal'. Return 'Nothing' if 'Proposal' is not tuneable. The size+-- of the proposal is proportional to the tuning parameter. Negative tuning+-- parameters are not allowed.+tune :: (Double -> Double) -> Proposal a -> Maybe (Proposal a)+tune f m = do+ (Tuner t g) <- pTuner m+ -- Ensure that the tuning parameter is strictly positive.+ let t' = max tuningParamMin (f t)+ return $ m {pSimple = g t', pTuner = Just $ Tuner t' g} --- The desired acceptance ratio 0.44 is optimal for one-dimensional proposals;--- one could also store the affected number of dimensions with the proposal and--- tune towards an acceptance ratio accounting for the number of dimensions.------ The optimal ratios seem to be:--- - One dimension: 0.44 (numerical result).--- - Five and more dimensions: 0.234 seems to be a good value (numerical result).--- - Infinite dimensions: 0.234 (theorem for specific target distributions).--- See Handbook of Markov chain Monte Carlo, chapter 4.-ratioOpt :: Double-ratioOpt = 0.44+-- | See 'PDimension'.+getOptimalRate :: PDimension -> Double+getOptimalRate (PDimension n)+ | n <= 0 = error "getOptimalRate: Proposal dimension is zero or negative."+ | n == 1 = 0.44+ -- Use a linear interpolation with delta 0.0515.+ | n == 2 = 0.3885+ | n == 3 = 0.337+ | n == 4 = 0.2855+ | n >= 5 = 0.234+ | otherwise = error "getOptimalRate: Proposal dimension is not an integer?"+getOptimalRate PDimensionUnknown = 0.234 --- Warn if acceptance ratio is lower.-ratioMin :: Double-ratioMin = 0.1+-- Warn if acceptance rate is lower.+rateMin :: Double+rateMin = 0.1 --- Warn if acceptance ratio is larger.-ratioMax :: Double-ratioMax = 0.9+-- Warn if acceptance rate is larger.+rateMax :: Double+rateMax = 0.9 -- | Define the order in which 'Proposal's are executed in a 'Cycle'. The total -- number of 'Proposal's per 'Cycle' may differ between 'Order's (e.g., compare@@ -241,6 +284,23 @@ instance Default Order where def = RandomO +-- Describe the order.+describeOrder :: Order -> BL.ByteString+describeOrder RandomO = "The proposals are executed in random order."+describeOrder SequentialO = "The proposals are executed sequentially."+describeOrder RandomReversibleO =+ BL.intercalate+ "\n"+ [ describeOrder RandomO,+ "A reversed copy of the shuffled proposals is appended to ensure reversibility."+ ]+describeOrder SequentialReversibleO =+ BL.intercalate+ "\n"+ [ describeOrder SequentialO,+ "A reversed copy of the sequential proposals is appended to ensure reversibility."+ ]+ -- | In brief, a 'Cycle' is a list of proposals. -- -- The state of the Markov chain will be logged only after all 'Proposal's in@@ -248,54 +308,69 @@ -- by one. The order in which the 'Proposal's are executed is specified by -- 'Order'. The default is 'RandomO'. ----- __Proposals must have unique names__, so that they can be identified.+-- No proposals with the same name and description are allowed in a 'Cycle', so+-- that they can be uniquely identified. data Cycle a = Cycle { ccProposals :: [Proposal a], ccOrder :: Order } -- | Create a 'Cycle' from a list of 'Proposal's.-fromList :: [Proposal a] -> Cycle a-fromList [] =- error "fromList: Received an empty list but cannot create an empty Cycle."-fromList xs =+cycleFromList :: [Proposal a] -> Cycle a+cycleFromList [] =+ error "cycleFromList: Received an empty list but cannot create an empty Cycle."+cycleFromList xs = if length (nub xs) == length xs then Cycle xs def- else error "fromList: Proposals are not unique."+ else error "cycleFromList: Proposals are not unique." -- | Set the order of 'Proposal's in a 'Cycle'. setOrder :: Order -> Cycle a -> Cycle a setOrder o c = c {ccOrder = o} -- | Replicate 'Proposal's according to their weights and possibly shuffle them.-getNIterations :: Cycle a -> Int -> GenIO -> IO [[Proposal a]]-getNIterations (Cycle xs o) n g = case o of- RandomO -> shuffleN ps n g- SequentialO -> return $ replicate n ps+orderProposals :: Cycle a -> GenIO -> IO [Proposal a]+orderProposals (Cycle xs o) g = case o of+ RandomO -> shuffle ps g+ SequentialO -> return ps RandomReversibleO -> do- psRs <- shuffleN ps n g- return [psR ++ reverse psR | psR <- psRs]- SequentialReversibleO -> return $ replicate n $ ps ++ reverse ps+ psR <- shuffle ps g+ return $ psR ++ reverse psR+ SequentialReversibleO -> return $ ps ++ reverse ps where- !ps = concat [replicate (fromPWeight $ pWeight m) m | m <- xs]+ !ps = concat [replicate (fromPWeight $ pWeight p) p | p <- xs] +-- The number of proposals depends on the order.+getNProposalsPerCycle :: Cycle a -> Int+getNProposalsPerCycle (Cycle xs o) = case o of+ RandomO -> once+ SequentialO -> once+ RandomReversibleO -> 2 * once+ SequentialReversibleO -> 2 * once+ where+ once = sum $ map (fromPWeight . pWeight) xs+ -- | Tune 'Proposal's in the 'Cycle'. See 'tune'.-tuneCycle :: Map (Proposal a) Double -> Cycle a -> Cycle a+tuneCycle :: M.Map (Proposal a) (Double -> Double) -> Cycle a -> Cycle a tuneCycle m c = if sort (M.keys m) == sort ps then c {ccProposals = map tuneF ps}- else error "tuneCycle: Map contains proposals that are not in the cycle."+ else error "tuneCycle: Propoals in map and cycle do not match." where ps = ccProposals c tuneF p = case m M.!? p of Nothing -> p- Just x -> fromMaybe p (tune x p)+ Just f -> fromMaybe p (tune f p) --- | Calculate acceptance ratios and auto tune the 'Proposal's in the 'Cycle'. For--- now, a 'Proposal' is enlarged when the acceptance ratio is above 0.44, and+-- | Calculate acceptance rates and auto tune the 'Proposal's in the 'Cycle'. For+-- now, a 'Proposal' is enlarged when the acceptance rate is above 0.44, and -- shrunk otherwise. Do not change 'Proposal's that are not tuneable.-autotuneCycle :: Acceptance (Proposal a) -> Cycle a -> Cycle a-autotuneCycle a = tuneCycle (M.map (\x -> exp $ x - ratioOpt) $ acceptanceRatios a)+autoTuneCycle :: Acceptance (Proposal a) -> Cycle a -> Cycle a+autoTuneCycle a = tuneCycle (M.mapWithKey tuningF $ acceptanceRates a)+ where+ tuningF proposal currentRate currentTuningParam =+ let optimalRate = getOptimalRate (pDimension proposal)+ in exp (currentRate - optimalRate) * currentTuningParam renderRow :: BL.ByteString ->@@ -306,65 +381,104 @@ BL.ByteString -> BL.ByteString -> BL.ByteString ->+ BL.ByteString -> BL.ByteString-renderRow name ptype weight nAccept nReject acceptRatio tuneParam manualAdjustment = " " <> nm <> pt <> wt <> na <> nr <> ra <> tp <> mt+renderRow name ptype weight nAccept nReject acceptRate optimalRate tuneParam manualAdjustment = nm <> pt <> wt <> na <> nr <> ra <> ro <> tp <> mt where nm = alignLeft 30 name pt = alignLeft 50 ptype wt = alignRight 8 weight- na = alignRight 15 nAccept- nr = alignRight 15 nReject- ra = alignRight 15 acceptRatio+ na = alignRight 14 nAccept+ nr = alignRight 14 nReject+ ra = alignRight 14 acceptRate+ ro = alignRight 14 optimalRate tp = alignRight 20 tuneParam mt = alignRight 30 manualAdjustment +-- | Header of proposal summaries. proposalHeader :: BL.ByteString proposalHeader =- renderRow "Name" "Description" "Weight" "Accepted" "Rejected" "Ratio" "Tuning parameter" "Consider manual adjustment"+ renderRow+ "Name"+ "Description"+ "Weight"+ "Accepted"+ "Rejected"+ "Rate"+ "Optimal rate"+ "Tuning parameter"+ "Consider manual adjustment" -summarizeProposal :: Proposal a -> Maybe (Int, Int, Double) -> BL.ByteString-summarizeProposal m r =+-- | Horizontal line of proposal summaries.+proposalHLine :: BL.ByteString+proposalHLine = BL.replicate (BL.length proposalHeader) '-'++-- | Proposal summary.+summarizeProposal ::+ PName ->+ PDescription ->+ PWeight ->+ -- Tuning parameter.+ Maybe Double ->+ PDimension ->+ Maybe (Int, Int, Double) ->+ BL.ByteString+summarizeProposal name description weight tuningParam dimension r = renderRow- (BL.pack $ fromPName $ pName m)- (BL.pack $ fromPDescription $ pDescription m)- weight+ (BL.pack $ fromPName name)+ (BL.pack $ fromPDescription description)+ weightStr nAccept nReject- acceptRatio+ acceptRate+ optimalRate tuneParamStr manualAdjustmentStr where- weight = BB.toLazyByteString $ BB.intDec $ fromPWeight $ pWeight m+ weightStr = BB.toLazyByteString $ BB.intDec $ fromPWeight weight nAccept = BB.toLazyByteString $ maybe "" (BB.intDec . (^. _1)) r nReject = BB.toLazyByteString $ maybe "" (BB.intDec . (^. _2)) r- acceptRatio = BL.fromStrict $ maybe "" (BC.toFixed 3 . (^. _3)) r- tuneParamStr = BL.fromStrict $ maybe "" (BC.toFixed 3) (tParam <$> pTuner m)+ acceptRate = BL.fromStrict $ maybe "" (BC.toFixed 2 . (^. _3)) r+ optimalRate = BL.fromStrict $ BC.toFixed 2 $ getOptimalRate dimension+ tuneParamStr = BL.fromStrict $ maybe "" (BC.toFixed 3) tuningParam check v- | v < ratioMin = "ratio too low"- | v > ratioMax = "ratio too high"+ | v < rateMin = "rate too low"+ | v > rateMax = "rate too high" | otherwise = "" manualAdjustmentStr = BL.fromStrict $ maybe "" (check . (^. _3)) r -hLine :: BL.ByteString -> BL.ByteString-hLine s = " " <> BL.replicate (BL.length s - 3) '-'---- | Summarize the 'Proposal's in the 'Cycle'. Also report acceptance ratios.+-- | Summarize the 'Proposal's in the 'Cycle'. Also report acceptance rates. summarizeCycle :: Acceptance (Proposal a) -> Cycle a -> BL.ByteString summarizeCycle a c = BL.intercalate "\n" $- [ "Summary of proposal(s) in cycle. " <> mpi <> " proposal(s) per iteration.",+ [ "Summary of proposal(s) in cycle.",+ nProposalsFullStr,+ describeOrder (ccOrder c), proposalHeader,- hLine proposalHeader+ proposalHLine ]- ++ [summarizeProposal m (ar m) | m <- ps]- ++ [hLine proposalHeader]+ ++ [ summarizeProposal+ (pName p)+ (pDescription p)+ (pWeight p)+ (tParam <$> pTuner p)+ (pDimension p)+ (ar p)+ | p <- ps+ ]+ ++ [proposalHLine] where ps = ccProposals c- mpi = BB.toLazyByteString $ BB.intDec $ sum $ map (fromPWeight . pWeight) ps- ar m = acceptanceRatio m a+ nProposals = getNProposalsPerCycle c+ nProposalsStr = BB.toLazyByteString $ BB.intDec nProposals+ nProposalsFullStr = case nProposals of+ 1 -> nProposalsStr <> " proposal is performed per iteration."+ _ -> nProposalsStr <> " proposals are performed per iterations."+ ar m = acceptanceRate m a -- | For each key @k@, store the number of accepted and rejected proposals.-newtype Acceptance k = Acceptance {fromAcceptance :: Map k (Int, Int)}+newtype Acceptance k = Acceptance {fromAcceptance :: M.Map k (Int, Int)}+ deriving (Eq, Read, Show) instance ToJSONKey k => ToJSON (Acceptance k) where toJSON (Acceptance m) = toJSON m@@ -380,16 +494,16 @@ emptyA ks = Acceptance $ M.fromList [(k, (0, 0)) | k <- ks] -- | For key @k@, prepend an accepted (True) or rejected (False) proposal.-pushA :: (Ord k, Show k) => k -> Bool -> Acceptance k -> Acceptance k-pushA k True = Acceptance . M.adjust (first succ) k . fromAcceptance-pushA k False = Acceptance . M.adjust (second succ) k . fromAcceptance+pushA :: Ord k => k -> Bool -> Acceptance k -> Acceptance k+pushA k True = Acceptance . M.adjust (force . first succ) k . fromAcceptance+pushA k False = Acceptance . M.adjust (force . second succ) k . fromAcceptance {-# INLINEABLE pushA #-} -- | Reset acceptance storage. resetA :: Ord k => Acceptance k -> Acceptance k resetA = emptyA . M.keys . fromAcceptance -transformKeys :: (Ord k1, Ord k2) => [k1] -> [k2] -> Map k1 v -> Map k2 v+transformKeys :: (Ord k1, Ord k2) => [k1] -> [k2] -> M.Map k1 v -> M.Map k2 v transformKeys ks1 ks2 m = foldl' insrt M.empty $ zip ks1 ks2 where insrt m' (k1, k2) = M.insert k2 (m M.! k1) m'@@ -399,13 +513,13 @@ transformKeysA :: (Ord k1, Ord k2) => [k1] -> [k2] -> Acceptance k1 -> Acceptance k2 transformKeysA ks1 ks2 = Acceptance . transformKeys ks1 ks2 . fromAcceptance --- | Acceptance counts and ratio for a specific proposal.-acceptanceRatio :: (Show k, Ord k) => k -> Acceptance k -> Maybe (Int, Int, Double)-acceptanceRatio k a = case fromAcceptance a M.!? k of+-- | Acceptance counts and rate for a specific proposal.+acceptanceRate :: Ord k => k -> Acceptance k -> Maybe (Int, Int, Double)+acceptanceRate k a = case fromAcceptance a M.!? k of Just (0, 0) -> Nothing Just (as, rs) -> Just (as, rs, fromIntegral as / fromIntegral (as + rs))- Nothing -> error $ "acceptanceRatio: Key not found in map: " ++ show k ++ "."+ Nothing -> error "acceptanceRate: Key not found in map." --- | Acceptance ratios for all proposals.-acceptanceRatios :: Acceptance k -> Map k Double-acceptanceRatios = M.map (\(as, rs) -> fromIntegral as / fromIntegral (as + rs)) . fromAcceptance+-- | Acceptance rates for all proposals.+acceptanceRates :: Acceptance k -> M.Map k Double+acceptanceRates = M.map (\(as, rs) -> fromIntegral as / fromIntegral (as + rs)) . fromAcceptance
src/Mcmc/Proposal/Bactrian.hs view
@@ -75,24 +75,24 @@ -- The [Bactrian -- kernel](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3845170/figure/fig01) is -- a mixture of two symmetrically arranged normal distributions. The spike--- parameter (0, 1) loosely determines the standard deviations (>0.0) of the--- individual humps while the second parameter refers to the standard deviation--- of the complete Bactrian kernel.+-- parameter \(m \in (0, 1)\) loosely determines the standard deviations of the+-- individual humps while the second parameter \(s > 0\) refers to the+-- standard deviation of the complete Bactrian kernel. -- -- See https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3845170/. slideBactrian ::- -- | Spike parameter.+ -- | Spike parameter \(m\). Double ->- -- | Standard deviation.+ -- | Standard deviation \(s\). Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal Double-slideBactrian m s = createProposal description (bactrianAdditiveSimple m s)+slideBactrian m s = createProposal description (bactrianAdditiveSimple m s) (PDimension 1) where description = PDescription $ "Slide Bactrian; spike: " ++ show m ++ ", sd: " ++ show s @@ -135,11 +135,11 @@ Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal Double-scaleBactrian m s = createProposal description (bactrianMultSimple m s)+scaleBactrian m s = createProposal description (bactrianMultSimple m s) (PDimension 1) where description = PDescription $ "Scale Bactrian; spike: " ++ show m <> ", sd: " <> show s
src/Mcmc/Proposal/Generic.hs view
@@ -1,6 +1,6 @@ -- | -- Module : Mcmc.Proposal.Generic--- Description : Generic interface to create proposals+-- Description : Generic interface for creating proposals -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --
src/Mcmc/Proposal/Scale.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Mcmc.Proposal.Scale--- Description : Scaling proposal with Gamma distribution+-- Description : Multiplicative proposals -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --@@ -35,38 +35,32 @@ where jac _ = Exp . log . recip --- | Multiplicative proposal with Gamma distributed kernel.+-- | Multiplicative proposal with gamma distributed kernel. scale :: -- | Shape. Double -> -- | Scale. Double ->- -- | Name. PName ->- -- | PWeight. PWeight ->- -- | Enable tuning. Tune -> Proposal Double-scale k th = createProposal description (scaleSimple k th)+scale k th = createProposal description (scaleSimple k th) (PDimension 1) where description = PDescription $ "Scale; shape: " ++ show k ++ ", scale: " ++ show th --- | Multiplicative proposal with Gamma distributed kernel.+-- | Multiplicative proposal with gamma distributed kernel. ----- The scale of the Gamma distributions is set to (shape)^{-1}, so that the mean--- of the Gamma distribution is 1.0.+-- The scale of the gamma distribution is set to (shape)^{-1}, so that the mean+-- of the gamma distribution is 1.0. scaleUnbiased :: -- | Shape. Double ->- -- | Name. PName ->- -- | PWeight. PWeight ->- -- | Enable tuning. Tune -> Proposal Double-scaleUnbiased k = createProposal description (scaleSimple k (1 / k))+scaleUnbiased k = createProposal description (scaleSimple k (1 / k)) (PDimension 1) where description = PDescription $ "Scale unbiased; shape: " ++ show k @@ -81,11 +75,7 @@ contra (x, y) u = (x * u, y / u) jac _ u = Exp $ log $ recip $ u * u --- -- Determinant of Jacobian matrix.--- contraJac :: (Double, Double) -> Double--- contraJac (x, y) = x * y---- | Multiplicative proposal with Gamma distributed kernel.+-- | Multiplicative proposal with gamma distributed kernel. -- -- The two values are scaled contrarily so that their product stays constant. -- Contrary proposals are useful when parameters are confounded.@@ -94,13 +84,10 @@ Double -> -- | Scale. Double ->- -- | Name. PName ->- -- | PWeight. PWeight ->- -- | Enable tuning. Tune -> Proposal (Double, Double)-scaleContrarily k th = createProposal description (scaleContrarilySimple k th)+scaleContrarily k th = createProposal description (scaleContrarilySimple k th) (PDimension 2) where description = PDescription $ "Scale contrariliy; shape: " ++ show k ++ ", scale: " ++ show th
src/Mcmc/Proposal/Simplex.hs view
@@ -88,7 +88,7 @@ t' = t / 10000 -- Extremely small tuning parameters lead to numeric overflow. The square -- root pulls the tuning parameter closer to 1.0. However, overflow may- -- still occur (the involved Gamma functions grow faster than the+ -- still occur (the involved gamma functions grow faster than the -- exponential). I did not observe numeric underflow in my tests. t'' = sqrt t' @@ -126,15 +126,18 @@ -- -- For a given element of a K-dimensional simplex, propose a new element of the -- K-dimensional simplex. The new element is sampled from the multivariate--- Dirichlet distribution with parameter vector being the old element of the--- simplex.+-- Dirichlet distribution with parameter vector being proportional to the+-- current element of the simplex. -- -- The tuning parameter is used to determine the concentration of the Dirichlet -- distribution: the lower the tuning parameter, the higher the concentration. ----- This proposal may have low acceptance ratios. In this case, please see the--- coordinate wise 'beta' proposal.-dirichlet :: PName -> PWeight -> Tune -> Proposal Simplex+-- The proposal dimension, which is the dimension of the simplex, is used to+-- determine the optimal acceptance rate.+--+-- For high dimensional simplices, this proposal may have low acceptance ratios.+-- In this case, please see the coordinate wise 'beta' proposal.+dirichlet :: PDimension -> PName -> PWeight -> Tune -> Proposal Simplex dirichlet = createProposal (PDescription "Dirichlet") dirichletSimple -- The tuning parameter is the inverted mean of the shape values.@@ -197,7 +200,10 @@ -- No "out of bounds" checks are performed during compile time. Run time errors -- can occur if @i@ is negative, or if @i-1@ is larger than the length of the -- element vector of the simplex.+--+-- This proposal has been assigned a dimension of 2. See the discussion at+-- 'PDimension'. beta :: Int -> PName -> PWeight -> Tune -> Proposal Simplex-beta i = createProposal description (betaSimple i)+beta i = createProposal description (betaSimple i) (PDimension 2) where description = PDescription $ "Beta; coordinate: " ++ show i
src/Mcmc/Proposal/Slide.hs view
@@ -2,7 +2,7 @@ -- | -- Module : Mcmc.Proposal.Slide--- Description : Normally distributed proposal+-- Description : Additive proposals -- Copyright : (c) Dominik Schrempf 2020 -- License : GPL-3.0-or-later --@@ -37,12 +37,12 @@ Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal Double-slide m s = createProposal description (slideSimple m s)+slide m s = createProposal description (slideSimple m s) (PDimension 1) where description = PDescription $ "Slide; mean: " ++ show m ++ ", sd: " ++ show s @@ -52,19 +52,19 @@ genericContinuous (normalDistr 0.0 (s * t)) (+) Nothing Nothing -- | Additive proposal with normally distributed kernel with mean zero. This--- proposal is very fast, because the Metropolis-Hastings ratio does not include--- calculation of the forwards and backwards kernels.+-- proposal is very fast, because the Metropolis-Hastings-Green ratio does not+-- include calculation of the forwards and backwards kernels. slideSymmetric :: -- | Standard deviation. Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal Double-slideSymmetric s = createProposal description (slideSymmetricSimple s)+slideSymmetric s = createProposal description (slideSymmetricSimple s) (PDimension 1) where description = PDescription $ "Slide symmetric; sd: " ++ show s @@ -74,19 +74,19 @@ genericContinuous (uniformDistr (- t * d) (t * d)) (+) Nothing Nothing -- | Additive proposal with uniformly distributed kernel with mean zero. This--- proposal is very fast, because the Metropolis-Hastings ratio does not include--- calculation of the forwards and backwards kernels.+-- proposal is very fast, because the Metropolis-Hastings-Green ratio does not+-- include calculation of the forwards and backwards kernels. slideUniformSymmetric :: -- | Delta. Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal Double-slideUniformSymmetric d = createProposal description (slideUniformSimple d)+slideUniformSymmetric d = createProposal description (slideUniformSimple d) (PDimension 1) where description = PDescription $ "Slide uniform symmetric; delta: " ++ show d @@ -108,11 +108,11 @@ Double -> -- | Name. PName ->- -- | PWeight.+ -- | Weight. PWeight -> -- | Enable tuning. Tune -> Proposal (Double, Double)-slideContrarily m s = createProposal description (slideContrarilySimple m s)+slideContrarily m s = createProposal description (slideContrarilySimple m s) (PDimension 2) where description = PDescription $ "Slide contrarily; mean: " ++ show m ++ ", sd: " ++ show s
− src/Mcmc/Save.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}---- |--- Module : Mcmc.Save--- Description : Save the state of a Markov chain--- Copyright : (c) Dominik Schrempf, 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Tue Jun 16 10:18:54 2020.------ Save and load an MCMC run. It is easy to save and restore the current state and--- likelihood (or the trace), but it is not feasible to store all the proposals and so--- on, so they have to be provided again when continuing a run.-module Mcmc.Save- ( saveStatus,- loadStatus,- )-where--import Codec.Compression.GZip-import Control.Monad-import Data.Aeson-import Data.Aeson.TH-import qualified Data.ByteString.Lazy.Char8 as BL-import Data.List hiding (cycle)-import qualified Data.Map as M-import Data.Maybe-import Data.Vector.Unboxed (Vector)-import Data.Word--- TODO: Splitmix. Reproposal as soon as split mix is used and is available with the--- statistics package.-import Mcmc.Item-import Mcmc.Monitor-import Mcmc.Proposal-import Mcmc.Status hiding (save)-import Mcmc.Trace-import Mcmc.Verbosity-import Numeric.Log-import System.Directory-import System.IO.Unsafe (unsafePerformIO)-import System.Random.MWC-import Prelude hiding (cycle)--data Save a- = Save- -- Variables related to the chain.- String -- Name.- (Item a)- Int -- Iteration.- (Trace a)- (Acceptance Int)- (Maybe Int) -- Burn in.- (Maybe Int) -- Auto tune.- Int -- Iterations.- Bool -- Force.- (Maybe Int) -- Save.- Verbosity- (Vector Word32) -- Current seed.-- -- Variables related to the algorithm.- [Maybe Double] -- Tuning parameters.--$(deriveJSON defaultOptions ''Save)--toSave :: Status a -> Save a-toSave (Status nm it i tr ac br at is f sv vb g _ _ _ _ _ c _) =- Save- nm- it- i- tr'- ac'- br- at- is- f- sv- vb- g'- ts- where- tr' = takeT (fromMaybe 0 sv) tr- ac' = transformKeysA (ccProposals c) [0 ..] ac- -- TODO: Splitmix. Remove as soon as split mix is used and is available with- -- the statistics package.- g' = fromSeed $ unsafePerformIO $ save g- ts = [fmap tParam mt | mt <- map pTuner $ ccProposals c]---- | Save a 'Status' to file.------ Some important values have to be provided upon restoring the status. See--- 'loadStatus'.-saveStatus :: ToJSON a => FilePath -> Status a -> IO ()-saveStatus fn s = BL.writeFile fn $ compress $ encode (toSave s)---- fromSav prior lh cycle monitor save-fromSave ::- (a -> Log Double) ->- (a -> Log Double) ->- Cycle a ->- Monitor a ->- Maybe (Cleaner a) ->- Save a ->- Status a-fromSave pr lh cc m cl (Save nm it i tr ac' br at is f sv vb g' ts) =- Status- nm- it- i- tr- ac- br- at- is- f- sv- vb- g- Nothing- Nothing- pr- lh- cl- cc'- m- where- ac = transformKeysA [0 ..] (ccProposals cc) ac'- -- TODO: Splitmix. Remove as soon as split mix is used and is available with- -- the statistics package.- g = unsafePerformIO $ restore $ toSeed g'- cc' = tuneCycle (M.mapMaybe id $ M.fromList $ zip (ccProposals cc) ts) cc---- | Load a 'Status' from file.------ Important information that cannot be saved and has to be provided again when--- a chain is restored:--- - prior function--- - likelihood function--- - cleaning function--- - cycle--- - monitor------ To avoid incomplete continued runs, the @.mcmc@ file is removed after load.-loadStatus ::- FromJSON a =>- -- | Prior function.- (a -> Log Double) ->- -- | Likelihood function.- (a -> Log Double) ->- Cycle a ->- Monitor a ->- -- | Cleaner, if needed.- Maybe (Cleaner a) ->- -- | Path of status to load.- FilePath ->- IO (Status a)-loadStatus pr lh cc mn cl fn = do- res <- eitherDecode . decompress <$> BL.readFile fn- let s = case res of- Left err -> error err- Right sv -> fromSave pr lh cc mn cl sv- -- Check if prior and likelihood matches.- let Item x svp svl = item s- -- Recompute and check the prior and likelihood for the last state because the- -- functions may have changed. Of course, we cannot test for the same- -- function, but having the same prior and likelihood at the last state is- -- already a good indicator.- when- (pr x /= svp)- (error "loadStatus: Provided prior function does not match the saved prior.")- when- (lh x /= svl)- (error "loadStatus: Provided likelihood function does not match the saved likelihood.")- removeFile fn- return s
+ src/Mcmc/Settings.hs view
@@ -0,0 +1,236 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module : Mcmc.Settings+-- Description : Settings of Markov chain Monte Carlo samplers+-- Copyright : (c) Dominik Schrempf, 2020+-- License : GPL-3.0-or-later+--+-- Maintainer : dominik.schrempf@gmail.com+-- Stability : unstable+-- Portability : portable+--+-- Creation date: Mon Nov 16 11:13:01 2020.+module Mcmc.Settings+ ( -- * Data types+ AnalysisName (..),+ BurnInSpecification (..),+ burnInIterations,+ Iterations (..),+ ExecutionMode (..),+ openWithExecutionMode,+ ParallelizationMode (..),+ SaveMode (..),+ Verbosity (..),++ -- * Settings+ Settings (..),+ settingsSave,+ settingsLoad,+ settingsCheck,+ )+where++import Data.Aeson+import Data.Aeson.TH+import qualified Data.ByteString.Lazy.Char8 as BL+import System.Directory+import System.IO++-- | Analysis name of the MCMC sampler.+newtype AnalysisName = AnalysisName {fromAnalysisName :: String}+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''AnalysisName)++-- | Burn in specification.+data BurnInSpecification+ = -- | No burn in.+ NoBurnIn+ | -- | Burn in for a given number of iterations.+ BurnInWithoutAutoTuning Int+ | -- | Burn in for a given number of iterations. Enable auto tuning with a+ -- given period.+ BurnInWithAutoTuning Int Int+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''BurnInSpecification)++-- | Get the number of burn in iterations.+burnInIterations :: BurnInSpecification -> Int+burnInIterations NoBurnIn = 0+burnInIterations (BurnInWithoutAutoTuning n) = n+burnInIterations (BurnInWithAutoTuning n _) = n++-- | Number of normal iterations after burn in.+--+-- Note that auto tuning only happens during burn in.+newtype Iterations = Iterations {fromIterations :: Int}+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''Iterations)++-- | Execution mode.+data ExecutionMode+ = -- | Perform new run.+ --+ -- Call 'error' if an output files exists.+ Fail+ | -- | Perform new run.+ --+ -- Overwrite existing output files.+ Overwrite+ | -- | Continue a previous run and append to output files.+ --+ -- Call 'error' if an output file does not exist.+ Continue+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''ExecutionMode)++-- | Open a file honoring the execution mode.+--+-- Call 'error' if execution mode is+--+-- - 'Continue' and file does not exist.+--+-- - 'Fail' and file exists.+openWithExecutionMode :: ExecutionMode -> FilePath -> IO Handle+openWithExecutionMode em fn = do+ fe <- doesFileExist fn+ case (em, fe) of+ (Continue, False) ->+ error $ "openWithExecutionMode: Cannot continue; file does not exist: " ++ fn ++ "."+ (Continue, True) ->+ openFile fn AppendMode+ (Fail, True) ->+ error $ "openWithExecutionMode: File exists: " ++ fn ++ "; use 'Overwrite'?"+ _ -> do+ h <- openFile fn WriteMode+ hSetBuffering h LineBuffering+ return h++-- One could automatically select 'Parallel' or 'Sequential' according to the+-- number of capabilities when initializing the environment or according to the+-- iteration time in dependence of the number of used capabilities. However, I+-- decided to opt for a manual configuration, because more capabilities may be+-- available and other parts of the program may be executed in parallel even if+-- sequential execution of the MCMC sampler is beneficial.++-- | Parallelization mode.+--+-- Parallel execution of the chains is only beneficial when the algorithm allows+-- for parallelization, and if computation of the next iteration takes a long+-- time. If the calculation of the next state is fast, sequential execution is+-- usually beneficial, even for algorithms involving parallel chains. If the+-- calculation of the next state is slow, parallel execution may be beneficial.+--+-- - The "Mcmc.Algorithm.Metropolis" algorithm is inherently sequential.+--+-- - The "Mcmc.Algorithm.MC3" algorithm works well with parallelization.+--+-- Of course, also the prior or likelihood functions can be computed in+-- parallel. However, this library is not aware of how these functions are+-- computed.+data ParallelizationMode+ = Sequential+ | Parallel+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''ParallelizationMode)++-- | Should the MCMC run be saved at the end of the run?+data SaveMode = NoSave | Save+ deriving (Eq, Read, Show)++$(deriveJSON defaultOptions ''SaveMode)++-- $(deriveJSON defaultOptions ''CleaningMode)++-- | Not much to say here.+data Verbosity = Quiet | Warn | Info | Debug+ deriving (Eq, Ord, Read, Show)++$(deriveJSON defaultOptions ''Verbosity)++-- | Settings of an MCMC sampler.+data Settings = Settings+ { sAnalysisName :: AnalysisName,+ sBurnIn :: BurnInSpecification,+ sIterations :: Iterations,+ sExecutionMode :: ExecutionMode,+ sParallelizationMode :: ParallelizationMode,+ sSaveMode :: SaveMode,+ sVerbosity :: Verbosity+ }+ deriving (Eq, Show)++$(deriveJSON defaultOptions ''Settings)++settingsFn :: String -> FilePath+settingsFn n = n ++ ".settings"++-- | Save settings to a file determined by the analysis name.+settingsSave :: Settings -> IO ()+settingsSave s = BL.writeFile fn $ encode s+ where+ fn = settingsFn $ fromAnalysisName $ sAnalysisName s++-- | Load settings.+settingsLoad :: AnalysisName -> IO Settings+settingsLoad (AnalysisName n) = either error id . eitherDecode <$> BL.readFile fn+ where+ fn = settingsFn n++-- Show settings and call 'error'.+settingsError :: Settings -> Int -> String -> a+settingsError s i err =+ error $+ show s+ ++ "\n"+ ++ "Current iteration: "+ ++ show i+ ++ "\n"+ ++ "settingsError: "+ ++ err++-- | Check settings.+--+-- Call 'error' if:+--+-- - The analysis name is the empty string.+--+-- - The number of burn in iterations is negative.+--+-- - Auto tuning period is zero or negative.+--+-- - The number of iterations is negative.+--+-- - The current iteration is larger than the total number of iterations.+--+-- - The current iteration is non-zero but the execution mode is not 'Continue'.+--+-- - The current iteration is zero but the execution mode is 'Continue'.+settingsCheck ::+ Settings ->+ -- | Current iteration.+ Int ->+ IO ()+settingsCheck s@(Settings nm bi i em _ _ _) iCurrent+ | null (fromAnalysisName nm) = serr "Analysis name is the empty string."+ | burnInIterations bi < 0 = serr "Number of burn in iterations is negative."+ | not $ burnInAutoTuningPeriodValid bi = serr "Auto tuning period is zero or negative."+ | fromIterations i < 0 = serr "Number of iterations is negative."+ | burnInIterations bi + fromIterations i - iCurrent < 0 =+ serr "Current iteration is larger than the total number of iterations."+ | iCurrent /= 0 && em /= Continue =+ serr "Current iteration is non-zero but execution mode is not 'Continue'."+ | iCurrent == 0 && em == Continue =+ serr "Current iteration is zero but execution mode is 'Continue'."+ | otherwise = return ()+ where+ serr = settingsError s iCurrent+ burnInAutoTuningPeriodValid :: BurnInSpecification -> Bool+ burnInAutoTuningPeriodValid (BurnInWithAutoTuning _ t) = t > 0+ burnInAutoTuningPeriodValid _ = True
− src/Mcmc/Status.hs
@@ -1,218 +0,0 @@--- Note: It is not necessary to add another type @b@ to store supplementary--- information about the chain. The information can just be stored in @a@--- equally well.---- XXX: Status tuned exclusively to the Metropolis-Hastings algorithm. We should--- abstract the algorithm from the chain. Maybe something like:------ @--- data Status a = Status { Chain a; Algorithm a}--- @---- |--- Module : Mcmc.Status--- Description : What is an MCMC?--- Copyright : (c) Dominik Schrempf 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Tue May 5 18:01:15 2020.-module Mcmc.Status- ( Cleaner (..),- Status (..),- status,- cleanWith,- saveWith,- force,- quiet,- debug,- noData,- )-where--import Data.Maybe-import Data.Time.Clock-import Mcmc.Item-import Mcmc.Monitor-import Mcmc.Proposal-import Mcmc.Trace-import Mcmc.Verbosity (Verbosity (..))-import Numeric.Log-import System.IO-import System.Random.MWC hiding (save)-import Prelude hiding (cycle)---- | Clean the state periodically.------ The prior and the likelihood will be updated after the cleaning process.------ For long chains, successive numerical errors can accumulate such that the--- state diverges from honoring specific required constraints. In these cases, a--- 'Cleaner' can be used to ensure that the required constraints of the state--- are honored. For example, the branches of an ultrametric phylogeny may--- diverge slightly after successful many proposals such that the phylogeny is--- not anymore ultrametric.------ Please be aware that the Markov chain will not converge to the true posterior--- distribution if the state is changed substantially! Only apply subtle changes--- that are absolutely necessary to preserve the required properties of the--- state such as specific numerical constraints.-data Cleaner a = Cleaner- { -- | Clean every given number of iterations.- clEvery :: Int,- -- | Cleaning function. Executed before monitoring the state.- clFunction :: a -> a- }---- | The 'Status' contains all information to run an MCMC chain. It is--- constructed using the function 'status'.------ The polymorphic type @a@ stores the state of the chain. It can also be used--- to store auxiliary information.-data Status a = Status- { -- MCMC related variables; saved.-- -- | The name of the MCMC chain; used as file prefix.- name :: String,- -- | The current 'Item' of the chain combines the current state and the- -- current likelihood.- item :: Item a,- -- | The iteration is the number of completed cycles.- iteration :: Int,- -- | The 'Trace' of the Markov chain in reverse order, the most recent- -- 'Item' is at the head of the list.- trace :: Trace a,- -- | For each 'Proposal', store the list of accepted (True) and rejected (False)- -- proposals; for reasons of efficiency, the list is also stored in reverse- -- order.- acceptance :: Acceptance (Proposal a),- -- | Number of burn in iterations; deactivate burn in with 'Nothing'.- burnInIterations :: Maybe Int,- -- | Auto tuning period (only during burn in); deactivate auto tuning with- -- 'Nothing'.- autoTuningPeriod :: Maybe Int,- -- | Number of normal iterations excluding burn in. Note that auto tuning- -- only happens during burn in.- iterations :: Int,- --- -- Auxiliary variables; saved.-- -- | Overwrite output files? Default is 'False', change with 'force'.- forceOverwrite :: Bool,- -- | Save the chain with trace of given length at the end of the run?- -- Default is no save ('Nothing'). Change with 'saveWith'.- save :: Maybe Int,- -- | Verbosity.- verbosity :: Verbosity,- -- | The random number generator.- generator :: GenIO,- --- -- Auxiliary variables; not saved.-- -- | Starting time and starting iteration of chain; used to calculate- -- run time and ETA.- start :: Maybe (Int, UTCTime),- -- | Handle to log file.- logHandle :: Maybe Handle,- --- -- Auxiliary functions; not saved.-- -- | The prior function. The un-normalized posterior is the product of the- -- prior and the likelihood.- priorF :: a -> Log Double,- -- | The likelihood function. The un-normalized posterior is the product of- -- the prior and the likelihood.- likelihoodF :: a -> Log Double,- -- | Clean the state periodically.- cleaner :: Maybe (Cleaner a),- --- -- Variables related to the algorithm; not saved.-- -- | A set of 'Proposal's form a 'Cycle'.- cycle :: Cycle a,- -- | A 'Monitor' observing the chain.- monitor :: Monitor a- }---- | Initialize the 'Status' of a Markov chain Monte Carlo run.-status ::- -- | Name of the Markov chain; used as file prefix.- String ->- -- | The prior function.- (a -> Log Double) ->- -- | The likelihood function.- (a -> Log Double) ->- -- | A list of 'Proposal's executed in forward order. The chain will be logged- -- after each cycle.- Cycle a ->- -- | A 'Monitor' observing the chain.- Monitor a ->- -- | The initial state in the state space @a@.- a ->- -- | Number of burn in iterations; deactivate burn in with 'Nothing'.- Maybe Int ->- -- | Auto tuning period (only during burn in); deactivate auto tuning with- -- 'Nothing'.- Maybe Int ->- -- | Number of normal iterations excluding burn in. Note that auto tuning only- -- happens during burn in.- Int ->- -- | A source of randomness. For reproducible runs, make sure to use- -- generators with the same, fixed seed.- GenIO ->- Status a-status n p l c m x mB mT nI g- | isJust mT && isNothing mB = error "status: Auto tuning period given, but no burn in."- | otherwise =- Status- n- i- 0- (singletonT i)- (emptyA $ ccProposals c)- mB- mT- nI- False- Nothing- Info- g- Nothing- Nothing- p- l- Nothing- c- m- where- i = Item x (p x) (l x)---- | Clean the state every given number of generations using the given function.--- See 'Cleaner'.-cleanWith :: Cleaner a -> Status a -> Status a-cleanWith c s = s {cleaner = Just c}---- | Save the Markov chain with trace of given length.-saveWith :: Int -> Status a -> Status a-saveWith n s = s {save = Just n}---- | Overwrite existing files; it is not necessary to use 'force', when a chain--- is continued.-force :: Status a -> Status a-force s = s {forceOverwrite = True}---- | Do not print anything to standard output. Do not create log file. File--- monitors and batch monitors are executed normally.-quiet :: Status a -> Status a-quiet s = s {verbosity = Quiet}---- | Be verbose.-debug :: Status a -> Status a-debug s = s {verbosity = Debug}---- | Set the likelihood function to 1.0. Useful for debugging and testing.-noData :: Status a -> Status a-noData s = s {likelihoodF = const 1.0}
− src/Mcmc/Trace.hs
@@ -1,63 +0,0 @@--- |--- Module : Mcmc.Trace--- Description : Trace of a Markov chain--- Copyright : (c) Dominik Schrempf 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Wed May 20 09:11:25 2020.-module Mcmc.Trace- ( Trace,- singletonT,- pushT,- headT,- takeItems,- takeT,- )-where--import Data.Aeson-import Mcmc.Item---- | A 'Trace' passes through a list of states with associated likelihoods which--- are called 'Item's. New 'Item's are prepended, and the path of the Markov--- chain is stored in reversed order.-newtype Trace a = Trace {fromTrace :: [Item a]}- deriving (Show, Read, Eq)--instance Semigroup (Trace a) where- (Trace l) <> (Trace r) = Trace (l <> r)--instance Monoid (Trace a) where- mempty = Trace []--instance ToJSON a => ToJSON (Trace a) where- toJSON (Trace xs) = toJSON xs- toEncoding (Trace xs) = toEncoding xs--instance FromJSON a => FromJSON (Trace a) where- parseJSON v = Trace <$> parseJSONList v---- | The empty trace.-singletonT :: Item a -> Trace a-singletonT i = Trace [i]---- | Prepend an 'Item' to a 'Trace'.-pushT :: Item a -> Trace a -> Trace a-pushT x = Trace . (:) x . fromTrace-{-# INLINEABLE pushT #-}---- | Get the most recent item of the trace.-headT :: Trace a -> Item a-headT = head . fromTrace---- | Get the N most recent items of the trace.-takeItems :: Int -> Trace a -> [Item a]-takeItems n = take n . fromTrace---- | Shorten the trace to given length.-takeT :: Int -> Trace a -> Trace a-takeT n = Trace . take n . fromTrace
− src/Mcmc/Verbosity.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}---- |--- Module : Mcmc.Verbosity--- Description : Be quiet! Or better not?--- Copyright : (c) Dominik Schrempf, 2020--- License : GPL-3.0-or-later------ Maintainer : dominik.schrempf@gmail.com--- Stability : unstable--- Portability : portable------ Creation date: Sat Jun 27 10:49:28 2020.-module Mcmc.Verbosity- ( Verbosity (..),- warn,- info,- debug,- )-where--import Control.Monad-import Data.Aeson.TH---- | Not much to say here.-data Verbosity = Quiet | Warn | Info | Debug deriving (Show, Eq, Ord)--$(deriveJSON defaultOptions ''Verbosity)---- | Perform action if 'Verbosity' is 'Warn' or higher.-warn :: Applicative m => Verbosity -> m () -> m ()-warn v = when (v >= Warn)---- | Perform action if 'Verbosity' is 'Info' or higher.-info :: Applicative m => Verbosity -> m () -> m ()-info v = when (v >= Info)---- | Perform action if 'Verbosity' is 'Debug'.-debug :: Applicative m => Verbosity -> m () -> m ()-debug v = when (v == Debug)
test/Mcmc/ProposalSpec.hs view
@@ -26,18 +26,18 @@ p2 = slideSymmetric 1.0 (PName "Test 2") (PWeight 3) Tune c :: Cycle Double-c = fromList [p1, p2]+c = cycleFromList [p1, p2] spec :: Spec spec =- describe "getNIterations" $+ describe "orderProposals" $ it "returns the correct number of proposals in a cycle" $ do g <- create- l1 <- length . head <$> getNIterations c 1 g+ l1 <- length <$> orderProposals c g l1 `shouldBe` 4- l2 <- length . head <$> getNIterations (setOrder RandomReversibleO c) 1 g+ l2 <- length <$> orderProposals (setOrder RandomReversibleO c) g l2 `shouldBe` 8- o3 <- head <$> getNIterations (setOrder SequentialReversibleO c) 1 g+ o3 <- orderProposals (setOrder SequentialReversibleO c) g length o3 `shouldBe` 8- o3 `shouldBe` [p1, p2, p2, p2, p2, p2, p2, p1]+ o3 == [p1, p2, p2, p2, p2, p2, p2, p1] `shouldBe` True
test/Mcmc/SaveSpec.hs view
@@ -15,15 +15,13 @@ where import Mcmc-import Mcmc.Save-import Mcmc.Status hiding (save)+import Mcmc.Chain.Chain+import Mcmc.Chain.Save+import Mcmc.Chain.Trace import Numeric.Log-import Statistics.Distribution hiding- ( mean,- stdDev,- )+import Statistics.Distribution import Statistics.Distribution.Normal-import System.Random.MWC+import qualified System.Random.MWC as R import Test.Hspec trueMean :: Double@@ -32,12 +30,12 @@ trueStdDev :: Double trueStdDev = 4 -lh :: Double -> Log Double+lh :: LikelihoodFunction Double lh = Exp . logDensity (normalDistr trueMean trueStdDev) proposals :: Cycle Double proposals =- fromList+ cycleFromList [ slideSymmetric 0.1 (PName "Small") (PWeight 5) Tune, slideSymmetric 1.0 (PName "Medium") (PWeight 2) Tune, slideSymmetric 5.0 (PName "Large") (PWeight 2) Tune,@@ -50,52 +48,57 @@ mon :: Monitor Double mon = Monitor monStd [] [] -nBurn :: Maybe Int-nBurn = Just 20--nAutoTune :: Maybe Int-nAutoTune = Just 10--nIter :: Int-nIter = 200- spec :: Spec-spec =- describe "saveStatus and loadStatus" $+spec = do+ describe "save and load" $ it "doesn't change the MCMC chain" $ do- gen <- create+ gen <- R.create let s =- force $- quiet $- saveWith 100 $- status "SaveSpec" (const 1) lh proposals mon 0 nBurn nAutoTune nIter gen- saveStatus "SaveSpec.json" s- s' <- loadStatus (const 1) lh proposals mon Nothing "SaveSpec.json"- r <- mh s- r' <- mh s'- -- Done during 'loadStatus'.- -- removeFile "SaveSpec.json"- item r `shouldBe` item r'+ Settings+ (AnalysisName "SaveSpec")+ (BurnInWithAutoTuning 20 10)+ (Iterations 200)+ Overwrite+ Sequential+ NoSave+ Quiet+ c <- fromMHG <$> mhg noPrior lh proposals mon 0 gen+ savedChain <- toSavedChain c+ c' <- fromSavedChain noPrior lh proposals mon savedChain+ putStrLn "@load . save@ should be @id@."+ link c `shouldBe` link c'+ iteration c `shouldBe` iteration c'+ frozenT1 <- freezeT (trace c)+ frozenT1' <- freezeT (trace c')+ frozenT1 `shouldBe` frozenT1'+ -- g1 <- R.save $ generator c+ -- g1' <- R.save $ generator c'+ -- g1 `shouldBe` g1'+ putStrLn "Sampling from the chains should be the same."+ r <- fromMHG <$> mcmc s (MHG c)+ r' <- fromMHG <$> mcmc s (MHG c')+ link r `shouldBe` link r' iteration r `shouldBe` iteration r'- trace r `shouldBe` trace r'- g <- save $ generator r- g' <- save $ generator r'- g `shouldBe` g'+ frozenT2 <- freezeT (trace c)+ frozenT2' <- freezeT (trace c')+ frozenT2 `shouldBe` frozenT2'+ g2 <- R.save $ generator r+ g2' <- R.save $ generator r'+ g2 `shouldBe` g2' --- -- TODO: Splitmix. This will only work with a splittable generator--- -- because getNIterations changes the generator.+-- -- TODO. -- describe "mhContinue" -- $ it "mh 200 + mhContinue 200 == mh 400" -- $ do -- gen1 <- create--- let s1 = status "SaveSpec" (const 1) likelihood proposals mon 0 nBurn nAutoTune 400 gen1+-- let s1 = chain "SaveSpec" (const 1) likelihood proposals mon 0 nBurn nAutoTune 400 gen1 -- r1 <- mh s1 -- gen2 <- create--- let s2 = status "SaveSpec" (const 1) likelihood proposals mon 0 nBurn nAutoTune 200 gen2+-- let s2 = chain "SaveSpec" (const 1) likelihood proposals mon 0 nBurn nAutoTune 200 gen2 -- r2' <- mh s2 -- r2 <- mhContinue 200 r2'--- item r1 `shouldBe` item r2+-- link r1 `shouldBe` link r2 -- iteration r1 `shouldBe` iteration r2 -- trace r1 `shouldBe` trace r2 -- g <- save $ generator r1