-- lighthouse.hs "LIGHTHOUSE" NESTED SAMPLING APPLICATION -- (GNU General Public License software, (C) Sivia and Skilling 2006) -- u=0 u=1 -- ------------------------------------- -- y=2 |:::::::::::::::::::::::::::::::::::::| v=1 -- |::::::::::::::::::::::LIGHT::::::::::| -- north|::::::::::::::::::::::HOUSE::::::::::| -- |:::::::::::::::::::::::::::::::::::::| -- |:::::::::::::::::::::::::::::::::::::| -- y=0 |:::::::::::::::::::::::::::::::::::::| v=0 -- --*--------------*----*--------*-**--**--*-*-------------*-------- -- x=-2 coastline -->east x=2 -- Problem: -- Lighthouse at (x,y) emitted n flashes observed at D[.] on coast. -- Inputs: -- Prior(u) is uniform (=1) over (0,1), mapped to x = 4*u - 2; and -- Prior(v) is uniform (=1) over (0,1), mapped to y = 2*v; so that -- Position is 2-dimensional -2 < x < 2, 0 < y < 2 with flat prior -- Likelihood is L(x,y) = PRODUCT[k] (y/pi) / ((D[k] - x)^2 + y^2) -- Outputs: -- Evidence is Z = INTEGRAL L(x,y) Prior(x,y) dxdy -- Posterior is P(x,y) = L(x,y) / Z estimating lighthouse position -- Information is H = INTEGRAL P(x,y) log(P(x,y)/Prior(x,y)) dxdy import qualified Data.Vector.Unboxed as UV import Control.Monad (mapM) import Statistics.MiniNest import System.Random (randomIO) import Text.Printf data Lighthouse = Lighthouse { lhU :: Double, lhV :: Double, lhX :: Double, lhY :: Double, lhLogL :: Double, lhLogWt :: Double } deriving (Eq, Show) instance Ord Lighthouse where a <= b = lhLogL a <= lhLogL b instance SamplingObject Lighthouse where setLogWt lh newLogWt = lh { lhLogWt = newLogWt } getLogWt lh = lhLogWt lh getLogL lh = lhLogL lh logLhoodOfData :: UV.Vector Double -> Double -> Double -> Double logLhoodOfData observations x y = UV.sum $ UV.map term observations where term dk = log (y / pi) - log ((dk - x)*(dk - x) + y*y) -- logLikelihood function -- x: Easterly position -- y: Northerly position logLhood :: Double -> Double -> Double logLhood x y = logLhoodOfData lhData x y lhData = UV.fromList [4.73, 0.45, -1.73, 1.09, 2.19, 0.12, 1.31, 1.00, 1.32, 1.07, 0.86, -0.49, -2.59, 1.73, 2.11, 1.61, 4.98, 1.71, 2.23,-57.20, 0.96, 1.25, -1.56, 2.45, 1.19, 2.17,-10.66, 1.91, -4.16, 1.92, 0.10, 1.98, -2.51, 5.55, -0.47, 1.91, 0.95, -0.78, -0.84, 1.72, -0.01, 1.48, 2.70, 1.21, 4.41, -4.79, 1.33, 0.81, 0.20, 1.58, 1.29, 16.19, 2.75, -2.38, -1.79, 6.50,-18.53, 0.72, 0.94, 3.64, 1.94, -0.11, 1.57, 0.57] -- |Sample from U[0,1] uniform :: IO Double uniform = randomIO sampleFromPrior :: IO Lighthouse sampleFromPrior = do u <- uniform v <- uniform let x=4*u - 2 y=2*v return $ Lighthouse u v x y (logLhood x y) 0 -- |Evolve Lighthouse within likelihood constraint -- obj: Lighthouse being evolved -- logLstar: Likelihood constraint L > Lstar explore :: Lighthouse -> Double -> IO Lighthouse explore obj logLstar = explore' step m accept reject (lhU obj) (lhV obj) (lhX obj) (lhY obj) (lhLogL obj) where step = 0.1 -- Initial guess suitable step-size in (0,1) m = 20 -- MCMC counter (pre-judged # steps) accept = 0 -- # MCMC acceptances reject = 0 -- # MCMC rejections explore' step m accept reject u v x y logL = do -- Trial Lighthouse unif1 <- uniform unif2 <- uniform let u' = wrapAround $ u + step * (2*unif1 - 1) -- |move| < step v' = wrapAround $ v + step * (2*unif2 - 1) -- |move| < step x' = 4*u' - 2 -- map to x y' = 2*v' -- map to y logL' = logLhood x' y' -- Accept if and only if within hard likelihood constraint obj' <- if logL' > logLstar then return $ Lighthouse u' v' x' y' logL' (lhLogWt obj) else return $ Lighthouse u v x y logL (lhLogWt obj) (accept, reject) <- if logL' > logLstar then return (accept + 1, reject) else return (accept, reject + 1) -- Refine step-size to let acceptance ratio converge around 50% step <- if accept > reject then return $ step * exp(1.0 / accept) else return step step <- if accept < reject then return $ step / exp(1.0 / reject) else return step if m == 0 then return obj' else explore' step (m-1) accept reject (lhU obj') (lhV obj') (lhX obj') (lhY obj') (lhLogL obj') wrapAround :: Double -> Double wrapAround x = x - (fromIntegral $ floor x) data Stats = Stats { meanX :: Double, meanY :: Double, stddevX :: Double, stddevY :: Double } instance Show Stats where show s = (printf "x = %.2f +- %.2f\n" (meanX s) (stddevX s) ++ printf "y = %.2f +- %.2f\n" (meanY s) (stddevY s)) -- Posterior properties, here mean and stddev of x,y -- Args: -- samples: Objects defining posterior -- logZ: Evidence (= total weight = SUM[Samples] Weight) getStats :: [Lighthouse] -> Double -> Stats getStats samples logZ = Stats {meanX=x, meanY=y, stddevX=sqrt $ xx - x*x, stddevY=sqrt $ yy - y*y } where weightsSamples = [(exp (lhLogWt s - logZ), s) | s <- samples] x = sum [w*(lhX s) | (w,s) <- weightsSamples] y = sum [w*(lhY s) | (w,s) <- weightsSamples] xx = sum [w*(lhX s)^2 | (w,s) <- weightsSamples] yy = sum [w*(lhY s)^2 | (w,s) <- weightsSamples] main = do let n = 100 -- # number of candidate lighthouses let maxIterations = 1000 -- # iterates priorSamples <- mapM (\_ -> sampleFromPrior) [1..n] result <- nestedSampling priorSamples explore maxIterations let stats = getStats (nsSamples result) (nsLogZ result) print result print stats