dataframe-learn-2.0.0.0: tests-internal/Learn/Symbolic.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Learn.Symbolic (tests) where
import qualified DataFrame.Functions as F
import DataFrame.Internal.Column (TypedColumn (..), toVector)
import qualified DataFrame.Internal.Column as DI
import DataFrame.Internal.Expression (Expr)
import DataFrame.Internal.Interpreter (interpret)
import qualified DataFrameApi as D
import DataFrame.PCA.Kernel
import DataFrame.SVM.RFF
import DataFrame.SymbolicRegression
import DataFrame.SymbolicRegression.Expr
import DataFrame.SymbolicRegression.Optimize (meanSquaredError)
import DataFrame.SymbolicRegression.Simplify (simplify)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import Test.HUnit
interpD :: D.DataFrame -> Expr Double -> [Double]
interpD df e = case interpret @Double df e of
Right (TColumn c) -> either (const []) VU.toList (toVector @Double @VU.Vector c)
Left err -> error (show err)
interpI :: D.DataFrame -> Expr Int -> [Int]
interpI df e = case interpret @Int df e of
Right (TColumn c) -> either (const []) VU.toList (toVector @Int @VU.Vector c)
Left err -> error (show err)
testSRRecovers :: Test
testSRRecovers = TestCase $ do
let xs = [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6] :: [Double]
df =
D.fromNamedColumns
[ ("x", DI.fromList xs)
, ("y", DI.fromList [x * x + x | x <- xs])
]
m =
fit
defaultSRConfig
{ srSeed = 3
, srGenerations = 60
, srPopSize = 300
, srUnaryOps = []
}
(F.col @Double "y")
df
preds = interpD df (srBest m)
truth = interpD df (F.col @Double "y")
err = sum (zipWith (\p t -> (p - t) ^ (2 :: Int)) preds truth) / 10
assertBool "SR recovers x*x+x to low error" (err < 1e-6)
assertBool "Pareto front non-empty" (not (null (srPareto m)))
testSRDeterminism :: Test
testSRDeterminism = TestCase $ do
let xs = [1 .. 8] :: [Double]
df =
D.fromNamedColumns
[ ("x", DI.fromList xs)
, ("y", DI.fromList (map (\x -> 2 * x + 1) xs))
]
run =
fit
defaultSRConfig{srSeed = 9, srGenerations = 20}
(F.col @Double "y")
df
assertEqual "SR deterministic best MSE" (srBestMSE run) (srBestMSE (rerun df))
where
rerun =
fit
defaultSRConfig{srSeed = 9, srGenerations = 20}
(F.col @Double "y")
testSimplifyPreservesEval :: Test
testSimplifyPreservesEval = TestCase $ do
let feats = V.singleton (VU.fromList [1, 2, 3, 4 :: Double])
n = 4
e = SBin SAdd (SBin SMul (SVar 0) (SConst 1)) (SConst 0)
target = VU.fromList [1, 2, 3, 4]
assertBool
"simplify preserves evaluation"
( abs
(meanSquaredError feats n target e - meanSquaredError feats n target (simplify e))
< 1e-12
)
assertBool "simplify is size non-increasing" (srSize (simplify e) <= srSize e)
assertEqual "simplify idempotent" (simplify e) (simplify (simplify e))
testKernelPCA :: Test
testKernelPCA = TestCase $ do
let df =
D.fromNamedColumns
[ ("a", DI.fromList ([0, 0.2, -0.1, 0.1, 8, 8.1, 7.9, 8.2] :: [Double]))
, ("b", DI.fromList ([0, -0.1, 0.2, 0.0, 5, 5.2, 4.9, 5.1] :: [Double]))
]
m =
fit
defaultKernelPCAConfig{kpcaNComponents = 2, kpcaNLandmarks = 8, kpcaSeed = 1}
[F.col @Double "a", F.col @Double "b"]
df
pc1 = case kernelPCAExprs m of
((_, e) : _) -> interpD df e
[] -> error "testKernelPCA: no kPCA components"
assertBool "kPCA finite" (not (any isNaN pc1))
pc1First <- case pc1 of
(p : _) -> pure p
[] -> assertFailure "kPCA produced no projection"
assertBool
"kPCA first component separates blobs"
(signum pc1First /= signum (last pc1))
testRFFSVM :: Test
testRFFSVM = TestCase $ do
let df =
D.fromNamedColumns
[ ("x", DI.fromList ([-3, -2, -1, -0.5, 0.5, 1, 2, 3] :: [Double]))
, ("label", DI.fromList ([0, 0, 0, 0, 1, 1, 1, 1] :: [Int]))
]
m =
fit
defaultRFFConfig{rffD = 80, rffGamma = 0.2, rffSeed = 2}
(F.col @Int "label")
df
preds = interpI df (predict m)
assertEqual "RFF SVM separates" [0, 0, 0, 0, 1, 1, 1, 1] preds
tests :: [Test]
tests =
[ testSRRecovers
, testSRDeterminism
, testSimplifyPreservesEval
, testKernelPCA
, testRFFSVM
]