mcmc 0.8.0.1 → 0.8.1.0
raw patch · 21 files changed
+556/−337 lines, 21 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Mcmc.Acceptance: instance (GHC.Classes.Ord k, Data.Aeson.Types.FromJSON.FromJSONKey k) => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Acceptance.Acceptance k)
- Mcmc.Acceptance: instance Data.Aeson.Types.ToJSON.ToJSONKey k => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Acceptance.Acceptance k)
- Mcmc.Acceptance: instance GHC.Classes.Eq k => GHC.Classes.Eq (Mcmc.Acceptance.Acceptance k)
- Mcmc.Acceptance: instance GHC.Show.Show k => GHC.Show.Show (Mcmc.Acceptance.Acceptance k)
- Mcmc.Acceptance: pushAcceptanceCounts :: Ord k => k -> AcceptanceCounts -> Acceptance k -> Acceptance k
- Mcmc.Algorithm.MC3: [mc3SwapAcceptance] :: MC3 a -> Acceptance Int
- Mcmc.Chain.Chain: [acceptance] :: Chain a -> Acceptance (Proposal a)
- Mcmc.Chain.Save: [savedAcceptance] :: SavedChain a -> Acceptance Int
- Mcmc.Proposal: LastTuningStep :: TuningType
- Mcmc.Proposal: NormalTuningStep :: TuningType
- Mcmc.Proposal: tuningFunctionOnlyAux :: (TuningType -> Vector a -> AuxiliaryTuningParameters -> AuxiliaryTuningParameters) -> TuningFunction a
- Mcmc.Proposal: tuningFunctionWithAux :: (TuningType -> Vector a -> AuxiliaryTuningParameters -> AuxiliaryTuningParameters) -> TuningFunction a
+ Mcmc.Acceptance: AcceptanceRates :: !Double -> !Int -> AcceptanceRates
+ Mcmc.Acceptance: ResetEverything :: ResetAcceptance
+ Mcmc.Acceptance: ResetExpectedRatesOnly :: ResetAcceptance
+ Mcmc.Acceptance: [nAcceptanceRates] :: AcceptanceRates -> !Int
+ Mcmc.Acceptance: [totalAcceptanceRate] :: AcceptanceRates -> !Double
+ Mcmc.Acceptance: data AcceptanceRates
+ Mcmc.Acceptance: data Acceptances k
+ Mcmc.Acceptance: data ResetAcceptance
+ Mcmc.Acceptance: instance (GHC.Classes.Ord k, Data.Aeson.Types.FromJSON.FromJSONKey k) => Data.Aeson.Types.FromJSON.FromJSON (Mcmc.Acceptance.Acceptances k)
+ Mcmc.Acceptance: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Acceptance.Acceptance
+ Mcmc.Acceptance: instance Data.Aeson.Types.FromJSON.FromJSON Mcmc.Acceptance.AcceptanceRates
+ Mcmc.Acceptance: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Acceptance.Acceptance
+ Mcmc.Acceptance: instance Data.Aeson.Types.ToJSON.ToJSON Mcmc.Acceptance.AcceptanceRates
+ Mcmc.Acceptance: instance Data.Aeson.Types.ToJSON.ToJSONKey k => Data.Aeson.Types.ToJSON.ToJSON (Mcmc.Acceptance.Acceptances k)
+ Mcmc.Acceptance: instance GHC.Classes.Eq Mcmc.Acceptance.Acceptance
+ Mcmc.Acceptance: instance GHC.Classes.Eq Mcmc.Acceptance.AcceptanceRates
+ Mcmc.Acceptance: instance GHC.Classes.Eq k => GHC.Classes.Eq (Mcmc.Acceptance.Acceptances k)
+ Mcmc.Acceptance: instance GHC.Show.Show Mcmc.Acceptance.Acceptance
+ Mcmc.Acceptance: instance GHC.Show.Show Mcmc.Acceptance.AcceptanceRates
+ Mcmc.Acceptance: instance GHC.Show.Show k => GHC.Show.Show (Mcmc.Acceptance.Acceptances k)
+ Mcmc.Algorithm.MC3: [mc3SwapAcceptances] :: MC3 a -> Acceptances Int
+ Mcmc.Chain.Chain: [acceptances] :: Chain a -> Acceptances (Proposal a)
+ Mcmc.Chain.Save: [savedAcceptances] :: SavedChain a -> Acceptances Int
+ Mcmc.Mcmc: instance GHC.Classes.Eq Mcmc.Mcmc.IntermediateTuningSpec
+ Mcmc.Proposal: IntermediateTuningAllProposals :: TuningType
+ Mcmc.Proposal: IntermediateTuningFastProposalsOnly :: TuningType
+ Mcmc.Proposal: LastTuningAllProposals :: TuningType
+ Mcmc.Proposal: LastTuningFastProposalsOnly :: TuningType
+ Mcmc.Proposal: NormalTuningAllProposals :: TuningType
+ Mcmc.Proposal: NormalTuningFastProposalsOnly :: TuningType
+ Mcmc.Proposal: [tSuitableForIntermediateTuning] :: Tuner a -> Bool
+ Mcmc.Proposal: instance GHC.Classes.Eq Mcmc.Proposal.TuningType
- Mcmc.Acceptance: acceptanceRate :: Ord k => k -> Acceptance k -> Maybe (Int, Int, AcceptanceRate)
+ Mcmc.Acceptance: acceptanceRate :: Ord k => k -> Acceptances k -> (Int, Int, Maybe AcceptanceRate, Maybe AcceptanceRate)
- Mcmc.Acceptance: acceptanceRates :: Acceptance k -> Map k (Maybe AcceptanceRate)
+ Mcmc.Acceptance: acceptanceRates :: Acceptances k -> Map k (Maybe AcceptanceRate)
- Mcmc.Acceptance: data Acceptance k
+ Mcmc.Acceptance: data Acceptance
- Mcmc.Acceptance: emptyA :: Ord k => [k] -> Acceptance k
+ Mcmc.Acceptance: emptyA :: Ord k => [k] -> Acceptances k
- Mcmc.Acceptance: pushAccept :: Ord k => k -> Acceptance k -> Acceptance k
+ Mcmc.Acceptance: pushAccept :: Ord k => Maybe AcceptanceRates -> k -> Acceptances k -> Acceptances k
- Mcmc.Acceptance: pushReject :: Ord k => k -> Acceptance k -> Acceptance k
+ Mcmc.Acceptance: pushReject :: Ord k => Maybe AcceptanceRates -> k -> Acceptances k -> Acceptances k
- Mcmc.Acceptance: resetA :: Ord k => Acceptance k -> Acceptance k
+ Mcmc.Acceptance: resetA :: Ord k => ResetAcceptance -> Acceptances k -> Acceptances k
- Mcmc.Acceptance: transformKeysA :: (Ord k1, Ord k2) => [(k1, k2)] -> Acceptance k1 -> Acceptance k2
+ Mcmc.Acceptance: transformKeysA :: (Ord k1, Ord k2) => [(k1, k2)] -> Acceptances k1 -> Acceptances k2
- Mcmc.Algorithm: aResetAcceptance :: Algorithm a => a -> a
+ Mcmc.Algorithm: aResetAcceptance :: Algorithm a => ResetAcceptance -> a -> a
- Mcmc.Algorithm.MC3: MC3 :: MC3Settings -> MHGChains a -> ReciprocalTemperatures -> Int -> Acceptance Int -> IOGenM StdGen -> MC3 a
+ Mcmc.Algorithm.MC3: MC3 :: MC3Settings -> MHGChains a -> ReciprocalTemperatures -> Int -> Acceptances Int -> IOGenM StdGen -> MC3 a
- Mcmc.Chain.Chain: Chain :: Link a -> Int -> Trace a -> Acceptance (Proposal a) -> IOGenM StdGen -> Int -> PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> Chain a
+ Mcmc.Chain.Chain: Chain :: Link a -> Int -> Trace a -> Acceptances (Proposal a) -> IOGenM StdGen -> Int -> PriorFunction a -> LikelihoodFunction a -> Cycle a -> Monitor a -> Chain a
- Mcmc.Chain.Save: SavedChain :: Link a -> Int -> Stack Vector (Link a) -> Acceptance Int -> (Word64, Word64) -> [Maybe (TuningParameter, AuxiliaryTuningParameters)] -> SavedChain a
+ Mcmc.Chain.Save: SavedChain :: Link a -> Int -> Stack Vector (Link a) -> Acceptances Int -> (Word64, Word64) -> [Maybe (TuningParameter, AuxiliaryTuningParameters)] -> SavedChain a
- Mcmc.Cycle: autoTuneCycle :: TuningType -> Acceptance (Proposal a) -> Maybe (Vector a) -> Cycle a -> Cycle a
+ Mcmc.Cycle: autoTuneCycle :: TuningType -> Acceptances (Proposal a) -> Maybe (Vector a) -> Cycle a -> Cycle a
- Mcmc.Cycle: summarizeCycle :: IterationMode -> Acceptance (Proposal a) -> Cycle a -> ByteString
+ Mcmc.Cycle: summarizeCycle :: IterationMode -> Acceptances (Proposal a) -> Cycle a -> ByteString
- Mcmc.Proposal: Tuner :: TuningParameter -> AuxiliaryTuningParameters -> Bool -> TuningFunction a -> (TuningParameter -> AuxiliaryTuningParameters -> Either String (PFunction a)) -> Tuner a
+ Mcmc.Proposal: Tuner :: TuningParameter -> AuxiliaryTuningParameters -> Bool -> Bool -> TuningFunction a -> (TuningParameter -> AuxiliaryTuningParameters -> Either String (PFunction a)) -> Tuner a
- Mcmc.Proposal: summarizeProposal :: PName -> PDescription -> PWeight -> Maybe TuningParameter -> PDimension -> Maybe (Int, Int, Double) -> ByteString
+ Mcmc.Proposal: summarizeProposal :: PName -> PDescription -> PWeight -> Maybe TuningParameter -> PDimension -> (Int, Int, Maybe Double, Maybe Double) -> ByteString
- Mcmc.Proposal: type PFunction a = a -> IOGenM StdGen -> IO (PResult a, Maybe AcceptanceCounts)
+ Mcmc.Proposal: type PFunction a = a -> IOGenM StdGen -> IO (PResult a, Maybe AcceptanceRates)
- Mcmc.Proposal: type TuningFunction a = TuningType -> PDimension -> AcceptanceRate " Acceptance rate of last tuning period." -> Maybe (Vector a) " Trace of last tuning period. Only available when requested by proposal." -> (TuningParameter, AuxiliaryTuningParameters) -> (TuningParameter, AuxiliaryTuningParameters)
+ Mcmc.Proposal: type TuningFunction a = TuningType -> PDimension -> Maybe AcceptanceRate " Acceptance rate of last tuning period. May not always be available because proposals may be skipped." -> Maybe (Vector a) " Trace of last tuning period. Not available for intermediate tuning' steps (see 'TuningType'), and only available for other tuning types when requested by proposal." -> (TuningParameter, AuxiliaryTuningParameters) -> (TuningParameter, AuxiliaryTuningParameters)
Files
- ChangeLog.md +8/−0
- bench/Bench.hs +1/−1
- bench/Poisson.hs +3/−4
- mcmc.cabal +1/−1
- src/Mcmc/Acceptance.hs +78/−44
- src/Mcmc/Algorithm.hs +2/−1
- src/Mcmc/Algorithm/MC3.hs +15/−15
- src/Mcmc/Algorithm/MHG.hs +27/−21
- src/Mcmc/Chain/Chain.hs +3/−4
- src/Mcmc/Chain/Save.hs +4/−4
- src/Mcmc/Cycle.hs +62/−33
- src/Mcmc/Internal/SpecFunctions.hs +2/−2
- src/Mcmc/MarginalLikelihood.hs +2/−2
- src/Mcmc/Mcmc.hs +54/−22
- src/Mcmc/Proposal.hs +54/−46
- src/Mcmc/Proposal/Generic.hs +17/−17
- src/Mcmc/Proposal/Hamiltonian/Common.hs +2/−1
- src/Mcmc/Proposal/Hamiltonian/Hamiltonian.hs +9/−19
- src/Mcmc/Proposal/Hamiltonian/Internal.hs +83/−48
- src/Mcmc/Proposal/Hamiltonian/Masses.hs +110/−33
- src/Mcmc/Proposal/Hamiltonian/Nuts.hs +19/−19
ChangeLog.md view
@@ -5,6 +5,14 @@ ## Unreleased changes +## 0.8.1.0++- Automatic intermediate tuning for HMC and NUTS.+- Fix documentation for generic proposals.+- Tooling updates.+- Slight runtime improvements (strictness annotations).++ ## 0.8.0.1 - Improve exception handling (also during execution of monitors; also improve
bench/Bench.hs view
@@ -22,7 +22,7 @@ import System.Random.Stateful gammaBenchG :: RealFloat a => (a -> a) -> [a] -> a-gammaBenchG f = foldl' (\acc x -> acc + (f x)) 0+gammaBenchG f = foldl' (\acc x -> acc + f x) 0 {-# SPECIALIZE gammaBenchG :: (Double -> Double) -> [Double] -> Double #-} gammaVals :: [Double]
bench/Poisson.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-orphans #-} @@ -60,10 +59,10 @@ lh x = product [f ft yr x | (ft, yr) <- zip fatalities normalizedYears] proposalAlpha :: Proposal I-proposalAlpha = (VB.element 0) @~ slideSymmetric 0.2 (PName "Alpha") (pWeight 1) NoTune+proposalAlpha = VB.element 0 @~ slideSymmetric 0.2 (PName "Alpha") (pWeight 1) NoTune proposalBeta :: Proposal I-proposalBeta = (VB.element 1) @~ slideSymmetric 0.2 (PName "Beta") (pWeight 1) NoTune+proposalBeta = VB.element 1 @~ slideSymmetric 0.2 (PName "Beta") (pWeight 1) NoTune proposals :: Cycle I proposals = cycleFromList [proposalAlpha, proposalBeta]@@ -103,7 +102,7 @@ void $ mcmc s a toVec :: I -> VS.Vector Double-toVec xs = VS.generate 2 (\i -> xs VB.! i)+toVec xs = VS.generate 2 (xs VB.!) fromVec :: I -> VS.Vector Double -> I fromVec _ xs = VB.mk2 (xs VS.! 0) (xs VS.! 1)
mcmc.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: mcmc-version: 0.8.0.1+version: 0.8.1.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>
src/Mcmc/Acceptance.hs view
@@ -15,11 +15,13 @@ ( -- * Acceptance rates AcceptanceRate, AcceptanceCounts (..),- Acceptance (fromAcceptance),+ AcceptanceRates (..),+ Acceptance,+ Acceptances (fromAcceptances), emptyA, pushAccept, pushReject,- pushAcceptanceCounts,+ ResetAcceptance (..), resetA, transformKeysA, acceptanceRate,@@ -44,48 +46,74 @@ $(deriveJSON defaultOptions ''AcceptanceCounts) -addAccept :: AcceptanceCounts -> AcceptanceCounts-addAccept (AcceptanceCounts a r) = AcceptanceCounts (a + 1) r+-- | Proposals based on Hamiltonian dynamics use expected acceptance rates, not counts.+data AcceptanceRates = AcceptanceRates+ { totalAcceptanceRate :: !Double,+ nAcceptanceRates :: !Int+ }+ deriving (Show, Eq) -addReject :: AcceptanceCounts -> AcceptanceCounts-addReject (AcceptanceCounts a r) = AcceptanceCounts a (r + 1)+$(deriveJSON defaultOptions ''AcceptanceRates) -addAcceptanceCounts :: AcceptanceCounts -> AcceptanceCounts -> AcceptanceCounts-addAcceptanceCounts (AcceptanceCounts al rl) (AcceptanceCounts ar rr) =- AcceptanceCounts (al + ar) (rl + rr)+-- | Stored actual acceptance counts and maybe expected acceptance rates.+data Acceptance = A AcceptanceCounts (Maybe AcceptanceRates)+ deriving (Show, Eq) +$(deriveJSON defaultOptions ''Acceptance)++addAccept :: Maybe AcceptanceRates -> Acceptance -> Acceptance+addAccept mr' (A (AcceptanceCounts a r) mr) = A (AcceptanceCounts (a + 1) r) (addAcceptanceRates mr' mr)++addReject :: Maybe AcceptanceRates -> Acceptance -> Acceptance+addReject mr' (A (AcceptanceCounts a r) mr) = A (AcceptanceCounts a (r + 1)) (addAcceptanceRates mr' mr)++addAcceptanceRates :: Maybe AcceptanceRates -> Maybe AcceptanceRates -> Maybe AcceptanceRates+addAcceptanceRates Nothing Nothing = Nothing+addAcceptanceRates (Just r) Nothing = Just r+addAcceptanceRates Nothing (Just r) = Just r+addAcceptanceRates (Just (AcceptanceRates al rl)) (Just (AcceptanceRates ar rr)) =+ Just $ AcceptanceRates (al + ar) (rl + rr)+ -- | For each key @k@, store the number of accepted and rejected proposals.-newtype Acceptance k = Acceptance {fromAcceptance :: M.Map k AcceptanceCounts}+newtype Acceptances k = Acceptances {fromAcceptances :: M.Map k Acceptance} deriving (Eq, Show) -instance ToJSONKey k => ToJSON (Acceptance k) where- toJSON (Acceptance m) = toJSON m- toEncoding (Acceptance m) = toEncoding m+instance ToJSONKey k => ToJSON (Acceptances k) where+ toJSON (Acceptances m) = toJSON m+ toEncoding (Acceptances m) = toEncoding m -instance (Ord k, FromJSONKey k) => FromJSON (Acceptance k) where- parseJSON v = Acceptance <$> parseJSON v+instance (Ord k, FromJSONKey k) => FromJSON (Acceptances k) where+ parseJSON v = Acceptances <$> parseJSON v -- | In the beginning there was the Word. -- -- Initialize an empty storage of accepted/rejected values.-emptyA :: Ord k => [k] -> Acceptance k-emptyA ks = Acceptance $ M.fromList [(k, AcceptanceCounts 0 0) | k <- ks]+emptyA :: Ord k => [k] -> Acceptances k+emptyA ks = Acceptances $ M.fromList [(k, A noCounts Nothing) | k <- ks]+ where+ noCounts = AcceptanceCounts 0 0 -- | For key @k@, add an accept.-pushAccept :: Ord k => k -> Acceptance k -> Acceptance k-pushAccept k = Acceptance . M.adjust addAccept k . fromAcceptance+pushAccept :: Ord k => Maybe AcceptanceRates -> k -> Acceptances k -> Acceptances k+pushAccept mr k = Acceptances . M.adjust (addAccept mr) k . fromAcceptances -- | For key @k@, add a reject.-pushReject :: Ord k => k -> Acceptance k -> Acceptance k-pushReject k = Acceptance . M.adjust addReject k . fromAcceptance+pushReject :: Ord k => Maybe AcceptanceRates -> k -> Acceptances k -> Acceptances k+pushReject mr k = Acceptances . M.adjust (addReject mr) k . fromAcceptances --- | For key @k@, add acceptance counts.-pushAcceptanceCounts :: Ord k => k -> AcceptanceCounts -> Acceptance k -> Acceptance k-pushAcceptanceCounts k c = Acceptance . M.adjust (addAcceptanceCounts c) k . fromAcceptance+-- | Reset acceptance specification.+data ResetAcceptance+ = -- | Reset actual acceptance counts and expected acceptance rates.+ ResetEverything+ | -- | Only reset expected acceptance rates.+ ResetExpectedRatesOnly -- | Reset acceptance counts.-resetA :: Ord k => Acceptance k -> Acceptance k-resetA = emptyA . M.keys . fromAcceptance+resetA :: Ord k => ResetAcceptance -> Acceptances k -> Acceptances k+resetA ResetEverything = emptyA . M.keys . fromAcceptances+resetA ResetExpectedRatesOnly = Acceptances . M.map f . fromAcceptances+ where+ f (A cs _) = A cs Nothing transformKeys :: (Ord k1, Ord k2) => [(k1, k2)] -> M.Map k1 v -> M.Map k2 v transformKeys ks m = foldl' insrt M.empty ks@@ -94,10 +122,11 @@ -- | Transform keys using the given lists. Keys not provided will not be present -- in the new 'Acceptance' variable.-transformKeysA :: (Ord k1, Ord k2) => [(k1, k2)] -> Acceptance k1 -> Acceptance k2-transformKeysA ks = Acceptance . transformKeys ks . fromAcceptance+transformKeysA :: (Ord k1, Ord k2) => [(k1, k2)] -> Acceptances k1 -> Acceptances k2+transformKeysA ks = Acceptances . transformKeys ks . fromAcceptances --- | Acceptance counts and rate for a specific proposal.+-- | Compute acceptance counts, and actual and expected acceptances rates for a+-- specific proposal. -- -- Return @Just (accepts, rejects, acceptance rate)@. --@@ -106,23 +135,28 @@ acceptanceRate :: Ord k => k ->- Acceptance k ->- Maybe (Int, Int, AcceptanceRate)-acceptanceRate k a = case fromAcceptance a M.!? k of- Just (AcceptanceCounts 0 0) -> Nothing- Just (AcceptanceCounts as rs) -> Just (as, rs, fromIntegral as / fromIntegral (as + rs))+ Acceptances k ->+ -- | (nAccepts, nRejects, actualRate, expectedRate)+ (Int, Int, Maybe AcceptanceRate, Maybe AcceptanceRate)+acceptanceRate k a = case fromAcceptances a M.!? k of+ Just (A (AcceptanceCounts as rs) mrs) -> (as, rs, mar, mtr)+ where+ s = as + rs+ mar = if s <= 0 then Nothing else Just $ fromIntegral as / fromIntegral s+ mtr = case mrs of+ Nothing -> Nothing+ Just (AcceptanceRates xs n) -> Just $ xs / fromIntegral n Nothing -> error "acceptanceRate: Key not found in map." --- | Acceptance rates for all proposals.+-- | Compute actual acceptance rates for all proposals. -- -- Set rate to 'Nothing' if no proposals have been accepted or rejected -- (division by zero).-acceptanceRates :: Acceptance k -> M.Map k (Maybe AcceptanceRate)-acceptanceRates =- M.map- ( \(AcceptanceCounts as rs) ->- if as + rs == 0- then Nothing- else Just $ fromIntegral as / fromIntegral (as + rs)- )- . fromAcceptance+acceptanceRates :: Acceptances k -> M.Map k (Maybe AcceptanceRate)+acceptanceRates = M.map getRate . fromAcceptances+ where+ getRate (A (AcceptanceCounts as rs) _) =+ let s = as + rs+ in if s <= 0+ then Nothing+ else Just $ fromIntegral as / fromIntegral s
src/Mcmc/Algorithm.hs view
@@ -16,6 +16,7 @@ import qualified Data.ByteString.Lazy.Char8 as BL import Data.Time+import Mcmc.Acceptance import Mcmc.Cycle import Mcmc.Proposal import Mcmc.Settings@@ -42,7 +43,7 @@ aAutoTune :: TuningType -> Int -> a -> IO a -- | Reset acceptance counts.- aResetAcceptance :: a -> a+ aResetAcceptance :: ResetAcceptance -> a -> a -- | Clean after burn in. In particular, this is used to reduce the length of -- the trace, if required.
src/Mcmc/Algorithm/MC3.hs view
@@ -127,7 +127,7 @@ savedMC3Chains :: V.Vector (SavedChain a), savedMC3ReciprocalTemperatures :: ReciprocalTemperatures, savedMC3Iteration :: Int,- savedMC3SwapAcceptance :: Acceptance Int,+ savedMC3SwapAcceptance :: Acceptances Int, savedMC3Generator :: (Word64, Word64) } deriving (Eq, Show)@@ -172,7 +172,7 @@ -- | Current iteration. mc3Iteration :: Int, -- | Number of accepted and rejected swaps.- mc3SwapAcceptance :: Acceptance Int,+ mc3SwapAcceptances :: Acceptances Int, mc3Generator :: IOGenM StdGen } @@ -252,7 +252,7 @@ initMHG prf lhf i beta a | i < 0 = error "initMHG: Chain index negative." -- Do nothing for the cold chain.- | i == 0 = return $ MHG $ c+ | i == 0 = return $ MHG c | 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@@ -411,11 +411,11 @@ -- traceIO $ "Log priors (left, right, after swap): " <> show (ln prL') <> " " <> show (ln prR') -- traceIO $ "Log likelihoods (left, right, before swap): " <> show (ln lhL) <> " " <> show (ln lhR) -- traceIO $ "Log likelihood (left, right, after swap): " <> show (ln lhL') <> " " <> show (ln lhR')- let !ac' = pushAccept i (mc3SwapAcceptance a)- return $ a {mc3MHGChains = y, mc3SwapAcceptance = ac'}+ let !ac' = pushAccept Nothing i (mc3SwapAcceptances a)+ return $ a {mc3MHGChains = y, mc3SwapAcceptances = ac'} else do- let !ac' = pushReject i (mc3SwapAcceptance a)- return $ a {mc3SwapAcceptance = ac'}+ let !ac' = pushReject Nothing i (mc3SwapAcceptances a)+ return $ a {mc3SwapAcceptances = ac'} where g = mc3Generator a @@ -483,7 +483,7 @@ mhgs' <- V.mapM (aAutoTune b l) $ mc3MHGChains a -- 2. Auto tune temperatures. let optimalRate = getOptimalRate PDimensionUnknown- mCurrentRates = acceptanceRates $ mc3SwapAcceptance a+ mCurrentRates = acceptanceRates $ mc3SwapAcceptances 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@@ -512,15 +512,15 @@ (V.tail mhgs') return $ a {mc3MHGChains = mhgs'', mc3ReciprocalTemperatures = bs'} -mc3ResetAcceptance :: ToJSON a => MC3 a -> MC3 a-mc3ResetAcceptance a = a'+mc3ResetAcceptance :: ToJSON a => ResetAcceptance -> MC3 a -> MC3 a+mc3ResetAcceptance x a = a' where -- 1. Reset acceptance of all chains.- mhgs' = V.map aResetAcceptance (mc3MHGChains a)+ mhgs' = V.map (aResetAcceptance x) (mc3MHGChains a) -- 2. Reset acceptance of swaps.- ac' = resetA $ mc3SwapAcceptance a+ ac' = resetA x $ mc3SwapAcceptances a --- a' = a {mc3MHGChains = mhgs', mc3SwapAcceptance = ac'}+ a' = a {mc3MHGChains = mhgs', mc3SwapAcceptances = ac'} mc3CleanAfterBurnIn :: ToJSON a => TraceLength -> MC3 a -> IO (MC3 a) mc3CleanAfterBurnIn tl a = do@@ -573,14 +573,14 @@ -- Acceptance rates may be 'Nothing' when no proposals have been undertaken. -- The 'sequence' operations pull the 'Nothing's out of the inner -- structures.- as = sequence $ V.map (sequence . acceptanceRates . acceptance) cs+ as = sequence $ V.map (sequence . acceptanceRates . acceptances) cs mVecAr = V.map (\mp -> sum mp / fromIntegral (length mp)) <$> as mAr = (\vec -> V.sum vec / fromIntegral (V.length vec)) <$> mVecAr bs = mc3ReciprocalTemperatures a bsB = map (BB.toLazyByteString . BB.formatDouble (BB.standard 2)) $ U.toList bs swapPeriod = fromSwapPeriod $ mc3SwapPeriod $ mc3Settings a swapPeriodB = BB.toLazyByteString $ BB.intDec swapPeriod- swapAcceptance = mc3SwapAcceptance a+ swapAcceptance = mc3SwapAcceptances a n = fromNChains $ mc3NChains $ mc3Settings a proposalHLine = BL.replicate (BL.length proposalHeader) '-'
src/Mcmc/Algorithm/MHG.hs view
@@ -233,7 +233,7 @@ mhgPropose :: MHG a -> Proposal a -> IO (MHG a) mhgPropose (MHG c) p = do -- 1. Sample new state.- !(pres, mcs) <- liftIO $ s x g+ (!pres, !mcs) <- liftIO $ s x g -- 2. Define new prior and likelihood calculation functions. Avoid actual -- calculation of the values. --@@ -242,15 +242,11 @@ -- https://stackoverflow.com/a/46603680/3536806. let calcPrLh y = (pF y, lF y) `using` parTuple2 rdeepseq rdeepseq accept y pr lh =- let !ac' = case mcs of- Nothing -> pushAccept p ac- Just cs -> pushAcceptanceCounts p cs ac- in pure $ MHG $ c {link = Link y pr lh, acceptance = ac'}+ let !ac' = pushAccept mcs p ac+ in pure $ MHG $ c {link = Link y pr lh, acceptances = ac'} reject =- let !ac' = case mcs of- Nothing -> pushReject p ac- Just cs -> pushAcceptanceCounts p cs ac- in pure $ MHG $ c {acceptance = ac'}+ let !ac' = pushReject mcs p ac+ in pure $ MHG $ c {acceptances = ac'} -- 3. Accept or reject. -- -- 3a. When rejection is inevitable, avoid calculation of the prior, the@@ -274,7 +270,7 @@ (Link x pX lX) = link c pF = priorFunction c lF = likelihoodFunction c- ac = acceptance c+ ac = acceptances c g = generator c mhgPush :: MHG a -> IO (MHG a)@@ -314,21 +310,31 @@ g = generator c mhgAutoTune :: TuningType -> Int -> MHG a -> IO (MHG a)-mhgAutoTune b n (MHG c) = do- mxs <-- if ccRequireTrace cc- then Just . VB.map state <$> takeT n tr- else pure Nothing- return $ MHG $ c {cycle = autoTuneCycle b ac mxs cc}+mhgAutoTune tt n (MHG c)+ | isIntermediate =+ pure . MHG $+ if ccHasIntermediateTuners cc+ then -- Do not provide trace when tuning intermediately.+ c {cycle = autoTuneCycle tt ac Nothing cc}+ else -- Skip intermediate tuning completely when unnecessary.+ c+ | otherwise = do+ mxs <-+ -- Provide the trace if required.+ if ccRequireTrace cc+ then Just . VB.map state <$> takeT n tr+ else pure Nothing+ pure $ MHG c {cycle = autoTuneCycle tt ac mxs cc} where- ac = acceptance c+ isIntermediate = tt == IntermediateTuningFastProposalsOnly || tt == IntermediateTuningAllProposals+ ac = acceptances c cc = cycle c tr = trace c -mhgResetAcceptance :: MHG a -> MHG a-mhgResetAcceptance (MHG c) = MHG $ c {acceptance = resetA ac}+mhgResetAcceptance :: ResetAcceptance -> MHG a -> MHG a+mhgResetAcceptance a (MHG c) = MHG $ c {acceptances = resetA a ac} where- ac = acceptance c+ ac = acceptances c mhgCleanAfterBurnIn :: TraceLength -> MHG a -> IO (MHG a) mhgCleanAfterBurnIn tl (MHG c) = do@@ -346,7 +352,7 @@ mhgSummarizeCycle m (MHG c) = summarizeCycle m ac cc where cc = cycle c- ac = acceptance c+ ac = acceptances c mhgOpenMonitors :: AnalysisName ->
src/Mcmc/Chain/Chain.hs view
@@ -92,10 +92,9 @@ -- | 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),+ -- | For each 'Proposal', store actual acceptance counts and for some+ -- proposals also expected acceptance rates.+ acceptances :: Acceptances (Proposal a), -- | The random number generator. generator :: IOGenM StdGen, --
src/Mcmc/Chain/Save.hs view
@@ -51,7 +51,7 @@ { savedLink :: Link a, savedIteration :: Int, savedTrace :: C.Stack VB.Vector (Link a),- savedAcceptance :: Acceptance Int,+ savedAcceptances :: Acceptances Int, savedSeed :: (Word64, Word64), savedTuningParameters :: [Maybe (TuningParameter, AuxiliaryTuningParameters)] }@@ -109,18 +109,18 @@ "fromSave: Given likelihood:" <> show (lh $ state it) <> "." ] in error msg- | length (fromAcceptance ac) /= length (ccProposals cc) =+ | length (fromAcceptances ac) /= length (ccProposals cc) = let msg = unlines [ "fromSave: The number of proposals does not match.",- "fromSave: Number of saved proposals:" <> show (length $ fromAcceptance ac) <> ".",+ "fromSave: Number of saved proposals:" <> show (length $ fromAcceptances ac) <> ".", "fromSave: Number of given proposals:" <> show (length $ ccProposals cc) <> "." ] in error msg | otherwise = fromSavedChainUnsafe pr lh cc mn sv where it = savedLink sv- ac = savedAcceptance sv+ ac = savedAcceptances sv -- | See 'fromSavedChain' but do not perform sanity checks. Useful when -- restarting a run with changed prior function, likelihood function or
src/Mcmc/Cycle.hs view
@@ -15,7 +15,7 @@ module Mcmc.Cycle ( -- * Cycles Order (..),- Cycle (ccProposals, ccRequireTrace),+ Cycle (ccProposals, ccRequireTrace, ccHasIntermediateTuners), cycleFromList, setOrder, IterationMode (..),@@ -27,10 +27,12 @@ ) where +import Control.Applicative import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Lazy.Char8 as BL import Data.List import qualified Data.Map.Strict as M+import Data.Maybe import qualified Data.Vector as VB import Mcmc.Acceptance import Mcmc.Internal.Shuffle@@ -91,7 +93,10 @@ { ccProposals :: [Proposal a], ccOrder :: Order, -- | Does the cycle require the trace when auto tuning? See 'tRequireTrace'.- ccRequireTrace :: Bool+ ccRequireTrace :: Bool,+ -- | Does the cycle include proposals that can be tuned every iterations?+ -- See 'tSuitableForIntermediateTuning'.+ ccHasIntermediateTuners :: Bool } -- | Create a 'Cycle' from a list of 'Proposal's; use 'RandomO', but see 'setOrder'.@@ -100,7 +105,7 @@ error "cycleFromList: Received an empty list but cannot create an empty Cycle." cycleFromList xs = if length uniqueXs == length xs- then Cycle xs RandomO (any needsTrace xs)+ then Cycle xs RandomO (any needsTrace xs) (any isIntermediate xs) else error $ "\n" ++ msg ++ "cycleFromList: Proposals are not unique." where uniqueXs = nub xs@@ -109,9 +114,8 @@ removedDescriptions = map (show . prDescription) removedXs removedMsgs = zipWith (\n d -> n ++ " " ++ d) removedNames removedDescriptions msg = unlines removedMsgs- needsTrace p = case prTuner p of- Nothing -> False- Just t -> tRequireTrace t+ needsTrace p = maybe False tRequireTrace (prTuner p)+ isIntermediate p = maybe False tSuitableForIntermediateTuning (prTuner p) -- | Set the order of 'Proposal's in a 'Cycle'. setOrder :: Order -> Cycle a -> Cycle a@@ -123,9 +127,13 @@ -- | Replicate 'Proposal's according to their weights and possibly shuffle them. prepareProposals :: StatefulGen g m => IterationMode -> Cycle a -> g -> m [Proposal a]-prepareProposals m (Cycle xs o _) g =+prepareProposals m (Cycle xs o _ _) g = if null ps- then error "prepareProposals: No proposals found."+ then+ let msg = case m of+ FastProposals -> "no fast proposals found"+ AllProposals -> "no proposals found"+ in error $ "prepareProposals: " <> msg else case o of RandomO -> shuffle ps g SequentialO -> return ps@@ -146,7 +154,7 @@ -- The number of proposals depends on the order. getNProposalsPerCycle :: IterationMode -> Cycle a -> Int-getNProposalsPerCycle m (Cycle xs o _) = case o of+getNProposalsPerCycle m (Cycle xs o _ _) = case o of RandomO -> once SequentialO -> once RandomReversibleO -> 2 * once@@ -160,40 +168,61 @@ -- See 'tuneWithTuningParameters' and 'Tuner'. tuneWithChainParameters :: TuningType ->- AcceptanceRate ->+ Maybe AcceptanceRate -> Maybe (VB.Vector a) -> Proposal a -> Either String (Proposal a)-tuneWithChainParameters b ar mxs p = case prTuner p of+tuneWithChainParameters tt mar mxs p = case prTuner p of Nothing -> Right p- Just (Tuner t ts rt fT _) -> case (rt, mxs) of- (True, Nothing) -> error "tuneWithChainParameters: trace required"- _ ->- let (t', ts') = fT b d ar mxs (t, ts)- in tuneWithTuningParameters t' ts' p- where- d = prDimension p+ Just (Tuner t ts rt it fT _) -> case (tt, it, prSpeed p) of+ (IntermediateTuningFastProposalsOnly, True, PFast) -> tuneIntermediate+ (IntermediateTuningAllProposals, True, _) -> tuneIntermediate+ (NormalTuningFastProposalsOnly, _, PFast) -> tuneNormally+ (NormalTuningAllProposals, _, _) -> tuneNormally+ (LastTuningFastProposalsOnly, _, _) -> tuneNormally+ (LastTuningAllProposals, _, _) -> tuneNormally+ _ -> Right p+ where+ hasTrace = isJust mxs+ err m = Left $ "tuneWithChainParameters: " <> m+ tuneIntermediate =+ if hasTrace+ then err "intermediate tuning but trace provided"+ else tune+ tuneNormally =+ if rt && not hasTrace+ then err "trace required"+ else tune+ tune =+ let (t', ts') = fT tt (prDimension p) mar mxs (t, ts)+ in tuneWithTuningParameters t' ts' p +-- (_, False, Just _) ->+-- (True, _, Just _) ->+-- (False, True, Nothing) ->+-- _ ->+ -- | Calculate acceptance rates and auto tunes the 'Proposal's in the 'Cycle'. -- -- Do not change 'Proposal's that are not tuneable.-autoTuneCycle :: TuningType -> Acceptance (Proposal a) -> Maybe (VB.Vector a) -> Cycle a -> Cycle a-autoTuneCycle b a mxs c = case (ccRequireTrace c, mxs) of- (False, Just _) -> error "autoTuneCycle: trace not required"- (True, Nothing) -> error "autoTuneCycle: trace required"- _ ->- if sort (M.keys ar) == sort ps- then c {ccProposals = map tuneF ps}- else error "autoTuneCycle: Proposals in map and cycle do not match."- where- ar = acceptanceRates a- ps = ccProposals c- tuneF p = case ar M.!? p of- Just (Just x) -> either error id $ tuneWithChainParameters b x mxs p- _ -> p+autoTuneCycle :: TuningType -> Acceptances (Proposal a) -> Maybe (VB.Vector a) -> Cycle a -> Cycle a+autoTuneCycle tt a mxs c+ | isJust mxs && not (ccRequireTrace c) = err "trace provided but not required"+ | otherwise =+ if sort (M.keys $ fromAcceptances a) == sort ps+ then c {ccProposals = map tuneF ps}+ else err "proposals in map and cycle do not match"+ where+ err msg = error $ "autoTuneCycle: " <> msg+ ps = ccProposals c+ tuneF p =+ let (_, _, mar, mtr) = acceptanceRate p a+ -- Favor the expected rate, if available.+ mr = mtr <|> mar+ in either error id $ tuneWithChainParameters tt mr mxs p -- | Summarize the 'Proposal's in the 'Cycle'. Also report acceptance rates.-summarizeCycle :: IterationMode -> Acceptance (Proposal a) -> Cycle a -> BL.ByteString+summarizeCycle :: IterationMode -> Acceptances (Proposal a) -> Cycle a -> BL.ByteString summarizeCycle m a c = BL.intercalate "\n" $ [ "Summary of proposal(s) in cycle.",
src/Mcmc/Internal/SpecFunctions.hs view
@@ -35,7 +35,7 @@ -- 'Numeric.SpecFunctions.logGamma'. logGammaG :: (Typeable a, RealFloat a) => a -> a logGammaG z- | typeOf z == typeOf (0 :: Double) = unsafeCoerce logGamma z+ | typeOf z == typeRep (Proxy :: Proxy Double) = unsafeCoerce logGamma z | otherwise = logGammaNonDouble z {-# SPECIALIZE logGammaG :: Double -> Double #-} @@ -214,7 +214,7 @@ -- 'Numeric.SpecFunctions.logFactorial'. logFactorialG :: forall a b. (Integral a, RealFloat b, Typeable b) => a -> b logFactorialG n- | typeOf (undefined :: b) == typeOf (0 :: Double) = unsafeCoerce $ logFactorial n+ | typeRep (Proxy :: Proxy b) == typeRep (Proxy :: Proxy Double) = unsafeCoerce $ logFactorial n | otherwise = logFactorialNonDouble n {-# SPECIALIZE logFactorialG :: Int -> Double #-}
src/Mcmc/MarginalLikelihood.hs view
@@ -147,12 +147,12 @@ sampleAtPoint x ss lhf a = do a'' <- liftIO $ mcmc ss' a' let ch'' = fromMHG a''- ac = acceptance ch''+ ac = acceptances ch'' mAr = sequence $ acceptanceRates ac logDebugB "sampleAtPoint: Summarize cycle." logDebugB $ summarizeCycle AllProposals ac $ cycle ch'' case mAr of- Nothing -> logWarnB "Some acceptance rates are unavailable. The tuning period may be too small."+ Nothing -> logWarnB "Some acceptance rates are unavailable." Just ar -> do unless (M.null $ M.filter (<= 0.1) ar) $ logWarnB "Some acceptance rates are below 0.1." unless (M.null $ M.filter (>= 0.9) ar) $ logWarnB "Some acceptance rates are above 0.9."
src/Mcmc/Mcmc.hs view
@@ -25,11 +25,13 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Reader+import Data.Functor+import Mcmc.Acceptance (ResetAcceptance (ResetEverything, ResetExpectedRatesOnly)) import Mcmc.Algorithm import Mcmc.Cycle import Mcmc.Environment import Mcmc.Logger-import Mcmc.Proposal (TuningType (LastTuningStep, NormalTuningStep))+import Mcmc.Proposal import Mcmc.Settings import System.IO import Prelude hiding (cycle)@@ -52,7 +54,7 @@ mcmcResetAcceptance :: Algorithm a => a -> MCMC a mcmcResetAcceptance a = do logDebugB "Reset acceptance rates."- return $ aResetAcceptance a+ return $ aResetAcceptance ResetEverything a mcmcExceptionHandler :: Algorithm a => Environment Settings -> a -> AsyncException -> IO b mcmcExceptionHandler e a err = do@@ -77,8 +79,16 @@ mStdLog <- liftIO $ aExecuteMonitors vb t0 iTotal a forM_ mStdLog (logOutB " ") -mcmcIterate :: Algorithm a => IterationMode -> Int -> a -> MCMC a-mcmcIterate m n a+-- When intermediate tuning is activated, specific proposals get tuned every+-- iterations.+data IntermediateTuningSpec+ = IntermediateTuningFastProposalsOnlyOn+ | IntermediateTuningAllProposalsOn+ | IntermediateTuningOff+ deriving (Eq)++mcmcIterate :: Algorithm a => IntermediateTuningSpec -> IterationMode -> Int -> a -> MCMC a+mcmcIterate t m n a | n < 0 = error "mcmcIterate: Number of iterations is negative." | n == 0 = return a | otherwise = do@@ -87,7 +97,20 @@ -- NOTE: Handle interrupts during iterations, before writing monitors, -- using the old algorithm state @a@. let handlerOld = mcmcExceptionHandler e a- actionIterate = aIterate m p a+ maybeIntermediateAutoTune x =+ -- Do not perform intermediate tuning at the last step, because a+ -- normal tuning will be performed.+ case t of+ IntermediateTuningFastProposalsOnlyOn+ | n > 1 ->+ aAutoTune IntermediateTuningFastProposalsOnly 1 x+ <&> aResetAcceptance ResetExpectedRatesOnly+ IntermediateTuningAllProposalsOn+ | n > 1 ->+ aAutoTune IntermediateTuningAllProposals 1 x+ <&> aResetAcceptance ResetExpectedRatesOnly+ _ -> pure x+ actionIterate = aIterate m p a >>= maybeIntermediateAutoTune a' <- liftIO $ actionIterate `catch` handlerOld -- NOTE: Mask asynchronous exceptions while writing monitor files. Handle -- interrupts after writing monitors; use the new state @a'@.@@ -100,8 +123,8 @@ -- recover from partly written monitor files. let handlerNew = mcmcExceptionHandler e a' actionWrite = runReaderT (mcmcExecuteMonitors a') e- liftIO $ (uninterruptibleMask_ actionWrite) `catch` handlerNew- mcmcIterate m (n - 1) a'+ liftIO $ uninterruptibleMask_ actionWrite `catch` handlerNew+ mcmcIterate t m (n - 1) a' mcmcNewRun :: Algorithm a => a -> MCMC a mcmcNewRun a = do@@ -112,15 +135,15 @@ mcmcExecuteMonitors a when (aIsInvalidState a) (logWarnB "The initial state is invalid!") a' <- mcmcBurnIn a- logInfoS $ "Cleaning chain after burn in."+ logInfoS "Cleaning chain after burn in." let tl = sTraceLength s a'' <- liftIO $ aCleanAfterBurnIn tl a'- logInfoS $ "Saving chain after burn in."+ logInfoS "Saving chain after burn in." mcmcSave a'' let i = fromIterations $ sIterations s logInfoS $ "Running chain for " ++ show i ++ " iterations." logInfoB $ aStdMonitorHeader a''- mcmcIterate AllProposals i a''+ mcmcIterate IntermediateTuningOff AllProposals i a'' mcmcContinueRun :: Algorithm a => a -> MCMC a mcmcContinueRun a = do@@ -139,7 +162,7 @@ logInfoB $ aSummarizeCycle AllProposals a logInfoS $ "Running chain for " ++ show di ++ " iterations." logInfoB $ aStdMonitorHeader a- mcmcIterate AllProposals di a+ mcmcIterate IntermediateTuningOff AllProposals di a mcmcBurnIn :: Algorithm a => a -> MCMC a mcmcBurnIn a = do@@ -154,7 +177,7 @@ logInfoS $ "Burning in for " <> show n <> " iterations." logInfoS "Auto tuning is disabled." logInfoB $ aStdMonitorHeader a- a' <- mcmcIterate AllProposals n a+ a' <- mcmcIterate IntermediateTuningOff AllProposals n a logInfoB $ aSummarizeCycle AllProposals a' a'' <- mcmcResetAcceptance a' logInfoB "Burn in finished."@@ -191,25 +214,34 @@ -- Auto tune the proposals. mcmcAutotune :: Algorithm a => TuningType -> Int -> a -> MCMC a-mcmcAutotune NormalTuningStep n a = do- logDebugB "Intermediate auto tune."- liftIO $ aAutoTune NormalTuningStep n a-mcmcAutotune LastTuningStep n a = do- logDebugB "Last auto tune."- liftIO $ aAutoTune LastTuningStep n a+mcmcAutotune t n a = do+ case t of+ NormalTuningFastProposalsOnly -> logDebugB "Normal auto tune; fast proposals only."+ IntermediateTuningFastProposalsOnly -> pure ()+ LastTuningFastProposalsOnly -> logDebugB "Last auto tune; fast proposals only."+ NormalTuningAllProposals -> logDebugB "Normal auto tune; all proposals."+ IntermediateTuningAllProposals -> pure ()+ LastTuningAllProposals -> logDebugB "Last auto tune; all proposals."+ liftIO $ aAutoTune t n a mcmcBurnInWithAutoTuning :: Algorithm a => IterationMode -> [Int] -> a -> MCMC a mcmcBurnInWithAutoTuning _ [] _ = error "mcmcBurnInWithAutoTuning: Empty list." mcmcBurnInWithAutoTuning m [x] a = do -- Last round.- a' <- mcmcIterate m x a- a'' <- mcmcAutotune LastTuningStep x a'+ let (tti, ttl) = case m of+ FastProposals -> (IntermediateTuningFastProposalsOnlyOn, LastTuningFastProposalsOnly)+ AllProposals -> (IntermediateTuningAllProposalsOn, LastTuningAllProposals)+ a' <- mcmcIterate tti m x a+ a'' <- mcmcAutotune ttl x a' logInfoB $ aSummarizeCycle m a'' logInfoS $ "Acceptance rates calculated over the last " <> show x <> " iterations." mcmcResetAcceptance a'' mcmcBurnInWithAutoTuning m (x : xs) a = do- a' <- mcmcIterate m x a- a'' <- mcmcAutotune NormalTuningStep x a'+ let (tti, ttn) = case m of+ FastProposals -> (IntermediateTuningFastProposalsOnlyOn, NormalTuningFastProposalsOnly)+ AllProposals -> (IntermediateTuningAllProposalsOn, NormalTuningAllProposals)+ a' <- mcmcIterate tti m x a+ a'' <- mcmcAutotune ttn x a' logDebugB $ aSummarizeCycle m a'' logDebugS $ "Acceptance rates calculated over the last " <> show x <> " iterations." logDebugB $ aStdMonitorHeader a''
src/Mcmc/Proposal.hs view
@@ -38,8 +38,6 @@ TuningFunction, AuxiliaryTuningParameters, tuningFunction,- tuningFunctionWithAux,- tuningFunctionOnlyAux, tuningParameterMin, tuningParameterMax, tuneWithTuningParameters,@@ -207,16 +205,14 @@ Propose !a !KernelRatio !Jacobian deriving (Show, Eq) --- TODO @Dominik (high, feature): Proposals should be aware of: Is this Burn in, or not?- -- | Simple proposal function without tuning information. -- -- Instruction about randomly moving from the current state to a new state, -- given some source of randomness. ----- Maybe report acceptance counts internal to the proposal (e.g., used by+-- Maybe report acceptance rates internal to the proposal (e.g., used by -- proposals based on Hamiltonian dynamics).-type PFunction a = a -> IOGenM StdGen -> IO (PResult a, Maybe AcceptanceCounts)+type PFunction a = a -> IOGenM StdGen -> IO (PResult a, Maybe AcceptanceRates) -- | Create a proposal with a single tuning parameter. --@@ -243,7 +239,7 @@ where fT = tuningFunction g t _ = Right $ f t- tuner = Tuner 1.0 VU.empty False fT g+ tuner = Tuner 1.0 VU.empty False False fT g createProposal r f s d n w NoTune = Proposal n r s d w (f 1.0) Nothing @@ -253,6 +249,8 @@ tAuxiliaryTuningParameters :: AuxiliaryTuningParameters, -- | Does the tuner require the trace over the last tuning period? tRequireTrace :: Bool,+ -- | Can the tuner be used for intermediate tuning (see 'TuningType')?+ tSuitableForIntermediateTuning :: Bool, tTuningFunction :: TuningFunction a, -- | Given the tuning parameter, and the auxiliary tuning parameters, get -- the tuned propose function.@@ -275,16 +273,35 @@ -- expected acceptance rate; and vice versa. type TuningParameter = Double --- | The last tuning step may be special.-data TuningType = NormalTuningStep | LastTuningStep+-- | Tuning type. To distinguish between fast and slow proposals, see+-- 'Mcmc.Cycle.IterationMode'.+data TuningType+ = -- | Normal tuning step with fast proposals only.+ NormalTuningFastProposalsOnly+ | -- | Intermediate tuning step executed after each iteration with fast+ -- proposals only. Only suitable for proposals which can calculate expected+ -- acceptance rates.+ IntermediateTuningFastProposalsOnly+ | -- | The last tuning step with fast proposals only may be special.+ LastTuningFastProposalsOnly+ | -- | Normal tuning step of all proposals.+ NormalTuningAllProposals+ | -- | Intermediate tuning step of all proposals.+ IntermediateTuningAllProposals+ | -- | The last tuning step with all proposals.+ LastTuningAllProposals+ deriving (Eq) -- | Compute new tuning parameters. type TuningFunction a = TuningType -> PDimension ->- -- | Acceptance rate of last tuning period.- AcceptanceRate ->- -- | Trace of last tuning period. Only available when requested by proposal.+ -- | Acceptance rate of last tuning period. May not always be available+ -- because proposals may be skipped.+ Maybe AcceptanceRate ->+ -- | Trace of last tuning period. Not available for intermediate tuning' steps+ -- (see 'TuningType'), and only available for other tuning types when+ -- requested by proposal. Maybe (VB.Vector a) -> (TuningParameter, AuxiliaryTuningParameters) -> (TuningParameter, AuxiliaryTuningParameters)@@ -301,27 +318,15 @@ -- | Default tuning function. ----- The default tuning function only uses the acceptance rate. In particular, it--- does not handle auxiliary tuning parameters and ignores the actual samples--- attained during the last tuning period.+-- The default tuning function only uses the actual acceptance rate. In+-- particular, it does not handle auxiliary tuning parameters, ignores+-- intermediate tuning steps, and ignores the actual samples attained during the+-- last tuning period. tuningFunction :: TuningFunction a-tuningFunction _ d r _ (!t, !ts) = bimap (tuningFunctionSimple d r) id (t, ts)---- | Also tune auxiliary tuning parameters.-tuningFunctionWithAux ::- -- | Auxiliary tuning function.- (TuningType -> VB.Vector a -> AuxiliaryTuningParameters -> AuxiliaryTuningParameters) ->- TuningFunction a-tuningFunctionWithAux _ _ _ _ Nothing _ = error "tuningFunctionWithAux: empty trace"-tuningFunctionWithAux f b d r (Just xs) (!t, !ts) = bimap (tuningFunctionSimple d r) (f b xs) (t, ts)---- | Only tune auxiliary tuning parameters.-tuningFunctionOnlyAux ::- -- | Auxiliary tuning function.- (TuningType -> VB.Vector a -> AuxiliaryTuningParameters -> AuxiliaryTuningParameters) ->- TuningFunction a-tuningFunctionOnlyAux _ _ _ _ Nothing _ = error "tuningFunctionOnlyAux: empty trace"-tuningFunctionOnlyAux f b _ _ (Just xs) (!t, !ts) = bimap id (f b xs) (t, ts)+tuningFunction IntermediateTuningFastProposalsOnly _ _ _ t = t+tuningFunction IntermediateTuningAllProposals _ _ _ t = t+tuningFunction _ _ Nothing _ t = t+tuningFunction _ d (Just r) _ (!t, !ts) = first (tuningFunctionSimple d r) (t, ts) -- IDEA: Per proposal type tuning parameter boundaries. For example, a sliding -- proposal with a large tuning parameter is not a problem. But then, if the@@ -330,7 +335,7 @@ -- | Minimal tuning parameter; subject to change. tuningParameterMin :: TuningParameter-tuningParameterMin = 1e-5+tuningParameterMin = 1e-6 -- | Maximal tuning parameter; subject to change. tuningParameterMax :: TuningParameter@@ -358,14 +363,14 @@ Either String (Proposal a) tuneWithTuningParameters t ts p = case prTuner p of Nothing -> Left "tuneWithTuningParameters: Proposal is not tunable."- Just (Tuner _ _ nt fT g) ->+ Just (Tuner _ _ reqTr inTn fT g) -> -- Ensure that the tuning parameter is strictly positive and well bounded. let t' = max tuningParameterMin t t'' = min tuningParameterMax t' psE = g t'' ts in case psE of Left err -> Left $ "tune: " <> err- Right ps -> Right $ p {prFunction = ps, prTuner = Just $ Tuner t'' ts nt fT g}+ Right ps -> Right $ p {prFunction = ps, prTuner = Just $ Tuner t'' ts reqTr inTn fT g} -- | See 'PDimension'. getOptimalRate :: PDimension -> Double@@ -431,7 +436,7 @@ -- Lift tuner from one data type to another. liftTunerWith :: JacobianFunction b -> Lens' b a -> Tuner a -> Tuner b-liftTunerWith jf l (Tuner p ps nt fP g) = Tuner p ps nt fP' g'+liftTunerWith jf l (Tuner p ps reqTr inTn fP g) = Tuner p ps reqTr inTn fP' g' where fP' b d r = fP b d r . fmap (VB.map (^. l)) g' x xs = liftPFunctionWith jf l <$> g x xs@@ -455,14 +460,15 @@ BL.ByteString -> BL.ByteString -> BL.ByteString-renderRow name ptype weight nAccept nReject acceptRate optimalRate tuneParam manualAdjustment = nm <> pt <> wt <> na <> nr <> ra <> ro <> tp <> mt+renderRow name ptype weight nAccept nReject acceptRateActual 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 14 nAccept nr = alignRight 14 nReject- ra = alignRight 14 acceptRate+ ra = alignRight 14 acceptRateActual ro = alignRight 14 optimalRate tp = alignRight 20 tuneParam mt = alignRight 30 manualAdjustment@@ -476,7 +482,7 @@ "Weight" "Accepted" "Rejected"- "Rate"+ "Actual rate" "Optimal rate" "Tuning parameter" "Consider manual adjustment"@@ -488,7 +494,7 @@ PWeight -> Maybe TuningParameter -> PDimension ->- Maybe (Int, Int, Double) ->+ (Int, Int, Maybe Double, Maybe Double) -> BL.ByteString summarizeProposal name description weight tuningParameter dimension ar = renderRow@@ -497,18 +503,19 @@ weightStr nAccept nReject- acceptRate+ acceptRateActual+ -- acceptRateExpected optimalRate tuneParamStr manualAdjustmentStr where fN n = BB.formatDouble (BB.standard n) weightStr = BB.toLazyByteString $ BB.intDec $ fromPWeight weight- nAccept = BB.toLazyByteString $ maybe "" (BB.intDec . (^. _1)) ar- nReject = BB.toLazyByteString $ maybe "" (BB.intDec . (^. _2)) ar- acceptRate = BB.toLazyByteString $ maybe "" (fN 2 . (^. _3)) ar+ nAccept = BB.toLazyByteString $ BB.intDec $ ar ^. _1+ nReject = BB.toLazyByteString $ BB.intDec $ ar ^. _2+ acceptRateActual = BB.toLazyByteString $ maybe "" (fN 2) (ar ^. _3) optimalRate = BB.toLazyByteString $ fN 2 $ getOptimalRate dimension- tuneParamStr = BB.toLazyByteString $ maybe "" (fN 4) tuningParameter+ tuneParamStr = BB.toLazyByteString $ maybe "" (fN 6) tuningParameter checkRate rate | rate < rateMin = Just "rate too low" | rate > rateMax = Just "rate too high"@@ -518,7 +525,8 @@ | tp >= (0.9 * tuningParameterMax) = Just "tuning parameter too high" | otherwise = Nothing tps = checkTuningParam =<< tuningParameter- ars = (checkRate . (^. _3)) =<< ar+ -- Use actual acceptance rate.+ ars = checkRate =<< (ar ^. _3) manualAdjustmentStr = let in case (ars, tps) of
src/Mcmc/Proposal/Generic.hs view
@@ -19,26 +19,25 @@ import Numeric.Log import Statistics.Distribution --- | Generic function to create proposals for continuous parameters (e.g.,--- 'Double').+-- | Generic function to create proposals using a continuous auxiliary variable+-- of type 'Double'. -- -- The procedure is as follows: Let \(\mathbb{X}\) be the state space and \(x\) -- be the current state. -- -- 1. Let \(D\) be a continuous probability distribution on \(\mathbb{D}\);--- sample an auxiliary variable \(epsilon \sim D\).+-- sample an auxiliary variable \(u \sim D\). ----- 2. Suppose \(\odot : \mathbb{X} \times \mathbb{D} \to \mathbb{X}\). PFunction a--- new state \(x' = x \odot \epsilon\).+-- 2. Let \(\odot : \mathbb{X} \times \mathbb{D} \to \mathbb{X}\). Propose a+-- new state \(x' = x \odot u\). ----- 3. If the proposal is unbiased, the Metropolis-Hastings-Green ratio can--- directly be calculated using the posterior function.+-- If the proposal is unbiased, the Metropolis-Hastings-Green ratio can directly+-- be calculated using the posterior function. ----- 4. However, if the proposal is biased: Suppose \(g : \mathbb{D} \to--- \mathbb{D}\) inverses the auxiliary variable \(\epsilon\) such that \(x =--- x' \odot g(\epsilon)\). Calculate the Metropolis-Hastings-Green ratio--- using the posterior function, \(g\), \(D\), \(\epsilon\), and possibly a--- Jacobian function.+-- However, if the proposal is biased: Let \(g : \mathbb{D} \to \mathbb{D}\);+-- \(g\) inverses the auxiliary variable \(u\) such that \(x = x' \odot g(u)\).+-- Calculate the Metropolis-Hastings-Green ratio using the posterior function,+-- \(g\), \(D\), \(u\), and possibly a Jacobian function. genericContinuous :: (ContDistr d, ContGen d) => -- | Probability distribution@@ -46,12 +45,12 @@ -- | Forward operator \(\odot\). -- -- For example, for a multiplicative proposal on one variable the forward- -- operator is @(*)@, so that @x * u = y@.+ -- operator is @(*)@, so that \(x' = x * u\). (a -> Double -> a) -> -- | Inverse operator \(g\) of the auxiliary variable. -- -- For example, 'recip' for a multiplicative proposal on one variable, since- -- @y * (recip u) = x * u * (recip u) = x@.+ -- \(x' * u^{-1} = x * u * u^{-1} = x\). -- -- Required for biased proposals. Maybe (Double -> Double) ->@@ -86,7 +85,8 @@ pure (Propose (x `f` u) r j, Nothing) {-# INLINEABLE genericContinuous #-} --- | Generic function to create proposals for discrete parameters (e.g., 'Int').+-- | Generic function to create proposals using a discrete auxiliary variable of+-- type 'Int'. -- -- See 'genericContinuous'. genericDiscrete ::@@ -95,11 +95,11 @@ d -> -- | Forward operator. --- -- For example, (+), so that x + dx = x'.+ -- For example, (+), so that \(x + dx = x'\). (a -> Int -> a) -> -- | Inverse operator \(g\) of the auxiliary variable. --- -- For example, 'negate', so that x' + (negate dx) = x.+ -- For example, 'negate', so that \(x' - dx = x + dx - dx = x\). -- -- Only required for biased proposals. Maybe (Int -> Int) ->
src/Mcmc/Proposal/Hamiltonian/Common.hs view
@@ -59,7 +59,8 @@ import qualified Numeric.LinearAlgebra as L -- NOTE: Implementing the Riemannian adaptation (state-dependent mass matrix).--- seems a little bit of an overkill.+-- seems a little bit of an overkill. See also+-- https://discourse.mc-stan.org/t/riemann-manifold-hmc-in-stan/19466/5. -- | The Hamiltonian proposal acts on a vector of floating point values referred -- to as positions.
src/Mcmc/Proposal/Hamiltonian/Hamiltonian.hs view
@@ -108,14 +108,6 @@ hParamsI <- fromAuxiliaryTuningParameters d ts pure $ hamiltonianPFunction hParamsI hstruct targetWith --- TODO @Dominik (high, issue): Acceptance counts. How to combine with values--- reported here and from the NUTS sampler.---- TODO @Dominik (high, feature): The expected acceptance counts should not be--- calculated after burn in. Rather, the actual acceptance counts should be--- reported. For this to work, the proposal needs to know if it is in "burn in--- phase" or not.- -- The inverted covariance matrix and the log determinant of the covariance -- matrix are calculated by 'hamiltonianPFunction'. hamiltonianPFunction ::@@ -130,11 +122,13 @@ -- of epsilon. I still think it should vary because otherwise, there may be -- dragons due to periodicity. let lM = la / eRan- lL = maximum [1 :: Int, floor $ 0.9 * lM]- lR = maximum [lL, ceiling $ 1.1 * lM]+ lL = max (1 :: Int) (floor $ 0.9 * lM)+ lR = max lL (ceiling $ 1.1 * lM) lRan <- uniformRM (lL, lR) g case leapfrog (targetWith x) msI lRan eRan q p of- Nothing -> pure (ForceReject, Just $ AcceptanceCounts 0 100)+ -- NOTE: I am not sure if it is correct to set the expected acceptance rate+ -- to 0 when the leapfrog integrator fails.+ Nothing -> pure (ForceReject, Just $ AcceptanceRates 0 1) -- Check if next state is accepted here, because the Jacobian is included in -- the target function. If not: pure (x, 0.0, 1.0). Just (q', p', prQ, prQ') -> do@@ -149,13 +143,9 @@ -- chain back to the previous state. However, we are only interested in -- the positions, and are not even storing the momenta. let pr = if accept then ForceAccept (fromVec x q') else ForceReject- ar = exp $ ln r- getCounts s = max 0 $ min 100 $ round $ s * 100- ac =- if ar >= 0- then let cs = getCounts ar in AcceptanceCounts cs (100 - cs)- else error $ "hamiltonianPFunction: Acceptance rate negative."- pure (pr, Just ac)+ -- Limit expected acceptance rate between 0 and 1.+ ar = max 0 $ min 1 (exp $ ln r)+ pure (pr, Just $ AcceptanceRates ar 1) where (HParamsI e la ms _ _ msI mus) = hparamsi (HStructure _ toVec fromVec) = hstruct@@ -204,7 +194,7 @@ tuner = do tfun <- hTuningFunctionWith dim toVec htconf let pfun = hamiltonianPFunctionWithTuningParameters dim hstruct targetWith- pure $ Tuner 1.0 ts True tfun pfun+ pure $ Tuner 1.0 ts True True tfun pfun in case checkHStructureWith (hpsMasses hParamsI) hstruct of Just err -> error err Nothing -> hamiltonianWith tuner
src/Mcmc/Proposal/Hamiltonian/Internal.hs view
@@ -55,6 +55,7 @@ import Control.Monad import Control.Monad.ST import Data.Foldable+import Data.Maybe import qualified Data.Vector.Storable as VS import qualified Data.Vector.Unboxed as VU import Mcmc.Proposal@@ -92,31 +93,48 @@ } deriving (Show) --- The default tuning parameters in [4] are:+-- The default tuning parameters in [4] which have been tweaked for tuning the+-- proposals after every iteration are: -- -- mu = log $ 10 * eps -- ga = 0.05 -- t0 = 10 -- ka = 0.75 ----- However, these default tuning parameters will be off, because the authors--- suggesting these values tune the proposal after every single iteration.+-- For reference, I used the following default parameters with longer auto+-- tuning intervals. ----- The following values are tweaked for our case, where tuning does not happen--- after each iteration. Of course, we could tune the leapfrog parameters after--- each generation. Even the mass parameters could be tuned each iteration when--- the masses are estimated from more past iterations spanning many tuning--- intervals.+-- mu = log $ 10 * eps+-- ga = 0.1+-- t0 = 3+-- ka = 0.5 ----- NOTE: In theory, these we could expose these internal tuning parameters to--- the user.+-- Another good resource:+-- https://mc-stan.org/docs/2_29/reference-manual/hmc-algorithm-parameters.html.+--+-- NOTE: In theory, we could expose these internal tuning parameters to the+-- user. tParamsFixedWith :: LeapfrogScalingFactor -> TParamsFixed tParamsFixedWith eps = TParamsFixed eps mu ga t0 ka where+ -- "Mu is a freely chosen point that the iterators are shrunk towards". I am+ -- not exactly sure what this means. The parameter does not seem to have+ -- much of an effect. mu = log $ 10 * eps- ga = 0.1- t0 = 3- ka = 0.5+ -- Gamma "controls the amount of shrinkage towards mu". The larger gamma is,+ -- the less variant epsilon is.+ --+ -- I changed this parameter from 0.05 to get better results in test runs.+ ga = 0.15+ -- "Free parameter that stabilizes the initial iterations". The larger t0+ -- is, the stabler epsilon is in the first iterations.+ t0 = 10+ -- "Setting the parameter ka < 1 allows us to give higher weight to more+ -- recent iterates and to more quickly forget the iterates produced during+ -- the early warmup stages."+ --+ -- I changed this parameter from 0.75 to get better results in test runs.+ ka = 0.75 -- All internal parameters. data HParamsI = HParamsI@@ -235,7 +253,7 @@ a :: Double a = if rI > 0.5 then 1 else (-1) go e r =- if r ** a > 2 ** (negate a)+ if r ** a > 2 ** negate a then case leapfrog t msI 1 e q p of Nothing -> e Just (_, p'', _, prQ'') ->@@ -257,38 +275,55 @@ (a -> Positions) -> HTuningConf -> Maybe (TuningFunction a)-hTuningFunctionWith n toVec (HTuningConf lc mc) = case (lc, mc) of- (HNoTuneLeapfrog, HNoTuneMasses) -> Nothing- (_, _) -> Just $- \tt pdim ar mxs (_, !ts) ->- case mxs of- Nothing -> error "hTuningFunctionWith: empty trace"- Just xs ->- let (HParamsI eps la ms tpv tpf msI mus) =- -- NOTE: Use error here, because a dimension mismatch is a serious bug.- either error id $ fromAuxiliaryTuningParameters n ts- (TParamsVar epsMean h m) = tpv- (TParamsFixed eps0 mu ga t0 ka) = tpf- (ms', msI') = case mc of- HNoTuneMasses -> (ms, msI)- HTuneDiagonalMassesOnly -> tuneDiagonalMassesOnly toVec xs (ms, msI)- HTuneAllMasses -> tuneAllMasses toVec xs (ms, msI)- (eps'', epsMean'', h'') = case lc of- HNoTuneLeapfrog -> (eps, epsMean, h)- HTuneLeapfrog ->- let delta = getOptimalRate pdim- c = recip $ m + t0- h' = (1.0 - c) * h + c * (delta - ar)- logEps' = mu - (sqrt m / ga) * h'- eps' = exp logEps'- mMKa = m ** (negate ka)- epsMean' = exp $ mMKa * logEps' + (1 - mMKa) * log epsMean- in (eps', epsMean', h')- eps''' = case tt of- NormalTuningStep -> eps''- LastTuningStep -> epsMean''- tpv' = TParamsVar epsMean'' h'' (m + 1.0)- in (eps''' / eps0, toAuxiliaryTuningParameters $ HParamsI eps''' la ms' tpv' tpf msI' mus)+hTuningFunctionWith _ _ (HTuningConf HNoTuneLeapfrog HNoTuneMasses) = Nothing+hTuningFunctionWith n toVec (HTuningConf lc mc) = Just $ \tt pdim mar mxs (_, !ts) ->+ case tt of+ IntermediateTuningFastProposalsOnly -> err "fast intermediate tuning step but slow proposal"+ NormalTuningFastProposalsOnly -> err "fast normal tuning step but slow proposal"+ _ ->+ let (HParamsI eps la ms tpv tpf msI mus) =+ -- NOTE: Use error here, because a dimension mismatch is a serious bug.+ either error id $ fromAuxiliaryTuningParameters n ts+ (TParamsVar epsMean h m) = tpv+ (TParamsFixed eps0 mu ga t0 ka) = tpf+ m' = SmoothingParameter $ round m+ (ms', msI') = case tt of+ IntermediateTuningAllProposals -> (ms, msI)+ _ ->+ let xs = fromMaybe (err "empty trace") mxs+ in case mc of+ HNoTuneMasses -> (ms, msI)+ HTuneDiagonalMassesOnly -> tuneDiagonalMassesOnly m' toVec xs (ms, msI)+ HTuneAllMasses -> tuneAllMasses m' toVec xs (ms, msI)+ (eps'', epsMean'', h'') = case tt of+ LastTuningFastProposalsOnly -> (eps, epsMean, h)+ _ -> case lc of+ HNoTuneLeapfrog -> (eps, epsMean, h)+ HTuneLeapfrog ->+ let ar = fromMaybe (err "no acceptance rate") mar+ delta = getOptimalRate pdim+ -- Algorithm 6; explained in Section 3.2.+ --+ -- Another good resource is the Tensorflow API+ -- documentation:+ -- https://www.tensorflow.org/probability/api_docs/python/tfp/mcmc/DualAveragingStepSizeAdaptation.+ --+ -- See also Nesterov (2007) Primal-dual subgradient methods+ -- for convex problems, Mathematical Programming.+ c = recip $ m + t0+ h' = (1.0 - c) * h + c * (delta - ar)+ eps' = exp $ mu - (sqrt m / ga) * h'+ mMKa = m ** negate ka+ -- Original formula is:+ -- epsMean' = exp $ mMKa * logEps' + (1 - mMKa) * log epsMean+ -- Which is the same as:+ epsMean' = (eps' ** mMKa) * (epsMean ** (1 - mMKa))+ epsF = if tt == LastTuningAllProposals then epsMean' else eps'+ in (epsF, epsMean', h')+ tpv' = TParamsVar epsMean'' h'' (m + 1.0)+ in (eps'' / eps0, toAuxiliaryTuningParameters $ HParamsI eps'' la ms' tpv' tpf msI' mus)+ where+ err msg = error $ "hTuningFunctionWith: " <> msg checkHStructureWith :: Foldable s => Masses -> HStructure s -> Maybe String checkHStructureWith ms (HStructure x toVec fromVec)@@ -361,7 +396,7 @@ else Nothing -- L-1 full steps for positions and momenta. This gives the positions q_{L-1}, -- and the momenta p_{L-1/2}.- (qLM1, pLM1Half) <- go (l - 1) $ Just $ (q, pHalf)+ (qLM1, pLM1Half) <- go (l - 1) $ Just (q, pHalf) -- The last full step of the positions. let qL = leapfrogStepPositions msI eps qLM1 pLM1Half -- The last half step of the momenta.@@ -379,7 +414,7 @@ let qs' = leapfrogStepPositions msI eps qs ps (x, ps') = leapfrogStepMomenta eps tF qs' p in if x > 0.0- then go (n - 1) $ Just $ (qs', ps')+ then go (n - 1) $ Just (qs', ps') else Nothing leapfrogStepMomenta ::
src/Mcmc/Proposal/Hamiltonian/Masses.hs view
@@ -21,6 +21,7 @@ massesToVector, -- * Tuning+ SmoothingParameter (..), tuneDiagonalMassesOnly, tuneAllMasses, )@@ -32,6 +33,7 @@ import qualified Data.Vector.Unboxed as VU import Mcmc.Proposal.Hamiltonian.Common import qualified Numeric.LinearAlgebra as L+import Numeric.Natural import qualified Statistics.Covariance as S import qualified Statistics.Function as S import qualified Statistics.Sample as S@@ -108,7 +110,7 @@ -- crucial. The Hamiltonian algorithms also work when the masses are off. cleanMatrix :: L.Matrix Double -> L.Matrix Double cleanMatrix xs =- (L.diag $ L.cmap cleanDiag xsDiag) + L.cmap cleanOffDiag xsOffDiag+ L.diag (L.cmap cleanDiag xsDiag) + L.cmap cleanOffDiag xsOffDiag where xsDiag = L.takeDiag xs cleanDiag x@@ -129,9 +131,9 @@ | n == 0 || m == 0 = error "getMassesI: Matrix empty." | n /= m = error "getMassesI: Matrix not square." | sign /= 1.0 = error "getMassesI: Determinant of matrix is negative."- | otherwise = toGMatrix $ cleanMatrix $ xsI+ | otherwise = toGMatrix $ cleanMatrix xsI where- xs' = L.unSym $ xs+ xs' = L.unSym xs n = L.rows xs' m = L.cols xs' (xsI, (_, sign)) = L.invlndet xs'@@ -179,20 +181,68 @@ getSampleSize :: VS.Vector Double -> Int getSampleSize = VS.length . VS.uniq . S.gsort -getNewMassDiagonalWithRescue :: Int -> Double -> Double -> Double-getNewMassDiagonalWithRescue sampleSize massOld massEstimate+rescueAfter :: Double -> Double+rescueAfter y+ | massMin > abs y = signum y * massMin+ | abs y > massMax = signum y * massMax+ | otherwise = y++rescueBefore :: (Double -> Double -> Double) -> Double -> Double -> Double+rescueBefore f old new+ -- Be permissive with NaN and infinite values.+ | isNaN new = old+ | isInfinite new = old+ | otherwise = f old new++rescue :: (Double -> Double -> Double) -> Double -> Double -> Double+rescue f old new = rescueAfter $ rescueBefore f old new++-- -- I do not know where I got this function from, but it works pretty well!+-- interpolate :: Double -> Double -> Double+-- interpolate old new = interSqrt ** 2+-- where+-- interSqrt = recip 3 * (sqrt old + 2 * sqrt new)++-- | This parameter plays the same role as @m@ in the dual averaging algorithm.+-- In the beginning, when @m@ is zero, the newly estimated masses will take full+-- precedence over the current masses. After some auto tuning steps, when @m@ is+-- larger, the newly estimated masses influence the current masses only+-- slightly.+newtype SmoothingParameter = SmoothingParameter Natural++-- Another interpolation function I came up with. It is pretty cool, because+-- (similar to above) it interpolates the square roots, which is what we want.+interpolate' :: SmoothingParameter -> Double -> Double -> Double+interpolate' (SmoothingParameter m) oldSquared newSquared = finSign * (fin ** 2)+ where+ oldSign = signum oldSquared+ newSign = signum newSquared+ sqrt' = sqrt . abs+ old = oldSign * sqrt' oldSquared+ new = newSign * sqrt' newSquared+ -- The new mass will be the second last boundary. That is, the larger the+ -- number of bins is, the more informative the new mass is compared to the+ -- old mass.+ nbins = max (100 - fromIntegral m) 2+ fin = recip nbins * (old + (nbins - 1) * new)+ finSign = signum fin++getNewMassDiagonalWithSampleSize :: SmoothingParameter -> Int -> Double -> Double -> Double+getNewMassDiagonalWithSampleSize m sampleSize massOld massEstimate | sampleSize < samplesDiagonalMin = massOld- -- Be permissive with NaN and negative diagonal masses. Diagonal masses are- -- variances which are strictly positive.- | isNaN massEstimate = massOld+ -- Diagonal masses are variances which are strictly positive. | massEstimate <= 0 = massOld- | massMin > massNew = massMin- | massNew > massMax = massMax- | otherwise = massNew- where- massNewSqrt = recip 3 * (sqrt massOld + 2 * sqrt massEstimate)- massNew = massNewSqrt ** 2+ | otherwise = rescue (interpolate' m) massOld massEstimate +getNewMassDiagonal :: SmoothingParameter -> Double -> Double -> Double+getNewMassDiagonal m massOld massEstimate+ -- Diagonal masses are variances which are strictly positive.+ | massEstimate <= 0 = massOld+ | otherwise = rescue (interpolate' m) massOld massEstimate++getNewMassOffDiagonal :: SmoothingParameter -> Double -> Double -> Double+getNewMassOffDiagonal m = rescue (interpolate' m)+ -- The Cholesky decomposition, which is performed when sampling new momenta with -- 'generateMomenta', requires a positive definite covariance matrix. The -- Graphical Lasso algorithm finds positive definite covariance matrices, but@@ -214,7 +264,7 @@ m = L.cols a b = L.unSym $ L.sym a (_, s, v) = L.svd b- h = (L.tr v) L.<> (L.diag s L.<> v)+ h = L.tr v L.<> (L.diag s L.<> v) a2 = L.scale 0.5 (b + h) a3 = L.unSym $ L.sym a2 isPositiveDefinite = isJust . L.mbChol . L.trustSym@@ -231,6 +281,7 @@ in go x' (k + 1) tuneDiagonalMassesOnly ::+ SmoothingParameter -> -- Conversion from value to vector. (a -> Positions) -> -- Value vector.@@ -242,7 +293,7 @@ -- NOTE: Here, we lose time because we convert the states to vectors again, -- something that has already been done. But then, auto tuning is not a runtime -- determining factor.-tuneDiagonalMassesOnly toVec xs (ms, msI)+tuneDiagonalMassesOnly m toVec xs (ms, msI) -- If not enough data is available, do not tune. | VB.length xs < samplesDiagonalMin = (ms, msI) | dimState /= dimMs = error "tuneDiagonalMassesOnly: Dimension mismatch."@@ -271,16 +322,40 @@ msDiagonalEstimate = VS.fromList $ map (recip . S.variance) $ L.toColumns xs'' msDiagonalNew = VS.zipWith3- getNewMassDiagonalWithRescue+ (getNewMassDiagonalWithSampleSize m) sampleSizes msDiagonalOld msDiagonalEstimate -- This value was carefully tuned using the example "hamiltonian". defaultGraphicalLassoPenalty :: Double-defaultGraphicalLassoPenalty = 0.4+defaultGraphicalLassoPenalty = 0.3 +interpolateElementWise :: SmoothingParameter -> L.Matrix Double -> L.Matrix Double -> L.Matrix Double+interpolateElementWise m old new+ | mO /= mN = err "different number of rows"+ | nO /= nN = err "different number of columns"+ | mO /= nO = err "not square"+ | mO < 1 = err "empty matrix"+ | otherwise = L.build (mO, nO) f+ where+ mO = L.rows old+ nO = L.cols old+ mN = L.rows new+ nN = L.cols new+ err msg = error $ "interpolateElementWise: " <> msg+ f iD jD =+ let -- This sucks a bit, because we need a function (e -> e -> e), and+ -- since the return type is Double, the indices are also Doubble.+ i = round iD+ j = round jD+ g = if i == j then getNewMassDiagonal m else getNewMassOffDiagonal m+ eO = old `L.atIndex` (i, j)+ eN = new `L.atIndex` (i, j)+ in g eO eN+ tuneAllMasses ::+ SmoothingParameter -> -- Conversion from value to vector. (a -> Positions) -> -- Value vector.@@ -292,16 +367,16 @@ -- NOTE: Here, we lose time because we convert the states to vectors again, -- something that has already been done. But then, auto tuning is not a runtime -- determining factor.-tuneAllMasses toVec xs (ms, msI)+tuneAllMasses m toVec xs (ms, msI) -- If not enough data is available, do not tune. | VB.length xs < samplesDiagonalMin = (ms, msI) -- If not enough data is available, only the diagonal masses are tuned. | VB.length xs < samplesAllMinWith dimMs = fallbackDiagonal | L.rank xs'' /= dimState = fallbackDiagonal | dimState /= dimMs = error "tuneAllMasses: Dimension mismatch."- | otherwise = (msPD', msPDI')+ | otherwise = (msNew, msINew) where- fallbackDiagonal = tuneDiagonalMassesOnly toVec xs (ms, msI)+ fallbackDiagonal = tuneDiagonalMassesOnly m toVec xs (ms, msI) -- xs: Each element contains all parameters of one iteration. -- xs': Each element is a vector containing all parameters changed by the -- proposal of one iteration.@@ -314,16 +389,18 @@ dimState = VS.length $ VB.head xs' dimMs = L.rows $ L.unSym ms (_, ss, xsNormalized) = S.scale xs''- sigmaNormalized =- L.unSym $- either error fst $- S.graphicalLasso defaultGraphicalLassoPenalty xsNormalized- -- Sigma is the inverted mass matrix.- sigma = S.rescaleSWith ss sigmaNormalized- msI' = toGMatrix sigma- -- The masses should be positive definite, but sometimes they happen to be- -- not because of numerical errors.- ms' = L.sym $ cleanMatrix $ L.inv sigma+ -- The first value is the covariance matrix sigma, which the inverted mass+ -- matrix (precision matrix). However, we interpolate the new mass matrix+ -- using the old one and the new estimate, so we have to recalculate the+ -- covariance matrix anyways.+ (_, precNormalized) =+ either error id $+ S.graphicalLasso defaultGraphicalLassoPenalty xsNormalized+ ms' = S.rescalePWith ss (L.unSym precNormalized)+ -- Clean NaNs, infinities; ensure positive definiteness. The masses should+ -- be positive definite, but sometimes they happen to be not because of+ -- numerical errors.+ msNewDirty = interpolateElementWise m (L.unSym ms) ms' -- Positive definite matrices are symmetric.- msPD' = L.trustSym $ findClosestPositiveDefiniteMatrix $ L.unSym ms'- msPDI' = if L.unSym ms' == L.unSym msPD' then msI' else getMassesI msPD'+ msNew = L.trustSym $ findClosestPositiveDefiniteMatrix $ cleanMatrix msNewDirty+ msINew = getMassesI msNew
src/Mcmc/Proposal/Hamiltonian/Nuts.hs view
@@ -79,7 +79,7 @@ -- Second function in Algorithm 3 and Algorithm 6, respectively in [4]. buildTreeWith :: -- The exponent of the total energy of the starting state is used to- -- calcaulate the expected acceptance rate 'Alpha'.+ -- calculate the expected acceptance rate 'Alpha'. Log Double -> MassesI -> Target ->@@ -108,8 +108,8 @@ n' = if u <= expEPot' * expEKin' then 1 else 0 errorIsSmall = u < deltaMax * expETot' alpha' = expETot' / expETot0- alpha = min 1.0 alpha'-+ -- Limit expected acceptance rate between 0 and 1.+ alpha = max 0 $ min 1 alpha' -- Recursive case. This is complicated because the algorithm is written for an -- imperative language, and because we have two stacked monads. | otherwise = do@@ -138,10 +138,10 @@ Nothing -> pure Nothing Just (qm'', pm'', qp'', pp'', q''', n''', a''', na''') -> do b <- uniformRM (0, 1) g :: IO Double- let q'''' = if b < fromIntegral n''' / (fromIntegral $ n' + n''') then q''' else q'+ let n'''' = n' + n'''+ q'''' = if b < fromIntegral n''' / fromIntegral n'''' then q''' else q' a'''' = a' + a''' na'''' = na' + na'''- n'''' = n' + n''' -- Important: Check for U-turn. This formula differs from the -- formula using indicator functions in Algorithm 3. However, -- check Equation (4).@@ -152,7 +152,7 @@ where buildTree = buildTreeWith expETot0 msI tfun g --- | Paramters of the NUTS proposal.+-- | Parameters of the NUTS proposal. -- -- Includes tuning parameters and tuning configuration. data NParams = NParams@@ -184,8 +184,8 @@ data IsNew = Old- | OldWith {_acceptanceCountsOld :: AcceptanceCounts}- | NewWith {_acceptanceCountsNew :: AcceptanceCounts}+ | OldWith {_acceptanceRatesOld :: AcceptanceRates}+ | NewWith {_acceptanceRatesNew :: AcceptanceRates} -- First function in Algorithm 3. nutsPFunction ::@@ -230,28 +230,28 @@ Nothing -> pure (y, isNew) Just (qm'', pm'', qp'', pp'', y'', n'', a, na) -> do let r = fromIntegral n'' / fromIntegral n :: Double- ar = (exp $ ln a) / fromIntegral na :: Double- getCounts s = max 0 $ min 100 $ round $ s * 100- ac =- if ar >= 0- then let cs = getCounts ar in AcceptanceCounts cs (100 - cs)- else error $ "nutsPFunction: Acceptance rate negative."+ -- Individual expected acceptance rates are limited in+ -- 'buildTreeWith'.+ ar = max 0 $ exp $ ln a+ ars = AcceptanceRates ar na isAccept <- if r > 1.0 then pure True else do b <- uniformRM (0, 1) g pure $ b < r- let (y''', isNew') = if isAccept then (y'', NewWith ac) else (y, OldWith ac)+ let (y''', isNew') = if isAccept then (y'', NewWith ars) else (y, OldWith ars) isUTurn = let dq = (qp'' - qm'') in (dq * pm'' < 0) || (dq * pp'' < 0) if isUTurn then pure (y''', isNew') else go qm'' pm'' qp'' pp'' (j + 1) y''' (n + n'') isNew' (x', isNew) <- go q p q p 0 q 1 Old pure $ case isNew of- Old -> (ForceReject, Just $ AcceptanceCounts 0 100)- OldWith ac -> (ForceReject, Just $ ac)- NewWith ac -> (ForceAccept $ fromVec x', Just $ ac)+ -- NOTE: I am not sure if it is correct to set the expected acceptance rate+ -- to 0 when the no u-turn leapfrog integrator fails.+ Old -> (ForceReject, Just $ AcceptanceRates 0 1)+ OldWith ac -> (ForceReject, Just ac)+ NewWith ac -> (ForceAccept $ fromVec x', Just ac) where (HParamsI e _ ms _ _ msI mus) = hparamsi (HStructure _ toVec fromVecWith) = hstruct@@ -299,7 +299,7 @@ tuner = do tfun <- hTuningFunctionWith dim toVec htconf let pfun = nutsPFunctionWithTuningParameters dim hstruct targetWith- pure $ Tuner 1.0 ts True tfun pfun+ pure $ Tuner 1.0 ts True True tfun pfun in case checkHStructureWith (hpsMasses hParamsI) hstruct of Just err -> error err Nothing -> nutsWith tuner