dataframe-learn-2.0.0.0: src-internal/DataFrame/SymbolicRegression/Expr.hs
{-# LANGUAGE FlexibleContexts #-}
{- | The symbolic-regression expression tree: a small first-order ADT with
vectorized evaluation and a total translation to a dataframe 'Expr Double'.
Division, log, and sqrt are protected so evaluation never produces @NaN@.
-}
module DataFrame.SymbolicRegression.Expr (
SRExpr (..),
BinOp (..),
UnOp (..),
evalSR,
toDataFrameExpr,
srSize,
constants,
setConstants,
allBinOps,
allUnOps,
) where
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import qualified DataFrame.Functions as F
import DataFrame.Internal.Expression (Expr (..))
import DataFrame.Operators ((.*.), (.+.), (.-.), (./.))
data BinOp = SAdd | SSub | SMul | SDiv
deriving (Eq, Ord, Show, Enum, Bounded)
data UnOp = SNeg | SSin | SCos | SExp | SLog | SSqrt
deriving (Eq, Ord, Show, Enum, Bounded)
-- | A symbolic-regression expression over feature variables and constants.
data SRExpr
= SVar !Int
| SConst !Double
| SUn !UnOp SRExpr
| SBin !BinOp SRExpr SRExpr
deriving (Eq, Ord, Show)
allBinOps :: [BinOp]
allBinOps = [minBound .. maxBound]
allUnOps :: [UnOp]
allUnOps = [minBound .. maxBound]
{- | Evaluate over a feature matrix given column-major (@feats ! j@ is feature
@j@ across all rows). Protected operators keep results finite.
-}
evalSR :: V.Vector (VU.Vector Double) -> Int -> SRExpr -> VU.Vector Double
evalSR feats n = go
where
go (SVar j)
| j < V.length feats = feats V.! j
| otherwise = VU.replicate n 0
go (SConst c) = VU.replicate n c
go (SUn op e) = VU.map (unFn op) (go e)
go (SBin op a b) = VU.zipWith (binFn op) (go a) (go b)
binFn :: BinOp -> Double -> Double -> Double
binFn SAdd a b = a + b
binFn SSub a b = a - b
binFn SMul a b = a * b
binFn SDiv a b = if abs b < 1e-9 then 1 else a / b
unFn :: UnOp -> Double -> Double
unFn SNeg = negate
unFn SSin = sin
unFn SCos = cos
unFn SExp = exp . min 50
unFn SLog = \x -> log (abs x + 1e-9)
unFn SSqrt = sqrt . abs
-- | Translate to a dataframe expression over the named feature columns.
toDataFrameExpr :: V.Vector T.Text -> SRExpr -> Expr Double
toDataFrameExpr names = go
where
go (SVar j)
| j < V.length names = Col (names V.! j)
| otherwise = F.lit 0
go (SConst c) = F.lit c
go (SUn op e) = unExpr op (go e)
go (SBin op a b) = binExpr op (go a) (go b)
unExpr SNeg = negate
unExpr SSin = sin
unExpr SCos = cos
unExpr SExp = exp
unExpr SLog = log
unExpr SSqrt = sqrt
binExpr SAdd = (.+.)
binExpr SSub = (.-.)
binExpr SMul = (.*.)
binExpr SDiv = (./.)
srSize :: SRExpr -> Int
srSize (SVar _) = 1
srSize (SConst _) = 1
srSize (SUn _ e) = 1 + srSize e
srSize (SBin _ a b) = 1 + srSize a + srSize b
-- | The constant values in left-to-right traversal order.
constants :: SRExpr -> [Double]
constants (SConst c) = [c]
constants (SVar _) = []
constants (SUn _ e) = constants e
constants (SBin _ a b) = constants a ++ constants b
-- | Replace the constants in traversal order; extra values are ignored.
setConstants :: [Double] -> SRExpr -> SRExpr
setConstants vals e = fst (go vals e)
where
go vs (SConst _) = case vs of
(v : rest) -> (SConst v, rest)
[] -> (SConst 0, [])
go vs (SVar j) = (SVar j, vs)
go vs (SUn op a) = let (a', vs') = go vs a in (SUn op a', vs')
go vs (SBin op a b) =
let (a', vs') = go vs a
(b', vs'') = go vs' b
in (SBin op a' b', vs'')