hanalyze-0.2.0.0: test/Hanalyze/Model/KernelSpec.hs
{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Hanalyze.Model.KernelSpec (spec) where
import Test.Hspec
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck
import Hanalyze.Model.Formula
import Hanalyze.Model.Formula.Frame
import Hanalyze.Model.Formula.Design
import Hanalyze.Model.Formula.RFormula
import Hanalyze.Model.Formula.Nonlinear
import Hanalyze.Model.Formula.Mixed
import Hanalyze.Model.GLMM
import Hanalyze.Model.GLM (Family (..), LinkFn (..))
import Hanalyze.Stat.Distribution (Transform)
import Data.List (sort, nub)
import Control.Monad (forM, forM_)
import System.IO.Temp (withSystemTempFile)
import System.IO (hPutStr, hClose)
import Hanalyze.Model.HBM.Ast (Expr (..), Lit (..), DoStmt (..), Err)
import Data.IORef (newIORef, readIORef, modifyIORef')
import qualified Numeric.LinearAlgebra as LA
import qualified Hanalyze.Model.KernelRegression as Kn
import qualified Hanalyze.Model.GP as GP
import qualified Hanalyze.Model.GPRobust as GPR
import qualified Hanalyze.Model.GP as GP
import qualified Hanalyze.Model.GPRobust as GPR
import SpecHelper
spec :: Spec
spec = do
describe "Hanalyze.Model.KernelRegression multi-input (MV)" $ do
-- 2D regression target: y = sin(x1) + 0.5 cos(x2)
let n = 60
h = 0.5
lam = 1e-4
f x1 x2 = sin x1 + 0.5 * cos x2
xs = LA.fromLists
[ [ fromIntegral i / 10
, fromIntegral (n - i) / 10
]
| i <- [0 .. n - 1] ]
ys = LA.asColumn $ LA.fromList
[ f (xs `LA.atIndex` (i, 0)) (xs `LA.atIndex` (i, 1))
| i <- [0 .. n - 1] ]
fit = Kn.kernelRidgeMV Kn.Gaussian h lam xs ys
yhat = Kn.fittedKernelRidgeMV fit
ssErr = LA.sumElements ((ys - yhat) ** 2)
ssTot = let muY = LA.sumElements ys / fromIntegral n
in LA.sumElements ((ys - LA.konst muY (n, 1)) ** 2)
r2 = 1 - ssErr / ssTot
it "achieves R² > 0.95 on a 2D smooth target" $
r2 > 0.95 `shouldBe` True
it "predict at training points equals fitted" $ do
let p = Kn.predictKernelRidgeMV fit xs
LA.norm_Inf (p - yhat) < 1e-9 `shouldBe` True
it "gramMatrixMV matches kernelFromSqDist by element" $ do
let xS = LA.fromLists [[0.0, 0.0], [1.0, 0.0], [0.5, 0.5]] :: LA.Matrix Double
gMV = Kn.gramMatrixMV Kn.Gaussian 1.0 xS
rs = LA.toRows xS
ref = LA.fromLists
[ [ let d = rs !! i - rs !! j
s = (d `LA.dot` d) / (1.0 * 1.0)
in Kn.kernelFromSqDist Kn.Gaussian s
| j <- [0 .. 2] ]
| i <- [0 .. 2] ]
LA.norm_Inf (gMV - ref) < 1e-12 `shouldBe` True
it "Hanalyze.Model.GP MV: 1D input matches legacy 1D fitGP within 1e-6" $ do
let xL = [fromIntegral i / 5 | i <- [0 .. 19 :: Int]]
yL = map sin xL
tL = [0.5, 1.5, 2.5]
mdl = GP.GPModel GP.RBF (GP.GPParams 1.0 1.0 0.05 1.0 Nothing)
legacy = GP.fitGP mdl xL yL tL
xMV = LA.fromLists (map (:[]) xL) :: LA.Matrix Double
yMV = LA.fromList yL
tMV = LA.fromLists (map (:[]) tL) :: LA.Matrix Double
mv = GP.fitGPMV mdl xMV yMV tMV
dMu = LA.norm_Inf
(GP.gpmvMean mv - LA.fromList (GP.gpMean legacy))
dVr = LA.norm_Inf
(GP.gpmvVar mv - LA.fromList (GP.gpVar legacy))
(dMu < 1e-6) `shouldBe` True
(dVr < 1e-6) `shouldBe` True
it "Hanalyze.Model.GP MV: 2D RBF reaches R² > 0.95 with optimized HP" $ do
let nN = 50
gx = [(fromIntegral i / 10, fromIntegral (nN - i) / 10)
| i <- [0 .. nN - 1 :: Int]]
ftn (x1, x2) = sin x1 + 0.5 * cos x2
xMV = LA.fromLists [ [a, b] | (a, b) <- gx ] :: LA.Matrix Double
yMV = LA.fromList [ ftn p | p <- gx ]
p0 = GP.GPParams 1.0 1.0 0.01 1.0 Nothing
po = GP.optimizeGPMV GP.RBF xMV yMV p0
mdl = GP.GPModel GP.RBF po
res = GP.fitGPMV mdl xMV yMV xMV
mu = GP.gpmvMean res
y = yMV
ss = LA.sumElements ((y - mu) ** 2)
mY = LA.sumElements y / fromIntegral nN
st = LA.sumElements ((y - LA.konst mY nN) ** 2)
r2 = 1 - ss / st
(r2 > 0.95) `shouldBe` True
it "Hanalyze.Model.GPRobust MV: 1D input matches legacy fitGPRobust" $ do
let xL = [fromIntegral i / 5 | i <- [0 .. 14 :: Int]]
yL = map sin xL
tL = [0.5, 1.5, 2.5]
ker = GP.RBF
ps = GP.GPParams 1.0 1.0 0.05 1.0 Nothing
lik = GPR.RGaussian 0.1
legFit = GPR.fitGPRobust ker ps lik xL yL
legacy = GPR.predictGPRobust legFit tL
legM = LA.fromList (map fst legacy)
xMV = LA.fromLists (map (:[]) xL) :: LA.Matrix Double
yMV = LA.fromList yL
tMV = LA.fromLists (map (:[]) tL) :: LA.Matrix Double
mvFit = GPR.fitGPRobustMV ker ps lik xMV yMV
(mvM, _) = GPR.predictGPRobustMV mvFit tMV
LA.norm_Inf (mvM - legM) < 1e-6 `shouldBe` True
it "MV gramMatrix on a single-column input agrees with kernelFromSqDist" $ do
let xs1 = LA.fromLists [[fromIntegral i / 5] | i <- [0 .. 19 :: Int]]
:: LA.Matrix Double
gMV2 = Kn.gramMatrixMV Kn.Gaussian 0.4 xs1
ref = LA.fromLists
[ [ let xi = xs1 `LA.atIndex` (i, 0)
xj = xs1 `LA.atIndex` (j, 0)
d = xi - xj
in Kn.kernelFromSqDist Kn.Gaussian (d * d / (0.4 * 0.4))
| j <- [0 .. 19] ]
| i <- [0 .. 19] ]
LA.norm_Inf (gMV2 - ref) < 1e-12 `shouldBe` True