hbayesian-0.1.0.0: examples/BivariateGaussianMALA.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
-- | Example 4: Bivariate Gaussian Target with MALA.
--
-- A simple 2D correlated Gaussian where MALA's single leapfrog step
-- is competitive. The gradient is trivial and closed-form.
module BivariateGaussianMALA
( bivariateLogPdf
, bivariateGrad
, makeKernel
, renderStepMlir
, runChain
, runChainV2
) where
import Data.Word (Word64)
import Data.Text (Text)
import HHLO.Core.Types
import HHLO.IR.AST (FuncArg(..), TensorType(..))
import HHLO.IR.Builder
import HBayesian.Core
import HBayesian.HHLO.Ops
import HBayesian.HHLO.PJRT
import HBayesian.MCMC.HMC (HMCState(..))
import HBayesian.MCMC.MALA
import HBayesian.Chain
import Common
-- | Precision matrix Lambda = Sigma^{-1} for
-- Sigma = [[1.0, 0.8], [0.8, 1.0]]
lambda11, lambda12, lambda22 :: Float
lambda11 = 2.7778
lambda12 = -2.2222
lambda22 = 2.7778
mu1, mu2 :: Float
mu1 = 1.0
mu2 = 2.0
-- | Log-density of the bivariate Gaussian.
bivariateLogPdf :: Tensor '[2] 'F32 -> Builder (Tensor '[] 'F32)
bivariateLogPdf theta = do
mu <- buildMu
diff <- tsub theta mu
d1 <- tslice1 @2 @'F32 diff 0
d2 <- tslice1 @2 @'F32 diff 1
l11 <- tconstant @'[] @'F32 (realToFrac lambda11)
l12 <- tconstant @'[] @'F32 (realToFrac lambda12)
l22 <- tconstant @'[] @'F32 (realToFrac lambda22)
term1a <- tmul l11 d1
term1b <- tmul l12 d2
term1 <- tadd term1a term1b
quad1 <- tmul d1 term1
term2a <- tmul l12 d1
term2b <- tmul l22 d2
term2 <- tadd term2a term2b
quad2 <- tmul d2 term2
quadForm <- tadd quad1 quad2
negHalf <- tconstant @'[] @'F32 (-0.5)
tmul negHalf quadForm
-- | Gradient of the bivariate Gaussian log-density.
-- grad = -Lambda * (theta - mu)
bivariateGrad :: Gradient '[2] 'F32
bivariateGrad theta = do
mu <- buildMu
diff <- tsub theta mu
d1 <- tslice1 @2 @'F32 diff 0
d2 <- tslice1 @2 @'F32 diff 1
l11 <- tconstant @'[] @'F32 (realToFrac lambda11)
l12 <- tconstant @'[] @'F32 (realToFrac lambda12)
l22 <- tconstant @'[] @'F32 (realToFrac lambda22)
g1a <- tmul l11 d1
g1b <- tmul l12 d2
g1Inner <- tadd g1a g1b
g1 <- tnegate g1Inner
g2a <- tmul l12 d1
g2b <- tmul l22 d2
g2Inner <- tadd g2a g2b
g2 <- tnegate g2Inner
tpack2 g1 g2
-- | Helper: build the mean vector as a constant tensor.
buildMu :: Builder (Tensor '[2] 'F32)
buildMu = do
m1 <- tconstant @'[] @'F32 (realToFrac mu1)
m2 <- tconstant @'[] @'F32 (realToFrac mu2)
tpack2 m1 m2
-- | Factory: build a MALA kernel for this target.
makeKernel :: MALAConfig -> Kernel '[2] 'F32 (HMCState '[2] 'F32) (Info '[2] 'F32)
makeKernel config = mala bivariateLogPdf bivariateGrad config
-- | Tier A: render one kernel step to MLIR text.
renderStepMlir :: Text
renderStepMlir =
renderKernelStep @'[2] @'F32
[ FuncArg "key" (TensorType [2] UI64)
, FuncArg "pos" (TensorType [2] F32)
, FuncArg "p" (TensorType [2] F32)
, FuncArg "ld" (TensorType [] F32)
, FuncArg "g" (TensorType [2] F32)
] $ do
key <- arg @'[2] @'UI64
pos <- arg @'[2] @'F32
p <- arg @'[2] @'F32
ld <- arg @'[] @'F32
g <- arg @'[2] @'F32
let config = MALAConfig { malaStepSize = 0.1 }
(state', _info) <- kernelStep (makeKernel config) (Key key) (HMCState pos p ld g)
return (hmcPosition state')
-- | Tier B: run a short chain on PJRT and return sampled positions.
runChain :: IO [[Float]]
runChain = withPJRTCPU $ \api client -> do
let config = MALAConfig { malaStepSize = 0.1 }
kernel = makeKernel config
-- Compile the log-pdf module
let ldMod = moduleFromBuilder @'[] @'F32 "main"
[ FuncArg "theta" (TensorType [2] F32) ] $ do
theta <- arg @'[2] @'F32
bivariateLogPdf theta
ldExe <- compileModule api client ldMod
-- Compile the gradient module
let gradMod = moduleFromBuilder @'[2] @'F32 "main"
[ FuncArg "theta" (TensorType [2] F32) ] $ do
theta <- arg @'[2] @'F32
bivariateGrad theta
gradExe <- compileModule api client gradMod
-- Compile the MALA step module (single result: position)
let stepMod = moduleFromBuilder @'[2] @'F32 "main"
[ FuncArg "key" (TensorType [2] UI64)
, FuncArg "pos" (TensorType [2] F32)
, FuncArg "p" (TensorType [2] F32)
, FuncArg "ld" (TensorType [] F32)
, FuncArg "g" (TensorType [2] F32)
] $ do
key <- arg @'[2] @'UI64
pos <- arg @'[2] @'F32
p <- arg @'[2] @'F32
ld <- arg @'[] @'F32
g <- arg @'[2] @'F32
(state', _info) <- kernelStep kernel (Key key) (HMCState pos p ld g)
return (hmcPosition state')
stepExe <- compileModule api client stepMod
let seed :: Word64 = 42
theta0 = [0.0, 0.0]
-- Compute initial log-density and gradient
thetaBuf0 <- bufferFromF32 api client [2] theta0
[ldBuf0] <- executeModule api ldExe [thetaBuf0]
[ld0] <- bufferToF32 api ldBuf0 1
[gBuf0] <- executeModule api gradExe [thetaBuf0]
g0 <- bufferToF32 api gBuf0 2
loop api client stepExe ldExe gradExe seed (0 :: Int) theta0 ld0 g0 (10 :: Int) []
where
loop _ _ _ _ _ _ _ _ _ _ 0 acc = return (reverse acc)
loop api client stepExe ldExe gradExe seed step pos ld g n acc = do
let key = [seed, fromIntegral step]
zeroP = [0.0, 0.0]
keyBuf <- bufferFromUI64 api client [2] key
posBuf <- bufferFromF32 api client [2] pos
pBuf <- bufferFromF32 api client [2] zeroP
ldBuf <- bufferFromF32 api client [] [ld]
gBuf <- bufferFromF32 api client [2] g
[newPosBuf] <- executeModule api stepExe [keyBuf, posBuf, pBuf, ldBuf, gBuf]
newPos <- bufferToF32 api newPosBuf 2
-- Recompute log-density and gradient for the next step
[newLdBuf] <- executeModule api ldExe [newPosBuf]
[newLd] <- bufferToF32 api newLdBuf 1
[newGBuf] <- executeModule api gradExe [newPosBuf]
newG <- bufferToF32 api newGBuf 2
loop api client stepExe ldExe gradExe seed (step + 1) newPos newLd newG (n - 1) (newPos : acc)
-- | v0.2: Run a chain using the 'Chain' combinators.
runChainV2 :: IO ([[Float]], [Diagnostic])
runChainV2 = do
let config = MALAConfig { malaStepSize = 0.1 }
kernel = makeKernel config
ck = compileHMC kernel bivariateLogPdf bivariateGrad
sampleChain ck [0.0, 0.0] $ defaultChainConfig
{ ccNumIterations = 10
, ccSeed = 42
}