packages feed

hanalyze-0.2.0.0: test/Hanalyze/Design/SpaceFillingSpec.hs

{-# OPTIONS_GHC -Wno-unused-imports #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Hanalyze.Design.SpaceFillingSpec (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.Design.SpaceFilling  as SF
import qualified System.Random.MWC as MWC
import qualified System.Random.MWC as MWC
import SpecHelper

spec :: Spec
spec = do
  describe "Hanalyze.Design.SpaceFilling (Phase 6.1)" $ do
    let inUnitBox m =
          all (\v -> v >= 0 && v < 1) (LA.toList (LA.flatten m))

    it "latinHypercube: n × d 行列、 [0,1) 内、 minDist > 0" $ do
      gen <- MWC.create
      des <- SF.latinHypercube 20 3 gen
      SF.sfdNPoints des `shouldBe` 20
      SF.sfdNDims   des `shouldBe` 3
      LA.rows (SF.sfdMatrix des) `shouldBe` 20
      LA.cols (SF.sfdMatrix des) `shouldBe` 3
      inUnitBox (SF.sfdMatrix des) `shouldBe` True
      SF.sfdMinDist des `shouldSatisfy` (> 0)
      SF.sfdMethod  des `shouldBe` "LHS"

    it "latinHypercube: 各列の stratification (n セルに 1 点ずつ)" $ do
      gen <- MWC.create
      des <- SF.latinHypercube 10 2 gen
      let mat = SF.sfdMatrix des
          col0 = LA.toList (LA.flatten (mat LA.¿ [0]))
          cellOf x = floor (x * 10) :: Int
          cells0 = map cellOf col0
      length (Data.List.nub cells0) `shouldBe` 10  -- 全 10 セル使用

    it "latinHypercubeMaximin: minDist ≥ plain LHS minDist (LHS から始まる)" $ do
      gen1 <- MWC.create
      gen2 <- MWC.create
      lhs <- SF.latinHypercube 15 3 gen1
      mxmn <- SF.latinHypercubeMaximin 15 3 200 gen2
      SF.sfdMethod mxmn `shouldBe` "MaximinLHS"
      -- maximin は LHS の swap 局所探索なので、 同 seed では LHS と
      -- 同等以上の minDist を期待。 異 seed でも壊滅的に悪くはならないはず:
      -- 「max(LHS, MaximinLHS) ≥ LHS の半分」 程度の緩い検証で OK
      SF.sfdMinDist mxmn `shouldSatisfy` (>= SF.sfdMinDist lhs / 2)

    it "haltonDesign: 決定的 (= 同じ n, d で同じ結果)" $ do
      let d1 = SF.haltonDesign 10 3
          d2 = SF.haltonDesign 10 3
      SF.sfdMatrix d1 `shouldBe` SF.sfdMatrix d2
      SF.sfdMethod  d1 `shouldBe` "Halton"
      inUnitBox (SF.sfdMatrix d1) `shouldBe` True
      SF.sfdMinDist d1 `shouldSatisfy` (> 0)

    it "designMinDistance: 既知 2 点 design で正しい距離" $ do
      let m = LA.fromLists [[0.0, 0.0], [3.0, 4.0]]  -- 距離 = 5
      abs (SF.designMinDistance m - 5.0) `shouldSatisfy` (< 1e-12)

    it "designMinDistance: 行数 < 2 で 0" $ do
      let m0 = (0 LA.>< 0) []
          m1 = LA.fromLists [[1.0, 2.0]]
      SF.designMinDistance m0 `shouldBe` 0
      SF.designMinDistance m1 `shouldBe` 0

    it "n=0 or d=0 で安全に empty design" $ do
      gen <- MWC.create
      des0 <- SF.latinHypercube 0 3 gen
      SF.sfdNPoints des0 `shouldBe` 0
      let halt0 = SF.haltonDesign 0 3
      SF.sfdNPoints halt0 `shouldBe` 0