packages feed

hbayesian-0.1.0.0: examples/LinearRegressionRandomWalk.hs

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

-- | Example 1: Bayesian Linear Regression with RandomWalk MH.
--
-- Model: y_i = alpha + beta * x_i + epsilon_i,  epsilon_i ~ N(0, 0.25)
-- Prior:  alpha ~ N(0, 1),  beta ~ N(0, 1)
module LinearRegressionRandomWalk
  ( dataset
  , linearRegLogPdf
  , 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.RandomWalk
import           HBayesian.Chain
import           Common

-- | Fixed synthetic dataset (n = 5).
dataset :: [(Float, Float)]
dataset =
  [ (0.0,  0.5)
  , (1.0,  2.0)
  , (2.0,  3.5)
  , (3.0,  5.0)
  , (4.0,  6.5)
  ]

-- | Log-posterior for Bayesian linear regression.
linearRegLogPdf :: Tensor '[2] 'F32 -> Builder (Tensor '[] 'F32)
linearRegLogPdf theta = do
  alpha <- tslice1 @2 @'F32 theta 0
  beta  <- tslice1 @2 @'F32 theta 1

  let likelihoodPoint (x, y) = do
        xT <- tconstant @'[] @'F32 (realToFrac x)
        yT <- tconstant @'[] @'F32 (realToFrac y)
        betaX <- tmul beta xT
        predVal <- tadd alpha betaX
        diff <- tsub yT predVal
        diffSq <- tmul diff diff
        negTwo <- tconstant @'[] @'F32 (-2.0)
        tmul negTwo diffSq

  llh0 <- likelihoodPoint (dataset !! 0)
  llh1 <- likelihoodPoint (dataset !! 1)
  llh2 <- likelihoodPoint (dataset !! 2)
  llh3 <- likelihoodPoint (dataset !! 3)
  llh4 <- likelihoodPoint (dataset !! 4)

  llh01 <- tadd llh0 llh1
  llh23 <- tadd llh2 llh3
  llh0123 <- tadd llh01 llh23
  llh <- tadd llh0123 llh4

  alphaSq <- tmul alpha alpha
  betaSq  <- tmul beta beta
  negHalf <- tconstant @'[] @'F32 (-0.5)
  priorAlpha <- tmul negHalf alphaSq
  priorBeta  <- tmul negHalf betaSq

  tadd llh =<< tadd priorAlpha priorBeta

-- | Factory: build a RandomWalk kernel for this model.
makeKernel :: RWConfig -> SimpleKernel '[2] 'F32
makeKernel config = randomWalk linearRegLogPdf 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 "ld"  (TensorType [] F32)
    ] $ do
      key <- arg @'[2] @'UI64
      pos <- arg @'[2] @'F32
      ld  <- arg @'[] @'F32
      (state', _info) <- kernelStep (makeKernel (RWConfig 0.1)) (Key key) (State pos ld)
      return (statePosition state')

-- | Tier B: run a short chain on PJRT and return the sampled positions.
runChain :: IO [[Float]]
runChain = withPJRTCPU $ \api client -> do
    let kernel = makeKernel (RWConfig 0.1)

    -- Compile the log-pdf module
    let ldMod = moduleFromBuilder @'[] @'F32 "main"
                  [ FuncArg "pos" (TensorType [2] F32) ] $ do
          pos <- arg @'[2] @'F32
          linearRegLogPdf pos
    ldExe <- compileModule api client ldMod

    -- Compile the kernel-step module (single result: position)
    let stepMod = moduleFromBuilder @'[2] @'F32 "main"
                    [ FuncArg "key" (TensorType [2] UI64)
                    , FuncArg "pos" (TensorType [2] F32)
                    , FuncArg "ld"  (TensorType [] F32)
                    ] $ do
          key <- arg @'[2] @'UI64
          pos <- arg @'[2] @'F32
          ld  <- arg @'[] @'F32
          (state', _info) <- kernelStep kernel (Key key) (State pos ld)
          return (statePosition state')
    stepExe <- compileModule api client stepMod

    let seed :: Word64 = 42
        pos0 = [0.0, 0.0]

    -- Compute initial log-density
    posBuf0 <- bufferFromF32 api client [2] pos0
    [ldBuf0] <- executeModule api ldExe [posBuf0]
    [ld0] <- bufferToF32 api ldBuf0 1

    loop api client stepExe ldExe seed (0 :: Int) pos0 ld0 (10 :: Int) []
  where
    loop _ _ _ _ _ _ _ _ 0 acc = return (reverse acc)
    loop api client stepExe ldExe seed step pos ld n acc = do
        let key = [seed, fromIntegral step]
        keyBuf <- bufferFromUI64 api client [2] key
        posBuf <- bufferFromF32 api client [2] pos
        ldBuf  <- bufferFromF32 api client [] [ld]
        [newPosBuf] <- executeModule api stepExe [keyBuf, posBuf, ldBuf]
        newPos <- bufferToF32 api newPosBuf 2
        -- Recompute log-density for the next step
        [newLdBuf] <- executeModule api ldExe [newPosBuf]
        [newLd] <- bufferToF32 api newLdBuf 1
        loop api client stepExe ldExe seed (step + 1) newPos newLd (n - 1) (newPos : acc)

-- | v0.2: Run a chain using the 'Chain' combinators.
runChainV2 :: IO ([[Float]], [Diagnostic])
runChainV2 = do
    let kernel = makeKernel (RWConfig 0.1)
        ck     = compileSimpleKernel kernel linearRegLogPdf
    sampleChain ck [0.0, 0.0] $ defaultChainConfig
        { ccNumIterations = 10
        , ccSeed = 42
        }