hanalyze-0.2.0.0: test/Hanalyze/Model/HBMSummarySpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
-- | Phase 103: HBM 事後要約の一発 API (hbmSummary / hbmSummaryNames) の spec。
--
-- fixture は A1 probe (experiments/phase103-hbm-summary-df-helpers/) と同型の
-- 決定的 fit (hbmModelPure = 既定 seed 42)。固定する配線:
-- * hbmSummary = posteriorSummary (latent+deterministic 名) (augment 済 chain)
-- の手繋ぎ直呼びと完全一致 (一発 API が既存経路の糖衣であること)
-- * deterministic 派生量 (mu) が既定で要約に含まれる (A1 確定の設計)
-- * deterministicNames は宣言順・runDeterministics の key 集合と一致
module Hanalyze.Model.HBMSummarySpec (spec) where
import qualified Data.Map.Strict as Map
import Data.Maybe (catMaybes)
import qualified DataFrame.Internal.DataFrame as DX
import Test.Hspec
import qualified Hanalyze.Data.Wrangle as W
import Hanalyze.DataIO.Preprocess (readMaybeDoubleColumn)
import Hanalyze.MCMC.Core (chainVals)
import qualified Hanalyze.Model.HBM as HBM
import Hanalyze.Model.Wrappers (HBMModel (..), HBMConfig (..), defaultHBM,
hbmModelPure, hbmSummary, hbmSummaryNames,
hbmSummaryDf, hbmDrawsDf)
import Hanalyze.Stat.Summary (SummaryRow (..), posteriorSummary)
-- | A1 probe と同型の最小モデル (latent 2 + deterministic 1)。
model :: HBM.ModelP ()
model = do
xs <- HBM.dataNamed "x" [1, 2, 3]
ys <- HBM.dataNamedObs "y" [2, 4, 6]
b <- HBM.sample "b" (HBM.Normal 0 1)
s <- HBM.sample "s" (HBM.HalfNormal 1)
mu <- HBM.deterministic "mu" (b * head xs)
HBM.observe "y" (HBM.Normal mu s) ys
-- | deterministic 無しモデル (augment 迂回パスの確認用)。
modelNoDet :: HBM.ModelP ()
modelNoDet = do
ys <- HBM.dataNamedObs "y" [2, 4, 6]
b <- HBM.sample "b" (HBM.Normal 0 1)
HBM.observe "y" (HBM.Normal b 1) ys
-- | 決定的 fit (seed 42 既定・test 速度優先の小 iteration)。
fitted :: HBMModel
fitted = hbmModelPure (defaultHBM { hbmChains = 2, hbmWarmup = 100, hbmSamples = 100 })
model []
rowKey :: SummaryRow -> (String, Double, Double, Double, Double, Double, Maybe Double)
rowKey r = (show (srName r), srMean r, srSD r, srHdiLo r, srHdiHi r, srEssV r, srRhat r)
spec :: Spec
spec = do
describe "hbmSummaryNames (Phase 103)" $ do
it "latent 宣言順 → deterministic 宣言順の連結" $
hbmSummaryNames fitted `shouldBe` ["b", "s", "mu"]
it "deterministicNames = runDeterministics の key 集合 (順序のみ宣言順)" $ do
let spec' :: HBM.ModelP ()
spec' = hbmModelSpec fitted
ks = Map.keys (HBM.runDeterministics spec' (Map.fromList [("b", 1), ("s", 1)]))
HBM.deterministicNames spec' `shouldMatchList` ks
describe "hbmSummary (Phase 103)" $ do
it "posteriorSummary 直呼び (augment 手繋ぎ) と完全一致" $ do
let spec' :: HBM.ModelP ()
spec' = hbmModelSpec fitted
names = HBM.sampleNames spec' ++ HBM.deterministicNames spec'
chains = map (HBM.augmentChainWithDeterministic spec') (hbmChainsR fitted)
direct = posteriorSummary names chains
map rowKey (hbmSummary fitted) `shouldBe` map rowKey direct
it "deterministic 派生量 mu が既定で要約に含まれ有限値を持つ" $ do
let rows = hbmSummary fitted
mus = [r | r <- rows, srName r == "mu"]
length mus `shouldBe` 1
all (\r -> not (isNaN (srMean r))) mus `shouldBe` True
it "multi-chain fit では r_hat が Just" $
all (\r -> srRhat r /= Nothing) (hbmSummary fitted) `shouldBe` True
it "deterministic 無しモデルでも成立 (augment 迂回パス)" $ do
let m = hbmModelPure (defaultHBM { hbmChains = 1, hbmWarmup = 50, hbmSamples = 50 })
modelNoDet []
rows = hbmSummary m
map srName rows `shouldBe` ["b"]
-- 単 chain は r_hat 無し (posteriorSummary の既存挙動に一致)
map srRhat rows `shouldBe` [Nothing]
describe "hbmSummaryDf (Phase 103)" $ do
it "multi-chain: 列 = param/mean/sd/hdi_lo/hdi_hi/ess_bulk/r_hat・行 = 全パラメタ" $ do
let df = hbmSummaryDf fitted
DX.columnNames df `shouldBe`
["param", "mean", "sd", "hdi_lo", "hdi_hi", "ess_bulk", "r_hat"]
let Just means = readMaybeDoubleColumn "mean" df
catMaybes means `shouldBe` map srMean (hbmSummary fitted)
it "単 chain: r_hat 列が付かない (printPosteriorSummary の列規約と同じ)" $ do
let m = hbmModelPure (defaultHBM { hbmChains = 1, hbmWarmup = 50, hbmSamples = 50 })
modelNoDet []
df = hbmSummaryDf m
DX.columnNames df `shouldBe`
["param", "mean", "sd", "hdi_lo", "hdi_hi", "ess_bulk"]
describe "hbmDrawsDf (Phase 103)" $ do
it "1 パラメタ = 1 列 (deterministic 込み)・全 chain 連結の draw 数" $ do
let df = hbmDrawsDf fitted
DX.columnNames df `shouldBe` ["b", "s", "mu"]
let Just mus = readMaybeDoubleColumn "mu" df
length (catMaybes mus) `shouldBe` 200 -- 2 chain x 100 draw
it "draw 値 = augment 済 chain の chainVals 連結 (chain 順)" $ do
let spec' :: HBM.ModelP ()
spec' = hbmModelSpec fitted
direct = concatMap (chainVals "mu")
(map (HBM.augmentChainWithDeterministic spec') (hbmChainsR fitted))
Just mus = readMaybeDoubleColumn "mu" (hbmDrawsDf fitted)
catMaybes mus `shouldBe` direct
it "Wrangle summarise (meanOf) がそのまま効く" $ do
let df = hbmDrawsDf fitted
out = W.summarise ["m" W.=: W.meanOf "mu"] df
Just [Just m] = readMaybeDoubleColumn "m" out
Just mus = readMaybeDoubleColumn "mu" df
xs = catMaybes mus
abs (m - sum xs / fromIntegral (length xs)) `shouldSatisfy` (< 1e-9)