packages feed

dataframe-learn 2.2.0.0 → 2.3.0.0

raw patch · 16 files changed

+197/−89 lines, 16 filesdep ~dataframe-coredep ~dataframe-operationsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: dataframe-core, dataframe-operations

API changes (from Hackage documentation)

+ DataFrame.Synthesis: [synMaxAllocBytes] :: SynthesisConfig -> !Int
- DataFrame.Synthesis: SynthesisConfig :: !Int -> !Int -> !LossFunction -> !Int -> SynthesisConfig
+ DataFrame.Synthesis: SynthesisConfig :: !Int -> !Int -> !LossFunction -> !Int -> !Int -> SynthesisConfig

Files

README.md view
@@ -57,7 +57,7 @@ import qualified DataFrame.Typed as T import Data.Maybe (fromJust) -salesT     = T.unsafeFreeze @'[T.Column "x" Double, T.Column "y" Double] sales+salesT     = T.unsafeFreeze @'[ '("x", Double), '("y", Double) ] sales typedModel = fit defaultLinearConfig (T.col @"y") salesT scored     = T.derive @"prediction" (predict typedModel) salesT 
dataframe-learn.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.4 name:               dataframe-learn-version:            2.2.0.0+version:            2.3.0.0 synopsis:           Interpretable, expression-returning machine learning for the dataframe ecosystem. description:     A small scikit-learn-style ML library where every model returns both an@@ -61,8 +61,8 @@                         containers >= 0.6.7 && < 0.10,                         parallel >= 3.3 && < 4,                         random >= 1.2 && < 2,-                        dataframe-core >= 2.2 && < 2.3,-                        dataframe-operations >= 2.2 && < 2.3,+                        dataframe-core >= 2.3 && < 2.4,+                        dataframe-operations >= 2.3 && < 2.4,                         text >= 2.1 && < 3,                         vector >= 0.13 && < 0.15,                         vector-algorithms >= 0.9 && < 0.11@@ -105,10 +105,10 @@                         containers >= 0.6.7 && < 0.10,                         parallel >= 3.3 && < 4,                         random >= 1.2 && < 2,-                        dataframe-core >= 2.2 && < 2.3,-                        dataframe-core >= 2.2 && < 2.3,-                        dataframe-operations >= 2.2 && < 2.3,-                        dataframe-operations >= 2.2 && < 2.3,+                        dataframe-core >= 2.3 && < 2.4,+                        dataframe-core >= 2.3 && < 2.4,+                        dataframe-operations >= 2.3 && < 2.4,+                        dataframe-operations >= 2.3 && < 2.4,                         dataframe-expr-serializer >= 1.2.0.1 && < 1.3,                         dataframe-learn:internal,                         text >= 2.1 && < 3,@@ -140,13 +140,13 @@                         aeson >= 0.11.0.0 && < 3,                         bytestring >= 0.11 && < 0.14,                         containers >= 0.6.7 && < 0.10,-                        dataframe-core >= 2.2 && < 2.3,-                        dataframe-core >= 2.2 && < 2.3,+                        dataframe-core >= 2.3 && < 2.4,+                        dataframe-core >= 2.3 && < 2.4,                         dataframe-csv >= 2.3 && < 2.4,                         dataframe-learn,                         dataframe-learn:internal,-                        dataframe-operations >= 2.2 && < 2.3,-                        dataframe-operations >= 2.2 && < 2.3,+                        dataframe-operations >= 2.3 && < 2.4,+                        dataframe-operations >= 2.3 && < 2.4,                         HUnit >= 1.6 && < 1.8,                         QuickCheck >= 2 && < 3,                         random >= 1 && < 2,
src-internal/DataFrame/DecisionTree/Cart.hs view
@@ -18,15 +18,22 @@ ) where  import DataFrame.DecisionTree.Types (Tree (..), TreeConfig (..))+import DataFrame.Errors (DataFrameException (..), TypeErrorContext (..)) import qualified DataFrame.Functions as F import DataFrame.Internal.Column-import DataFrame.Internal.DataFrame (DataFrame, columnNames, unsafeGetColumn)+import DataFrame.Internal.DataFrame (+    DataFrame,+    columnNames,+    getColumn,+    unsafeGetColumn,+ ) import DataFrame.Internal.Expression (Expr (..)) import DataFrame.Internal.Interpreter (interpret) import DataFrame.Internal.Types import DataFrame.Operations.Core (nRows) import DataFrame.Operators +import Control.Exception (throw) import Data.Either (fromRight) import Data.Function (on) import Data.List (foldl')@@ -37,7 +44,7 @@ import qualified Data.Vector as V import qualified Data.Vector.Algorithms.Merge as VA import qualified Data.Vector.Unboxed as VU-import Type.Reflection (typeRep)+import Type.Reflection (TypeRep, typeRep)  {- | A one-hot feature column: per-row Double values plus the sklearn LEFT predicate (@x <= threshold@) over the ORIGINAL DataFrame.@@ -89,12 +96,25 @@             (maxTreeDepth cfg)             (max 1 (minLeafSize cfg)) +{- | Read the target column at the type the tree is being fitted at. Names the+column and both types on failure: a bare @fromIntegral@ defaults to 'Integer'+and lands here, and the old message said only that something went wrong.+-} cartLabels :: forall a. (Columnable a) => DataFrame -> T.Text -> V.Vector a cartLabels df target = case interpret @a df (Col target) of-    Right (TColumn column) -> fromRight err (toVector @a column)-    _ -> err+    Right (TColumn column) -> fromRight (throw err) (toVector @a column)+    Left e -> throw e   where-    err = error "buildCartTree: cannot interpret target column"+    err =+        TypeMismatchException+            ( MkTypeErrorContext+                (Right (typeRep @a))+                ( Left (maybe "missing" columnTypeString (getColumn target df)) ::+                    Either String (TypeRep a)+                )+                (Just (T.unpack target))+                (Just "buildCartTree")+            )  cartClasses :: (Ord a) => V.Vector a -> V.Vector a cartClasses = V.fromList . Set.toList . Set.fromList . V.toList
src-internal/DataFrame/DecisionTree/Fit.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -38,6 +39,7 @@ import DataFrame.DecisionTree.Prune (pruneDead, pruneExpr) import DataFrame.DecisionTree.Tao (taoOptimize, taoOptimizeCV) import DataFrame.DecisionTree.Types (Tree (..), TreeConfig (..))+import DataFrame.Errors (DataFrameException (..)) import qualified DataFrame.Functions as F import DataFrame.Internal.Column (Columnable, TypedColumn (..), toVector) import DataFrame.Internal.DataFrame (DataFrame)@@ -69,7 +71,8 @@     condVecs = candidatePool @a cfg target df     initialTree = buildCartTree @a cfg target df     indices = V.enumFromN 0 (nRows df)-fitDecisionTree _ expr _ = error ("Cannot create tree for compound expression: " ++ show expr)+fitDecisionTree _ expr _ =+    throw (NonColumnReferenceException ("fitDecisionTree: " <> T.pack (show expr)))  -- | The deduplicated numeric + discrete candidate pool for a target column. candidatePool ::@@ -116,7 +119,7 @@  majorityValue :: forall a. (Columnable a, Ord a) => T.Text -> DataFrame -> a majorityValue target df-    | M.null counts = error "Empty DataFrame in leaf"+    | M.null counts = throw (EmptyDataSetException "majorityValue (tree leaf)")     | otherwise = fst (maximumBy (compare `on` snd) (M.toList counts))   where     counts = getCounts @a target df@@ -197,7 +200,8 @@     pruned =         pruneDead             (taoOptimize @a cfg target conds df indices (buildCartTree @a cfg target df))-fitProbTree _ expr _ = error ("Cannot create prob tree for compound expression: " ++ show expr)+fitProbTree _ expr _ =+    throw (NonColumnReferenceException ("fitProbTree: " <> T.pack (show expr)))  -- | Convert a 'ProbTree' into one @Expr Double@ per class. probExprs ::
src-internal/DataFrame/DecisionTree/Linear.hs view
@@ -15,7 +15,7 @@     materializeFeatureForCare, ) where -import DataFrame.DecisionTree.Numeric (NumExpr (..), numericCols)+import DataFrame.DecisionTree.Numeric (NumExpr (..), numExprCols, numericCols) import DataFrame.DecisionTree.Types (     CarePoint (..),     Direction (..),@@ -33,27 +33,34 @@ import qualified Data.Vector.Unboxed as VU  {- | Best oblique candidate, or 'Nothing' when the linear path is disabled or-there are too few care points to fit on.+there are too few care points to fit on. The target column is named so it can+be kept out of the feature set. -} bestLinearCandidate ::-    TreeConfig -> DataFrame -> [CarePoint] -> Maybe (Expr Bool)-bestLinearCandidate cfg df carePoints+    TreeConfig -> T.Text -> DataFrame -> [CarePoint] -> Maybe (Expr Bool)+bestLinearCandidate cfg target df carePoints     | not (useLinearSolver cfg) = Nothing     | length carePoints < minCarePointsForLinear cfg = Nothing-    | otherwise = fitLinearCandidate cfg df carePoints+    | otherwise = fitLinearCandidate cfg target df carePoints  {- | Fit an L1 logistic regression to the care points and convert the resulting hyperplane to a condition, or 'Nothing' when no numeric features exist or the fitted model is all-zero or degenerate. -} fitLinearCandidate ::-    TreeConfig -> DataFrame -> [CarePoint] -> Maybe (Expr Bool)-fitLinearCandidate cfg df carePoints = case materializedFeatures df carePoints of-    [] -> Nothing-    mats -> linearFromFeatures cfg carePoints mats+    TreeConfig -> T.Text -> DataFrame -> [CarePoint] -> Maybe (Expr Bool)+fitLinearCandidate cfg target df carePoints =+    case materializedFeatures target df carePoints of+        [] -> Nothing+        mats -> linearFromFeatures cfg carePoints mats -materializedFeatures :: DataFrame -> [CarePoint] -> [(T.Text, VU.Vector Double)]-materializedFeatures df carePoints = mapMaybe (materializeFeatureForCare df carePoints) (numericCols df)+materializedFeatures ::+    T.Text -> DataFrame -> [CarePoint] -> [(T.Text, VU.Vector Double)]+materializedFeatures target df carePoints =+    mapMaybe (materializeFeatureForCare df carePoints) (featureCols target df)++featureCols :: T.Text -> DataFrame -> [NumExpr]+featureCols target df = filter (notElem target . numExprCols) (numericCols df)  linearFromFeatures ::     TreeConfig -> [CarePoint] -> [(T.Text, VU.Vector Double)] -> Maybe (Expr Bool)
src-internal/DataFrame/DecisionTree/Predict.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -32,6 +33,7 @@     Tree (..),     TreeConfig (..),  )+import DataFrame.Errors (DataFrameException (..)) import DataFrame.Internal.Column (Columnable, TypedColumn (..), toVector) import DataFrame.Internal.DataFrame (DataFrame) import DataFrame.Internal.Expression (Expr (..))@@ -183,7 +185,7 @@  majorityOf :: M.Map a Int -> a majorityOf counts-    | M.null counts = error "Empty indices in majorityValueFromIndices"+    | M.null counts = throw (EmptyDataSetException "majorityValueFromIndices")     | otherwise = fst (maximumBy (compare `on` snd) (M.toList counts))  computeTreeLoss ::
src-internal/DataFrame/DecisionTree/Tao.hs view
@@ -213,7 +213,7 @@             leftTree             rightTree     penaltyCV = evalWithPenaltyVec cfg carePoints-    linearCandidate = bestLinearCandidate cfg (teDf env) carePoints+    linearCandidate = bestLinearCandidate cfg (teTarget env) (teDf env) carePoints     valid = filterValidCandidates cfg indices (teConds env)     pool =         candidatePool
src-internal/DataFrame/Featurize/Internal.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -166,7 +167,8 @@ -- | The column name behind a @Col@ feature expression. columnExprName :: Expr Double -> T.Text columnExprName (Col n) = n-columnExprName e = error ("expected a column expression, got " ++ show e)+columnExprName e =+    throw (NonColumnReferenceException ("columnExprName: " <> T.pack (show e)))  -- | Interpret a @Col@ (or numeric) expression to a @Double@ vector. materializeColumn :: DataFrame -> Expr Double -> VU.Vector Double@@ -197,7 +199,7 @@ argExtreme ::     (Columnable a) =>     (Expr Double -> Expr Double -> Expr Bool) -> [(a, Expr Double)] -> Expr a-argExtreme _ [] = error "argExtreme: no classes"+argExtreme _ [] = throw (EmptyDataSetException "argExtreme") argExtreme _ [(c, _)] = Lit c argExtreme cmp ((c, sc) : rest) =     If
src/DataFrame/Boosting/AdaBoost.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}@@ -19,10 +20,13 @@     AdaBoostModel (..), ) where +import Control.Exception (throw) import Data.List (sort) import Data.Maybe (fromMaybe, maybeToList)+import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU+import DataFrame.Errors (DataFrameException (..))  import DataFrame.DecisionTree.Cart (     CartFeature (..),@@ -101,7 +105,7 @@     clamp e = max 1e-10 (min (1 - 1e-10) e)     normalize v = let s = VU.sum v in if s == 0 then v else VU.map (/ s) v fitAdaBoost _ expr _ =-    error ("fitAdaBoost: target must be a column, got " ++ show expr)+    throw (NonColumnReferenceException ("fitAdaBoost: " <> T.pack (show expr)))  predictCodes ::     forall a.@@ -113,7 +117,7 @@     preds :: [a]     preds = case interpret df (treeToExpr stump) of         Right (TColumn c) -> either (const []) V.toList (toVector @a @V.Vector c)-        Left e -> error (show e)+        Left e -> throw e     toCode v = fromMaybe 0 (V.findIndex (== v) classesV)  -- | A depth-bounded weighted classification tree (weighted Gini splits).
src/DataFrame/Boosting/GBM.hs view
@@ -23,11 +23,13 @@     gbDecisionExpr, ) where +import Control.Exception (throw) import Data.Either (fromRight) import qualified Data.Map.Strict as M import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU+import DataFrame.Errors (DataFrameException (..))  import DataFrame.DecisionTree.Cart (cartFeatures) import DataFrame.DecisionTree.Fit (treeToExpr)@@ -118,16 +120,32 @@     boost !m fScores ts ss usageAcc         | m >= gbNEstimators cfg = (ts, ss, usageAcc)         | otherwise =-            let grad = negGradient (gbLoss cfg) y fScores-                tree = fitRegTreeOn rtCfg feats grad Nothing+            let (target', weights) = newtonStep (gbLoss cfg) y fScores+                tree = fitRegTreeOn rtCfg feats target' weights                 pred = predictTree df tree                 fScores' = VU.zipWith (\f p -> f + lr * p) fScores pred                 score = lossValue (gbLoss cfg) y fScores'                 usage' = foldr (\c -> M.insertWith (+) c 1) usageAcc (treeColumns tree)              in boost (m + 1) fScores' (tree : ts) (score : ss) usage' fitGBM _ expr _ =-    error ("fitGBM: target must be a column, got " ++ show expr)+    throw (NonColumnReferenceException ("fitGBM: " <> T.pack (show expr))) +newtonStep ::+    GBLoss ->+    VU.Vector Double ->+    VU.Vector Double ->+    (VU.Vector Double, Maybe (VU.Vector Double))+newtonStep SquaredError y f = (negGradient SquaredError y f, Nothing)+newtonStep LogisticDeviance y f = (z, Just h)+  where+    p = VU.map sigmoid f+    h = VU.map (\pi' -> max hFloor (pi' * (1 - pi'))) p+    z = VU.zipWith3 (\yi pi' hi -> (yi - pi') / hi) y p h++-- | Floor on the Hessian, so a saturated row cannot produce an unbounded step.+hFloor :: Double+hFloor = 1e-6+ negGradient ::     GBLoss -> VU.Vector Double -> VU.Vector Double -> VU.Vector Double negGradient SquaredError y f = VU.zipWith (-) y f@@ -159,7 +177,7 @@ predictTree :: DataFrame -> Tree Double -> VU.Vector Double predictTree df t = case interpret @Double df (treeToExpr t) of     Right (TColumn c) -> fromRight VU.empty (toVector @Double @VU.Vector c)-    Left e -> error (show e)+    Left e -> throw e  treeColumns :: Tree Double -> [T.Text] treeColumns = getColumns . treeToExpr
src/DataFrame/DecisionTree/Model.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -17,8 +18,10 @@     DecisionTreeRegressor (..), ) where +import Control.Exception (throw) import qualified Data.Map.Strict as M import qualified Data.Text as T+import DataFrame.Errors (DataFrameException (..))  import qualified Data.Vector as V @@ -83,8 +86,10 @@                     (targetDoubles target df)                     Nothing             _ ->-                error-                    ("fit @DecisionTreeRegressor: target must be a column, got " ++ show target)+                throw+                    ( NonColumnReferenceException+                        ("fit @DecisionTreeRegressor: " <> T.pack (show target))+                    )         e = treeToExpr t  instance Predict DecisionTreeRegressor where
src/DataFrame/Metrics.hs view
@@ -38,6 +38,7 @@     f1Of, ) where +import Control.Exception (throw) import Data.Either (fromRight) import Data.List (nub, sort, sortBy) import Data.Ord (comparing)@@ -70,7 +71,7 @@ columnOf :: DataFrame -> Expr Double -> VU.Vector Double columnOf df e = case interpret @Double df e of     Right (TColumn c) -> fromRight VU.empty (toVector @Double @VU.Vector c)-    Left err -> error (show err)+    Left err -> throw err  n2 :: VU.Vector Double -> Double n2 = fromIntegral . VU.length
src/DataFrame/SVM/RFF.hs view
@@ -18,10 +18,12 @@     RFFSVMModel (..), ) where +import Control.Exception (throw) import Data.List (sort) import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU+import DataFrame.Errors (DataFrameException (..))  import DataFrame.Featurize.Internal (featureNames, numericMatrix, targetValues) import qualified DataFrame.Functions as F@@ -100,7 +102,11 @@ fitRFFSVM cfg target df =     case classes of         [neg, pos] -> build neg pos-        _ -> error "fitRFFSVM: binary classification only (got /= 2 classes)"+        _ ->+            throw+                ( InternalException+                    "fitRFFSVM: binary classification only, but the target has /= 2 classes"+                )   where     names = featureNames target df     (nameVec, mat) = numericMatrix names df
src/DataFrame/Segmented.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}@@ -23,6 +24,7 @@     SegmentFit (..), ) where +import Control.Exception (throw) import Data.List (foldl', (\\)) import qualified Data.Map.Strict as M import Data.Maybe (isJust)@@ -30,6 +32,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU+import DataFrame.Errors (DataFrameException (..))  import DataFrame.Featurize.Internal (featureNames, numericMatrix, targetDoubles) import DataFrame.Internal.Column (@@ -302,7 +305,7 @@   where     names = case dfs of         (d0 : _) -> featureNames target d0-        [] -> error "shrinkLinear: no segments"+        [] -> throw (EmptyDataSetException "shrinkLinear")     d = length names     mats = [snd (numericMatrix names dframe) | dframe <- dfs]     ys = [targetDoubles target dframe | dframe <- dfs]
src/DataFrame/Synthesis.hs view
@@ -1,18 +1,16 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}  {- | Feature synthesis by bottom-up enumerative search with observational equivalence — the canonical enumerative method from Solar-Lezama's-/Introduction to Program Synthesis/, hardened for a numeric, examples-only-setting.+/Introduction to Program Synthesis/.  Given a frame and a numeric target column, it searches for a small, interpretable-arithmetic expression over the other columns whose values track the target. The-specification is purely the example rows; there is no SMT solver and no logical-spec. Deterministic and pure.+arithmetic expression over the other columns whose values track the target.  The engine: @@ -48,6 +46,7 @@     synthesizeFeatures, ) where +import Control.Exception (throw) import Data.Bits (xor) import Data.Either (fromRight) import Data.List (sortBy)@@ -59,6 +58,7 @@ import Data.Word (Word64) import GHC.Float (castDoubleToWord64) +import DataFrame.Errors (DataFrameException (..)) import DataFrame.Featurize.Internal (featureNames) import qualified DataFrame.Functions as F import DataFrame.Internal.DataFrame (DataFrame)@@ -91,6 +91,8 @@     , synLoss :: !LossFunction     , synTopK :: !Int     -- ^ How many ranked features to return in the bank.+    , synMaxAllocBytes :: !Int+    -- ^ Refuse a search whose largest layer would allocate beyond this.     }     deriving (Eq, Show) @@ -101,6 +103,7 @@         , synBankCap = 500         , synLoss = PearsonCorrelation         , synTopK = 16+        , synMaxAllocBytes = 8 * 1024 * 1024 * 1024         }  {- | A synthesized feature. 'sfExpr' is the best-scoring expression and 'sfFeatures'@@ -134,6 +137,7 @@     SynthesisConfig -> Expr Double -> DataFrame -> SynthesizedFeature synthesizeFeatures cfg target df     | null leaves || VU.null tgt = SynthesizedFeature (Lit 0) (negate (1 / 0)) []+    | Just err <- oversizedSearch cfg (length leaves) n = throw err     | otherwise = SynthesizedFeature best bestScore ranked   where     feats = featureNames target df@@ -276,6 +280,49 @@ absorb cfg tgt seen0 cands = (capLayer cfg tgt fresh, seen')   where     (fresh, seen') = dedupProgs seen0 cands++oversizedSearch :: SynthesisConfig -> Int -> Int -> Maybe DataFrameException+oversizedSearch cfg nLeaves n+    | synMaxSize cfg <= 3 = Nothing+    | estimate <= budget = Nothing+    | otherwise =+        Just+            ( InternalException+                ( "synthesizeFeatures: a search to synMaxSize="+                    <> T.pack (show (synMaxSize cfg))+                    <> " over "+                    <> T.pack (show nLeaves)+                    <> " leaves and "+                    <> T.pack (show n)+                    <> " rows would allocate about "+                    <> T.pack (show (estimate `div` (1024 * 1024 * 1024)))+                    <> " GiB, past the "+                    <> T.pack (show (budget `div` (1024 * 1024 * 1024)))+                    <> " GiB synMaxAllocBytes budget. Lower synMaxSize to "+                    <> T.pack (show largestFittingSize)+                    <> ", narrow the column set, or raise synMaxAllocBytes."+                )+            )+  where+    budget = synMaxAllocBytes cfg+    bytesPerProg = 8 * max 1 n+    -- 4 commutative ops over unordered pairs plus sub and div over ordered+    -- pairs, all quadratic in the bank; unaries and powers are lower order.+    binaryOpCount = 4 :: Int+    -- Layer k pairs the capped bank with itself over the binary operators.+    candidatesAt k+        | k <= 2 = nLeaves+        | otherwise =+            binaryOpCount * min (synBankCap cfg) (candidatesAt (k - 1)) ^ (2 :: Int)+    estimate = sum [candidatesAt k * bytesPerProg | k <- [2 .. synMaxSize cfg]]+    largestFittingSize =+        last+            ( 3+                : [ k+                  | k <- [3 .. synMaxSize cfg]+                  , sum [candidatesAt j * bytesPerProg | j <- [2 .. k]] <= budget+                  ]+            )  -- | When a layer has more distinct programs than the cap, keep the best-scoring. capLayer :: SynthesisConfig -> Output -> [Prog] -> [Prog]
tests-internal/DecisionTree.hs view
@@ -441,7 +441,6 @@         0.0         finalLoss --- Shared setup for C2 (a) and (b): axis-aligned pool only, oblique label. obliqueAxisAlignedFixture ::     (D.DataFrame, V.Vector Int, [Expr Bool], Tree T.Text) obliqueAxisAlignedFixture =@@ -463,8 +462,6 @@                 Tree T.Text      in (df, indices, axisConds, initTree) --- C2 (a): with the linear solver OFF, axis-aligned pool cannot recover the--- oblique decision boundary. Preserves the original guarantee of the test. taoAxisAlignedInsufficientForObliqueDiscreteOnly :: Test taoAxisAlignedInsufficientForObliqueDiscreteOnly = TestCase $ do     let (df, indices, axisConds, initTree) = obliqueAxisAlignedFixture@@ -481,8 +478,6 @@         "axis-aligned stump cannot recover oblique label without linear solver (loss > 0.1)"         (finalLoss > 0.1) --- C2 (b): with the linear solver ON, the L1-LR fit discovers the oblique--- (x + y) hyperplane even though only axis-aligned conditions are in the pool. taoLinearRecoversObliqueFromAxisAlignedPool :: Test taoLinearRecoversObliqueFromAxisAlignedPool = TestCase $ do     let (df, indices, axisConds, initTree) = obliqueAxisAlignedFixture@@ -501,10 +496,6 @@         0.0         finalLoss ---------------------------------------------------------------------------- Nullable numeric feature tests-------------------------------------------------------------------------- -- Cleanly separable nullable column (no actual nulls): Just 1..6 -> "pos", -- Just 7..12 -> "neg". Exercises the nullable numeric path. nullableSepDF :: D.DataFrame@@ -633,10 +624,6 @@         "combined exprs include NMaybeDouble (nullable arithmetic)"         (any (\case NMaybeDouble _ -> True; _ -> False) exprs) ---------------------------------------------------------------------------- Probability tree tests-------------------------------------------------------------------------- -- probsFromIndices: counts correct on a 3-row slice probsFromIndicesBasic :: Test probsFromIndicesBasic = TestCase $ do@@ -780,13 +767,6 @@                         )                         indices ---------------------------------------------------------------------------- C4-C9 / D-series: linear solver integration tests----------------------------------------------------------------------------- C4: Nested oblique recovery without oblique hints; label set by two oblique--- boundaries but only axis-aligned thresholds in the pool. The linear solver--- should learn both splits and reach zero loss. taoRecoversNestedObliqueWithoutHint :: Test taoRecoversNestedObliqueWithoutHint = TestCase $ do     let labelExpr =@@ -828,9 +808,6 @@         0.0         finalLoss --- C5: Monotone loss across iterations with the linear solver enabled.--- Resolves Issue 1 from the prior plan (currentCond included in the--- competition pool). taoMonotoneWithLinear :: Test taoMonotoneWithLinear = TestCase $ do     let indices = V.enumFromN 0 20@@ -868,8 +845,6 @@         0.0         finalLoss --- C8: Linear solver respects the L1 penalty and produces sparse hyperplanes--- on data where only some features are informative. taoLinearProducesSparsity :: Test taoLinearProducesSparsity = TestCase $ do     let n = 50 :: Int@@ -911,7 +886,32 @@         )         ("a" `elem` rootCols || "b" `elem` rootCols) --- C9: Determinism — same training data produces an equal (eqExpr) tree.+taoLinearDoesNotUseTarget :: Test+taoLinearDoesNotUseTarget = TestCase $ do+    let n = 40 :: Int+        as = [fromIntegral (i `div` 4) :: Double | i <- [0 .. n - 1]]+        bs = [fromIntegral (i `mod` 3) :: Double | i <- [0 .. n - 1]]+        targets = [if even i then 1.0 else 0.0 :: Double | i <- [0 .. n - 1]]+        df =+            D.fromNamedColumns+                [ ("target", DI.fromList targets)+                , ("a", DI.fromList as)+                , ("b", DI.fromList bs)+                ]+        cfg =+            defaultTreeConfig+                { maxTreeDepth = 1+                , taoIterations = 10+                , minLeafSize = 1+                , useLinearSolver = True+                , minCarePointsForLinear = 2+                }+        result = fitDecisionTree @Double cfg (Col "target") df+        rootCols = getColumns result+    assertBool+        ("target must not appear in the fitted Expr (got " ++ show result ++ ")")+        ("target" `notElem` rootCols)+ taoLinearDeterministic :: Test taoLinearDeterministic = TestCase $ do     let cfg =@@ -945,13 +945,6 @@         "skipping linear solver yields same expression as linear-off baseline"         (eqExpr result resultOff) ---------------------------------------------------------------------------- Categorical-condition generator tests (Phase 1-2 of the plan)----------------------------------------------------------------------------- A binary-target DataFrame with a 5-level Text column whose levels have--- monotonically-increasing positive rates. Breiman's algorithm should--- enumerate the 4 contiguous-prefix splits in that exact rate order. breimanBinaryDF :: D.DataFrame breimanBinaryDF =     let n = 100 :: Int@@ -1085,11 +1078,6 @@         feats' = filter (\c -> feat `elem` getColumns c) conds     assertEqual "Breiman prefixes on nullable column ignore nulls" 2 (length feats') ---------------------------------------------------------------------------- PR 2 extended: threshold-consolidation rewrite in combineAndVec /--- combineOrVec. Positive cases, negative cases, semantic-preservation check.-------------------------------------------------------------------------- -- A small synthetic DataFrame to materialize CondVecs against. threshFixtureDF :: D.DataFrame threshFixtureDF =@@ -1329,6 +1317,7 @@     , TestLabel "C5 taoMonotoneWithLinear" taoMonotoneWithLinear     , TestLabel "C6 taoLinearVsDiscreteCompetition" taoLinearVsDiscreteCompetition     , TestLabel "C8 taoLinearProducesSparsity" taoLinearProducesSparsity+    , TestLabel "C8b taoLinearDoesNotUseTarget" taoLinearDoesNotUseTarget     , TestLabel "C9 taoLinearDeterministic" taoLinearDeterministic     , TestLabel "D1 taoLinearTinyCareSet" taoLinearTinyCareSet     , TestLabel "E1 categoricalBreimanBinary" testCategoricalBreimanBinary